---
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.