fix(08): revise oauth plans after checker feedback
This commit is contained in:
@@ -20,7 +20,7 @@ Decimal phases appear between their surrounding integers in numeric order.
|
||||
- [x] **Phase 5: Data layer full fidelity** - All 25 models and their squashed migrations with fillable/hidden/cast/soft-delete discipline (completed 2026-09-18)
|
||||
- [x] **Phase 6: HTTP routing, auth groups and rate limiting** - Three auth groups, named rate buckets, OAuth-safe middleware structure (completed 2026-09-21)
|
||||
- [x] **Phase 7: User plugin and authentication** - Registration, login, JWT, organizations, personal tokens, must-change-password (completed 2026-09-22)
|
||||
- [ ] **Phase 8: OAuth2.1 authorization server** - zitadel/oidc server for fonoteka-mcp and the ChatGPT connector
|
||||
- [ ] **Phase 8: OAuth2.1 authorization server** - direct standard-library `wristband` server for fonoteka-mcp and the ChatGPT connector
|
||||
- [ ] **Phase 9: Backend admin authentication and schema pipeline** - Admin roles, fields.yaml/columns.yaml, relation manager
|
||||
- [ ] **Phase 10: Admin Vue SPA** - Login, navigation, lists, forms and relation manager for five controllers
|
||||
- [ ] **Phase 11: Jobs, realtime and search infrastructure** - River, Centrifugo and Typesense sync brought up before the API phases that need them
|
||||
@@ -318,26 +318,26 @@ Plans:
|
||||
3. The token endpoint returns exactly `WWW-Authenticate: Basic realm="OAuth"` on `invalid_client`, the backend personal-token 401 remains unchanged with no added challenge, and fonoteka-mcp's own rich Bearer challenge plus protected-resource metadata are verified through its actual discovery flow, not just a Go unit test.
|
||||
4. Connected apps can be listed and revoked; `OAuthClient`/`OAuthAuthCode`/`OAuthRefreshToken` models persist correctly; fonoteka-mcp completes its install and auth flow unchanged; client-secret comparison uses `crypto/subtle.ConstantTimeCompare`.
|
||||
|
||||
**Plans**: 6 plans
|
||||
**Plans**: 10 plans
|
||||
**Research flag:** yes
|
||||
|
||||
Plans:
|
||||
|
||||
**Wave 1**
|
||||
|
||||
- [ ] 08-01-PLAN.md — Define and prove the app-agnostic metadata and DCR engine
|
||||
- [ ] 08-01-PLAN.md — Mount exact connector-visible metadata and establish fail-closed RED verification
|
||||
|
||||
**Wave 2** *(blocked on 08-01)*
|
||||
|
||||
- [ ] 08-02-PLAN.md — Correct OAuth schema and implement transaction-scoped Postgres stores
|
||||
- [ ] 08-02-PLAN.md — Deliver persistent connector-visible DCR with corrected schema and transaction-scoped stores
|
||||
|
||||
**Wave 3** *(blocked on 08-02)*
|
||||
|
||||
- [ ] 08-03-PLAN.md — Mount persistent metadata and DCR on the assembled raw route surface
|
||||
- [ ] 08-03-PLAN.md — Create durable PKCE-bound authorize requests on the assembled raw route surface
|
||||
|
||||
**Wave 4** *(blocked on 08-03)*
|
||||
|
||||
- [ ] 08-04-PLAN.md — Implement ordered authorize validation and atomic PKCE code exchange
|
||||
- [ ] 08-04-PLAN.md — Implement atomic PKCE-bound authorization-code exchange
|
||||
|
||||
**Wave 5** *(blocked on 08-04)*
|
||||
|
||||
@@ -354,7 +354,7 @@ Plans:
|
||||
|
||||
**Wave 8** *(blocked on 08-07 and 08-08)*
|
||||
|
||||
- [ ] 08-09-PLAN.md — Replay PHP OAuth flows and run the unchanged real MCP lifecycle through the pre-security gate
|
||||
- [ ] 08-09-PLAN.md — Replay PHP OAuth flows and assemble the self-validating final unchanged-MCP gate
|
||||
|
||||
**Wave 9** *(blocked on 08-09; blocking security checkpoint)*
|
||||
|
||||
|
||||
@@ -6,43 +6,42 @@ wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- wristband/server.go
|
||||
- wristband/stores.go
|
||||
- wristband/crypto.go
|
||||
- wristband/register.go
|
||||
- wristband/registration_test.go
|
||||
- wristband/server_test.go
|
||||
- scripts/check-phase8-red.sh
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/plugin.go
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/routes.go
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/oauth_metadata_test.go
|
||||
autonomous: true
|
||||
requirements: [AUTH-05, AUTH-06]
|
||||
must_haves:
|
||||
truths:
|
||||
- "D-01: The app-agnostic standard-library wristband package serves exact RFC 8414 metadata and validates RFC 7591 registration without zitadel/oidc."
|
||||
- "D-01: A connector can fetch exact RFC 8414 metadata from the assembled Go app without zitadel/oidc."
|
||||
- "D-06: PHP-minimal response shapes and configurable metadata fields are wristband defaults with no response hooks."
|
||||
- "D-02: Registration is JSON-only, and D-21: its body is bounded at 64 KiB before decoding with endpoint-native errors."
|
||||
- "D-04: Client-secret checks use constant-time fixed transforms and DCR cap/sweep behavior is deterministic under concurrency."
|
||||
- "D-09: Metadata is mounted on the raw route surface without house, JWT, personal-token, or oauth-guard middleware."
|
||||
artifacts:
|
||||
- path: "wristband/server.go"
|
||||
provides: "Options, exact metadata writer, and app-agnostic server contract"
|
||||
- path: "wristband/register.go"
|
||||
provides: "RFC 7591 validation, issuance, cap, sweep, and bounded handler"
|
||||
- path: "../fonoteka.go/plugins/golem15/fonoteka/routes.go"
|
||||
provides: "Connector-visible raw metadata route"
|
||||
- path: "scripts/check-phase8-red.sh"
|
||||
provides: "Fail-closed RED verifier rejecting syntax/setup/missing-test failures"
|
||||
key_links:
|
||||
- from: "wristband/register.go"
|
||||
to: "wristband.Backend.WithinTx"
|
||||
via: "serialized sweep, cap check, and create"
|
||||
pattern: "WithinTx"
|
||||
- from: "../fonoteka.go/plugins/golem15/fonoteka/routes.go"
|
||||
to: "wristband.Server.Metadata"
|
||||
via: "assembled raw GET route"
|
||||
pattern: "oauth-authorization-server"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Define and implement the framework-only discovery and dynamic-registration contract before app persistence or routing.
|
||||
Deliver an assembled connector-visible RFC 8414 metadata endpoint and the fail-closed RED infrastructure used by every later slice.
|
||||
|
||||
Purpose: Keep D-01's protocol engine small and app-agnostic while making the RED phase executable and diagnostic.
|
||||
Output: `wristband` metadata/DCR contracts, deterministic tests, crypto helpers, and the shared RED verifier.
|
||||
Purpose: Obtain end-to-end feedback in the first wave while keeping the protocol writer app-agnostic and making every later RED phase diagnostic.
|
||||
Output: `wristband` metadata contract, mounted raw route, assembled exact-byte tests, and the shared RED verifier.
|
||||
</objective>
|
||||
|
||||
## Phase Goal
|
||||
|
||||
**As a** connector implementer, **I want to** exercise discovery and registration against a deterministic OAuth engine, **so that** the app adapter can persist and mount an already proven wire contract.
|
||||
**As a** connector implementer, **I want to** discover the assembled Go authorization server, **so that** I can obtain its exact OAuth endpoints and capabilities before registering.
|
||||
|
||||
<execution_context>
|
||||
@/home/jin/.codex/get-shit-done/workflows/execute-plan.md
|
||||
@@ -62,33 +61,60 @@ Output: `wristband` metadata/DCR contracts, deterministic tests, crypto helpers,
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Create compiling RED discovery and registration contracts</name>
|
||||
<files>wristband/server.go, wristband/stores.go, wristband/registration_test.go, scripts/check-phase8-red.sh</files>
|
||||
<name>Task 1: Create fail-closed RED verification and metadata contracts</name>
|
||||
<files>wristband/server.go, wristband/server_test.go, scripts/check-phase8-red.sh</files>
|
||||
<read_first>
|
||||
.planning/phases/08-oauth2-1-authorization-server/08-CONTEXT.md
|
||||
.planning/phases/08-oauth2-1-authorization-server/08-PATTERNS.md
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthMetadataController.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/functional/OAuthMetadataTest.php
|
||||
wire/response.go
|
||||
scripts/check-phase3.sh
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Metadata is the exact unwrapped 11-field document with recorded order, content type, and cache header.
|
||||
- Public and confidential DCR validate PHP-compatible URI, grant, response, auth-method, cap, sweep, and 65,536-byte rules.
|
||||
- Test failures use `PHASE8_RED:registration` only for missing behavior; syntax, build, setup, missing-test, panic, and unrelated failures are rejected.
|
||||
- Metadata is the exact unwrapped 11-field document with recorded order, `application/json`, no trailing newline, and the PHP cache header.
|
||||
- The selected RED test fails with `PHASE8_RED:metadata` only because metadata behavior is absent.
|
||||
- Any other failed test/package/action, compilation/setup failure, panic, malformed JSON event stream, or zero selected tests makes the verifier fail.
|
||||
</behavior>
|
||||
<action>D-06: define the exported options, typed records, transaction-scoped Backend/Tx contracts, handler signatures, and deterministic clock/random seams with compiling stubs. D-18: add behavior tests that compile and intentionally fail through `PHASE8_RED:registration` assertions. Create `scripts/check-phase8-red.sh` to run the supplied command, require a nonzero result and the requested marker, and fail if output contains `build failed`, `setup failed`, `syntax error`, `no tests to run`, `no test files`, or a panic. Include named T-08-DCR-FLOOD, T-08-SECRET-TIMING, and T-08-REQUEST-LEAK cases. Commit RED separately.</action>
|
||||
<action>D-06 and D-18: define only the exported metadata options/server handler needed by this slice, with compiling stubs and an exact `TestPhase8RedMetadata` assertion. Implement `scripts/check-phase8-red.sh` with two explicit modes. In `go` mode accept `sentinel`, exact package import path, exact test name, `--`, and a required `go test -json` command; parse every JSON event, require the selected test to emit the exact sentinel and fail, require its package to fail, require at least one selected test run, and reject every other `Action:"fail"` test/package, non-JSON output, compile/build/setup/syntax failure, panic, timeout, and no-test/zero-selection result. Package-level fail is allowed only for the named package after the named test failure. In `shell` mode accept `sentinel`, exact stage, and a command; require exit 86 and exactly one line `PHASE8_STAGE:<stage>:FAIL:<sentinel>`, reject every other FAIL/ERROR/PANIC stage and missing/extra sentinel. D-01: use only the standard library. Commit RED separately.</action>
|
||||
<verify>
|
||||
<automated>scripts/check-phase8-red.sh registration go test ./wristband -run 'Test(Metadata|Register|Registration)' -count=1</automated>
|
||||
<automated>scripts/check-phase8-red.sh go PHASE8_RED:metadata git.golem15.com/golem15/summercms/wristband TestPhase8RedMetadata -- go test -json ./wristband -run '^TestPhase8RedMetadata$' -count=1</automated>
|
||||
</verify>
|
||||
<done>The tests compile, the named tests execute, and the verifier accepts only the expected missing-behavior RED marker.</done>
|
||||
<acceptance_criteria>
|
||||
- The verifier accepts one JSON stream containing only `TestPhase8RedMetadata` plus its package failure and exact sentinel.
|
||||
- Fixture self-tests reject an unrelated failing test, another failing package/action, compile/setup failure, panic, malformed/non-JSON output, missing sentinel, duplicate sentinel, and zero selected tests.
|
||||
- Metadata RED asserts the exact 11-field byte order, `Content-Type: application/json`, cache header, status 200, and no trailing newline.
|
||||
</acceptance_criteria>
|
||||
<done>The metadata RED test compiles and the shared verifier is fail-closed against every unrelated or non-behavior failure class.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Implement exact metadata, DCR, bounds, and cryptography</name>
|
||||
<files>wristband/server.go, wristband/stores.go, wristband/crypto.go, wristband/register.go, wristband/registration_test.go</files>
|
||||
<name>Task 2: Implement and mount exact metadata on the assembled app</name>
|
||||
<files>wristband/server.go, wristband/server_test.go, ../fonoteka.go/plugins/golem15/fonoteka/plugin.go, ../fonoteka.go/plugins/golem15/fonoteka/routes.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_metadata_test.go</files>
|
||||
<read_first>
|
||||
wristband/server_test.go
|
||||
surf/router.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/plugin.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/routes.go
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/routes.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthMetadataController.php
|
||||
../fonoteka.go/parity/fixtures/mcp/mcp-oauth.yaml
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Fixed SHA-256 transforms use `crypto/subtle.ConstantTimeCompare`; raw client secrets are returned once and never persisted/logged.
|
||||
- Sweep, cap check, and create occur within one backend transaction; concurrent registrations cannot cross the cap.
|
||||
- Oversized and malformed registration input returns exact `invalid_client_metadata` bytes without a house envelope.
|
||||
- Direct handler and assembled route return the same exact metadata bytes and headers.
|
||||
- The route has no house/JWT/personal-token middleware and no `oauth` guard registration.
|
||||
- Issuer is `app.url` with one trailing slash trim; endpoint/resource/service-documentation/scope/auth-method values match PHP defaults.
|
||||
</behavior>
|
||||
<action>D-01: use only `crypto/rand`, `crypto/sha256`, `crypto/subtle`, `encoding/base64`, `encoding/json`, `net/http`, and `net/url`; add no dependency. D-03: model configurable TTLs, cap 200, stale age 24h, issuer/resource/endpoints, and `RegisterMaxBytes: 65536`. D-05: keep all protocol rules in wristband and import no fonoteka/GORM code. D-06: use a local no-newline exact JSON writer with no response hooks. D-07: use only the transaction-scoped interfaces. D-17: expose expired-row and unconsented-client sweep operations without timers/goroutines. D-21: apply `http.MaxBytesReader` before JSON decode. Strip control characters and cap names at 120 characters.</action>
|
||||
<action>D-01, D-03, D-05, and D-06: implement the local no-newline exact JSON metadata writer and configurable values without response hooks or app imports. Construct the metadata-capable server during app boot from `app.url` and locked PHP defaults, retain it on Plugin, and mount only `GET /.well-known/oauth-authorization-server` in the existing raw group. D-09/D-10/D-12: attach no middleware, register no oauth guard, and do not add protected-resource metadata or Bearer challenges. Add an assembled test that boots the real plugin/router and compares status, headers, and exact bytes to the PHP contract.</action>
|
||||
<verify>
|
||||
<automated>go test ./wristband -run 'Test(Metadata|Register|Registration)' -count=1</automated>
|
||||
<automated>go test ./wristband -run '^TestMetadata' -count=1 && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestOAuthMetadataAssembled$' -count=1)</automated>
|
||||
</verify>
|
||||
<done>Framework discovery and DCR tests pass with exact bytes, atomic cap behavior, bounded decoding, hash-only persistence, and no app-tier imports.</done>
|
||||
<acceptance_criteria>
|
||||
- Direct and assembled GET return status 200, exact PHP metadata bytes in field order, `Content-Type: application/json`, the expected cache header, and no envelope/newline.
|
||||
- Route inspection shows only the raw GET path and rejects `jwt.auth`, `inv_token`, `inv.scope`, body-limit, house tags, and an `oauth` guard.
|
||||
- Changing `app.url` changes issuer/endpoints after exactly one trailing-slash trim; framework import checks find no fonoteka or GORM reference.
|
||||
</acceptance_criteria>
|
||||
<done>A connector can fetch the exact metadata contract from the assembled production router in Wave 1.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
@@ -98,28 +124,26 @@ Output: `wristband` metadata/DCR contracts, deterministic tests, crypto helpers,
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| Connector → wristband | Untrusted metadata/DCR requests cross into protocol parsing. |
|
||||
| wristband → Backend | Protocol state crosses into an app-provided transaction. |
|
||||
| Connector → assembled raw route | Untrusted discovery traffic reaches the exact wristband writer. |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-08-DCR-FLOOD | Denial of Service | register handler/store | mitigate | 64 KiB cap, serialized client cap, stale sweep, concurrency tests. |
|
||||
| T-08-SECRET-TIMING | Information Disclosure | crypto/client secret | mitigate | Fixed SHA-256 transforms and `subtle.ConstantTimeCompare`. |
|
||||
| T-08-REQUEST-LEAK | Information Disclosure | handler/tests | mitigate | Hash-only records and no sensitive-value logging. |
|
||||
| T-08-SURFACE | Elevation | raw metadata route | mitigate | Assembled route inspection proves no guard/house middleware. |
|
||||
| T-08-REQUEST-LEAK | Information Disclosure | metadata/config | mitigate | Only public configured metadata fields are serialized. |
|
||||
| T-08-SC | Tampering | dependencies | mitigate | No package install; stdlib-only import audit. |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- `go test ./wristband -count=1`
|
||||
- `go test ./wristband -run '^TestMetadata' -count=1`
|
||||
- `cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestOAuthMetadataAssembled$' -count=1`
|
||||
- `go list -deps ./wristband | rg 'fonoteka|gorm.io'` returns no matches.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Exact metadata and DCR behavior is green in a self-contained framework package.
|
||||
- RED verification cannot pass on mere file presence, compile errors, missing tests, or unrelated failures.
|
||||
- DCR is bounded, concurrency-safe, and secret-safe before app integration.
|
||||
- Exact metadata is connector-visible through the assembled app.
|
||||
- RED verification cannot pass on compile/setup/panic/no-test errors or any unrelated test/package/stage failure.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -5,36 +5,36 @@ type: execute
|
||||
wave: 3
|
||||
depends_on: [08-02]
|
||||
files_modified:
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/config/config.yaml
|
||||
- ../fonoteka.go/config/app.yaml
|
||||
- wristband/authorize.go
|
||||
- wristband/authorize_test.go
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/plugin.go
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/routes.go
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/oauth_registration_test.go
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/oauth_authorize_test.go
|
||||
autonomous: true
|
||||
requirements: [AUTH-05, AUTH-06, AUTH-07]
|
||||
must_haves:
|
||||
truths:
|
||||
- "D-03: The assembled app exposes configured PHP-default TTLs, caps, issuer, resource, consent URL, and registration bound."
|
||||
- "D-09: Metadata and registration are raw routes and registration alone carries its named throttle."
|
||||
- "D-02: Authorize reads query only and validates the client and exact redirect before any redirect response."
|
||||
- "D-05: S256, scope/resource policy, ordered RFC3986 errors, and pending-request creation live in wristband."
|
||||
- "D-09: Authorize is connector-visible on the raw route surface without house/auth middleware."
|
||||
- "D-10: No oauth guard is registered; OAuth access remains on inv_token."
|
||||
- "D-12: Backend challenge ownership stays unchanged and RFC 9728 behavior remains in fonoteka-mcp."
|
||||
artifacts:
|
||||
- path: "../fonoteka.go/plugins/golem15/fonoteka/plugin.go"
|
||||
provides: "Configured store-backed wristband server construction"
|
||||
- path: "wristband/authorize.go"
|
||||
provides: "Ordered validation, S256/resource/scope policy, and pending request creation"
|
||||
- path: "../fonoteka.go/plugins/golem15/fonoteka/routes.go"
|
||||
provides: "Raw metadata and register route mounting"
|
||||
provides: "Assembled raw authorize route"
|
||||
key_links:
|
||||
- from: "plugin.go"
|
||||
to: "wristband.New"
|
||||
via: "configured Options and GORM backend"
|
||||
pattern: "wristband\\.New"
|
||||
to: "wristband.Server.Authorize"
|
||||
via: "configured server retained from persistent DCR slice"
|
||||
pattern: "oauth/mcp/authorize"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Mount the proven discovery/DCR engine on the real application with persistent state and exact raw-route isolation.
|
||||
Deliver an assembled connector-visible authorize-request slice that creates durable pending consent state with exact PKCE/redirect/scope/resource behavior.
|
||||
|
||||
Purpose: Deliver the first connector-visible vertical outcome without mixing schema work into protocol implementation.
|
||||
Output: OAuth config, boot wiring, raw routes, and assembled Postgres-backed tests.
|
||||
Purpose: Let a connector start authorization immediately after persistent DCR, leaving only consent and exchange for subsequent slices.
|
||||
Output: Wristband authorize handler, assembled raw route, pending-request persistence, and exact unit/integration tests.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@@ -47,39 +47,69 @@ Output: OAuth config, boot wiring, raw routes, and assembled Postgres-backed tes
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/08-oauth2-1-authorization-server/08-CONTEXT.md
|
||||
@.planning/phases/08-oauth2-1-authorization-server/08-UI-SPEC.md
|
||||
@.planning/phases/08-oauth2-1-authorization-server/08-PATTERNS.md
|
||||
@.planning/phases/08-oauth2-1-authorization-server/08-02-SUMMARY.md
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Specify assembled discovery and registration in RED</name>
|
||||
<files>../fonoteka.go/plugins/golem15/fonoteka/oauth_registration_test.go</files>
|
||||
<name>Task 1: Specify authorize validation and assembled pending-request behavior in RED</name>
|
||||
<files>wristband/authorize.go, wristband/authorize_test.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_authorize_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
|
||||
wristband/server.go
|
||||
wristband/stores.go
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthAuthorizeController.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/models/OAuthClient.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/functional/OAuthAuthorizeTest.php
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Assembled routes return exact metadata and persistent public/confidential DCR responses.
|
||||
- Route table rejects JWT, inv_token, inv.scope, body-limit, and house middleware; register has only its named throttle.
|
||||
- Failures use `PHASE8_RED:registration-app`, not compile/setup/missing-test failure.
|
||||
- Unknown/unusable client and unregistered redirect return exact local text/plain 400 with no Location.
|
||||
- Later failures redirect with ordered `error`, `error_description`, `iss`, optional `state` using RFC3986 bytes.
|
||||
- Valid S256 request stores one pending row and redirects to `/connect?request=<opaque>`; exact framework/app RED tests are the only failures.
|
||||
</behavior>
|
||||
<action>D-18: add an assembled-router real-Postgres test against existing boot seams. Assert bytes and headers before decoding, exact configured defaults, route isolation, and no oauth guard. Keep the test compiling against 08-01/08-02 contracts and mark only absent app wiring with `PHASE8_RED:registration-app`.</action>
|
||||
<action>D-02/D-04/D-05/D-18: define compiling authorize seams, deterministic unit cases, and an assembled real-Postgres case. Preserve exact validation order: usable client, exact redirect, `response_type=code`, `code_challenge_method=S256`, verifier syntax/challenge, scope ceiling, resource, pending creation. Build redirects from ordered pairs, never `url.Values.Encode`. Use exact `TestPhase8RedAuthorize`/`PHASE8_RED:authorize` and `TestPhase8RedAuthorizeApp`/`PHASE8_RED:authorize-app` JSON verifier invocations; reject every unexpected failing action/package/test and non-behavior failure.</action>
|
||||
<verify>
|
||||
<automated>scripts/check-phase8-red.sh registration-app bash -lc "cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestOAuth(Metadata|Register|RawRoute|Config)' -count=1"</automated>
|
||||
<automated>scripts/check-phase8-red.sh go PHASE8_RED:authorize git.golem15.com/golem15/summercms/wristband TestPhase8RedAuthorize -- go test -json ./wristband -run '^TestPhase8RedAuthorize$' -count=1 && scripts/check-phase8-red.sh go PHASE8_RED:authorize-app git.golem15.com/golem15/fonoteka/plugins/golem15/fonoteka TestPhase8RedAuthorizeApp -- bash -lc "cd ../fonoteka.go && go test -json ./plugins/golem15/fonoteka -run '^TestPhase8RedAuthorizeApp$' -count=1"</automated>
|
||||
</verify>
|
||||
<done>Assembled tests execute and fail solely because config/boot/routes are not wired.</done>
|
||||
<acceptance_criteria>
|
||||
- Both RED invocations select exactly one named test in one named package and reject compile/setup/panic/no-test/unrelated failures.
|
||||
- Tables assert exact status, Content-Type, body, Location absence/presence, parameter order, `%20` encoding, optional state placement, and no credential/request-handle logging.
|
||||
- The assembled RED test uses the real boot/router/Postgres seams and fails only because authorize is not mounted/implemented.
|
||||
</acceptance_criteria>
|
||||
<done>Executable RED evidence completely specifies the connector-visible authorize contract.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Configure, boot, and route persistent discovery and DCR</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>
|
||||
<name>Task 2: Implement and mount exact authorize request creation</name>
|
||||
<files>wristband/authorize.go, wristband/authorize_test.go, ../fonoteka.go/plugins/golem15/fonoteka/plugin.go, ../fonoteka.go/plugins/golem15/fonoteka/routes.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_authorize_test.go</files>
|
||||
<read_first>
|
||||
wristband/authorize_test.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/oauth_authorize_test.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/plugin.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/routes.go
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthAuthorizeController.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/classes/auth/OAuthCodeManager.php
|
||||
../fonoteka.go/parity/fixtures/mcp/mcp-oauth.yaml
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Defaults are pending/code 600s, access 3600s, refresh 30 days, DCR cap 200, stale age 24h, resource URL, and 65,536-byte register maximum.
|
||||
- Issuer trims the app URL once; metadata and registration use the actual GORM backend.
|
||||
- Valid requests persist a hash-only opaque pending handle with requested/ceiling scopes, exact redirect/resource/client binding, challenge, state, and 600s expiry.
|
||||
- Scope output preserves PHP order and never exceeds the registered ceiling; resource mismatch cannot create pending state.
|
||||
- Assembled authorize route is raw and metadata/DCR remain unchanged.
|
||||
</behavior>
|
||||
<action>D-03: add `plugins.golem15.fonoteka.oauth.*` defaults and use `app.url` as issuer. Construct the backend and wristband server in Plugin.Boot and retain it for later route/command factories. D-09: mount metadata and register inside the existing raw group; pass `throttle:fonoteka-oauth-register` only to register. D-10: register no oauth guard. D-12: preserve the exact backend personal-token 401 and do not add protected-resource metadata or rich Bearer challenges.</action>
|
||||
<action>D-02/D-03/D-04/D-05/D-06: implement query-only parsing, exact ordered validation/redirects, constant-time S256 verification seam, scope/resource policy, opaque pending creation, and 600-second expiry using the configured server/backend from 08-02. D-09: mount GET `/oauth/mcp/authorize` raw with no middleware. D-10/D-12: register no oauth guard and add no backend Bearer/resource metadata. Preserve metadata/register behavior byte-for-byte and prove invalid requests create no rows.</action>
|
||||
<verify>
|
||||
<automated>cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestOAuth(Metadata|Register|RawRoute|Config)' -count=1</automated>
|
||||
<automated>go test ./wristband -run '^Test(Authorize|OrderedRedirect|PKCE)' -count=1 && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestOAuthAuthorizeAssembled$' -count=1)</automated>
|
||||
</verify>
|
||||
<done>An unchanged connector can discover and dynamically register against the assembled app with persistent Postgres state and exact route boundaries.</done>
|
||||
<acceptance_criteria>
|
||||
- Unknown client/unregistered redirect have no Location; each later error has exact ordered RFC3986 Location bytes including issuer and optional state.
|
||||
- Missing/plain/malformed S256, excess/unknown scope, wrong resource, query/body ambiguity, and unusable client create zero pending rows.
|
||||
- A valid assembled request creates one durable hash-only pending row with 600s expiry and redirects to the configured `/connect?request=` URL; raw route inspection is clean.
|
||||
- 08-01 metadata and 08-02 DCR exact-byte tests remain green.
|
||||
</acceptance_criteria>
|
||||
<done>An unchanged connector can register and start a PKCE-bound authorization request through the assembled production router.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
@@ -89,26 +119,27 @@ Output: OAuth config, boot wiring, raw routes, and assembled Postgres-backed tes
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| Internet → raw routes | Unauthenticated protocol traffic enters the assembled app. |
|
||||
| Config → public metadata | Deployment values become client trust anchors. |
|
||||
| Connector → raw authorize | Untrusted query data requests a durable consent transaction. |
|
||||
| Validated redirect → Location | Client-controlled redirect is trusted only after exact allow-list match. |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-08-DCR-FLOOD | Denial of Service | register route | mitigate | Named per-IP limiter plus framework body/cap controls. |
|
||||
| T-08-PKCE | Spoofing/Elevation | authorize | mitigate | Mandatory S256 syntax/policy and bound pending state. |
|
||||
| T-08-OPEN-REDIRECT | Spoofing/Disclosure | authorize | mitigate | Exact redirect validation before any Location. |
|
||||
| T-08-SCOPE-CEILING | Elevation | authorize | mitigate | Requested scopes intersect the registered ceiling before persistence. |
|
||||
| T-08-SURFACE | Elevation | route groups | mitigate | Assembled route-table test for exact middleware. |
|
||||
| T-08-SC | Tampering | dependencies | mitigate | No new package. |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- Focused assembled discovery/DCR tests pass.
|
||||
- `go vet ./... && go test ./...` passes in both repositories at the wave boundary.
|
||||
- Focused wristband and assembled authorize tests pass in the task feedback budget.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Metadata and DCR are reachable through the real app with exact PHP-compatible responses.
|
||||
- Public/confidential clients persist and raw routes remain isolated.
|
||||
- A registered connector can create a durable PKCE-bound pending authorization request through the real app.
|
||||
- All local/redirect error bytes and raw-route boundaries match PHP exactly.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
|
||||
@@ -5,9 +5,7 @@ type: execute
|
||||
wave: 4
|
||||
depends_on: [08-03]
|
||||
files_modified:
|
||||
- wristband/authorize.go
|
||||
- wristband/token.go
|
||||
- wristband/authorize_test.go
|
||||
- wristband/token_test.go
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_token_issuer.go
|
||||
@@ -16,13 +14,11 @@ autonomous: true
|
||||
requirements: [AUTH-05, AUTH-06]
|
||||
must_haves:
|
||||
truths:
|
||||
- "D-02: Authorize reads query only and token rejects JSON before ParseForm body-over-query parsing."
|
||||
- "D-02: Token rejects JSON before ParseForm body-over-query parsing."
|
||||
- "D-04: S256/client-secret comparisons are constant-time and code replay has one winner."
|
||||
- "D-05: Wristband owns authorize, PKCE, scope/resource policy, and atomic code exchange."
|
||||
- "D-05: Wristband owns client authentication, PKCE verification, and atomic code exchange."
|
||||
- "D-11: Issued access tokens retain the configured inv_ prefix."
|
||||
artifacts:
|
||||
- path: "wristband/authorize.go"
|
||||
provides: "Ordered validation, redirects, PKCE, resource and scope policy"
|
||||
- path: "wristband/token.go"
|
||||
provides: "Client authentication and atomic authorization-code exchange"
|
||||
key_links:
|
||||
@@ -33,10 +29,10 @@ must_haves:
|
||||
---
|
||||
|
||||
<objective>
|
||||
Implement the protocol core from authorize validation through one atomic authorization-code exchange.
|
||||
Complete one atomic authorization-code exchange for the assembled pending-request slice from 08-03.
|
||||
|
||||
Purpose: Prove PKCE, redirect, parser, scope, client-auth, and code-replay rules independently of the browser controller.
|
||||
Output: Authorize/token handlers, issuer adapter, store transitions, and deterministic/concurrent tests.
|
||||
Purpose: Prove token parsing, client authentication, constant-time PKCE, issuance, and code-replay rules before browser consent wiring.
|
||||
Output: Token handler, issuer adapter, locked store transition, and deterministic/concurrent tests.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@@ -56,31 +52,62 @@ Output: Authorize/token handlers, issuer adapter, store transitions, and determi
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Specify authorize and code exchange with executable RED tests</name>
|
||||
<files>wristband/authorize.go, wristband/token.go, wristband/authorize_test.go, wristband/token_test.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_token_issuer.go</files>
|
||||
<name>Task 1: Specify atomic code exchange with executable RED tests</name>
|
||||
<files>wristband/token.go, wristband/token_test.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_token_issuer.go</files>
|
||||
<read_first>
|
||||
.planning/phases/08-oauth2-1-authorization-server/08-CONTEXT.md
|
||||
.planning/phases/08-oauth2-1-authorization-server/08-PATTERNS.md
|
||||
wristband/authorize.go
|
||||
wristband/stores.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/classes/auth/api_token_manager.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/classes/auth/token_guard.go
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthTokenController.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/classes/auth/OAuthCodeManager.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/functional/OAuthTokenTest.php
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Unknown client/unregistered redirect are local 400 without Location; later errors use exact ordered RFC3986 redirects.
|
||||
- S256 is mandatory; parser precedence, Basic override, cache headers, and exact challenge are tested.
|
||||
- Tests compile and fail only through `PHASE8_RED:authorize-token`.
|
||||
- JSON rejection, ParseForm body-over-query precedence, Basic override, cache headers, exact challenge, and grant dispatch are tested.
|
||||
- S256 code/client/redirect/resource binding and synchronized one-winner replay are tested.
|
||||
- `TestPhase8RedCodeExchange` is the sole selected failure with `PHASE8_RED:code-exchange`.
|
||||
</behavior>
|
||||
<action>D-18: extend the existing interfaces with compiling authorize/token stubs and add deterministic unit plus real-store adapter tests. Use explicit `PHASE8_RED:authorize-token` assertions for absent behavior. Reject syntax/build/setup/missing-test failures through the shared RED verifier. Cover T-08-PKCE, CODE-REPLAY, OPEN-REDIRECT, SECRET-TIMING, SCOPE-CEILING, and REQUEST-LEAK, including synchronized concurrent code exchange.</action>
|
||||
<action>D-18: extend the existing interfaces with compiling token stubs and add deterministic unit plus real-store adapter tests. Validate exact package/test/sentinel through `check-phase8-red.sh go` and `go test -json`; cover T-08-PKCE, CODE-REPLAY, SECRET-TIMING, SCOPE-CEILING, and REQUEST-LEAK, including synchronized concurrent exchange. Every unexpected failing JSON action/package/test, build/setup/panic/no-test event, or missing/duplicate sentinel must fail the RED command.</action>
|
||||
<verify>
|
||||
<automated>scripts/check-phase8-red.sh authorize-token go test ./wristband -run 'Test(Authorize|Token|PKCE|Code)' -count=1</automated>
|
||||
<automated>scripts/check-phase8-red.sh go PHASE8_RED:code-exchange git.golem15.com/golem15/summercms/wristband TestPhase8RedCodeExchange -- go test -json ./wristband -run '^TestPhase8RedCodeExchange$' -count=1</automated>
|
||||
</verify>
|
||||
<done>Named tests compile and execute, with the verifier accepting only the intended missing-behavior marker.</done>
|
||||
<acceptance_criteria>
|
||||
- The selected RED test compiles/runs and is the only test fail event; its package is the only package fail event and emits the exact sentinel once.
|
||||
- Tests assert exact status/body/no-newline, `Cache-Control: no-store`, `Pragma: no-cache`, and `Basic realm="OAuth"` only for invalid confidential client.
|
||||
- Synchronized exchange proves exactly one success and one `invalid_grant`; syntax/setup/panic/no-test or any unrelated failure is rejected.
|
||||
</acceptance_criteria>
|
||||
<done>The fail-closed RED suite specifies the full code-exchange contract without re-owning authorize behavior.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Implement ordered authorize and atomic code exchange</name>
|
||||
<files>wristband/authorize.go, wristband/token.go, wristband/authorize_test.go, wristband/token_test.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_token_issuer.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/api_token_manager.go</files>
|
||||
<name>Task 2: Implement atomic PKCE-bound code exchange</name>
|
||||
<files>wristband/token.go, wristband/token_test.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_token_issuer.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/api_token_manager.go</files>
|
||||
<read_first>
|
||||
wristband/token_test.go
|
||||
wristband/token.go
|
||||
wristband/crypto.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/classes/auth/api_token_manager.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/models/api_token.go
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthTokenController.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/classes/auth/OAuthCodeManager.php
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Validation order is usable client, exact redirect, response type, S256 method/challenge, scope ceiling, resource, pending creation.
|
||||
- Code lock/consume, access-token mint/stamp, and refresh creation commit atomically once.
|
||||
- Token parsing/authentication precedes locked code lookup; code/client/redirect/resource/verifier bindings all match.
|
||||
- Code lock/consume, `inv_` access-token mint/stamp, and refresh creation commit atomically once.
|
||||
</behavior>
|
||||
<action>D-02: implement endpoint-specific parsing and reject JSON token calls before ParseForm. D-03: apply configured TTL/resource. D-04: compare fixed transforms with `subtle.ConstantTimeCompare`. D-05 and D-06: keep state/policy and exact raw responses in wristband. D-07: exchange under one app transaction/row lock. Build redirects from ordered pairs, never `url.Values.Encode`. D-11: make the app adapter prefix config-backed while retaining `inv_`. D-17: run expired-only sweep on token entry. Preserve exact `Basic realm="OAuth"`, no-store, and no-cache headers.</action>
|
||||
<action>D-02: reject JSON before ParseForm, use body-over-query form values, and let Basic credentials override form credentials. D-03: apply configured code/access/refresh TTL and resource. D-04: compare fixed transforms with `subtle.ConstantTimeCompare`. D-05/D-06: keep grant policy and exact raw responses in wristband. D-07: lock/consume/mint/stamp/create refresh within one callback transaction. D-11: make the app issuer prefix config-backed while retaining exact `inv_`. D-17: run expired-only sweep on token entry. Preserve exact Basic challenge and cache headers.</action>
|
||||
<verify>
|
||||
<automated>go test ./wristband -run 'Test(Authorize|Token|PKCE|Code)' -count=1 && cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/classes/auth -run 'TestOAuth(Code|Issuer)' -count=1</automated>
|
||||
<automated>go test ./wristband -run '^Test(Token|PKCE|Code)' -count=1 && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/classes/auth -run '^TestOAuth(Code|Issuer)' -count=1)</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Exact parser cases cover JSON rejection, body-over-query values, Basic-over-form credentials, unsupported/missing grant, public/confidential authentication, and challenge/no-challenge branches.
|
||||
- Correct verifier exchanges once for an `inv_` access token and refresh token with configured lifetimes; wrong verifier/client/redirect/resource and repeated code return exact native errors without minting.
|
||||
- Real-Postgres synchronized exchange has exactly one winner; persisted code is consumed and token/client/collection/scope bindings are correct, with no raw code/secret/verifier logged or stored.
|
||||
</acceptance_criteria>
|
||||
<done>One exact PKCE-bound code produces one inv_ access/refresh grant, and all validation/parser/replay failures are exact and tested.</done>
|
||||
</task>
|
||||
|
||||
@@ -108,7 +135,7 @@ Output: Authorize/token handlers, issuer adapter, store transitions, and determi
|
||||
|
||||
<verification>
|
||||
- Focused wristband and issuer/store tests pass.
|
||||
- `go test -race ./wristband` passes at wave boundary.
|
||||
- Complete race execution is reserved exclusively for Plan 08-10's final blocking checkpoint.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
@@ -58,28 +58,58 @@ Output: JWT consent controllers/routes, assembled flow tests, and an external re
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Specify assembled consent and route behavior in executable RED</name>
|
||||
<files>../fonoteka.go/plugins/golem15/fonoteka/controllers/api/oauth_consent_controller_test.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_connect_test.go</files>
|
||||
<read_first>
|
||||
.planning/phases/08-oauth2-1-authorization-server/08-UI-SPEC.md
|
||||
.planning/phases/08-oauth2-1-authorization-server/08-PATTERNS.md
|
||||
wristband/authorize.go
|
||||
wristband/token.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/token_api_controller.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/routes.go
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthConsentController.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/security/OAuthConsentScopeCeilingTest.php
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Show/allow/deny cover exact 200/404/422 payloads, host-only redirect, canonical scopes, active collection, ownership, and ordered redirects.
|
||||
- Tests compile and fail only through `PHASE8_RED:consent`.
|
||||
- Controller and assembled tests compile and fail only through their exact consent RED sentinels.
|
||||
</behavior>
|
||||
<action>D-18: add controller and assembled PKCE flow tests against 08-04 interfaces. Use `PHASE8_RED:consent` only for absent controller/route behavior and reject syntax/setup/missing-test failures via the RED verifier. Cover T-08-CROSS-USER, SCOPE-CEILING, REQUEST-LEAK, and SURFACE, including duplicate action single-use.</action>
|
||||
<action>D-18: add controller and assembled PKCE flow tests against 08-04 interfaces. Use exact `TestPhase8RedConsentController`/`PHASE8_RED:consent-controller` and `TestPhase8RedConsentApp`/`PHASE8_RED:consent-app` package/test/sentinel triples with `go test -json`; reject every unexpected failing action/package/test, compile/setup/panic/no-test case, and missing/duplicate sentinel. Cover T-08-CROSS-USER, SCOPE-CEILING, REQUEST-LEAK, and SURFACE, including duplicate action single-use.</action>
|
||||
<verify>
|
||||
<automated>scripts/check-phase8-red.sh consent bash -lc "cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestOAuth(Authorize|Consent|Deny|CodeExchange|Surface)' -count=1"</automated>
|
||||
<automated>scripts/check-phase8-red.sh go PHASE8_RED:consent-controller git.golem15.com/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api TestPhase8RedConsentController -- bash -lc "cd ../fonoteka.go && go test -json ./plugins/golem15/fonoteka/controllers/api -run '^TestPhase8RedConsentController$' -count=1" && scripts/check-phase8-red.sh go PHASE8_RED:consent-app git.golem15.com/golem15/fonoteka/plugins/golem15/fonoteka TestPhase8RedConsentApp -- bash -lc "cd ../fonoteka.go && go test -json ./plugins/golem15/fonoteka -run '^TestPhase8RedConsentApp$' -count=1"</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Each RED invocation observes exactly one selected test failure and its package failure with the exact sentinel; any other failure action is rejected.
|
||||
- Tests assert exact show 200, missing/stale/used/foreign 404, empty-intersection 422, allow/deny redirect bytes, ownership, active collection, ordered scopes, and duplicate-action single use.
|
||||
- Assembled route inspection proves consent paths are JWT+locale+must-change-password only while authorize/token remain raw.
|
||||
</acceptance_criteria>
|
||||
<done>Consent tests compile, execute, and fail only on the intended missing behavior.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Implement owner-bound JWT consent and raw route wiring</name>
|
||||
<files>../fonoteka.go/plugins/golem15/fonoteka/controllers/api/oauth_consent_controller.go, ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/oauth_consent_controller_test.go, ../fonoteka.go/plugins/golem15/fonoteka/plugin.go, ../fonoteka.go/plugins/golem15/fonoteka/routes.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_connect_test.go</files>
|
||||
<read_first>
|
||||
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/oauth_consent_controller_test.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/oauth_connect_test.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/token_api_controller.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/classes/active_collection.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/routes.go
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthConsentController.php
|
||||
/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/app/stores/fonoteka.ts
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Show returns sanitized client, host-only redirect, ordered mintable scopes, active collection name, and ISO expiry.
|
||||
- Allow grants submitted ∩ requested ∩ ceiling ∩ mintable; deny consumes pending state; both return nonblank ordered redirect_to.
|
||||
</behavior>
|
||||
<action>D-08: implement show/allow/deny in the JWT+locale+must-change-password group using `bouncer.User`, `ResolveActiveCollection`, `lagoon.Validate`, and wristband operations; never accept collection IDs or extra scopes. Collapse missing/stale/used/foreign handles to exact 404 and empty/no-longer-grantable intersection to exact 422. D-09: mount authorize/token in raw routes and only token receives its throttle. D-10 and D-12: add no oauth guard, house middleware, backend Bearer challenge, or RFC 9728 document.</action>
|
||||
<verify>
|
||||
<automated>cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestOAuth(Authorize|Consent|Deny|CodeExchange|Surface)' -count=1</automated>
|
||||
<automated>(cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/controllers/api ./plugins/golem15/fonoteka -run '^TestOAuth(Consent|Deny|CodeExchange|Surface)' -count=1)</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Show returns only sanitized client name, host-only redirect, ordered mintable scopes, active collection name, acting user, and ISO expiry; no secret/handle beyond the submitted opaque request id leaks.
|
||||
- Allow grants exactly submitted ∩ requested ∩ client ceiling ∩ mintable and server-derived collection IDs; deny grants none; both consume once and return one nonblank ordered `redirect_to`.
|
||||
- Missing/stale/used/foreign handles have identical exact 404 bytes, empty/no-longer-grantable scope has exact 422, and no state mutation survives a failing transaction.
|
||||
- Route tests prove JWT/raw/personal groups and unchanged Basic/token-surface headers remain isolated.
|
||||
</acceptance_criteria>
|
||||
<done>The unchanged consent client can inspect, allow, or deny one owner-bound request and complete exact PKCE code exchange.</done>
|
||||
</task>
|
||||
|
||||
@@ -94,11 +124,16 @@ Output: JWT consent controllers/routes, assembled flow tests, and an external re
|
||||
/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/app/components/fonoteka/ConnectedAppsManager.vue
|
||||
/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/app/stores/fonoteka.ts
|
||||
</read_first>
|
||||
<action>Create a read-only harness outside the Nuxt checkout using its already-installed Playwright runtime. First run `pnpm verify:oauth-return-path` and `pnpm verify:oauth-i18n`. Then boot the unchanged app and intercept API responses to prove: invalid/missing/repeated-first-invalid handles send no oauth/request call; logged-out entry preserves only a validated localized return path; 200, 404, network/error, empty-scope, allow-pending, deny-pending, and one-redirect outcomes render correctly; connected-app error/empty/manual-count/populated/cancel/revoke-pending/success/failure/identical-404 states render correctly. Assert keyboard order, visible 2px focus, dialog trap/Escape/restore, native disabled semantics, checkbox/revoke targets at least 44px, narrow/mobile stacking and no horizontal overflow, and both English/Polish strings with no raw keys. Snapshot the OAuth-related Nuxt paths before/after and fail on any diff; do not write fixtures, snapshots, generated files, or source inside Nuxt.</action>
|
||||
<action>Create a read-only harness outside the Nuxt checkout using its already-installed Playwright runtime. Define focused scenario selectors for invalid/missing/repeated-first-invalid handles; safe localized login return; consent 200/404/network/empty-scope/pending/one-redirect states; connected-app error/empty/manual-count/populated/cancel/revoke pending/success/failure/identical-404 states; keyboard/focus/dialog/disabled/44px/mobile/en-pl assertions. Add `--contract-self-test` that validates scenario completeness, source-path hashes, intercept definitions, and no-write guards without booting browsers/services; reserve the actual return-path/i18n/browser run for 08-10's final checkpoint. Never write inside Nuxt.</action>
|
||||
<verify>
|
||||
<automated>cd /media/nvme/dev/golem15/fonoteka/vue-fonoteka-app && pnpm verify:oauth-return-path && pnpm verify:oauth-i18n && cd /media/nvme/dev/golem15/summercms.io/summercms/summercms.go && node scripts/check-phase8-ui.mjs --focused</automated>
|
||||
<automated>node scripts/check-phase8-ui.mjs --contract-self-test</automated>
|
||||
</verify>
|
||||
<done>The full UI-SPEC state/accessibility/return-path/i18n matrix passes against unchanged Nuxt files, and the harness is callable from the final gate.</done>
|
||||
<acceptance_criteria>
|
||||
- Self-test enumerates every UI-SPEC consent/connected-app state, keyboard/focus/dialog/44px/mobile assertion, English/Polish check, and invalid-handle no-request case exactly once.
|
||||
- Source-hash/no-write guards cover the listed Nuxt component/store/i18n/return-path files and fail on any before/after change.
|
||||
- Harness exposes one final-gate mode that runs existing `verify:oauth-return-path`, `verify:oauth-i18n`, and the real Playwright matrix; Plan 08-10 is its only full execution site.
|
||||
</acceptance_criteria>
|
||||
<done>The harness encodes and self-validates the full UI-SPEC matrix without changing Nuxt; Plan 08-10's sole final gate executes it.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
@@ -124,7 +159,7 @@ Output: JWT consent controllers/routes, assembled flow tests, and an external re
|
||||
|
||||
<verification>
|
||||
- Focused consent/app tests pass under 30 seconds where possible.
|
||||
- UI harness runs at the wave boundary and is invoked again by the final gate.
|
||||
- UI harness contract self-test stays under 30 seconds; the complete browser matrix runs only in 08-10's final blocking checkpoint.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
@@ -104,9 +104,9 @@ Existing serializer:
|
||||
- Connected-app list is newest-first, owner-only, live OAuth tokens only, with manual count separate and no secret/client-id fields.
|
||||
- Revoke of an owned OAuth token kills its refresh lineage; foreign, missing, and manual token IDs share the exact 404.
|
||||
</behavior>
|
||||
<action>D-04: and D-18: extend the RED suite with deterministic in-memory tests and synchronized real-Postgres contention tests for T-08-REFRESH-REPLAY, T-08-CROSS-USER, T-08-SCOPE-CEILING, T-08-REQUEST-LEAK, and T-08-SURFACE. D-16: cover the lifecycle sequence later recorded by parity. D-17: prove exact sweep retention. Include an assembled lifecycle that starts with the grant from 08-05, refreshes, replays the spent predecessor, verifies the new branch and access token are dead, creates another grant, lists it, revokes it, and proves refresh afterward fails. Use compiling stubs and separate `PHASE8_RED:lifecycle-framework` and `PHASE8_RED:lifecycle-app` assertions; reject syntax/build/setup/missing-test failures through the shared RED verifier. Assert exact UI response allow-lists and 404 bytes.</action>
|
||||
<action>D-04 and D-18: extend the RED suite with deterministic in-memory tests and synchronized real-Postgres contention tests for T-08-REFRESH-REPLAY, T-08-CROSS-USER, T-08-SCOPE-CEILING, T-08-REQUEST-LEAK, and T-08-SURFACE. D-16: cover the lifecycle sequence later recorded by parity. D-17: prove exact sweep retention. Include an assembled lifecycle that starts with the grant from 08-05, refreshes, replays the spent predecessor, verifies the new branch and access token are dead, creates another grant, lists it, revokes it, and proves refresh afterward fails. Use compiling stubs and exact `TestPhase8RedLifecycleFramework`/`PHASE8_RED:lifecycle-framework` and `TestPhase8RedLifecycleApp`/`PHASE8_RED:lifecycle-app` triples under `go test -json`; reject every unexpected failing action/package/test, compile/setup/panic/no-test case, and missing/duplicate sentinel. Assert exact UI response allow-lists and 404 bytes.</action>
|
||||
<verify>
|
||||
<automated>scripts/check-phase8-red.sh lifecycle-framework go test ./wristband -run 'Test(Refresh|Replay|Sweep)' -count=1 && scripts/check-phase8-red.sh lifecycle-app bash -lc "cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestOAuth(Refresh|Replay|Connected|Revoke|Sweep)' -count=1"</automated>
|
||||
<automated>scripts/check-phase8-red.sh go PHASE8_RED:lifecycle-framework git.golem15.com/golem15/summercms/wristband TestPhase8RedLifecycleFramework -- go test -json ./wristband -run '^TestPhase8RedLifecycleFramework$' -count=1 && scripts/check-phase8-red.sh go PHASE8_RED:lifecycle-app git.golem15.com/golem15/fonoteka/plugins/golem15/fonoteka TestPhase8RedLifecycleApp -- bash -lc "cd ../fonoteka.go && go test -json ./plugins/golem15/fonoteka -run '^TestPhase8RedLifecycleApp$' -count=1"</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Tests include sequential replay, a barrier-synchronized double refresh, committed lineage kill, expiry retention, owner isolation, manual-token exclusion, list ordering, and post-revoke refresh failure.
|
||||
@@ -140,7 +140,7 @@ Existing serializer:
|
||||
<acceptance_criteria>
|
||||
- Normal refresh and sequential/concurrent replay tests pass under real Postgres.
|
||||
- A spent-token replay leaves every lineage refresh row and its live access token revoked after the response transaction commits.
|
||||
- `go test -race ./wristband` passes and the app contention test produces a single usable branch.
|
||||
- The focused app contention test produces a single usable branch; full race execution is reserved for 08-10's final checkpoint.
|
||||
- Sweep tests prove expired rows are removed and unexpired rotated/revoked rows remain.
|
||||
</acceptance_criteria>
|
||||
<done>Refresh rotation is atomic, preserves replay evidence, and commits whole-lineage revocation before emitting the protocol error.</done>
|
||||
@@ -164,7 +164,7 @@ Existing serializer:
|
||||
</behavior>
|
||||
<action>Per D-08 and the UI-SPEC, add GET and DELETE connected-app controllers in the JWT group. Reuse `serializeToken`; append only the sanitized/truncated client name, initialize collection/scope arrays as arrays, count live manual tokens separately, and order OAuth tokens newest first. Scope every query by `bouncer.User`. For DELETE, require an owned OAuth token, invoke the wristband lineage-revoke operation in the same committed transaction, and collapse missing/foreign/manual IDs to exact `{"error":"Token not found"}` 404. Mount only under `/_fonoteka/api/v1/oauth`; do not expose these routes on the personal-token or raw groups.</action>
|
||||
<verify>
|
||||
<automated>cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestOAuth(ConnectedApps|Revoke|Lifecycle|Surface)' -count=1</automated>
|
||||
<automated>(cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/controllers/api ./plugins/golem15/fonoteka -run '^TestOAuth(ConnectedApps|Revoke|Lifecycle|Surface)$' -count=1)</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Empty, populated, manual-count, newest-first, foreign/manual 404, and successful atomic revoke tests pass with exact bytes.
|
||||
@@ -200,8 +200,8 @@ Existing serializer:
|
||||
|
||||
<verification>
|
||||
- `go test ./wristband -run 'Test(Refresh|Replay|Sweep)' -count=1`
|
||||
- `cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestOAuth(Refresh|Replay|ConnectedApps|Revoke|Lifecycle|Surface)' -count=1`
|
||||
- `cd ../fonoteka.go && go test -race ./plugins/golem15/fonoteka/classes/auth ./plugins/golem15/fonoteka`
|
||||
- `cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/classes/auth ./plugins/golem15/fonoteka/controllers/api ./plugins/golem15/fonoteka -run '^TestOAuth(Refresh|Replay|ConnectedApps|Revoke|Lifecycle|Surface)$' -count=1`
|
||||
- Complete race execution is reserved exclusively for Plan 08-10's final blocking checkpoint.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
@@ -54,29 +54,58 @@ Output: Repeatable bonfire flags, client command, plugin registration, and comma
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Specify repeatable flags and command output in executable RED</name>
|
||||
<files>bonfire/output_test.go, ../fonoteka.go/plugins/golem15/fonoteka/console/oauth_client_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
|
||||
bonfire/command.go
|
||||
bonfire/root.go
|
||||
bonfire/output_test.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/console/console_test.go
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/console/IssueOAuthClient.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/functional/OAuthClientCommandTest.php
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Repeated redirect/scope flags preserve order without breaking scalar/bare flags.
|
||||
- Create prints id, secret, warning once; update/list never reveal secret/hash.
|
||||
- Tests compile and fail only through separate `PHASE8_RED:bonfire-flags` and `PHASE8_RED:oauth-command` markers.
|
||||
</behavior>
|
||||
<action>D-18: and D-19: add real command-root tests for create/update/list, exact lines, one-time secret, scope ceiling, and non-recovery. Define compiling flag/command seams first; mark only missing bonfire behavior with `PHASE8_RED:bonfire-flags` and missing app-command behavior with `PHASE8_RED:oauth-command`. Use the shared verifier to reject syntax/setup/missing tests.</action>
|
||||
<action>D-18 and D-19: add real command-root tests for create/update/list, exact lines, one-time secret, scope ceiling, and non-recovery. Define compiling flag/command seams first. Use exact `TestPhase8RedBonfireFlags`/`PHASE8_RED:bonfire-flags` and `TestPhase8RedOAuthClientCommand`/`PHASE8_RED:oauth-command` package/test/sentinel triples with `go test -json`; reject any unexpected failing action/package/test, compile/setup/panic/no-test result, or missing/duplicate sentinel.</action>
|
||||
<verify>
|
||||
<automated>scripts/check-phase8-red.sh bonfire-flags go test ./bonfire -run 'Test.*Flag' -count=1 && scripts/check-phase8-red.sh oauth-command bash -lc "cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run TestOAuthClientCommand -count=1"</automated>
|
||||
<automated>scripts/check-phase8-red.sh go PHASE8_RED:bonfire-flags git.golem15.com/golem15/summercms/bonfire TestPhase8RedBonfireFlags -- go test -json ./bonfire -run '^TestPhase8RedBonfireFlags$' -count=1 && scripts/check-phase8-red.sh go PHASE8_RED:oauth-command git.golem15.com/golem15/fonoteka/plugins/golem15/fonoteka/console TestPhase8RedOAuthClientCommand -- bash -lc "cd ../fonoteka.go && go test -json ./plugins/golem15/fonoteka/console -run '^TestPhase8RedOAuthClientCommand$' -count=1"</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Each RED stream contains exactly its named test fail plus named package fail and exact sentinel once; unrelated/compile/setup/panic/no-test failures are rejected.
|
||||
- Repeatable redirect/scope flags preserve input order, repeated/scalar/bare values remain distinguishable, and existing scalar callers retain behavior.
|
||||
- Command tests assert exact `client_id=`, `client_secret=`, warning lines once on create and forbid raw/hash secret output on update/list and error paths.
|
||||
</acceptance_criteria>
|
||||
<done>RED command tests execute and fail only for absent repeatable-flag/command behavior.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Add repeatable flags and exact OAuth client command</name>
|
||||
<files>bonfire/command.go, bonfire/root.go, bonfire/output_test.go, ../fonoteka.go/plugins/golem15/fonoteka/console/oauth_client.go, ../fonoteka.go/plugins/golem15/fonoteka/console/oauth_client_test.go, ../fonoteka.go/plugins/golem15/fonoteka/plugin.go</files>
|
||||
<read_first>
|
||||
bonfire/output_test.go
|
||||
bonfire/command.go
|
||||
bonfire/root.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/console/oauth_client_test.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/plugin.go
|
||||
wristband/register.go
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/console/IssueOAuthClient.php
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Flag/Input distinguish scalar and repeated values; existing callers remain compatible.
|
||||
- Create/update/list share wristband validation/issuance and artisan clients have null registration_ip.
|
||||
</behavior>
|
||||
<action>D-19: extend bonfire with explicit string-slice flags and `Input.Flags(name)`, using Cobra StringSlice only for that kind. Implement the exact name/redirect-uri/scope/auth-method/client-id/list signature thinly over wristband and ClientStore; never parse os.Args. Print the exact creation lines/warning and never recover or print secrets on list/update. Register through plugin command capability.</action>
|
||||
<verify>
|
||||
<automated>go test ./bonfire -run 'Test.*Flag' -count=1 && cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run TestOAuthClientCommand -count=1</automated>
|
||||
<automated>go test ./bonfire -run '^Test.*Flag' -count=1 && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/console -run '^TestOAuthClientCommand' -count=1)</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Create supports ordered repeated `--redirect-uri` and `--scope`, validates auth method/ceiling through wristband, persists hash only with null registration IP, and prints the raw secret exactly once.
|
||||
- Update by client id changes only supplied values and never rotates/recovers a secret; list shows id/name/revocation/redirects/ceiling but no raw/hash credential.
|
||||
- Existing bonfire scalar and bare-flag tests remain unchanged and green; plugin command registration exposes exactly `fonoteka:oauth-client` without parsing `os.Args`.
|
||||
</acceptance_criteria>
|
||||
<done>Operators can safely provision and inspect OAuth clients with exact repeatable flags and no secret recovery.</done>
|
||||
</task>
|
||||
|
||||
|
||||
@@ -51,29 +51,59 @@ Output: `/api/v1/fonoteka/me`, route isolation, and assembled tests.
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Specify exact MCP bootstrap and token-surface behavior in RED</name>
|
||||
<files>../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller_test.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_tools_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/auth/token_guard.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_locale_controller.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/routes.go
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/MeTokenController.php
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/security/TokenSurfaceIsolationTest.php
|
||||
/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/http.ts
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Valid read-scoped inv_ token returns exactly four fields; arrays are never null.
|
||||
- Missing/invalid/wrong-scope tokens preserve existing exact 401/403 bytes and headers.
|
||||
- Tests compile and fail only through `PHASE8_RED:mcp-me`.
|
||||
</behavior>
|
||||
<action>D-18 and D-20: use the assembled surf router and existing inv_token guard, not direct controller injection. Add exact positive/negative payload and route-isolation tests; mark only missing `/me` behavior with `PHASE8_RED:mcp-me` and reject syntax/setup/missing-test failures via the shared verifier.</action>
|
||||
<action>D-18 and D-20: use the assembled surf router and existing inv_token guard, not direct controller injection. Add exact positive/negative payload and route-isolation tests. Use exact `TestPhase8RedMCPMe`/`PHASE8_RED:mcp-me` package/test/sentinel under `go test -json`; reject any unexpected failing action/package/test, compile/setup/panic/no-test result, or missing/duplicate sentinel.</action>
|
||||
<verify>
|
||||
<automated>scripts/check-phase8-red.sh mcp-me bash -lc "cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'Test(MeToken|TokenSurface|OAuthTools)' -count=1"</automated>
|
||||
<automated>scripts/check-phase8-red.sh go PHASE8_RED:mcp-me git.golem15.com/golem15/fonoteka/plugins/golem15/fonoteka TestPhase8RedMCPMe -- bash -lc "cd ../fonoteka.go && go test -json ./plugins/golem15/fonoteka -run '^TestPhase8RedMCPMe$' -count=1"</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- RED contains exactly the selected test/package failure and exact sentinel once; unrelated/build/setup/panic/no-test failures are rejected.
|
||||
- Valid `inv_` read-scoped token expects exact `{"data":{"scopes":[],"collection_ids":[],"user_id":...,"name":...}}` field set with arrays never null.
|
||||
- Missing/invalid token retains exact `{"error":"Invalid token"}` 401 with no challenge; wrong scope retains the existing exact 403; route table proves personal-token group only.
|
||||
</acceptance_criteria>
|
||||
<done>The assembled RED tests run through the real guard and fail only on absent `/me` behavior.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Mount exact personal-token MCP bootstrap</name>
|
||||
<files>../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller.go, ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller_test.go, ../fonoteka.go/plugins/golem15/fonoteka/routes.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_tools_test.go</files>
|
||||
<read_first>
|
||||
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller_test.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/oauth_tools_test.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/classes/auth/token_guard.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/models/api_token.go
|
||||
../fonoteka.go/plugins/golem15/fonoteka/routes.go
|
||||
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/MeTokenController.php
|
||||
/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/http.ts
|
||||
</read_first>
|
||||
<behavior>
|
||||
- Handler reads matched ApiToken and principal, emits only exact four fields, and performs no second lookup.
|
||||
- Route inherits inv_token, throttle, inv.scope:read in order and appears nowhere else.
|
||||
</behavior>
|
||||
<action>D-20: implement the exact handler using `bouncer.Credential` and `bouncer.User`, initialize arrays, preserve nullable name, and emit only locked fields through wire.WriteJSON. Mount GET `/me` in the existing personal-token group after its three middleware. D-12: leave invalid-token bytes/headers unchanged and add no RFC 9728 or Bearer challenge.</action>
|
||||
<verify>
|
||||
<automated>cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'Test(MeToken|TokenSurface|OAuthTools)' -count=1</automated>
|
||||
<automated>(cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/controllers/api ./plugins/golem15/fonoteka -run '^Test(MeToken|TokenSurface|OAuthTools)' -count=1)</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Handler reuses `bouncer.Credential` and `bouncer.User` with no bearer reparse or second token query and emits only scopes, collection_ids, user_id, and name.
|
||||
- Nil scopes/collection ids serialize as `[]`; nullable name matches PHP; raw token/hash/client id/other profile fields never appear.
|
||||
- GET `/api/v1/fonoteka/me` inherits exactly `inv_token`, `throttle:fonoteka-api-token`, `inv.scope:read` in order and has no JWT/raw duplicate.
|
||||
- Missing/invalid/wrong-scope exact bytes and absence of backend Bearer/resource-metadata headers remain unchanged.
|
||||
</acceptance_criteria>
|
||||
<done>The unchanged MCP process can bootstrap from an issued inv_ token without profile-surface expansion or header drift.</done>
|
||||
</task>
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ must_haves:
|
||||
truths:
|
||||
- "D-13: All four raw OAuth routes and five JWT OAuth management routes replay their recorded PHP contracts against Go and count as ported only after passing."
|
||||
- "D-16: A clean recorded lifecycle proves DCR, authorize, consent, token, refresh, replay kill, list, revoke, post-revoke failure, deny, confidential Basic auth, and scope ceiling."
|
||||
- "D-14: The unchanged real fonoteka-mcp process completes discovery, DCR, PKCE, JWT consent, token bootstrap, an MCP tool call, and refresh against the Go backend."
|
||||
- "D-14: The final gate contains a fail-closed unchanged-real-MCP stage for discovery, DCR, PKCE, JWT consent, token bootstrap, tool call, refresh, replay, and revoke."
|
||||
- "D-15: Live vendor connects remain excluded; automated DCR-plus-PKCE evidence is the Phase 8 acceptance boundary."
|
||||
artifacts:
|
||||
- path: "../fonoteka.go/parity/oauth_flow_test.go"
|
||||
@@ -39,10 +39,10 @@ must_haves:
|
||||
---
|
||||
|
||||
<objective>
|
||||
Prove the completed server through recorded PHP parity and the unchanged real MCP client rather than only implementation-local tests.
|
||||
Prove the completed server through recorded PHP parity and assemble the fail-closed unchanged-real-MCP gate that Plan 08-10 executes once at final sign-off.
|
||||
|
||||
Purpose: Turn exact route bytes, lifecycle security semantics, protected-resource discovery ownership, and actual SDK compatibility into one repeatable acceptance gate.
|
||||
Output: Lifecycle fixture/capture policy, projected replay tests, nine ported manifest entries, and `scripts/check-phase8.sh`.
|
||||
Output: Lifecycle fixture/capture policy, projected replay tests, nine ported manifest entries, and a self-validating final `scripts/check-phase8.sh`.
|
||||
</objective>
|
||||
|
||||
## Phase Goal
|
||||
@@ -100,15 +100,15 @@ Unchanged MCP inputs:
|
||||
- The clean lifecycle flow is required and every terminal security action is asserted before routes can be marked ported.
|
||||
- The gate starts real Postgres, Go app, and unchanged Node MCP; it verifies MCP-owned protected-resource metadata and Bearer hint separately from backend-owned metadata and Basic invalid-client challenge.
|
||||
</behavior>
|
||||
<action>D-12: distinguish backend Basic/no-challenge responses from MCP RFC 9728 behavior. D-13: project `mcp-oauth` and `mcp-tools` by stable named step IDs. D-14: declare real Postgres/app/MCP stages with all three environment variables. D-15: keep live vendor connects excluded. D-16: require the full clean lifecycle. D-18: create compiling failing parity tests and a fail-closed gate skeleton before changing fixtures/status. Use `PHASE8_RED:parity-gate` and the shared RED verifier so shell/Go syntax, missing tests, setup failures, and unrelated failures cannot satisfy RED. Plan 08-10 alone adds the security-review validation stage after the review exists. Do not edit or patch MCP or Nuxt.</action>
|
||||
<action>D-12: distinguish backend Basic/no-challenge responses from MCP RFC 9728 behavior. D-13: project `mcp-oauth` and `mcp-tools` by stable named step IDs. D-14: declare real Postgres/app/MCP stages with all three environment variables. D-15: keep live vendor connects excluded. D-16: require the full clean lifecycle. D-18: create compiling `TestPhase8RedParityGate` and a fail-closed gate skeleton before changing fixtures/status. Validate the Go RED with exact package/test/sentinel under `go test -json`. Also add a shell-contract RED self-test whose only allowed failure is exit 86 plus exactly `PHASE8_STAGE:real-mcp:FAIL:PHASE8_RED:real-mcp-stage`; reject any other FAIL/ERROR/PANIC stage, extra sentinel, syntax failure, or zero stage. Plan 08-10 alone adds and executes the complete security-review/final gate. Do not edit or patch MCP or Nuxt.</action>
|
||||
<verify>
|
||||
<automated>scripts/check-phase8-red.sh parity-gate bash -lc "cd ../fonoteka.go && go test ./parity -run 'TestOAuthFlows' -count=1"</automated>
|
||||
<automated>scripts/check-phase8-red.sh go PHASE8_RED:parity-gate git.golem15.com/golem15/fonoteka/parity TestPhase8RedParityGate -- bash -lc "cd ../fonoteka.go && go test -json ./parity -run '^TestPhase8RedParityGate$' -count=1" && scripts/check-phase8-red.sh shell PHASE8_RED:real-mcp-stage real-mcp -- scripts/check-phase8.sh --red-contract real-mcp</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `oauth_flow_test.go` names projections for `mcp-oauth`, `mcp-tools`, and the complete `mcp-lifecycle`, and missing expected steps fail.
|
||||
- `scripts/check-phase8.sh` uses `set -euo pipefail`, fail-closed dependency/Docker checks, cleanup traps, and all three MCP environment variables.
|
||||
- The gate distinguishes MCP RFC 9728 metadata/rich Bearer challenge from backend exact Basic invalid-client challenge and unchanged token-surface 401.
|
||||
- Tests/gate fail because the lifecycle fixture/status/evidence is not yet complete, not because of shell or Go syntax errors.
|
||||
- Go RED has no unexpected failing JSON action; shell RED has exactly one expected stage/sentinel and exit 86, with syntax/setup/extra-stage failures rejected.
|
||||
</acceptance_criteria>
|
||||
<done>The acceptance harness demands the exact recorded and real-client lifecycle before any route can be claimed ported.</done>
|
||||
</task>
|
||||
@@ -134,7 +134,7 @@ Unchanged MCP inputs:
|
||||
</behavior>
|
||||
<action>D-16: extend the existing capture script/rules and use the Phase 2 isolated-PHP process to record the locked lifecycle. Issue the confidential client through `fonoteka:oauth-client`; exercise scope ceiling truncation and invalid-scope redirect with `client_secret_basic`. Capture all secret-bearing values with explicit pkce/token/credential categories into the private store, confirm both vars files are 0600, and commit only symbolic variable references. Add full and projected replays through `newConfiguredTarget` with real Postgres. After each of the four raw and five JWT route subtests passes, change only those manifest entries to `status: ported`; keep honest corpus accounting.</action>
|
||||
<verify>
|
||||
<automated>cd ../fonoteka.go && go test ./parity -run 'TestOAuthFlows|TestParityCorpus|TestParityContract' -count=1</automated>
|
||||
<automated>(cd ../fonoteka.go && go test ./parity -run '^TestOAuthFlows$' -count=1)</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `mcp-lifecycle.yaml` contains the locked sequence and placeholder references, not recoverable credential values.
|
||||
@@ -146,7 +146,7 @@ Unchanged MCP inputs:
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Complete the real unchanged-MCP phase gate</name>
|
||||
<name>Task 3: Complete and self-validate the final unchanged-MCP gate</name>
|
||||
<files>scripts/check-phase8.sh</files>
|
||||
<read_first>
|
||||
scripts/check-phase8.sh
|
||||
@@ -157,17 +157,17 @@ Unchanged MCP inputs:
|
||||
/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/config.ts
|
||||
/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/install.ts
|
||||
</read_first>
|
||||
<action>D-14: finish the executable pre-security gate using the existing gate family and installed Node MCP dependencies. Add `--core-smoke` for focused syntax/dependency/service lifecycle feedback and `--pre-security` for the complete wave-boundary gate. Allocate loopback ports, start disposable Postgres and the assembled Go app, start unchanged MCP with all three URLs, and drive SDK discovery/DCR/PKCE/login/consent/token, `/me`, tool, refresh, replay, and revoke. Verify RFC 9728 ownership separately from exact backend Basic/no-challenge responses. Run both modules' vet/test/race, parity/corpus/secret checks, `scripts/check-phase8-ui.mjs`, and unchanged Nuxt/MCP path diffs. Do not require `08-SECURITY-REVIEW.md` in either mode; Plan 08-10 adds the final fail-closed review stage. Preserve cleanup and never print secrets.</action>
|
||||
<action>D-14: finish the executable final gate using the existing gate family and installed Node MCP dependencies. Encode stages for disposable Postgres, assembled Go app, unchanged MCP with all three URLs, SDK discovery/DCR/PKCE/login/consent/token, `/me`, tool, refresh, replay, revoke, both repositories' vet/test/race, parity/corpus/secret checks, full UI harness, and unchanged Nuxt/MCP diffs. Verify RFC 9728 ownership separately from exact backend Basic/no-challenge responses. Add `--contract-self-test` that validates `bash -n`, required stage names/order, cleanup traps, loopback-only allocation, redacted output, expected commands, and unchanged-client path scopes without booting services or running long suites. Do not offer `--pre-security` or any mode that executes the complete gate before 08-10; Plan 08-10 adds the review stage and is the sole complete execution site.</action>
|
||||
<verify>
|
||||
<automated>scripts/check-phase8.sh --core-smoke</automated>
|
||||
<automated>bash -n scripts/check-phase8.sh && scripts/check-phase8.sh --contract-self-test</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- The gate starts the real unchanged MCP checkout and completes metadata, DCR, PKCE, JWT consent, token, `/me`, one MCP tool call, and refresh against the Go backend.
|
||||
- The gate proves spent refresh replay and connected-app revoke kill the lineage and later access/refresh attempts fail.
|
||||
- Both repositories pass `go vet ./...`, `go test ./...`, and `go test -race ./...`; corpus and secret scans exit 0.
|
||||
- `git -C /media/nvme/dev/golem15/fonoteka/fonoteka-mcp status --short` and the Nuxt equivalent show no Phase 8 diff.
|
||||
- Contract self-test finds required real-MCP lifecycle, replay/revoke, two-repository vet/test/race, parity, UI, secret-scan, security-review, and unchanged-client stages in fail-closed order.
|
||||
- The script has cleanup traps and loopback/service readiness checks, exports all three MCP URLs only to the child process, and redacts every raw secret/token/code/verifier.
|
||||
- No non-final mode can run the complete long suite; `--contract-self-test` performs syntax/source assertions only and is designed for under 30 seconds.
|
||||
- Final execution remains mandatory in 08-10 Task 3 and fails if either unchanged client worktree gains a Phase 8 source diff.
|
||||
</acceptance_criteria>
|
||||
<done>The actual connector stack, including RFC 9728 resource-server behavior, runs unchanged through the complete Go authorization lifecycle.</done>
|
||||
<done>The complete unchanged-client gate is fail-closed, self-validating, and ready for its sole execution at the final blocking checkpoint.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
@@ -199,13 +199,13 @@ Unchanged MCP inputs:
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- `cd ../fonoteka.go && go test ./parity -run 'TestOAuthFlows|TestParityCorpus|TestParityContract' -count=1`
|
||||
- `scripts/check-phase8.sh --pre-security` at the Wave 8 boundary; this mode proves the complete lifecycle but intentionally does not require the not-yet-created security review.
|
||||
- `cd ../fonoteka.go && go test ./parity -run '^TestOAuthFlows$' -count=1`
|
||||
- `bash -n scripts/check-phase8.sh && scripts/check-phase8.sh --contract-self-test`; the full gate runs only in 08-10 Task 3.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Nine OAuth routes and the full lifecycle replay pass against Go with no leaked fixture secret.
|
||||
- The real unchanged MCP discovers, authorizes, initializes, executes a tool, refreshes, and observes replay/revoke failure.
|
||||
- The final gate encodes the real unchanged-MCP lifecycle and cannot execute incompletely or before the security review.
|
||||
- Resource-server and authorization-server header ownership is proven exactly, not conflated.
|
||||
</success_criteria>
|
||||
|
||||
|
||||
@@ -108,12 +108,12 @@ PHP test inventory contract:
|
||||
</behavior>
|
||||
<action>D-18: enumerate all 103 PHP methods into `08-PHP-TEST-MAP.md`, map each to existing Phase 8 tests, and add focused coverage tests only where no named evidence exists. Add an executable audit that parses the inventory/map and Go test list so counts alone cannot hide missing or duplicate mappings. Close framework handler/store branches, app boot/config/route/controller/command branches, parity projections, UI harness invocation, and every exact response/header path. Do not replace behavior assertions with coverage-only calls or map one broad test to methods whose distinct assertions are absent.</action>
|
||||
<verify>
|
||||
<automated>go test ./wristband -count=1 && cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... ./parity -run 'Test(OAuth|Phase08|PHPTestMap|TokenSurface|MeToken)' -count=1</automated>
|
||||
<automated>go test ./wristband -run '^Test(Phase08Coverage|PHPTestMap)' -count=1 && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka ./parity -run '^Test(Phase08Coverage|PHPTestMap)$' -count=1)</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- The map contains exactly 103 unique PHP method rows distributed 11/8/2/7/10/10/7/10/8/30 by source suite.
|
||||
- The executable audit confirms every mapped Go `TestName[/subtest]` exists and executes; missing or duplicate rows make it fail.
|
||||
- Both repositories pass full `go vet ./...`, `go test ./...`, and `go test -race ./...`, including nested plugin modules.
|
||||
- The focused map/coverage audit is designed for under 30 seconds; complete repository vet/test/race runs only in Task 3's final gate.
|
||||
- Coverage additions retain exact byte/header/concurrency assertions for security branches.
|
||||
</acceptance_criteria>
|
||||
<done>All PHP OAuth behavior has one-to-one named Go evidence and the phase's code paths are covered by meaningful regression tests.</done>
|
||||
@@ -162,7 +162,7 @@ PHP test inventory contract:
|
||||
.planning/phases/08-oauth2-1-authorization-server/08-VALIDATION.md
|
||||
.planning/phases/08-oauth2-1-authorization-server/08-09-SUMMARY.md
|
||||
</read_first>
|
||||
<action>Present the completed automated evidence after the security-review agent has produced zero open high-severity findings. Do not ask the user to rerun automation; show the exact gate result, threat totals, 103-method audit result, nine-route parity result, real-MCP lifecycle result, and unchanged Nuxt/MCP worktree checks. Block completion if any displayed result is missing or non-green.</action>
|
||||
<action>After the security-review agent reports zero open high-severity findings, execute `scripts/check-phase8.sh` exactly once as the sole complete long gate. It must run both repositories' `go vet ./...`, `go test ./...`, and `go test -race ./...`; 103-method executable audit; nine-route/full-lifecycle parity and corpus secret scan; full return-path/i18n/Playwright UI matrix; disposable Postgres/app plus unchanged real MCP discovery/DCR/PKCE/JWT consent/token/`/me`/tool/refresh/replay/revoke; security-review fail-closed checks; and scoped unchanged Nuxt/MCP worktree assertions. Present the completed evidence; do not ask the user to rerun automation. Block completion if any displayed result is missing or non-green.</action>
|
||||
<verify>
|
||||
<automated>scripts/check-phase8.sh</automated>
|
||||
</verify>
|
||||
|
||||
@@ -8,22 +8,22 @@ All required GOAL, REQ, RESEARCH, CONTEXT, VALIDATION, and UI-SPEC items are pla
|
||||
| REQ | AUTH-05 | Metadata, DCR, S256 authorize/consent, code/refresh grants, resource handling, exact discovery/challenges | 01, 03-07, 09-10 | COVERED | Backend Basic challenge and MCP RFC 9728 ownership are tested separately. |
|
||||
| REQ | AUTH-06 | Form/query/JSON source rules, CSRF-free raw routes, rate limits, unwrapped responses, cache headers | 01, 03-05, 09-10 | COVERED | Route-table, byte, header, parity, and final audit coverage. |
|
||||
| REQ | AUTH-07 | Persistent OAuth models, connected-app list/revoke, unchanged MCP install/auth/tool flow | 02-03, 05-10 | COVERED | Includes schema correction, `/me`, lifecycle, and real MCP. |
|
||||
| RESEARCH | R-01 | Additive nullability/index migration and pointer models | 02 | COVERED | Safe rollback refusal is explicit. |
|
||||
| RESEARCH | R-02 | App-agnostic transaction-scoped store bundle with GORM row locks in app tier | 01-03 | COVERED | Framework never imports GORM/fonoteka. |
|
||||
| RESEARCH | R-01 | Additive nullability/index migration and pointer models | 02 | COVERED | Persistent DCR slice includes safe rollback refusal. |
|
||||
| RESEARCH | R-02 | App-agnostic transaction-scoped store bundle with GORM row locks in app tier | 02-04 | COVERED | Framework never imports GORM/fonoteka; DCR is connector-visible at the end of 02. |
|
||||
| RESEARCH | R-03 | Commit refresh replay lineage kill before returning `invalid_grant` | 06, 10 | COVERED | Persisted post-error evidence and concurrency tests. |
|
||||
| RESEARCH | R-04 | Ordered RFC3986 redirects and endpoint-specific parsers | 04-05, 09-10 | COVERED | Exact bytes/parity. |
|
||||
| RESEARCH | R-05 | No new package; standard-library crypto/HTTP and package-legitimacy audit not applicable | 01-10 | COVERED | T-08-SC included in every threat model. |
|
||||
| RESEARCH | R-06 | 103 PHP-method audit and complete validation architecture | 10 | COVERED | Exact distribution and executable missing-name gate. |
|
||||
| RESEARCH | R-07 | Real MCP `/me` prerequisite and 64 KiB DCR bound | 01, 08-10 | COVERED | Both resolved questions are locked as D-20/D-21. |
|
||||
| CONTEXT | D-01 | Direct stdlib port; no zitadel/oidc; correct roadmap/requirement wording | 01, planning update | COVERED | No dependency install. |
|
||||
| CONTEXT | D-02 | Query/form/JSON parameter sources | 01, 04, 09-10 | COVERED | JSON token rejection, ParseForm precedence, JSON-only register. |
|
||||
| CONTEXT | D-03 | TTLs, caps, issuer/resource/consent configuration | 01-03 | COVERED | Exact PHP defaults in plan 01. |
|
||||
| CONTEXT | D-01 | Direct stdlib port; no zitadel/oidc; correct roadmap/requirement wording | 01-04, planning update | COVERED | No dependency install. |
|
||||
| CONTEXT | D-02 | Query/form/JSON parameter sources | 02-04, 09-10 | COVERED | JSON-only register, query-only authorize, JSON token rejection and ParseForm precedence. |
|
||||
| CONTEXT | D-03 | TTLs, caps, issuer/resource/consent configuration | 01-04 | COVERED | Metadata mounted in 01; exact DCR/TTL defaults wired in 02. |
|
||||
| CONTEXT | D-04 | Full T-08 security treatment and constant-time comparisons | 01-10 | COVERED | Independent security agent and blocking approval in 10. |
|
||||
| CONTEXT | D-05 | `wristband` owns RFC surface/state machine | 01-03 | COVERED | Framework structure and import boundary explicit. |
|
||||
| CONTEXT | D-06 | PHP-minimal response shapes are defaults; no hooks | 01-03, 05 | COVERED | Exact response/header tests and parity. |
|
||||
| CONTEXT | D-07 | App stores, issuer, transaction boundary, row locks | 01-03 | COVERED | Real Postgres concurrency tests. |
|
||||
| CONTEXT | D-05 | `wristband` owns RFC surface/state machine | 01-04 | COVERED | Each opening plan ends in an assembled connector-visible slice. |
|
||||
| CONTEXT | D-06 | PHP-minimal response shapes are defaults; no hooks | 01-05 | COVERED | Exact response/header tests and parity. |
|
||||
| CONTEXT | D-07 | App stores, issuer, transaction boundary, row locks | 02-04 | COVERED | Persistent DCR plus real Postgres concurrency tests. |
|
||||
| CONTEXT | D-08 | App owns consent and connected apps | 05-06 | COVERED | Exact UI payloads and ownership. |
|
||||
| CONTEXT | D-09 | Raw routes and per-route token/register throttles | 03, 05, 10 | COVERED | Route-table inspection. |
|
||||
| CONTEXT | D-09 | Raw routes and per-route token/register throttles | 01-05, 10 | COVERED | Metadata in 01, register in 02, authorize in 03, token in 05 wiring; route-table inspection throughout. |
|
||||
| CONTEXT | D-10 | Retire reserved oauth guard; access stays `inv_token` | 03-05, 08, 10 | COVERED | Negative guard/source tests. |
|
||||
| CONTEXT | D-11 | Preserve configured `inv_` prefix | 04, 08-09 | COVERED | Actual MCP install/HTTP consumption. |
|
||||
| CONTEXT | D-12 | Backend Basic challenge; MCP owns rich Bearer/resource metadata | 03-05, 08-10 | COVERED | Unit, route, and real-process evidence. |
|
||||
@@ -36,13 +36,13 @@ All required GOAL, REQ, RESEARCH, CONTEXT, VALIDATION, and UI-SPEC items are pla
|
||||
| CONTEXT | D-19 | Exact app-side `fonoteka:oauth-client` | 07 | COVERED | Repeatable bonfire flags and one-time secret. |
|
||||
| CONTEXT | D-20 | Exact personal-token `/me` MCP prerequisite | 08-09 | COVERED | Existing guard/middleware and positive allow-list. |
|
||||
| CONTEXT | D-21 | Register body bounded at 64 KiB with native error | 01, 10 | COVERED | Bound precedes JSON decode. |
|
||||
| VALIDATION | W0-01 | Framework metadata/authorize/token/register/PKCE/refresh tests | 01, 04, 06, 10 | COVERED | Fast in-memory tests plus audit. |
|
||||
| VALIDATION | W0-01 | Framework metadata/authorize/token/register/PKCE/refresh tests | 01-04, 06, 10 | COVERED | Fast in-memory tests plus audit. |
|
||||
| VALIDATION | W0-02 | Real-Postgres migration/store locking/replay/sweep tests | 02, 04, 06, 10 | COVERED | Existing auth TestMain harness. |
|
||||
| VALIDATION | W0-03 | Raw routing/parser/rate/body/header isolation | 03-05, 10 | COVERED | Assembled route tests. |
|
||||
| VALIDATION | W0-04 | Consent/collection/connected-app ownership | 05-06, 10 | COVERED | Real-Postgres controllers. |
|
||||
| VALIDATION | W0-05 | Nine routes, lifecycle replay, 103-method map | 09-10 | COVERED | Corpus/fixture/map gates. |
|
||||
| VALIDATION | W0-06 | Personal-token `/me` | 08-09 | COVERED | MCP startup prerequisite. |
|
||||
| VALIDATION | W0-07 | Full unchanged MCP and security gate | 09-10 | COVERED | Final script plus review checkpoint. |
|
||||
| VALIDATION | W0-07 | Full unchanged MCP and security gate | 09-10 | COVERED | 09 self-validates gate structure; 10 final checkpoint is the sole long execution. |
|
||||
| UI-SPEC | UI-01 | Nuxt remains unchanged | 05-10 | COVERED | Read-only harness and scoped path-diff checks. |
|
||||
| UI-SPEC | UI-02 | Consent read/allow/deny states and exact payload/status/redirect semantics | 05, 09-10 | COVERED | Includes invalid-handle no-request, safe login return, stale/foreign/used 404, and empty-scope 422. |
|
||||
| UI-SPEC | UI-03 | Connected-app empty/populated/error/list/revoke contracts | 05-06, 09-10 | COVERED | Browser matrix plus positive allow-list, manual count, identical 404. |
|
||||
|
||||
@@ -21,17 +21,17 @@ created: 2026-09-23
|
||||
| **Config file** | Existing `go.work`, repository package tests, `../fonoteka.go/plugins/golem15/fonoteka/classes/TestMain`, and planned `scripts/check-phase8.sh` |
|
||||
| **Quick run command** | `go test ./wristband -count=1` |
|
||||
| **App-focused command** | `cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestOAuth|TestMe' -count=1` |
|
||||
| **Full suite command** | `scripts/check-phase8.sh` |
|
||||
| **Estimated runtime** | Quick package checks under 30 seconds; full two-repository parity/race/e2e gate may take several minutes |
|
||||
| **Full suite command** | `scripts/check-phase8.sh` — Plan 08-10 Task 3 only |
|
||||
| **Estimated runtime** | Every task command is focused and designed for ≤30 seconds; the sole final two-repository parity/race/UI/real-MCP gate may take several minutes |
|
||||
|
||||
---
|
||||
|
||||
## Sampling Rate
|
||||
|
||||
- **After every task commit:** Run the narrowest affected package test; `go test ./wristband -count=1` is the default framework check.
|
||||
- **After every plan wave:** Run `go vet ./...` and `go test ./...` in each affected repository; storage waves also run focused real-Postgres tests.
|
||||
- **Before `$gsd-verify-work`:** `scripts/check-phase8.sh` must pass, including both repositories' vet/test/race suites, parity corpus audit, secret scan, security review, and unchanged real-MCP lifecycle.
|
||||
- **Max feedback latency:** 30 seconds for task-level sampling; slow Postgres, race, parity, and real-MCP gates run at wave/phase boundaries.
|
||||
- **After every task:** Run only the named package/test, shell syntax, source assertion, or contract self-test shown in that task; focused Postgres tests select one behavior family.
|
||||
- **Final blocking checkpoint only (08-10 Task 3):** `scripts/check-phase8.sh` runs both repositories' vet/test/race, full parity/corpus, full UI, secret scan, security review, and unchanged real-MCP lifecycle exactly once.
|
||||
- **Max feedback latency:** Task-level commands are designed for ≤30 seconds. Complete repository suites, race, full parity/corpus, browser UI, Docker services, and real MCP are forbidden before the final checkpoint.
|
||||
|
||||
---
|
||||
|
||||
@@ -39,14 +39,14 @@ created: 2026-09-23
|
||||
|
||||
| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|
||||
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
|
||||
| 08-W0-01 | 08-01, 08-04, 08-06, 08-10 | 1, 4, 6, 9 | AUTH-05 | T-08-PKCE / T-08-CODE-REPLAY | Metadata, authorize, PKCE S256, code exchange, refresh, DCR, and ordered redirects have deterministic framework tests | unit | `go test ./wristband -run 'Test(Metadata|Authorize|Token|Register|PKCE|Refresh)' -count=1` | ❌ W0 | ⬜ pending |
|
||||
| 08-W0-02 | 08-02, 08-04, 08-06, 08-10 | 2, 4, 6, 9 | AUTH-05, AUTH-07 | T-08-CODE-REPLAY / T-08-REFRESH-REPLAY | Nullability, row locks, single-use codes, committed lineage kill, sweeps, and indexes work on real Postgres | integration | `cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/classes/... -run 'TestOAuth' -count=1` | ❌ W0 | ⬜ pending |
|
||||
| 08-W0-03 | 08-03, 08-04, 08-05, 08-10 | 3-5, 9 | AUTH-06 | T-08-DCR-FLOOD / T-08-SURFACE | Raw routing, parser rules, rate limits, 64 KiB DCR bound, exact bare bodies, and headers remain isolated from house middleware | route/integration | `cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestOAuth' -count=1` | ❌ W0 | ⬜ pending |
|
||||
| 08-W0-01 | 08-01, 08-03, 08-04, 08-06, 08-10 | 1, 3, 4, 6, 9 | AUTH-05 | T-08-PKCE / T-08-CODE-REPLAY | Mounted metadata plus deterministic authorize, PKCE S256, code exchange, refresh, and ordered redirects have focused tests | unit/route | `go test ./wristband -run 'Test(Metadata|Authorize|Token|PKCE|Refresh)' -count=1` | ❌ W0 | ⬜ pending |
|
||||
| 08-W0-02 | 08-02, 08-04, 08-06, 08-10 | 2, 4, 6, 9 | AUTH-05, AUTH-07 | T-08-DCR-FLOOD / T-08-CODE-REPLAY / T-08-REFRESH-REPLAY | Nullability, persistent DCR, row locks, single-use codes, committed lineage kill, sweeps, and indexes work on real Postgres | integration | `cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/classes/auth -run '^TestOAuth' -count=1` | ❌ W0 | ⬜ pending |
|
||||
| 08-W0-03 | 08-01-05, 08-10 | 1-5, 9 | AUTH-06 | T-08-DCR-FLOOD / T-08-SURFACE | Metadata/DCR/authorize/token raw routing, parsers, rate limits, 64 KiB bound, exact bare bodies, and headers remain isolated | route/integration | `cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestOAuth' -count=1` | ❌ W0 | ⬜ pending |
|
||||
| 08-W0-04 | 08-05, 08-06, 08-10 | 5-6, 9 | AUTH-07 | T-08-SCOPE-CEILING / T-08-CROSS-USER | Consent, active-collection binding, connected-app ownership, list, and revoke semantics match PHP | Postgres integration | `cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/controllers/... -run 'TestOAuth' -count=1` | ❌ W0 | ⬜ pending |
|
||||
| 08-W0-05 | 08-09, 08-10 | 8-9 | AUTH-05, AUTH-06, AUTH-07 | T-08-REQUEST-LEAK / T-08-SURFACE | Nine manifest routes plus `mcp-lifecycle` replay exactly and every one of 103 PHP OAuth/security methods maps to a named Go test | parity/corpus | `cd ../fonoteka.go && go test ./parity -run 'TestOAuthFlows|TestParityCorpus' -count=1` | ❌ W0 | ⬜ pending |
|
||||
| 08-W0-05 | 08-09, 08-10 | 8-9 | AUTH-05, AUTH-06, AUTH-07 | T-08-REQUEST-LEAK / T-08-SURFACE | Nine manifest routes plus `mcp-lifecycle` replay exactly and every one of 103 PHP OAuth/security methods maps to a named Go test | parity/corpus | Focused `TestOAuthFlows`/map audits during tasks; full corpus only in `scripts/check-phase8.sh` at 08-10 Task 3 | ❌ W0 | ⬜ pending |
|
||||
| 08-W0-06 | 08-08, 08-09 | 7-8 | AUTH-07 | T-08-SURFACE | Exact authenticated `/api/v1/fonoteka/me` lets the unchanged MCP process initialize without expanding the profile API surface | integration/e2e | `cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestMe|TestTokenSurface' -count=1` | ❌ W0 | ⬜ pending |
|
||||
| 08-W0-07 | 08-09, 08-10 | 8-9 | AUTH-05, AUTH-07 | All T-08 threats | Real SDK discovery, DCR, PKCE, JWT consent, token, MCP tool call, refresh/replay, connected-app revoke, and post-revoke failure complete unchanged | e2e | `scripts/check-phase8.sh` | ❌ W0 | ⬜ pending |
|
||||
| 08-W0-08 | 08-05, 08-09, 08-10 | 5, 8-9 | AUTH-05, AUTH-07 | T-08-CROSS-USER / T-08-SURFACE | Invalid-handle no-request, safe login return, consent/connected-app state matrices, accessibility, mobile, and en/pl copy remain intact without Nuxt changes | browser/contract | `node scripts/check-phase8-ui.mjs --focused` plus existing Nuxt `verify:oauth-return-path` and `verify:oauth-i18n` | ❌ W0 | ⬜ pending |
|
||||
| 08-W0-07 | 08-09, 08-10 | 8-9 | AUTH-05, AUTH-07 | All T-08 threats | 08-09 self-validates the gate contract; 08-10 final checkpoint alone runs real SDK discovery, DCR, PKCE, JWT consent, token, tool, refresh/replay, revoke, and post-revoke failure unchanged | e2e | `scripts/check-phase8.sh` only at 08-10 Task 3 | ❌ W0 | ⬜ pending |
|
||||
| 08-W0-08 | 08-05, 08-09, 08-10 | 5, 8-9 | AUTH-05, AUTH-07 | T-08-CROSS-USER / T-08-SURFACE | 08-05 self-validates scenario coverage; 08-10 final checkpoint alone executes invalid-handle, return-path, consent/apps, accessibility, mobile, and en/pl browser checks without Nuxt changes | browser/contract | Full `scripts/check-phase8-ui.mjs` plus Nuxt verifiers only inside final `scripts/check-phase8.sh` | ❌ W0 | ⬜ pending |
|
||||
|
||||
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
|
||||
|
||||
@@ -62,7 +62,7 @@ created: 2026-09-23
|
||||
- [ ] `../fonoteka.go/parity/oauth_flow_test.go` and `mcp-lifecycle` fixture — projected existing flows and clean lifecycle/replay coverage.
|
||||
- [ ] `scripts/check-phase8.sh` — two-repository vet/test/race, corpus, secret, security-review, and real-MCP gate.
|
||||
- [ ] `scripts/check-phase8-ui.mjs` — read-only unchanged-Nuxt state, accessibility, return-path, i18n, and responsive contract gate.
|
||||
- [ ] `scripts/check-phase8-red.sh` — compiling RED verifier that rejects syntax/setup/missing-test/unrelated failures.
|
||||
- [ ] `scripts/check-phase8-red.sh` — machine-readable `go test -json` verifier requiring exact selected test/package/sentinel and zero unexpected fail actions, plus exact exit-86 stage/sentinel shell protocol.
|
||||
- [ ] `08-SECURITY-REVIEW.md` — map every `T-08-*` threat to a failing-when-broken test and close all high-severity threats.
|
||||
|
||||
---
|
||||
@@ -79,7 +79,7 @@ All phase behaviors are automated. Live Claude, ChatGPT, and Grok connections ar
|
||||
- [ ] Sampling continuity: no three consecutive implementation tasks lack automated verification.
|
||||
- [ ] Wave 0 covers every currently missing test/gate reference above.
|
||||
- [ ] No watch-mode flags appear in validation commands.
|
||||
- [ ] Task-level feedback remains under 30 seconds; slow suites are assigned to wave/phase gates.
|
||||
- [ ] Task-level feedback is designed for ≤30 seconds; all complete suite/race/parity/UI/real-MCP work appears only in 08-10 Task 3.
|
||||
- [ ] `nyquist_compliant: true` is set after task IDs are finalized and every mapping is implemented.
|
||||
|
||||
**Approval:** pending plan verification
|
||||
|
||||
Reference in New Issue
Block a user