Files
summercms/.planning/phases/08-oauth2-1-authorization-server/08-01-SUMMARY.md

11 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 01 auth
oauth2
rfc8414
wristband
surf
raw-routes
standard-library
phase provides
06-http-routing-auth-groups-and-rate-limiting surf.GroupRaw raw-route surface, the reserved (now-retired) "oauth" guard slot, and fonoteka-oauth-token/fonoteka-oauth-register buckets
phase provides
07-user-plugin-and-authentication the JWT/inv_token guard split and bouncer.Registry that oauth-issued inv_ tokens will later authenticate against
wristband: a new app-agnostic framework package (Options, Server, DefaultOptions, exact RFC 8414 writer) that never imports fonoteka or GORM
GET /.well-known/oauth-authorization-server mounted on the assembled fonoteka.go raw route group with zero middleware and no oauth guard
scripts/check-phase8-red.sh: the shared fail-closed RED verifier (go and shell modes) every later Phase 8 plan uses before implementing its GREEN
08-02-persistence-and-registration
08-03-authorize
08-04-token-exchange
08-05-consent-and-connected-apps
08-06-lifecycle-and-sweeps
08-07-oauth-client-command
08-08-mcp-me-prerequisite
08-09-parity-and-real-mcp-gate
08-10-unit-tests-and-security-review
added patterns
wristband package: app-agnostic OAuth/RFC 8414 surface with Options for every deployment-specific value; only response_types/grant_types/code_challenge_methods stay fixed protocol constants (D-06)
Local exact-JSON writer in wristband (not wire.WriteJSON): unwrapped body, no trailing newline, no HTML escaping, and on encode failure a bare 500 instead of the house opaque-500 envelope, so raw RFC responses never acquire a house-shaped body
Fail-closed go-test-json RED verifier pattern: a stdlib-only Go program (invoked via `go run` from the bash wrapper) parses every JSON event, requires the named test to run+fail with its package, requires the exact sentinel exactly once, and rejects any other fail action, build/setup failure, panic substring, or malformed line
created modified
wristband/server.go
wristband/server_test.go
scripts/check-phase8-red.sh
../fonoteka.go/plugins/golem15/fonoteka/oauth_metadata_test.go
../fonoteka.go/plugins/golem15/fonoteka/plugin.go
../fonoteka.go/plugins/golem15/fonoteka/routes.go
wristband.Options carries only the four PHP-configurable metadata fields (service_documentation path, scopes_supported, token_endpoint_auth_methods_supported, authorization_response_iss_parameter_supported); response_types/grant_types/code_challenge_methods are hardcoded protocol constants per D-06, not Options fields
Plugin.Boot always constructs wristband.Server even when app.url is unset (issuer becomes empty string) rather than failing loud, to avoid a breaking change across the many existing fonoteka tests that boot the plugin without app.url configured; production deployments must set app.url
D-10 (oauth guard retirement) is recorded here rather than by editing Phase 6 docs: TestOAuthMetadataRouteIsolation asserts no "oauth" guard is registered anywhere in the assembled bouncer.Registry
Two-repo TDD plan structure: RED and GREEN land as separate commits per repo (test commit in the repo whose test fails, feat commit in the repo(s) whose code makes it pass), matching the two-repositories execution contract
AUTH-05
AUTH-06
25min 2026-09-23

Phase 08 Plan 01: Fail-Closed RED Infrastructure and Assembled RFC 8414 Metadata Summary

New wristband framework package serves the exact 11-field unwrapped RFC 8414 document from the assembled Go app's raw route group, backed by a stdlib-only fail-closed go test -json RED verifier every later Phase 8 plan will reuse.

Performance

  • Duration: ~25 min
  • Started: 2026-09-23T17:05:00Z (approx.)
  • Completed: 2026-09-23T17:14:26Z
  • Tasks: 2 completed (4 commits: RED/GREEN pairs across both repos)
  • Files modified: 6 (3 created in summercms.go, 1 created + 2 modified in fonoteka.go)

Accomplishments

  • wristband package created: app-agnostic Options/Server/DefaultOptions plus an exact-byte RFC 8414 metadata writer that never imports fonoteka or gorm.io (verified via go list -deps)
  • GET /.well-known/oauth-authorization-server is now connector-visible on the real assembled Go app: exact PHP-parity bytes, Content-Type: application/json, Cache-Control: no-cache, private, no envelope, no trailing newline
  • Route-table inspection proves the metadata route is Raw, carries zero middleware, and no "oauth" guard is registered anywhere — closing the Phase 6 D-09 reservation per D-10
  • scripts/check-phase8-red.sh implements the shared go/shell fail-closed RED contract (D-04/D-18) that every remaining Phase 8 plan's verify step depends on; manually fuzz-tested against 8 rejection classes (unrelated failing test, compile failure, panic, malformed output, missing sentinel, duplicate sentinel, zero selection, plus both shell-mode wrong-exit and extra-stage-line cases) and the one acceptance case

Task Commits

