fix(08): revise oauth plans after checker feedback
This commit is contained in:
@@ -5,23 +5,35 @@ type: execute
|
||||
wave: 2
|
||||
depends_on: [08-01]
|
||||
files_modified:
|
||||
- wristband/stores.go
|
||||
- wristband/crypto.go
|
||||
- wristband/register.go
|
||||
- wristband/registration_test.go
|
||||
- ../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
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/config/config.yaml
|
||||
- ../fonoteka.go/config/app.yaml
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/plugin.go
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/routes.go
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/oauth_registration_test.go
|
||||
autonomous: true
|
||||
requirements: [AUTH-05, AUTH-07]
|
||||
requirements: [AUTH-05, AUTH-06, 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."
|
||||
- "D-02/D-21: A connector can dynamically register through the assembled JSON-only 64 KiB-bounded route and receive an exact persistent response."
|
||||
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"
|
||||
- path: "../fonoteka.go/plugins/golem15/fonoteka/routes.go"
|
||||
provides: "Connector-visible persistent RFC 7591 registration route"
|
||||
key_links:
|
||||
- from: "oauth_store.go"
|
||||
to: "wristband.Backend"
|
||||
@@ -30,10 +42,10 @@ must_haves:
|
||||
---
|
||||
|
||||
<objective>
|
||||
Make the existing Postgres schema and app store faithfully represent wristband's client and pending-request state.
|
||||
Deliver a connector-visible persistent RFC 7591 registration slice, including the schema and transaction semantics it requires.
|
||||
|
||||
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.
|
||||
Purpose: Let a real connector register in Wave 2 while proving nullability, indexes, bounds, constant-time secret handling, locking, cap serialization, and sweep semantics.
|
||||
Output: Corrected models/migration, wristband DCR, GORM backend, configured raw route, and exact assembled tests.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@@ -53,33 +65,92 @@ Output: Corrected models, additive migration, GORM backend, and real-Postgres te
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Specify schema and store behavior in compiling RED tests</name>
|
||||
<files>../fonoteka.go/plugins/golem15/fonoteka/updates/oauth_schema_correction_test.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store_test.go</files>
|
||||
<name>Task 1: Correct the OAuth lifecycle schema with executable migration evidence</name>
|
||||
<files>../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</files>
|
||||
<read_first>
|
||||
.planning/phases/08-oauth2-1-authorization-server/08-PATTERNS.md
|
||||
../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/11_secrets_slice.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/updates/registry.go
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/updates/v1.1.7/create_oauth_tables.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/updates/v1.1.9/add_scope_ceiling_to_oauth_clients.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/functional/OAuthMigrationTest.php
|
||||
</read_first>
|
||||
<behavior>
|
||||
- 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.
|
||||
- Public client secret and pending request id/code hash/user id are pointer-backed and nullable in real Postgres.
|
||||
- PHP-equivalent named operational indexes exist and safe rollback refuses when null lifecycle data exists.
|
||||
- `TestPhase8RedOAuthSchema` is the only selected failing test/action during RED.
|
||||
</behavior>
|
||||
<action>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.</action>
|
||||
<action>D-07 and D-18: first add a compiling real-Postgres `TestPhase8RedOAuthSchema`, validate it with `check-phase8-red.sh go`, then change the four model fields to pointers and add a new gormigrate correction rather than editing applied history. Drop four NOT NULL constraints, create the exact named indexes idempotently, and make rollback refuse without coercing/deleting when null lifecycle rows exist. Use the existing migration/Postgres harness and PHP schema/tests as the contract.</action>
|
||||
<verify>
|
||||
<automated>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"</automated>
|
||||
<automated>(cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/updates -run '^TestOAuthSchemaCorrection$' -count=1)</automated>
|
||||
</verify>
|
||||
<done>The real-Postgres RED suite compiles, runs named tests, and fails only through the persistence marker.</done>
|
||||
<acceptance_criteria>
|
||||
- Before implementation, `scripts/check-phase8-red.sh go PHASE8_RED:persistence-schema git.golem15.com/golem15/fonoteka/plugins/golem15/fonoteka/updates TestPhase8RedOAuthSchema -- bash -lc "cd ../fonoteka.go && go test -json ./plugins/golem15/fonoteka/updates -run '^TestPhase8RedOAuthSchema$' -count=1"` accepts only the exact behavior RED.
|
||||
- After implementation, real-Postgres assertions prove all four nullable columns and every PHP-equivalent named index.
|
||||
- Down succeeds when safe and refuses with rows unchanged when any required field is null; applied migration history remains byte-unchanged.
|
||||
</acceptance_criteria>
|
||||
<done>The corrected additive schema can represent public clients and every pending/code transition without destructive rollback.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Correct OAuth schema and implement the transaction-scoped store</name>
|
||||
<files>../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</files>
|
||||
<name>Task 2: Implement bounded DCR and the transaction-scoped persistent backend</name>
|
||||
<files>wristband/stores.go, wristband/crypto.go, wristband/register.go, wristband/registration_test.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store_test.go</files>
|
||||
<read_first>
|
||||
.planning/phases/08-oauth2-1-authorization-server/08-CONTEXT.md
|
||||
.planning/phases/08-oauth2-1-authorization-server/08-PATTERNS.md
|
||||
../fonoteka.go/plugins/golem15/fonoteka/classes/active_collection.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/classes/auth/postgres_test.go
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthRegisterController.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/models/OAuthClient.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/functional/OAuthRegisterTest.php
|
||||
</read_first>
|
||||
<behavior>
|
||||
- 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.
|
||||
- JSON-only DCR enforces URI/grant/response/auth-method/name rules, 65,536-byte maximum, cap 200, and 24h stale-unconsented sweep.
|
||||
- Raw client secrets are returned once, SHA-256 hashes alone persist, and verification uses `crypto/subtle.ConstantTimeCompare` over fixed transforms.
|
||||
- Sweep/cap/create share one transaction; concurrent cap-1 registration yields one success and one native error.
|
||||
</behavior>
|
||||
<action>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.</action>
|
||||
<action>D-01/D-02/D-04/D-05/D-06/D-07/D-17/D-21: add app-agnostic Backend/Tx records, deterministic clock/entropy seams, fixed-transform crypto, a local exact response writer, and the RFC 7591 handler. Apply `http.MaxBytesReader` before decode and return the native `invalid_client_metadata` body for overflow/malformed/non-JSON. Strip control characters, cap names at 120, return raw secrets once, and persist hashes only. Implement the app GORM adapter using only the callback `*gorm.DB`; serialize stale sweep/cap/create in one transaction and expose later row-lock lifecycle methods without importing GORM into wristband. First validate exact `TestPhase8RedRegistration` and `TestPhase8RedRegistrationStore` JSON RED streams, then make focused unit/Postgres tests green.</action>
|
||||
<verify>
|
||||
<automated>cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/classes/auth ./plugins/golem15/fonoteka/updates -run 'TestOAuth(Schema|Store|RegistrationCap|Sweep)' -count=1</automated>
|
||||
<automated>go test ./wristband -run '^Test(Register|Registration)' -count=1 && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/classes/auth -run '^TestOAuth(RegistrationStore|RegistrationCap)$' -count=1)</automated>
|
||||
</verify>
|
||||
<done>Postgres can persist public clients and concurrent pending requests, exposes required indexes, serializes DCR cap enforcement, and sweeps only expired rows.</done>
|
||||
<acceptance_criteria>
|
||||
- RED uses `go test -json` with exact package/test/sentinel for `TestPhase8RedRegistration` and `TestPhase8RedRegistrationStore`; no unrelated failure can satisfy either invocation.
|
||||
- Exact tests cover public/confidential responses, wrong content type, malformed/oversized 65,537-byte input, five-URI/count/length bounds, unsupported grant/response/auth method, control-character name cleaning, and no newline/envelope.
|
||||
- A synchronized real-Postgres cap-1 test yields exactly one created row; stale unconsented rows are swept while consented/fresh rows remain, and all mutations use the callback transaction.
|
||||
- Persisted/logged/output audits find no raw client secret; fixed-transform comparison contains `crypto/subtle.ConstantTimeCompare`.
|
||||
</acceptance_criteria>
|
||||
<done>Wristband and Postgres provide exact bounded, concurrency-safe, secret-safe registration behavior.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 3: Configure and mount persistent DCR on the assembled raw surface</name>
|
||||
<files>../fonoteka.go/plugins/golem15/fonoteka/config/config.yaml, ../fonoteka.go/config/app.yaml, ../fonoteka.go/plugins/golem15/fonoteka/plugin.go, ../fonoteka.go/plugins/golem15/fonoteka/routes.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_registration_test.go</files>
|
||||
<read_first>
|
||||
.planning/phases/08-oauth2-1-authorization-server/08-UI-SPEC.md
|
||||
../fonoteka.go/plugins/golem15/fonoteka/plugin.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/routes.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/config/config.yaml
|
||||
../fonoteka.go/config/app.yaml
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/routes.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/config/fonoteka.php
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Assembled POST `/oauth/mcp/register` persists public/confidential clients and returns exact PHP bytes/headers.
|
||||
- Only register carries `throttle:fonoteka-oauth-register`; metadata remains raw with no middleware.
|
||||
- Config defaults are pending/code 600s, access 3600s, refresh 30 days, DCR cap 200, stale age 24h, resource URL, and register max 65,536.
|
||||
</behavior>
|
||||
<action>D-03: add `plugins.golem15.fonoteka.oauth.*` defaults and construct the store-backed server in Plugin.Boot while preserving 08-01 metadata. D-09: mount register in the raw group with only its named throttle. D-10/D-12: register no oauth guard and add no rich Bearer/resource-server surface. Add an assembled real-Postgres `TestPhase8RedRegistrationApp` first, validate its exact JSON RED stream, then assert exact bytes/headers, durable reload, middleware isolation, config values, and unchanged metadata.</action>
|
||||
<verify>
|
||||
<automated>(cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestOAuth(RegisterAssembled|MetadataAssembled|RawRegistrationSurface)$' -count=1)</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- RED command names exact app package, `TestPhase8RedRegistrationApp`, and `PHASE8_RED:registration-app` under `go test -json`; compile/setup/no-test or another failing test is rejected.
|
||||
- A public and confidential registration each return status 201 and exact fields/order/headers; reload through a fresh transaction finds hash-only rows with null/non-null secret hash as appropriate.
|
||||
- Oversized and wrong-content-type requests retain endpoint-native errors through the assembled router; register has only its named limiter and metadata remains byte-identical to 08-01.
|
||||
</acceptance_criteria>
|
||||
<done>An unchanged connector can dynamically register against the assembled app and its client persists correctly in Postgres.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
@@ -102,7 +173,7 @@ Output: Corrected models, additive migration, GORM backend, and real-Postgres te
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- Focused migration/store Postgres tests pass.
|
||||
- Focused migration/store/DCR tests pass; no task command runs full repositories, race, parity, UI, or real MCP.
|
||||
- `rg -n 'clause.Locking' ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go` finds app-tier locks only.
|
||||
</verification>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user