--- phase: 08-oauth2-1-authorization-server plan: 02 type: execute wave: 2 depends_on: [08-01] files_modified: - ../fonoteka.go/plugins/golem15/fonoteka/models/oauth_client.go - ../fonoteka.go/plugins/golem15/fonoteka/models/oauth_auth_code.go - ../fonoteka.go/plugins/golem15/fonoteka/updates/12_oauth_schema_correction.go - ../fonoteka.go/plugins/golem15/fonoteka/updates/oauth_schema_correction_test.go - ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go - ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store_test.go autonomous: true requirements: [AUTH-05, AUTH-07] must_haves: truths: - "D-07: Public clients and multiple pending authorization requests persist through one transaction-scoped GORM adapter." - "D-17: Expiry sweeps delete only expired lifecycle rows and retain unexpired replay evidence." artifacts: - path: "../fonoteka.go/plugins/golem15/fonoteka/updates/12_oauth_schema_correction.go" provides: "Additive nullability and index correction with safe rollback refusal" - path: "../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go" provides: "GORM transaction-scoped wristband backend" key_links: - from: "oauth_store.go" to: "wristband.Backend" via: "WithinTx callback whose methods all use callback *gorm.DB" pattern: "WithinTx" --- Make the existing Postgres schema and app store faithfully represent wristband's client and pending-request state. Purpose: Separate persistence correctness from protocol and route wiring so nullability, indexes, locking, cap serialization, and sweep semantics are independently verifiable. Output: Corrected models, additive migration, GORM backend, and real-Postgres tests. @/home/jin/.codex/get-shit-done/workflows/execute-plan.md @/home/jin/.codex/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/08-oauth2-1-authorization-server/08-CONTEXT.md @.planning/phases/08-oauth2-1-authorization-server/08-RESEARCH.md @.planning/phases/08-oauth2-1-authorization-server/08-01-SUMMARY.md Task 1: Specify schema and store behavior in compiling RED tests ../fonoteka.go/plugins/golem15/fonoteka/updates/oauth_schema_correction_test.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store_test.go - Public client secret, pending request id/code hash/user id nullability, named indexes, and rollback refusal are proven on real Postgres. - Two pending requests coexist; atomic cap/sweep/create cannot exceed the configured cap under contention. - Failures emit `PHASE8_RED:persistence` only for absent persistence behavior. D-18: add real-Postgres tests using the existing auth TestMain harness. Compile them against the interfaces from 08-01; use `PHASE8_RED:persistence` assertions for intentionally missing migration/store behavior, and do not use undefined symbols as RED. Include T-08-DCR-FLOOD and transaction-handle tests that detect accidental use of the outer DB. scripts/check-phase8-red.sh persistence bash -lc "cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/classes/auth ./plugins/golem15/fonoteka/updates -run 'TestOAuth(Schema|Store|RegistrationCap)' -count=1" The real-Postgres RED suite compiles, runs named tests, and fails only through the persistence marker. Task 2: Correct OAuth schema and implement the transaction-scoped store ../fonoteka.go/plugins/golem15/fonoteka/models/oauth_client.go, ../fonoteka.go/plugins/golem15/fonoteka/models/oauth_auth_code.go, ../fonoteka.go/plugins/golem15/fonoteka/updates/12_oauth_schema_correction.go, ../fonoteka.go/plugins/golem15/fonoteka/updates/oauth_schema_correction_test.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store_test.go - Four lifecycle fields are pointers and database nullable; PHP-equivalent operational indexes exist. - Down migration refuses when null lifecycle rows would be lost. - Every store mutation uses the callback transaction and app-tier `FOR UPDATE` where required. D-07: correct `client_secret_hash`, `request_id`, `code_hash`, and `user_id` model fields to pointers and implement the wristband Backend/Tx adapter without importing GORM into wristband. Add a new gormigrate step rather than editing applied history; drop four NOT NULL constraints, create named indexes idempotently, and fail rollback if null rows exist. Implement atomic DCR sweep/cap/create, exact expired-row sweep, and row-lock-capable lifecycle methods using only the callback `*gorm.DB`. D-17: retain unexpired rotated/revoked refresh rows. cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/classes/auth ./plugins/golem15/fonoteka/updates -run 'TestOAuth(Schema|Store|RegistrationCap|Sweep)' -count=1 Postgres can persist public clients and concurrent pending requests, exposes required indexes, serializes DCR cap enforcement, and sweeps only expired rows. ## Trust Boundaries | Boundary | Description | |----------|-------------| | wristband records → GORM | App-agnostic state crosses into persistent rows and locks. | ## STRIDE Threat Register | Threat ID | Category | Component | Disposition | Mitigation Plan | |-----------|----------|-----------|-------------|-----------------| | T-08-DCR-FLOOD | Denial of Service | client store | mitigate | Transactionally serialized cap/sweep/create with contention test. | | T-08-CODE-REPLAY | Spoofing | auth-code store | mitigate | App-tier row-lock methods and single transaction handle. | | T-08-REFRESH-REPLAY | Spoofing/Elevation | refresh store | mitigate | Preserve replay evidence until expiry and expose locked traversal. | | T-08-SC | Tampering | dependencies | mitigate | Existing GORM/Postgres only; no install. | - Focused migration/store Postgres tests pass. - `rg -n 'clause.Locking' ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go` finds app-tier locks only. - Schema and store can represent every client/pending/code/refresh lifecycle state. - DCR cap and single-use operations have real-Postgres concurrency evidence. Create `.planning/phases/08-oauth2-1-authorization-server/08-02-SUMMARY.md` when done.