Each task was committed atomically (TDD RED then GREEN, split per repo where both repos changed):

  1. Task 1: fail-closed RED verification and metadata contracts — 24d35d8 (test, summercms.go): stub Server.Metadata (501), failing TestPhase8RedMetadata, and scripts/check-phase8-red.sh
  2. Task 2a: implement exact metadata writer (GREEN for wristband) — c578bb5 (feat, summercms.go): real Metadata handler, TestMetadataExactBytes, TestMetadataUsesConfiguredOptions
  3. Task 2b: assembled metadata route test (RED for fonoteka.go) — 58bb628 (test, fonoteka.go): TestOAuthMetadataAssembled/RouteIsolation/IssuerTracksAppURL, all failing 404 since the route was unmounted
  4. Task 2c: mount the metadata route (GREEN for fonoteka.go) — 26b2478 (feat, fonoteka.go): Plugin.Boot constructs the server from app.url; routes.go mounts the raw GET route

Plan metadata: committed as part of this summary/state-update commit.

Note: both tasks carry tdd="true"; RED/GREEN pairs land as separate commits, and Task 2 splits its RED/GREEN across the two repositories it touches.

Files Created/Modified

  • wristband/server.go — Options, DefaultOptions, Server, NewServer, Metadata handler, local exact-JSON writer
  • wristband/server_test.go — TestPhase8RedMetadata (RED anchor), TestMetadataExactBytes, TestMetadataUsesConfiguredOptions
  • scripts/check-phase8-red.sh — shared go/shell fail-closed RED verifier for the rest of Phase 8
  • ../fonoteka.go/plugins/golem15/fonoteka/oauth_metadata_test.go — assembled route tests (metadata bytes, route isolation, issuer tracking)
  • ../fonoteka.go/plugins/golem15/fonoteka/plugin.go — constructs wristband.Server in Boot from app.url
  • ../fonoteka.go/plugins/golem15/fonoteka/routes.go — mounts the metadata route on the existing raw group

Decisions Made

  • wristband.Options exposes only the four PHP-configurable metadata fields (D-06); response_types_supported, grant_types_supported, and code_challenge_methods_supported are hardcoded protocol constants inside Metadata, not Options, since this authorization server only ever supports one fixed set of grants/PKCE method (D-01)
  • Plugin.Boot always constructs the OAuth server, even with an empty app.url (issuer becomes ""), rather than failing Boot loudly. A strict fail-loud check was considered (matching the project's general "fail loud, no default" convention) but rejected here because it would break every existing test in the package that boots via bootConfig/testConfig without app.url set — dozens of unrelated tests across plugins/golem15/fonoteka and parity. This is scoped narrowly to this plan; a later plan may tighten validation once the full plugins.golem15.fonoteka.oauth.* config surface (D-03) is wired.
  • D-10 (retiring the Phase 6 "oauth" guard reservation) is recorded here rather than by editing Phase 6 historical docs, per 08-PATTERNS.md's explicit guidance to prefer a supersession note in the Phase 8 plan/summary. TestOAuthMetadataRouteIsolation is the failing-when-broken proof: it asserts reg.Middleware("oauth") returns an error.
  • requirements.mark-complete AUTH-05 AUTH-06 was run per the state-update protocol, then manually reverted in REQUIREMENTS.md (checkbox and traceability table back to Pending). AUTH-05's own text spans authorize/PKCE/consent/token/DCR/refresh, none of which exist yet — only the metadata slice does. Unlike the HTTP-04/AUTH-01 precedent from Phases 6/7 (where the first touching plan already implemented the full requirement), marking AUTH-05/AUTH-06 Complete after Task 2's metadata-only slice would misrepresent phase progress; a later Phase 8 plan (the one that ships authorize/token/DCR/refresh, or 08-10's closing review) should be the one to flip these.

Deviations from Plan

None — plan executed exactly as written. The RED verifier's fixture self-tests (compile failure, panic, malformed output, missing/duplicate sentinel, zero selection, unrelated failure, shell-mode wrong-exit/extra-stage-line) were exercised manually against throwaway fixtures in /tmp during Task 1 to prove the fail-closed contract described in the plan's acceptance criteria; per the plan's file list (wristband/server.go, wristband/server_test.go, scripts/check-phase8-red.sh only), these fixture self-tests are not committed as a permanent Go test suite here — 08-VALIDATION.md row 08-W0-07 assigns the RED gate's own self-validation to plan 08-09.

Issues Encountered

None.

User Setup Required

None — no external service configuration required.

Next Phase Readiness

  • wristband exists as the framework home for the rest of Phase 8's OAuth surface (authorize, token, register, refresh rotation, sweeps) with an established Options/writer convention to extend.
  • scripts/check-phase8-red.sh is ready for immediate reuse by plans 08-02 through 08-09, all of which reference it directly in their verify steps.
  • The corrective OAuth schema/model nullability work flagged in 08-RESEARCH.md ("Required Schema Correction") is still outstanding and is Wave 0 for plan 08-02, not touched here.
  • No blockers.

Self-Check: PASSED

  • FOUND: wristband/server.go, wristband/server_test.go, scripts/check-phase8-red.sh, .planning/phases/08-oauth2-1-authorization-server/08-01-SUMMARY.md
  • FOUND: ../fonoteka.go/plugins/golem15/fonoteka/oauth_metadata_test.go, plugin.go, routes.go
  • FOUND commits: 24d35d8, c578bb5 (summercms.go); 58bb628, 26b2478 (fonoteka.go)