docs(14.1): create phase plan
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
---
|
||||
phase: 14.1-oauth-identities-and-fonoteka-me-routes
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["14.1-01"]
|
||||
files_modified:
|
||||
- ../fonoteka.go/plugins/golem15/user/oauth_identities_test.go
|
||||
- ../fonoteka.go/plugins/golem15/user/updates/oauth_identities_test.go
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller_test.go
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/phase08_coverage_test.go
|
||||
- ../fonoteka.go/parity/parity_test.go
|
||||
autonomous: true
|
||||
requirements: [API-09, QA-05, HTTP-01, HTTP-03, DATA-02, DATA-07, I18N-01]
|
||||
estimate:
|
||||
tokens: 280000
|
||||
raw_tokens: 280000
|
||||
tasks: 3
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "Per D-11, Go tests ported from OAuthIdentityApiTest.php prove unlink 204 keeps the other row, last-method 409 EN and PL texts match the user-plugin lang strings, missing/foreign/unknown-provider Winter HTML 404 bodies are byte-identical, and both identity routes return 401 without a JWT on `/google`."
|
||||
- "Per HTTP-03 and PHP TokenSurfaceIsolationTest, identity routes exist on the JWT group only; `/api/v1/fonoteka` never lists them; DELETE middleware includes `throttle:10,1`."
|
||||
- "Per DATA-02, the oauth-identities migration migrates up and rolls back: table, both unique indexes, jsonb `profile_data`, and cascade FK are present after up and absent after down."
|
||||
- "Per D-09, `TestMeTokenHandlerNilScopesAndCollectionIDsSerializeAsEmptyArraysAndNullableName` (renamed as needed) requires `scopes` as `[]` and `collection_ids` as JSON null."
|
||||
- "Per DATA-07 and T-14.1-01, GET list with seeded Encrypted tokens never contains the plaintext, the key names `access_token`/`refresh_token`/`profile_data`, or `[redacted]`."
|
||||
- "Per API-09, QA-05 and D-12, `TestParityCorpus` prints `recorded 175/175 passing 175 failing 0 unrecorded 0 pending 0`."
|
||||
artifacts:
|
||||
- path: "../fonoteka.go/plugins/golem15/user/oauth_identities_test.go"
|
||||
provides: "D-11 API tests and secret-leak assertions"
|
||||
contains: "TestOAuthIdentities"
|
||||
- path: "../fonoteka.go/plugins/golem15/user/updates/oauth_identities_test.go"
|
||||
provides: "migration up/down"
|
||||
contains: "golem15_user_oauth_identities"
|
||||
- path: "../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller_test.go"
|
||||
provides: "D-09 nil collection_ids JSON null"
|
||||
contains: "collection_ids"
|
||||
- path: "../fonoteka.go/plugins/golem15/fonoteka/phase08_coverage_test.go"
|
||||
provides: "jwt-only identity surfaces"
|
||||
contains: "oauth-identities"
|
||||
key_links:
|
||||
- from: "../fonoteka.go/plugins/golem15/user/oauth_identities_test.go"
|
||||
to: "../fonoteka.go/plugins/golem15/user/controllers/oauth_identities.go"
|
||||
via: "httptest calls OAuthIdentitiesIndex/Destroy constructors with bouncer.WithUser"
|
||||
pattern: "OAuthIdentitiesDestroy"
|
||||
- from: "../fonoteka.go/parity/parity_test.go"
|
||||
to: "../fonoteka.go/parity/manifest.yaml"
|
||||
via: "TestParityCorpus counts 175 recorded and 175 ported"
|
||||
pattern: "expectedPortedRoutes"
|
||||
prohibitions:
|
||||
- requirement_id: HTTP-01
|
||||
category: safety
|
||||
statement: "401 tests use path /google so jwt.auth runs; an unauthenticated unknown provider is not used as the 401 probe"
|
||||
status: resolved
|
||||
verification: test
|
||||
- requirement_id: DATA-07
|
||||
category: privacy
|
||||
statement: "Secret-leak tests fail if the GET body contains plaintext tokens, Encrypted JSON keys, or the redacted literal"
|
||||
status: resolved
|
||||
verification: test
|
||||
- requirement_id: API-09
|
||||
category: transparency
|
||||
statement: "Pending routes MUST NOT count as passing; the corpus gate is 175/175/0"
|
||||
status: resolved
|
||||
verification: test
|
||||
---
|
||||
|
||||
## Phase Goal
|
||||
|
||||
**As a** signed-in Nuxt user (and as a fonoteka-mcp token caller), **I want to** list and unlink connected OAuth identities and receive the full personal-token `/me` body, **so that** Settings → Connected accounts and MCP bootstrap work against Go with zero pending routes.
|
||||
|
||||
This plan's slice: the dedicated unit-test plan (CLAUDE.md lean mode). Plan 01 already shipped the production path; this plan makes every D-11 behaviour, migration, `/me` null, threat, and corpus count fail when broken.
|
||||
|
||||
<objective>
|
||||
Port OAuthIdentityApiTest.php, prove EN/PL 409, byte-identical Winter 404s, unknown provider, 401 on `/google`, jwt-only TokenSurfaceIsolation, migration up/down, rewritten MeToken nil-collection, secret-leak threat tests, and TestParityCorpus 175/175/0.
|
||||
|
||||
Purpose: lean-mode last plan; QA-05 / API-09 evidence for `/gsd-verify-work 14.1`.
|
||||
Output: tests in sm-user-plugin, fonoteka plugin, and parity. No summercms.go module API changes.
|
||||
Repos: fonoteka.go / sm-user-plugin. Never add co-author tags.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.codex/gsd-core/workflows/execute-plan.md
|
||||
@~/.codex/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/14.1-oauth-identities-and-fonoteka-me-routes/14.1-01-PLAN.md
|
||||
@.planning/phases/14.1-oauth-identities-and-fonoteka-me-routes/14.1-RESEARCH.md
|
||||
@.planning/phases/14.1-oauth-identities-and-fonoteka-me-routes/14.1-PATTERNS.md
|
||||
@.planning/phases/14.1-oauth-identities-and-fonoteka-me-routes/14.1-VALIDATION.md
|
||||
@../fonoteka.go/plugins/golem15/user/api_tokens_test.go
|
||||
@../fonoteka.go/plugins/golem15/user/api_tokens_edge_test.go
|
||||
@../fonoteka.go/plugins/golem15/user/updates/api_tokens_test.go
|
||||
@../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller_test.go
|
||||
@../fonoteka.go/plugins/golem15/fonoteka/phase08_coverage_test.go
|
||||
@/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/functional/OAuthIdentityApiTest.php
|
||||
@/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/security/TokenSurfaceIsolationTest.php
|
||||
|
||||
<interfaces>
|
||||
- Plan 01 exports `OAuthIdentitiesIndex`, `OAuthIdentitiesDestroy`, `OAuthIdentitiesOptions`, `OAuthIdentity`, migration `202610050001_create_oauth_identities`, JWT mount, D-09 MeToken, extras `oauth-identities-linked` / `me-unrestricted`, `expectedPortedRoutes = 175`.
|
||||
- Analog tests: `api_tokens_test.go` (httptest + `bouncer.WithUser` + constructor), `api_tokens_edge_test.go` (foreign destroy 404, list isolation), `updates/api_tokens_test.go` (`dedicatedDB`, `gormigrate.New` with `TableName: "summer_migrations_golem15_user"`, `HasTable`/`HasColumn`, `RollbackMigration`).
|
||||
- MeToken tests: `meTokenRequest` uses `bouncer.WithUser` + `bouncer.WithCredential`; `TestMeTokenHandlerNilScopesAndCollectionIDsSerializeAsEmptyArraysAndNullableName` currently forbids `"collection_ids":null` (lines 113-117) — D-09 reverses that field only; `scopes` still must not be JSON null. Keep `TestMeTokenHandlerExactFourFieldsWithScopesAndCollectionIDs` and the no-requery test.
|
||||
- phase08 `assertRouteSurfaces(method, suffix, wantJWT, wantToken)`; DELETE must list `throttle:10,1`.
|
||||
- Winter 404 bytes: recorded DELETE fixture `text/html; charset=UTF-8`, title `Nie znaleziono strony`. Foreign and missing bodies must `bytes.Equal`.
|
||||
- 401 without JWT is group `jwt.auth` (`{"error":true,"message":...}` + `Cache-Control: no-cache, private`). Probe path `/google` (research A1).
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
## Artifacts this phase produces
|
||||
|
||||
(This plan's share.)
|
||||
|
||||
- `TestOAuthIdentitiesIndexEmptyList` (from 01) plus D-11: unlink 204 keeps sibling row, 409 EN/PL, missing/foreign/unknown Winter 404, 401 on `/google`, secret-leak, last-method still 409 when the account has a password.
|
||||
- `updates/oauth_identities_test.go` migration up/down (skip on `-short` via existing `dedicatedDB`).
|
||||
- Rewritten MeToken nil-collection test requiring `"collection_ids":null` and `"scopes":[]`.
|
||||
- phase08 jwt-only GET and DELETE plus DELETE `throttle:10,1`.
|
||||
- `TestParityCorpus` 175/175/0.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- Assumption-delta remains closed: D-06/D-13 already answered second-method lockout; tests prove count-only 409, they do not add social login.
|
||||
- Plan 01 flipped GET (and DELETE) surfaces; this plan asserts them fail-closed and adds throttle contains-check if Task 3 of 01 left a gap.
|
||||
- Do not retarget `scripts/check-phase14.sh` (`EXPECTED_PENDING=3` is a Phase 14 artifact).
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Port OAuthIdentityApiTest.php — 204, EN/PL 409, Winter 404s, 401 /google, jwt-only surfaces</name>
|
||||
<files>../fonoteka.go/plugins/golem15/user/oauth_identities_test.go, ../fonoteka.go/plugins/golem15/fonoteka/phase08_coverage_test.go</files>
|
||||
<read_first>/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/functional/OAuthIdentityApiTest.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/security/TokenSurfaceIsolationTest.php, ../fonoteka.go/plugins/golem15/user/api_tokens_test.go, ../fonoteka.go/plugins/golem15/user/api_tokens_edge_test.go, ../fonoteka.go/plugins/golem15/user/session_test.go (sessionApp, insertUser), ../fonoteka.go/plugins/golem15/user/lang/en/lang.yaml, ../fonoteka.go/plugins/golem15/user/lang/pl/lang.yaml, ../fonoteka.go/parity/fixtures/routes/DELETE___fonoteka_api_v1_oauth-identities_{provider}_jwt.yaml, ../fonoteka.go/plugins/golem15/fonoteka/phase08_coverage_test.go (assertRouteSurfaces, oauth-identity subtest), ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/http_errors.go</read_first>
|
||||
<action>Per D-04, D-06, D-11, HTTP-01, HTTP-03, I18N-01.
|
||||
|
||||
(1) Expand plugin-root `oauth_identities_test.go` (package `user`) using `sessionApp`, `insertUser`, `httptest`, `bouncer.WithUser`, and the constructors. Seed rows with struct literals (empty Fillable). Destroy tests pass `OAuthIdentitiesOptions{WriteNotFound: ...}` writing the same Winter HTML 404 bytes the host uses (`WriteWinterHTTPError` via a test double that copies `winter_404.html` headers/body, or a stub that records identical bytes for every 404 branch).
|
||||
|
||||
Behaviours: unlink 204 empty body and the sibling provider row remains; last remaining identity returns 409 JSON `error` equal to the EN string when locale is en and the PL string when locale is pl (phrasebook Get / request locale analog already used in account tests); missing, foreign, and unknown provider (`linkedin` while authenticated) return status 404, `Content-Type: text/html; charset=UTF-8`, and byte-identical bodies; 401 without a JWT on GET and DELETE `/google` (not an unknown provider). Last-method 409 still fires when that user also has a password (D-06). Keep `TestOAuthIdentitiesIndexEmptyList`.
|
||||
|
||||
(2) phase08 `test_oauth_identity_routes_exist_on_jwt_surface_only`: `assertRouteSurfaces` GET `/oauth-identities` jwt-only and DELETE `/oauth-identities/{provider}` jwt-only. Assert DELETE middleware contains `throttle:10,1`. No identity suffix on `/api/v1/fonoteka`.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>go -C ../fonoteka.go vet ./plugins/golem15/user/... ./plugins/golem15/fonoteka/... && go -C ../fonoteka.go test ./plugins/golem15/user ./plugins/golem15/fonoteka -count=1 -v -run 'OAuthIdentit|oauth_identity_routes_exist_on_jwt'</automated>
|
||||
<fails_when>Non-zero exit; verbose run prints "--- FAIL", "no tests to run" or "--- SKIP", or lacks PASS for the oauth-identity tests including the phase08 jwt-only subtest.</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -c 'StatusNoContent' ../fonoteka.go/plugins/golem15/user/oauth_identities_test.go` prints at least 1.
|
||||
- `grep -c 'last_method_blocked' ../fonoteka.go/plugins/golem15/user/oauth_identities_test.go` prints at least 1.
|
||||
- `grep -c '/google' ../fonoteka.go/plugins/golem15/user/oauth_identities_test.go` prints at least 1.
|
||||
- `grep -c 'assertRouteSurfaces(t, rt, http.MethodGet, "/oauth-identities", true, false)' ../fonoteka.go/plugins/golem15/fonoteka/phase08_coverage_test.go` prints at least 1.
|
||||
- `grep -c 'assertRouteSurfaces(t, rt, http.MethodDelete, "/oauth-identities/{provider}", true, false)' ../fonoteka.go/plugins/golem15/fonoteka/phase08_coverage_test.go` prints at least 1.
|
||||
- `grep -c 'assertAbsent(t, "oauth-identit"' ../fonoteka.go/plugins/golem15/fonoteka/phase08_coverage_test.go` prints 0.
|
||||
</acceptance_criteria>
|
||||
<done>D-11 identity behaviours and jwt-only surfaces fail when broken, including EN/PL 409 and byte-identical Winter 404s.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Migration up/down, MeToken null collection_ids, and secret-leak threat tests</name>
|
||||
<files>../fonoteka.go/plugins/golem15/user/updates/oauth_identities_test.go, ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller_test.go, ../fonoteka.go/plugins/golem15/user/oauth_identities_test.go</files>
|
||||
<read_first>../fonoteka.go/plugins/golem15/user/updates/api_tokens_test.go, ../fonoteka.go/plugins/golem15/user/updates/postgres_test.go (dedicatedDB, -short skip), ../fonoteka.go/plugins/golem15/user/updates/202610050001_create_oauth_identities.go, ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller.go, ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller_test.go (nil-array test ~78-117), modules/lagoon/encrypted.go (MarshalJSON redactedLiteral), .planning/phases/14.1-oauth-identities-and-fonoteka-me-routes/14.1-RESEARCH.md (DATA-02/DATA-07 validation map, T-14.1-01)</read_first>
|
||||
<action>Per D-07, D-09, DATA-02, DATA-07.
|
||||
|
||||
(1) `updates/oauth_identities_test.go`: `dedicatedDB`, `lagoon.Use`, `gormigrate.New(..., TableName: "summer_migrations_golem15_user", UseTransaction: true, All())`, `Migrate()`, assert `HasTable` `golem15_user_oauth_identities`, columns including `access_token`, `refresh_token`, `profile_data`, `linked_at`, unique index names `oauth_identities_user_provider_unique` and `oauth_identities_provider_identity_unique`, `information_schema.columns` udt `jsonb` for `profile_data`, FK `user_id` → `users(id)` ON DELETE CASCADE. `RollbackMigration` of this migration drops the table. Skip through existing dedicatedDB/`-short` behaviour; a missing container is a fail, not a pass.
|
||||
|
||||
(2) Rewrite `TestMeTokenHandlerNilScopesAndCollectionIDsSerializeAsEmptyArraysAndNullableName`: `scopes` remains a JSON array (not null); `collection_ids` MUST be the JSON null token when CollectionIDs is invalid or empty (D-09). Keep the four-field restricted test and the no-requery test. Rename the test if the old name would lie.
|
||||
|
||||
(3) Secret-leak (T-14.1-01 / DATA-07): insert an identity with `lagoon.NewEncrypted` plaintext plus `profile_data` containing a distinctive string; GET Index body must not contain the plaintext, must not contain JSON keys `access_token`, `refresh_token`, or `profile_data`, and must not contain `[redacted]` (Encrypted marshal leak of key names). Same assertion on the D-10 list-with-rows shape (provider + linked_at only).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>go -C ../fonoteka.go vet ./plugins/golem15/user/... ./plugins/golem15/fonoteka/controllers/api/... && go -C ../fonoteka.go test ./plugins/golem15/user/updates ./plugins/golem15/user ./plugins/golem15/fonoteka/controllers/api -count=1 -v -run 'OAuthIdentit|MeTokenHandlerNil|oauth_identities'</automated>
|
||||
<fails_when>Non-zero exit; verbose run prints "--- FAIL", "no tests to run" or "--- SKIP" for the named migration, MeToken nil, or secret-leak tests; updates tests skip because Postgres is missing.</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -c 'golem15_user_oauth_identities' ../fonoteka.go/plugins/golem15/user/updates/oauth_identities_test.go` prints at least 1 and `grep -c 'jsonb' ../fonoteka.go/plugins/golem15/user/updates/oauth_identities_test.go` prints at least 1.
|
||||
- `grep -c '"collection_ids":null' ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller_test.go` prints at least 1.
|
||||
- `grep -c '"scopes":null' ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller_test.go` prints at least 1 (the rewritten test still treats a null scopes token as failure).
|
||||
- `grep -c 'access_token' ../fonoteka.go/plugins/golem15/user/oauth_identities_test.go` prints at least 1 (the leak assertion names the keys that must be absent from the body).
|
||||
</acceptance_criteria>
|
||||
<done>Migration up/down, D-09 `/me` null, and Encrypted-token leak tests fail when their protections are removed.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: TestParityCorpus is 175 recorded, 175 passing, 0 pending</name>
|
||||
<files>../fonoteka.go/parity/parity_test.go</files>
|
||||
<read_first>../fonoteka.go/parity/parity_test.go (expectedPHPRoutes, expectedPortedRoutes, TestParityCorpus, assertPortedMismatch after 14.1-01), ../fonoteka.go/parity/manifest.yaml (three ported identity/me routes), .planning/phases/14.1-oauth-identities-and-fonoteka-me-routes/14.1-VALIDATION.md</read_first>
|
||||
<action>Per D-12, API-09, QA-05. Confirm `expectedPHPRoutes` and `expectedPortedRoutes` are both 175 and `assertPortedMismatch` no longer probes an unported identity GET. Run the corpus. If a fixture drifts, re-record through `php_parity.sh` + `summer parity:record` as in plan 01 Task 4 — do not hand-edit PHP response bytes. Do not change `scripts/check-phase14.sh` pending counts (Phase 14 gate stays historical).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>go -C ../fonoteka.go vet ./parity/... ./plugins/golem15/user/... ./plugins/golem15/fonoteka/... && go -C ../fonoteka.go test ./parity -count=1 -run 'TestParityCorpus' -timeout 30m</automated>
|
||||
<fails_when>Non-zero exit; summary line is not `recorded 175/175 passing 175 failing 0 unrecorded 0 pending 0`.</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -nE 'expectedPortedRoutes = 175' ../fonoteka.go/parity/parity_test.go` prints a matching line (non-empty).
|
||||
- `grep -c 'unported PHP route must not pass' ../fonoteka.go/parity/parity_test.go` prints 0.
|
||||
- Corpus output contains `pending 0` and `passing 175`.
|
||||
</acceptance_criteria>
|
||||
<done>Zero pending routes: the parity harness is green on the three formerly orphan routes, which is the API-09/QA-05 evidence this phase can give (Nuxt/MCP stay unchanged consumers).</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| Test process → handlers | Tests must not mint production JWTs or log `Encrypted.Reveal()` |
|
||||
| Corpus replay → Go app | Pending must never count as passing |
|
||||
| Test 404 writer → Destroy | Foreign/missing/unknown must be indistinguishable |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-14.1-09 | Information Disclosure | oauth_identities_test.go GET | high | mitigate | Fail if body contains plaintext, Encrypted JSON keys, or `[redacted]` |
|
||||
| T-14.1-10 | Information Disclosure | Destroy 404 tests | high | mitigate | Byte-identical Winter HTML for missing, foreign, unknown; JSON 404 fails the test |
|
||||
| T-14.1-11 | Tampering | TestParityCorpus | high | mitigate | expectedPortedRoutes 175; pending 0; rewritten mismatch helper on a ported fixture |
|
||||
| T-14.1-12 | Elevation of Privilege | phase08 TokenSurfaceIsolation | high | mitigate | assertRouteSurfaces jwt-only; token group never gains identity paths |
|
||||
| T-14.1-SC | Tampering | package installs | high | mitigate | No new modules |
|
||||
|
||||
ASVS L1: all high threats mitigated. Plan 01's T-14.1-01..08 remain in force; these IDs are the test-plane controls that make those mitigations fail-closed.
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
Full app-side gate: `go -C ../fonoteka.go vet ./...` and `go -C ../fonoteka.go test ./plugins/golem15/user/... ./plugins/golem15/fonoteka/... ./parity/... -count=1`. Corpus 175/175/0. Framework tree in summercms.go unchanged besides this planning commit.
|
||||
|
||||
<human-check>
|
||||
QA-05 consumers are frozen: sign in to the Nuxt app, open Settings → Connected accounts against the Go backend (list/unlink). Call fonoteka-mcp `me()` against Go; extra `collection_ids` is ignored by its TypeScript type.
|
||||
</human-check>
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- D-11 PHP test port is green.
|
||||
- Migration up/down proven on real Postgres.
|
||||
- MeToken unrestricted `collection_ids` is JSON null under test.
|
||||
- Secret-leak tests fail when the explicit map is replaced by model marshal.
|
||||
- `TestParityCorpus` is 175/175/0.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/14.1-oauth-identities-and-fonoteka-me-routes/14.1-02-SUMMARY.md` when done
|
||||
</output>
|
||||
Reference in New Issue
Block a user