feat(todo/go): generate Go/chi backend from todo.candy — 37/37 hurl green#48
Merged
Conversation
PasswordStrength policy requires at least one digit; passwords like "correct horse battery staple admin" have no digit. Appending " 9" satisfies the constraint without breaking other length/letter requirements. This is the provably-wrong-fixture case established in the Phase C handoff.
Implements every flow, actor, controller, and policy from examples/todo/todo.candy with three-role RBAC (Admin/Manager/User). - JWT-based sessions (golang-jwt/jwt v5, HS256, 7d TTL) - Revocation table for idempotent logout with LogoutBearerAuth - Role refreshed from DB on each request so promotions take effect without re-login (required by the hurl test sequence) - FIRST_ADMIN_EMAIL env var bootstraps first admin at signup - PasswordStrength, CanEditTodo, CanDeleteTodo, CanAssignTodo, RoleGated policies implemented; all spec examples unit-tested - Idempotency on Signup and CreateTodo via idempotency_keys table - 2151 Go LOC; all 37 hurl scenarios green
This was referenced May 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase E1 of the candy alpha plan. Closes alpha criterion 5 (todo
example with RBAC, generated and eval-green on at least one target).
A Go/chi backend generated from `examples/todo/todo.candy` using the
codegen prompts merged in #41. Three roles (Admin, Manager, User), JWT
sessions (matching the auth target's design), 37/37 hurl scenarios
green. Inlined auth uses the same JWT-self-contained pattern as PR #45;
RBAC realised via candy-native `policy` blocks attached at flow,
controller, and route scope.
What landed
Total: 2,151 Go LOC (under the 4,000 budget).
Library pins (per `examples/todo/preferences.candy`)
`go-chi/chi/v5`, `mattn/go-sqlite3`, `segmentio/ksuid`,
`golang-jwt/jwt/v5`, `golang.org/x/crypto/argon2`. No KSUID-substituted-
for-JWT shortcuts.
Commits (atomic, in order)
Spec → realisation choices (HANDOFF, full detail)
Role freshness (worth flagging)
The hurl test promotes a Manager (B5) and immediately uses the
original token (issued at signup, role=User) to edit an assigned
todo (C3). No re-login between promotion and the assigned-edit test.
Strict reading of the spec (auth.candy: "JWT self-contained, no
session-store lookup on the hot path") would require role to live in
the JWT and re-issuance on promotion. But the eval doesn't simulate a
re-login.
Resolution. The implementation introduces `BearerAuthWithUsers` —
parses + verifies + checks revocation, then reads the caller's role
from the `users` table. The JWT is the identity credential; the DB
is the authoritative source of role. Promotions take effect on the
next request without re-login. Same lookup is one indexed read, well
within "small" by the spec's wording.
This is a spec/eval tension worth a grammar-side clarification later
— either a documented "stale-after-state-change" allowance for
JWT claims, or a re-issue protocol on role change. Not blocking
alpha.
Bootstrap-admin via `FIRST_ADMIN_EMAIL`
todo.candy declares admin-only routes but no spec-level path to
bootstrap the first admin. The session-handoff (§7 "Open") lists three
options for this: (a) seed admin via DB fixture, (b) test-only signup
hook, (c) external runner harness.
Resolution. Option (b). The signup handler reads
`FIRST_ADMIN_EMAIL`; if set and no admin user exists in the DB, the
matching first signup is auto-promoted to Admin and issued an
Admin-role JWT. Set in the test environment, unset in production.
PasswordStrength order: blocklist before length
Same as Go auth (PR #45) and Rust auth (PR #47). The spec example
`"password123" → InBlocklist` requires this ordering.
Argon2 salt (production gap, documented)
Static salt for test determinism. Production needs random per-user
salt. Flagged in HANDOFF §1.
Verification
Closes / Refs