16 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | duration | completed | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 08-oauth2-1-authorization-server | 07 | auth |
|
|
|
|
|
|
|
|
~35min (approx.) | 2026-09-23 |
Phase 08 Plan 07: OAuth Client Operator Command Summary
fonoteka:oauth-client bonfire command (create/--client-id/--list) sharing wristband's exact client-issuing hash/validation path, on top of a new repeatable-flag contract in bonfire (Flag.Repeatable / Input.Flags).
Performance
- Duration: ~35 min (approx.)
- Started: ~2026-09-23T19:25:00Z (approx.)
- Completed: ~2026-09-23T20:00:08Z (approx.)
- Tasks: 2 completed (4 commits: RED/GREEN pair in summercms.go for bonfire's repeatable-flag seam, RED test + one GREEN commit in fonoteka.go for the command)
- Files modified: 9 (3 created + 3 modified in summercms.go's bonfire/wristband packages; 3 modified in fonoteka.go, one of them a pre-existing sibling test file fixed for the new Input method)
Accomplishments
bonfire.FlaggainedRepeatableandInputgainedFlags(name) []string: a Repeatable flag registers as a CobraStringSliceinstead of a scalarStringflag, so--redirect-uri=a --redirect-uri=bis readable back in insertion order without disturbing any existing scalar/bare flag on the same command (TestFlagAndArgumentParsingand every other pre-existing bonfire test stayed green unchanged)wristband.IssueClientCredentials/RejectRedirectURIexport the exact random-id/secret/sha256-hash and redirect-URI validation RFC 7591 registration already used internally, so the operator command shares one implementation of "how a client secret is generated and hashed" with DCR (T-08-SECRET-TIMING) instead of re-deriving itfonoteka:oauth-client(OAuthClientCommand) portsIssueOAuthClient.phpbyte-for-byte in shape: create printsclient_id=/client_secret=once plus the non-recoverable warning,--client-id <id>merges new--redirect-urivalues into an existing client without touching its secret hash, and--listprintsclient_id=/client_name=/revoked=/redirect_uri=/scope_ceiling=for every client and never a secret or hash- Fixed a silent gap in
clientRecordToModel(fonoteka's GORM adapter):wristband.ClientRecord.ScopeCeilinghas existed since 08-02 but was never mapped onto the persistedmodels.OAuthClientrow, because DCR-created clients never set a ceiling; this plan's command is the first caller that needs the ceiling to actually surviveCreateWithCap, and the gap would have silently dropped every--scopevalue without the fix - Both Phase 8 RED sentinels (
PHASE8_RED:bonfire-flagsinbonfire,PHASE8_RED:oauth-commandin the fonotekaconsolepackage) were verified fail-closed viascripts/check-phase8-red.shagainst the genuine pre-implementation state (unwiredRepeatable; a "not implemented" command stub) before their GREEN commits;go vet/go test/go test -raceare green acrosssummercms.goand every touchedfonoteka.goworkspace module (rootfonoteka/parity,plugins/golem15/fonotekaand its subpackages,plugins/golem15/user)
Task Commits
Each task's RED test was committed and verified fail-closed via scripts/check-phase8-red.sh before its GREEN implementation:
- Task 1: repeatable-flag and command RED anchors
7096a90(test, summercms.go):TestPhase8RedBonfireFlagsfails against unwiredRepeatable(PHASE8_RED:bonfire-flags); adds the compiling seam (Flag.Repeatable,Input.Flags,cobraInput.Flags)4efce33(test, fonoteka.go):TestPhase8RedOAuthClientCommandfails against the "not implemented" command stub (PHASE8_RED:oauth-command); addsconsole/oauth_client.go's exact flag/argument contract and fixesuser/console_test.go'semailArgto satisfy the extendedbonfire.Inputinterface
- Task 2: implement repeatable flags and the exact OAuth client command
398353b(feat, summercms.go): wiresRepeatableinto CobraStringSliceregistration inwrap(); exportswristband.IssueClientCredentials/RejectRedirectURI; addsTestRepeatableFlagUnsetReturnsEmpty/TestRepeatableFlagCoexistsWithBareAndScalar9e82cac(feat, fonoteka.go): the realOAuthClientCommandcreate/update/list implementation,Plugin.Commands()registration, theclientRecordToModelScopeCeilingfix, and the fullOAuthClientCommandTestparity test matrix
Plan metadata: committed as part of this summary/state-update commit.
Note: both tasks carry tdd="true"; RED/GREEN pairs land as separate commits, split per repo.
Files Created/Modified
bonfire/command.go—Flag.Repeatable,Input.Flags(name) []string,cobraInput.Flagsbonfire/root.go—wrap()registers a Repeatable flag as CobraStringSliceP/StringSlicebonfire/output_test.go—TestPhase8RedBonfireFlags,TestRepeatableFlagUnsetReturnsEmpty,TestRepeatableFlagCoexistsWithBareAndScalarwristband/client_issue.go—IssueClientCredentials,RejectRedirectURI(exported wrappers over existing unexported primitives)../fonoteka.go/plugins/golem15/fonoteka/console/oauth_client.go—OAuthClientCommand: create/--client-id/--list, plusoauthClientCollectRedirectURIs/oauthClientCollectScopeCeiling/oauthClientMergeUniqueURIshelpers../fonoteka.go/plugins/golem15/fonoteka/console/oauth_client_test.go—TestPhase8RedOAuthClientCommandplus theOAuthClientCommandTestparity matrix (list-never-leaks-secret, add-redirect-uri-keeps-secret, unknown client_id, scope-ceiling persistence/listing, invalid-scope rejects without creating a client, 1..5-redirect-uri bound) and the package's own real-PostgresTestMainharness../fonoteka.go/plugins/golem15/fonoteka/plugin.go—Commands()registersconsole.OAuthClientCommand(p.app);pact.HasCommandsassertion../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go—clientRecordToModelnow mapsScopeCeiling../fonoteka.go/plugins/golem15/user/console_test.go—emailArg.Flags(string) []stringto satisfy the extendedbonfire.Inputinterface
Decisions Made
See frontmatter key-decisions. The most load-bearing: list/update deliberately stay outside the wristband.ClientStore abstraction (ordinary *gorm.DB queries against models.OAuthClient), while create deliberately goes through wristband.Tx.CreateWithCap with math.MaxInt32 as the cap — only client issuance needs the shared hash/validation path the threat model calls for; list/redirect-URI-merge are plain app-layer reads/writes with no security-sensitive generation logic to share.
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - Blocking] user/console_test.go's emailArg did not satisfy the extended bonfire.Input interface
- Found during: Task 1, immediately after adding
Input.Flagstobonfire/command.go - Issue:
bonfire.Inputgaining a new method broke compilation of every existing structural implementer;user/console_test.go'semailArg(used byTestRequirePasswordChange) is the only other one in either repo - Fix: Added
func (e emailArg) Flags(string) []string { return nil } - Files modified:
../fonoteka.go/plugins/golem15/user/console_test.go - Verification:
go build ./...andgo test ./...green in theplugins/golem15/usermodule - Committed in:
4efce33(Task 1 RED commit, fonoteka.go)
2. [Rule 2 - Missing Critical] clientRecordToModel never persisted ScopeCeiling
- Found during: Task 2, while wiring the create path's scope-ceiling persistence
- Issue:
wristband.ClientRecord.ScopeCeilinghas existed since 08-02, andclientModelToRecordalready read it back, but the reverse mapping inclientRecordToModelwas missing — every ceiling passed toCreateWithCapwould have been silently dropped, defeating the ceiling this plan's command exists to set (T-08-SCOPE-CEILING) - Fix:
clientRecordToModelnow mapsScopeCeilingonto the persistedmodels.OAuthClientrow - Files modified:
../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go - Verification:
TestOAuthClientCommandScopeCeilingPersistsAndListsNeverTheSecret - Committed in:
9e82cac(Task 2 GREEN commit, fonoteka.go)
3. [Rule 2 - Missing Critical] wristband/client_issue.go added beyond the plan's literal files_modified list
- Found during: Task 2, implementing the create path
- Issue: The plan's threat model (T-08-SECRET-TIMING) requires the command to share DCR's exact hash/validation path, but no exported wristband function existed for random client-id/secret generation, hashing, or redirect-URI validation
- Fix: Added
wristband/client_issue.goexportingIssueClientCredentials/RejectRedirectURIas thin wrappers over the existing unexportedrandomBase64URL/sha256Hex/rejectRedirectURI, adding no new logic - Files modified:
wristband/client_issue.go(new) - Verification:
go test ./wristband/... -race;TestOAuthClientCommandListPrintsClientIDAndURIsNeverTheSecretet al. exercise the shared path end to end - Committed in:
398353b(Task 2 GREEN commit, summercms.go)
Total deviations: 3 auto-fixed (1 blocking compile fix, 2 missing-critical-functionality additions required by the plan's own threat model and objective) Impact on plan: No scope change; all three were necessary for the plan's stated D-19/T-08-SCOPE-CEILING/T-08-SECRET-TIMING behavior to actually work, not separate feature additions.
Issues Encountered
None beyond the auto-fixed items above. Full go vet/go test ./... (and go test -race on the touched packages) are green in summercms.go and in every fonoteka.go workspace module: the root fonoteka/parity module, plugins/golem15/fonoteka and its classes, classes/auth, console, controllers/api, middleware, models, updates subpackages, and plugins/golem15/user and its classes/updates subpackages.
User Setup Required
None — no external service configuration required.
Next Phase Readiness
fonoteka:oauth-clientis available for operators to issue confidential clients for chat-app connectors ahead of 08-09's real-MCP gate, and for the D-16 lifecycle corpus's confidential-client-with-ceiling fixture.bonfire.Flag.Repeatable/Input.Flagsis the established shape for any future PHP=*console option; no further bonfire interface changes are anticipated for the remaining Phase 8 plans' known scope.- AUTH-05/AUTH-07 remain Pending in REQUIREMENTS.md: this plan ships the D-19 operator command, but both requirements' full text also needs 08-08/08-09's unchanged-fonoteka-mcp-install/auth-flow proof.
08-08(mcp-me prerequisite) and08-09(parity-and-real-mcp-gate) can proceed without any further change to this plan's surface.- No blockers.
Self-Check: PASSED
- FOUND: bonfire/command.go, bonfire/root.go, bonfire/output_test.go
- FOUND: wristband/client_issue.go
- FOUND: ../fonoteka.go/plugins/golem15/fonoteka/console/oauth_client.go, oauth_client_test.go
- FOUND: ../fonoteka.go/plugins/golem15/fonoteka/plugin.go, classes/auth/oauth_store.go
- FOUND: ../fonoteka.go/plugins/golem15/user/console_test.go
- FOUND commits (summercms.go):
7096a90,398353b - FOUND commits (fonoteka.go): 4efce33, 9e82cac
Phase: 08-oauth2-1-authorization-server Completed: 2026-09-23