diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 7bb892b..e2e0c5b 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -223,7 +223,7 @@ Plans:
4. The guarded outbound fetch helper rejects a non-allow-listed host and enforces a byte cap and timeout on a user-supplied cover URL fetch (manual cover URL, Discogs cover).
5. OpenAPI is generated from swaggo/swag annotations on handlers and `openapi-typescript` produces valid TypeScript types from it; CORS and JSON body-size limits match the PHP deployment.
-**Plans**: 6 plans
+**Plans**: 10 plans
Plans:
**Wave 1** *(parallel)*
@@ -325,27 +325,40 @@ Plans:
**Wave 1**
-- [ ] 08-01-PLAN.md — Discover and dynamically register clients through the real app and corrected OAuth schema
+- [ ] 08-01-PLAN.md — Define and prove the app-agnostic metadata and DCR engine
**Wave 2** *(blocked on 08-01)*
-- [ ] 08-02-PLAN.md — Complete S256 authorize, JWT consent, and atomic authorization-code exchange
+- [ ] 08-02-PLAN.md — Correct OAuth schema and implement transaction-scoped Postgres stores
**Wave 3** *(blocked on 08-02)*
-- [ ] 08-03-PLAN.md — Rotate refresh grants, kill replayed lineages, and manage connected apps
+- [ ] 08-03-PLAN.md — Mount persistent metadata and DCR on the assembled raw route surface
**Wave 4** *(blocked on 08-03)*
-- [ ] 08-04-PLAN.md — Provision confidential clients and serve the MCP personal-token bootstrap
+- [ ] 08-04-PLAN.md — Implement ordered authorize validation and atomic PKCE code exchange
**Wave 5** *(blocked on 08-04)*
-- [ ] 08-05-PLAN.md — Replay PHP OAuth flows and run the unchanged real MCP lifecycle
+- [ ] 08-05-PLAN.md — Wire JWT consent and prove the unchanged Nuxt UI contract
-**Wave 6** *(blocked on 08-05; blocking security checkpoint)*
+**Wave 6** *(blocked on 08-05)*
-- [ ] 08-06-PLAN.md — Close 103-method coverage, independent security review, and final phase gate
+- [ ] 08-06-PLAN.md — Rotate refresh grants, kill replayed lineages, and manage connected apps
+
+**Wave 7** *(parallel; blocked on 08-06)*
+
+- [ ] 08-07-PLAN.md — Provision confidential clients through the exact operator command
+- [ ] 08-08-PLAN.md — Serve the MCP personal-token bootstrap on the existing token surface
+
+**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
+
+**Wave 9** *(blocked on 08-09; blocking security checkpoint)*
+
+- [ ] 08-10-PLAN.md — Close 103-method coverage, independent security review, and the final fail-closed gate
### Phase 9: Backend admin authentication and schema pipeline
@@ -483,7 +496,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 →
| 5. Data layer full fidelity | 6/6 | Complete | 2026-09-18 |
| 6. HTTP routing, auth groups and rate limiting | 14/14 | Complete | 2026-09-21 |
| 7. User plugin and authentication | 8/8 | Complete | 2026-09-23 |
-| 8. OAuth2.1 authorization server | 0/TBD | Not started | - |
+| 8. OAuth2.1 authorization server | 0/10 | Not started | - |
| 9. Backend admin authentication and schema pipeline | 0/TBD | Not started | - |
| 10. Admin Vue SPA | 0/TBD | Not started | - |
| 11. Jobs, realtime and search infrastructure | 0/TBD | Not started | - |
diff --git a/.planning/phases/08-oauth2-1-authorization-server/08-01-PLAN.md b/.planning/phases/08-oauth2-1-authorization-server/08-01-PLAN.md
index 57312d1..6132a75 100644
--- a/.planning/phases/08-oauth2-1-authorization-server/08-01-PLAN.md
+++ b/.planning/phases/08-oauth2-1-authorization-server/08-01-PLAN.md
@@ -10,55 +10,39 @@ files_modified:
- 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
+ - scripts/check-phase8-red.sh
autonomous: true
-requirements: [AUTH-05, AUTH-06, AUTH-07]
+requirements: [AUTH-05, AUTH-06]
must_haves:
truths:
- - "An unauthenticated connector can fetch the exact RFC 8414 metadata document and dynamically register a public or confidential client."
- - "Registration is JSON-only, rate-limited, bounded to 64 KiB, and emits bare PHP-compatible success and error bodies."
- - "Public clients, multiple pending requests, and the required OAuth lookup indexes persist correctly in Postgres."
+ - "D-01: The app-agnostic standard-library wristband package serves exact RFC 8414 metadata and validates RFC 7591 registration 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."
artifacts:
- path: "wristband/server.go"
- provides: "App-agnostic OAuth options and RFC 8414 metadata handler"
- exports: ["Options", "Server", "New"]
+ provides: "Options, exact metadata writer, and app-agnostic server contract"
- path: "wristband/register.go"
- provides: "RFC 7591 validation, client issuance, cap, sweep, and bounded JSON handler"
- - path: "../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store.go"
- provides: "GORM-backed transaction-scoped wristband store"
- - path: "../fonoteka.go/plugins/golem15/fonoteka/updates/12_oauth_schema_correction.go"
- provides: "Additive nullability and index correction"
+ provides: "RFC 7591 validation, issuance, cap, sweep, and bounded handler"
+ - path: "scripts/check-phase8-red.sh"
+ provides: "Fail-closed RED verifier rejecting syntax/setup/missing-test failures"
key_links:
- - from: "../fonoteka.go/plugins/golem15/fonoteka/plugin.go"
- to: "wristband.New"
- via: "Boot constructs the server from app config and the GORM backend"
- pattern: "wristband\\.New"
- - from: "../fonoteka.go/plugins/golem15/fonoteka/routes.go"
- to: "wristband.Server"
- via: "raw metadata and register handlers, with register throttle"
- pattern: "GroupRaw|fonoteka-oauth-register"
+ - from: "wristband/register.go"
+ to: "wristband.Backend.WithinTx"
+ via: "serialized sweep, cap check, and create"
+ pattern: "WithinTx"
---
-Deliver the first real OAuth vertical slice: an unchanged connector can discover this authorization server and register a client against persistent Postgres state.
+Define and implement the framework-only discovery and dynamic-registration contract before app persistence or routing.
-Purpose: Establish the exact raw wire contract and correct data representation that every later grant flow uses, while honoring D-01's direct standard-library implementation instead of zitadel/oidc.
-Output: The `wristband` metadata/DCR surface, corrected OAuth schema and models, GORM store adapter, app configuration, boot wiring, and raw routes.
+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.
## Phase Goal
-**As a** fonoteka-mcp or ChatGPT connector, **I want to** discover and register with SummerCMS using the same OAuth contract as the PHP backend, **so that** I can begin the unchanged PKCE connection flow.
+**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.
@/home/jin/.codex/get-shit-done/workflows/execute-plan.md
@@ -73,118 +57,38 @@ Output: The `wristband` metadata/DCR surface, corrected OAuth schema and models,
@.planning/phases/08-oauth2-1-authorization-server/08-RESEARCH.md
@.planning/phases/08-oauth2-1-authorization-server/08-PATTERNS.md
@.planning/phases/08-oauth2-1-authorization-server/08-VALIDATION.md
-
-
-Existing routing contract from `pact/capabilities.go` and `surf/router.go`:
-- `Router.GroupRaw(prefix string, middleware []string, fn func(Router))`
-- `Router.Get/Post/Delete(path string, handler http.HandlerFunc, middleware ...string)`
-
-Existing application records:
-- `models.OAuthClient` owns client id, optional secret hash, redirect/grant/auth-method JSON, registration IP, consent/revocation, and scope ceiling.
-- `models.OAuthAuthCode` must represent a pre-consent row with nullable request/code/user fields.
-- `Plugin.Buckets()` already exposes `fonoteka-oauth-register` at 30 requests/minute keyed by trusted client IP.
-
-New framework contract established by this plan:
-- `wristband.Options` carries issuer, endpoint paths, scopes/auth methods, resource, consent URL builder, TTLs, DCR cap/stale age, and `RegisterMaxBytes: 65536`.
-- `wristband.Backend.WithinTx(context.Context, func(wristband.Tx) error) error` is the only mutation boundary; `Tx` composes client/code/refresh/token-issuer operations without importing GORM.
-
- Task 1: Specify the discovery and registration path with failing tests
- wristband/registration_test.go, ../fonoteka.go/plugins/golem15/fonoteka/updates/oauth_schema_correction_test.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store_test.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_registration_test.go
-
- .planning/phases/08-oauth2-1-authorization-server/08-CONTEXT.md
- .planning/phases/08-oauth2-1-authorization-server/08-RESEARCH.md
- .planning/phases/08-oauth2-1-authorization-server/08-VALIDATION.md
- ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/postgres_test.go
- ../fonoteka.go/plugins/golem15/fonoteka/plugin_boot_test.go
- ../fonoteka.go/plugins/golem15/fonoteka/updates/11_secrets_slice.go
- /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthMetadataController.php
- /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthRegisterController.php
- /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/functional/OAuthMetadataTest.php
- /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/functional/OAuthRegisterTest.php
-
+ Task 1: Create compiling RED discovery and registration contracts
+ wristband/server.go, wristband/stores.go, wristband/registration_test.go, scripts/check-phase8-red.sh
- - Metadata returns the exact unwrapped 11-field document, field order, content type, and recorded cache header.
- - JSON DCR creates public `none` and confidential `client_secret_post`/`client_secret_basic` clients; a secret is returned once and only its SHA-256 hex is stored.
- - Non-JSON, invalid URI/auth/grant/response metadata, more than five URIs, cap overflow, and a body larger than 65,536 bytes return exact endpoint-native errors.
- - Concurrent registrations cannot cross the configured client cap; artisan clients with null registration IP are not swept.
- - The additive migration permits null public-client/pending fields, creates named operational indexes, and refuses rollback when null lifecycle rows would be lost.
+ - 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.
- Per D-18 and the MVP test-first rule, add failing framework, real-Postgres, and assembled-route tests before production code. Use deterministic clock/random readers in wristband tests. Assert bytes and headers before decoding JSON. Include named failures for T-08-DCR-FLOOD, T-08-SECRET-TIMING, T-08-SURFACE, and T-08-REQUEST-LEAK. Prove the raw route table has no JWT, `inv_token`, `inv.scope`, body-limit, or house middleware, while `/register` carries only `throttle:fonoteka-oauth-register`. Commit the RED tests separately; do not weaken assertions to make current code pass.
+ 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.
- test -f wristband/registration_test.go && cd ../fonoteka.go && test -f plugins/golem15/fonoteka/oauth_registration_test.go
+ scripts/check-phase8-red.sh registration go test ./wristband -run 'Test(Metadata|Register|Registration)' -count=1
-
- - `rg -n 'Test(Metadata|Register|RegistrationBodyLimit|RegistrationCap|OAuthSchema)' wristband/registration_test.go ../fonoteka.go/plugins/golem15/fonoteka/{oauth_registration_test.go,updates/oauth_schema_correction_test.go,classes/auth/oauth_store_test.go}` finds named tests for every behavior above.
- - At least one focused test fails because the wristband production package or raw routes do not yet exist; the failure is implementation-related, not a syntax error.
- - Test source contains literal assertions for `65536`, `Basic realm=`, `Cache-Control`, and the absence of house/auth middleware.
-
- The executable happy-path and adversarial discovery/DCR contract exists in RED state, including schema, concurrency, body-bound, header, and surface-isolation coverage.
+ The tests compile, the named tests execute, and the verifier accepts only the expected missing-behavior RED marker.
- Task 2: Implement wristband discovery, DCR, cryptography, and persistent storage
- wristband/server.go, wristband/stores.go, wristband/crypto.go, wristband/register.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/classes/auth/oauth_store.go
-
- wristband/registration_test.go
- ../fonoteka.go/plugins/golem15/fonoteka/updates/oauth_schema_correction_test.go
- ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/oauth_store_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/models/oauth_refresh_token.go
- ../fonoteka.go/plugins/golem15/fonoteka/updates/11_secrets_slice.go
- .planning/phases/08-oauth2-1-authorization-server/08-PATTERNS.md
-
+ Task 2: Implement exact metadata, DCR, bounds, and cryptography
+ wristband/server.go, wristband/stores.go, wristband/crypto.go, wristband/register.go, wristband/registration_test.go
- - Fixed-length SHA-256 transforms use `crypto/subtle.ConstantTimeCompare`; raw client secrets are never persisted or logged.
- - DCR validates the exact PHP allow-list and URI rules, serializes cap/sweep/create atomically, and strips control characters from a maximum-120-character client name.
- - The GORM adapter applies `FOR UPDATE` only in app code and every transaction-scoped method uses the callback's `*gorm.DB`.
+ - 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.
- Implement the app-agnostic `wristband` package per D-01, D-03, D-05, D-06, D-07, D-17, and D-21. Use `crypto/rand`, `sha256`, `subtle.ConstantTimeCompare`, `base64.RawURLEncoding`, `encoding/json`, and `net/http`; add no dependency. Define typed records and a transaction-scoped backend, a local no-newline JSON writer, exact metadata, and RFC 7591 registration. Enforce the 64 KiB bound with `http.MaxBytesReader` before decoding and map overflow to `invalid_client_metadata`. Create client IDs from 16 random bytes and secrets from 32; store only SHA-256 hex. Correct the two model files to pointer fields and add a new gormigrate step that drops the four `NOT NULL` constraints, adds the PHP-equivalent operational indexes idempotently, and refuses down migration when null rows exist. Implement atomic client cap/sweep/create in the GORM adapter; do not import app/GORM code from wristband.
+ 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.
- go test ./wristband -run 'Test(Metadata|Register|Registration)' -count=1 && cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/classes/auth ./plugins/golem15/fonoteka/updates -run 'TestOAuth' -count=1
+ go test ./wristband -run 'Test(Metadata|Register|Registration)' -count=1
-
- - `go list -deps ./wristband | rg 'fonoteka|gorm.io'` returns no matches.
- - `rg -n 'subtle\.ConstantTimeCompare' wristband/crypto.go` finds the fixed-transform comparison and `rg -n 'client_secret_hash.*\*string|request_id.*\*string|code_hash.*\*string|user_id.*\*uint' ../fonoteka.go/plugins/golem15/fonoteka/models/oauth_{client,auth_code}.go` finds all nullable model corrections.
- - Focused framework and real-Postgres tests pass, including concurrent cap enforcement and rollback refusal.
- - No raw request id, code, verifier, client secret, access token, or refresh token is written through a logging call in `wristband/`.
-
- The framework protocol/storage contracts and corrected Postgres schema make public/confidential registration safe, durable, bounded, and byte-compatible.
-
-
-
- Task 3: Mount the configured metadata and DCR slice in the real app
- ../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
-
- ../fonoteka.go/plugins/golem15/fonoteka/oauth_registration_test.go
- ../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
- surf/router.go
- .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-03-SUMMARY.md
-
-
- - `GET /.well-known/oauth-authorization-server` and `POST /oauth/mcp/register` run through the assembled app, not a hand-built router.
- - Defaults are pending/code 600 seconds, access 3600 seconds, refresh 30 days, DCR cap 200, stale-client age 24 hours, resource `https://mcp.plytarium.com/mcp`, and registration maximum 65,536 bytes.
- - `/register` is throttled per trusted client IP and no `oauth` guard is registered.
-
- Per D-03, D-09, D-10, and D-12, add plugin config keys under `plugins.golem15.fonoteka.oauth.*`, keep `app.url` as the issuer source with its trailing slash trimmed once, construct the GORM backend and wristband server in `Plugin.Boot`, and retain it for route/command factories. Mount metadata and register inside the existing raw group; pass `throttle:fonoteka-oauth-register` only to register. Do not attach the Phase 6 body limiter or add an `oauth` guard. Preserve the backend token-surface 401 contract and do not add RFC 9728 metadata/challenges owned by fonoteka-mcp.
-
- cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestOAuth(Metadata|Register|RawRoute|Config)' -count=1
-
-
- - Assembled-route tests return 200 metadata and 201 DCR with the exact unwrapped bodies and expected headers.
- - Route-table assertions show `/oauth/mcp/register` has `throttle:fonoteka-oauth-register` and neither RFC route has JWT, `inv_token`, `inv.scope`, or house middleware.
- - `rg -n 'Register\(.*oauth|"oauth"' ../fonoteka.go/plugins/golem15/fonoteka` finds no new guard registration.
- - `go vet ./... && go test ./...` passes in both `summercms.go` and `../fonoteka.go`.
-
- A real connector can discover and register against the assembled Go application with exact PHP-compatible routing, configuration, persistence, limits, and security boundaries.
+ Framework discovery and DCR tests pass with exact bytes, atomic cap behavior, bounded decoding, hash-only persistence, and no app-tier imports.
@@ -194,32 +98,28 @@ New framework contract established by this plan:
| Boundary | Description |
|----------|-------------|
-| Internet connector → raw OAuth routes | Unauthenticated metadata and attacker-controlled JSON cross into the authorization server. |
-| wristband → app Backend | App-agnostic protocol state crosses into transaction-scoped Postgres persistence. |
-| app config → public metadata | Deployment-controlled issuer/resource/endpoints become client trust anchors. |
+| Connector → wristband | Untrusted metadata/DCR requests cross into protocol parsing. |
+| wristband → Backend | Protocol state crosses into an app-provided transaction. |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
-| T-08-DCR-FLOOD | Denial of Service | `register.go`, client store | mitigate | 64 KiB pre-decode cap, existing per-IP limiter, atomic cap 200, and 24-hour unconsented sweep with concurrency tests. |
-| T-08-SECRET-TIMING | Information Disclosure | `crypto.go`, client authentication helper | mitigate | Compare fixed SHA-256 hex transforms only through `crypto/subtle.ConstantTimeCompare`; source and behavior tests reject direct equality. |
-| T-08-REQUEST-LEAK | Information Disclosure | handlers, logs, fixtures | mitigate | No secret/handle logging, hash-only persistence, one-time secret response, log-capture/source scans. |
-| T-08-SURFACE | Elevation of Privilege | raw route registration | mitigate | Route-table test proves raw routes have only their named throttle and no auth/house middleware. |
-| T-08-SC | Tampering | dependencies | mitigate | No package install occurs; fail if a new module dependency appears without a package-legitimacy audit. |
+| 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-SC | Tampering | dependencies | mitigate | No package install; stdlib-only import audit. |
- `go test ./wristband -count=1`
-- `cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestOAuth(Metadata|Register|Schema|Store|RawRoute)' -count=1`
-- `go vet ./... && go test ./...` passes in each repository.
+- `go list -deps ./wristband | rg 'fonoteka|gorm.io'` returns no matches.
-- The exact metadata document and RFC 7591 responses are reachable on the assembled app without an envelope.
-- Public and confidential clients persist with hash-only secrets; concurrent cap enforcement cannot create client 201.
-- The corrected schema represents public clients and multiple pending requests and includes the required indexes.
-- Registration rejects over-64-KiB and non-JSON requests through endpoint-native errors and the named limiter is present.
+- 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.