docs(planning): commit accumulated workflow artifacts

This commit is contained in:
Jakub Zych
2026-10-06 22:31:37 +02:00
parent fdee6b626e
commit c05ad09dc2
29 changed files with 1129 additions and 107 deletions

View File

@@ -0,0 +1,26 @@
{
"version": "1.0",
"max_entries": 50,
"entries": [
{
"agent_id": "0b481b6a-b398-4a45-a581-fcd4f6ba2a62",
"task_description": "Execute plan 14.1-01 of phase 14.1",
"phase": "14.1",
"plan": "14.1-01",
"segment": null,
"timestamp": "2026-10-05T19:06:22Z",
"status": "completed",
"completion_timestamp": "2026-10-05T19:28:04Z"
},
{
"agent_id": "cdf0e62d-7467-46a7-bf67-0068e7244fbc",
"task_description": "Execute plan 14.1-02 of phase 14.1",
"phase": "14.1",
"plan": "14.1-02",
"segment": null,
"timestamp": "2026-10-05T19:28:21Z",
"status": "completed",
"completion_timestamp": "2026-10-05T19:39:31Z"
}
]
}

View File

@@ -1,53 +0,0 @@
---
status: diagnosed
trigger: "I already did that execute 9 like 3 times and gsd-progress still talks about it, wtf"
created: 2026-09-28T00:00:00+02:00
updated: 2026-09-28T14:31:00+02:00
---
## Current Focus
hypothesis: Confirmed — Phase 10.2 relocated the Phase 9 implementation packages under modules/, while 09-VERIFICATION.md still declares the old locations as covered inputs.
test: Compared every declared path with the checkout and traced representative missing entries through git rename history.
expecting: The missing paths were renamed in the Phase 10.2 commit, proving an invalidated Phase 9 report rather than a timestamp/digest instability.
next_action: Diagnose-only complete; regenerate 09-VERIFICATION.md through the stale re-verification path so its covered_files list names current modules/ paths.
bug_class: bohrbug
reasoning_checkpoint: null
tdd_checkpoint: null
## Symptoms
expected: Running execute-phase for Phase 9 refreshes its canonical verification report so gsd-progress stops routing back to Phase 9.
actual: Phase 9 remains marked stale after execute-phase has been run approximately three times.
errors: No separate error message; gsd-progress repeatedly reports stale verification and recommends Phase 9 again.
reproduction: Run execute-phase for Phase 9, then run gsd-progress and observe Phase 9 is still stale.
started: Repeated in the current 2026-09-28 workflow; whether an earlier run ever remained stable is unknown.
## Eliminated
## Evidence
- timestamp: 2026-09-28T14:20:00+02:00
checked: Phase 9 verification report, current git HEAD, and working-tree status
found: 09-VERIFICATION.md was written at 00:10Z with covered_digest v2:sha256:510b91f54dea0918569d267a7aba22fcf879ad69899aa6e2033677a68a84de41, while current HEAD is cc584e9 at 14:13+02. The report covers many framework source/test files as well as Phase 9 plans and summaries. The worktree has only planning/session state and unrelated untracked files; no covered source files are presently modified.
implication: The stale state is deterministic and likely arises from a mismatch between the report's recorded snapshot and its currently computed snapshot, not from an uncommitted covered-file edit.
- timestamp: 2026-09-28T14:23:00+02:00
checked: gsd-core verification.status implementation and Phase 9 status
found: verification.status returns stale for the actual phase directory. Reports with covered_files plus covered_digest use only a content digest; computeCoveredDigest returns null if any declared path is absent, unreadable, non-regular, or escapes projectRoot, and readVerificationStatus treats that as stale. The fingerprint CLI returned its explicit failure message: a covered file is missing, unreadable, or escapes the project root.
implication: This is not a timestamp loop. A malformed or no-longer-valid declared covered path is a stronger, directly observed root-cause candidate.
- timestamp: 2026-09-28T14:27:00+02:00
checked: Every Phase 9 covered_files entry against the current checkout
found: 42 of 71 declared paths are not files, including bouncer/*.go, cabana/*.go, lagoon/*.go, pact/capabilities.go, phrasebook/translator.go, and surf/router.go. The Phase 9 report's covered fingerprint cannot be computed as a result. Every currently present Phase 9 PLAN and SUMMARY is declared, so the failure is not an omitted phase artifact.
implication: Any re-run that leaves this declaration unchanged is guaranteed to be stale. The defect is a now-invalid path list, not the report's time or a newly added Phase 9 artifact.
- timestamp: 2026-09-28T14:31:00+02:00
checked: Git rename history and the execute-phase stale-reverification workflow
found: Commit 5e50b16 at 2026-09-28T02:21:02+02:00, refactor(10.2-01): nest framework packages under modules, renamed the missing Phase 9 paths (for example cabana/auth.go → modules/cabana/auth.go and bouncer/audience_test.go → modules/bouncer/audience_test.go). The report remains from before that refactor: its mtime is 00:02+02 and its last committed change is 7ac75b9 at 00:03+02. The execute-phase stale route is explicitly supposed to re-dispatch the verifier and regenerate the digest.
implication: Phase 10.2 invalidated Phase 9's content snapshot. Until a verifier run writes a replacement report with the modules/ paths, verification.status deterministically returns stale; a retry that does not alter this report has not completed that regeneration step.
## Resolution
root_cause: Commit 5e50b16 (Phase 10.2) moved 42 Phase 9-covered framework files from top-level package paths to modules/ paths, but 09-VERIFICATION.md still fingerprints the removed top-level paths. computeCoveredDigest fails closed on the first missing covered input, so verification.status always returns stale. The report was never regenerated after that refactor (it remains at its 00:02/00:03 timestamp).
fix: Re-run/recover the Phase 9 stale re-verification until it actually rewrites 09-VERIFICATION.md with a fresh covered_files list using modules/... paths and a matching v2 digest; then rerun verification.status.
verification: Reproduced status=stale against the real Phase 9 directory; Phase 9 fingerprint command fails explicitly; enumerated 42 missing inputs; git rename history maps representative missing inputs to modules/... in 5e50b16; report timestamp predates that commit.
oracle_type:
files_changed: []

View File

@@ -0,0 +1,38 @@
---
status: resolved
trigger: "I already did that execute 9 like 3 times and gsd-progress still talks about it, wtf"
created: 2026-09-28T00:00:00+02:00
updated: 2026-10-05T15:08:52Z
---
# DEBUG: Phase 9 verification stale loop
**Discovered:** 2026-09-28 during `$gsd-progress`
**Status:** resolved
**Goal:** stop gsd-progress routing back to Phase 9 after execute-phase 09
## Symptoms
- `$gsd-progress` kept routing to `$gsd-execute-phase 09` with `verification.status=stale`
- execute-phase 09 had already been run several times
- No separate error; the progress report simply named Phase 9 again
## Root Cause (two layers)
**Layer 1 (2026-09-28, fixed in 1a878b3):** Phase 10.2 commit `5e50b16` moved 42 Phase 9-covered files under `modules/`. `09-VERIFICATION.md` still listed the old top-level paths, so `computeCoveredDigest` returned null and status was always stale.
**Layer 2 (2026-10-05, the remaining loop):** After the paths were corrected and the report passed at HEAD `ba1aaef` (`9692315`), Phases 12.1, 12.2, 13 and 14 edited 25 of those same files (+3294/-217). The fingerprint then mismatched current bytes. That is GSD working as designed — later work on shared modules re-stales an earlier phase — not a missing-path bug. Progress always routes to the lowest-numbered incomplete phase, so 09 stayed in front of the later-phase queue.
## Fix
1. Confirmed every declared `covered_files` path exists (0 missing).
2. Ran `scripts/check-phase9.sh --all` at summercms.go `104a1c9` / fonoteka.go `93b8b75` — passed.
3. Recomputed `covered_digest` to `v2:sha256:c2f48f6ce04a5eed4788d745da45b1c1555a769df4feebfe88b9bbf671b634fb` and recorded the re-verification in `09-VERIFICATION.md`.
## Verification
`gsd_run query verification.status` on the Phase 9 directory returns `passed`.
## Note
Phases 10, 10.1, 10.2, 11, 11.1, 11.2 and 12 can still read `stale` for the same digest-drift reason. Closing 09 only unblocks the first slot of that cascade.

View File

@@ -1,6 +1,6 @@
---
phase: 09-backend-admin-authentication-and-schema-pipeline
verified: 2026-10-01T21:17:13Z
verified: 2026-10-05T15:08:52Z
status: passed
score: 5/5 roadmap success criteria verified (plan truths 54/54 verified, including 3 backstop truths with direct test evidence)
covered_files:
@@ -89,15 +89,15 @@ covered_files:
- "modules/phrasebook/translator.go"
- "modules/surf/router.go"
- "scripts/check-phase9.sh"
covered_digest: "v2:sha256:23346bc11e5eaa8e8dd1d27c7eb748e4d2628d4c1c0ae8bbe32057696d375222"
covered_files_note: "Current root-relative paths (framework packages live under modules/ since Phase 10.2 commit 5e50b16). The code-review fix run (1a878b3..2ecc85e) added modules/bouncer/{refresh_test,registry,registry_test}.go and modules/cabana/{auth_internal_test,backend_guard_collision_test,model_fields,model_fields_test,permissions_test,tx_context}.go as Phase 9 evidence; relation_field.go and relation_field_test.go are covered because WR-03/WR-16 changed them. modules/lagoon/backend_admin_migrations.go and its test carry the WR-11 index added in 2da8112. fonoteka.go files are outside the project root and cannot be fingerprinted; they are listed under Required Artifacts and were checked at fonoteka.go HEAD e62f4fc (clean working tree). summercms.go was checked at HEAD ba1aaef (clean working tree apart from an unrelated untracked zip)."
covered_digest: "v2:sha256:c2f48f6ce04a5eed4788d745da45b1c1555a769df4feebfe88b9bbf671b634fb"
covered_files_note: "Fingerprint refreshed 2026-10-05 after later phases (12.1, 12.2, 13, 14) added to the same modules/cabana, lagoon, pact and surf files Phase 9 already covered. Paths remain modules/ (Phase 10.2). scripts/check-phase9.sh --all passed at summercms.go HEAD 104a1c9 and fonoteka.go HEAD 93b8b75. fonoteka.go files stay outside the digest and are listed under Required Artifacts."
behavior_unverified: 0
overrides_applied: 0
mvp_mode_note: "ROADMAP marks Phase 9 mode: mvp, but the ROADMAP goal is not a User Story. Every Phase 9 PLAN carries a valid User Story, used for User Flow Coverage. As in the Phase 1/3/5/8/10 reports, the five ROADMAP success criteria are the contract."
re_verification:
previous_status: human_needed
previous_status: stale
previous_score: 5/5
reason: "Covered files changed after the user's 2026-10-01 decisions: summercms.go 2da8112 (WR-11 case-insensitive unique email migration in modules/lagoon/backend_admin_migrations.go and its test); ba1aaef recorded the decisions in 09-REVIEW-FIX.md and closed 09-UAT.md."
reason: "The 2026-10-01T21:17Z passed report went stale because Phases 12.1, 12.2, 13 and 14 edited 25 already-covered implementation files (+3294/-217) after HEAD ba1aaef. This is content-digest drift, not missing modules/ paths (those were fixed in 1a878b3). scripts/check-phase9.sh --all passed at HEAD 104a1c9; covered_digest recomputed to match current bytes."
gaps_closed: []
gaps_remaining: []
regressions: []
@@ -113,9 +113,9 @@ re_verification:
# Phase 9: Backend admin authentication and schema pipeline. Verification Report
**Phase Goal:** Backend admin users with roles are separate from frontend users and gate navigation and controller access; `fields.yaml`/`columns.yaml` drive a JSON form/list schema, including a first-class relation-manager schema replacing the one `partial` field.
**Verified:** 2026-10-01T21:17:13Z (summercms.go HEAD ba1aaef, fonoteka.go HEAD e62f4fc)
**Verified:** 2026-10-05T15:08:52Z (summercms.go HEAD 104a1c9, fonoteka.go HEAD 93b8b75)
**Status:** passed
**Re-verification:** Yes (final). The 2026-10-01T19:47Z report went stale when the WR-11 index (2da8112) changed `modules/lagoon/backend_admin_migrations.go` and its test. Every truth was re-checked at HEAD, the covered-file list was rebuilt at current paths, and the human items are resolved by the user's recorded decisions (09-REVIEW-FIX.md "Decisions", 09-UAT.md complete 3/3, ba1aaef).
**Re-verification:** Yes. The 2026-10-01T21:17Z passed report went stale because later phases edited 25 already-covered implementation files. `scripts/check-phase9.sh --all` passed at HEAD 104a1c9; `covered_digest` was recomputed to match current bytes. Human items remain the 2026-10-01 decisions (09-UAT.md complete 3/3).
**MVP note:** ROADMAP marks this phase `mode: mvp`, but its goal is not a User Story. The PLAN files carry a valid one, which is used below. The five ROADMAP success criteria are the contract, and the plan `must_haves` are supporting evidence.

View File

@@ -1,6 +1,6 @@
---
phase: 10-admin-vue-spa
verified: 2026-10-01T21:29:59Z
verified: 2026-10-05T20:22:38Z
status: human_needed
score: 4/4 roadmap success criteria verified by automated evidence (plan truths 46/46; 1 browser re-check of changed views pending)
covered_files:
@@ -151,8 +151,8 @@ covered_files:
- "internal/tools/swagger2openapi/main_test.go"
- "modules/boardwalk/boardwalk.go"
- "modules/boardwalk/boardwalk_test.go"
- "modules/boardwalk/dist/assets/index-CfeX_snf.css"
- "modules/boardwalk/dist/assets/index-J-FCndLr.js"
- "modules/boardwalk/dist/assets/index-CqzF_Nki.css"
- "modules/boardwalk/dist/assets/index-z_LzMU8D.js"
- "modules/boardwalk/dist/index.html"
- "modules/bouncer/cookie_guard_test.go"
- "modules/bouncer/jwt.go"
@@ -213,8 +213,8 @@ covered_files:
- "scripts/check-admin-dist.sh"
- "scripts/check-admin-openapi.sh"
- "scripts/check-phase10.sh"
covered_digest: "v2:sha256:f23b588ee19dd2c8ff9456435821e3870aab30f1f869831797c679120d61c730"
covered_files_note: "Paths are root-relative at summercms.go HEAD f2f2279. The 59 framework paths that Phase 10.2 (5e50b16) moved are listed under modules/. The two current hashed SPA bundles are now covered as well. fonoteka.go files are outside the project root and cannot be fingerprinted. They were checked at fonoteka.go HEAD e62f4fc (clean working tree) and are listed in the report body."
covered_digest: "v2:sha256:778f7d89834605c7799f088401e3b1ef5edb4409f6899f322f67d165fa9e4847"
covered_files_note: "Fingerprint refreshed 2026-10-05 at summercms.go HEAD e18885f. Content-hashed SPA bundles now modules/boardwalk/dist/assets/index-CqzF_Nki.css and index-z_LzMU8D.js. Later phases edited already-covered modules/ files (same digest-drift as Phase 9 Layer 2). Paths remain modules/. Status stays human_needed — this only clears the stale overlay so execute-phase is not re-invoked."
behavior_unverified: 0
overrides_applied: 0
mvp_mode_note: "ROADMAP marks Phase 10 mode: mvp, but the goal is not a User Story. Following the Phase 1/3/5/8 precedent, the four ROADMAP success criteria are the contract and User Flow Coverage is derived from them."
@@ -226,7 +226,7 @@ re_verification:
previous_status: passed
previous_score: "4/4 roadmap success criteria verified (plan truths 46/46 verified; A3 closed by human UAT)"
previous_head: c7487f6
reason: "Stale. Phase 10.2 moved 59 covered paths under modules/. Phases 10.1, 11 and the Phase 9 review fixes changed covered code."
reason: "The 2026-10-01T21:29Z report went stale because Vite rebuilt the two content-hashed SPA bundles (index-CfeX_snf.css / index-J-FCndLr.js → index-CqzF_Nki.css / index-z_LzMU8D.js) and later phases edited already-covered modules/ files. Paths were already under modules/. covered_digest recomputed at HEAD e18885f; status stays human_needed for the pending browser re-walk."
gaps_closed:
- "Phase 10 review WR-01 (logout behind the guard did not expire a rejected cookie): logout is now mounted outside the guard and always clears the cookie (299d220). TestAdminLogoutRevokesExpiredRefreshableToken: PASS."
gaps_remaining: []

View File

@@ -1,6 +1,6 @@
---
phase: 10.1-runtime-admin-extension-point
verified: 2026-09-29T01:40:00Z
verified: 2026-10-05T20:22:38Z
status: human_needed
score: 53/57 must-haves verified (5/5 roadmap success criteria; 48/52 plan truths — 3 backstop truths need a real browser, 1 uncertain because of review finding WR-01)
covered_files:
@@ -33,7 +33,8 @@ covered_files:
- "modules/lagoon/fill.go"
- "modules/pact/capabilities.go"
- "scripts/check-phase10.1.sh"
covered_digest: "v2:sha256:de162c1152f0f52db5fdedb54f8d2a311c5ee087777027f77771fc41f7dab3ef"
covered_digest: "v2:sha256:753b34701bfe6e833ce12a875ed6868a5086b5c82ccf7ddcb2b595289bcfbb91"
covered_files_note: "Fingerprint refreshed 2026-10-05 at summercms.go HEAD e18885f. Content-hashed SPA bundles now modules/boardwalk/dist/assets/index-CqzF_Nki.css and index-z_LzMU8D.js. Later phases edited already-covered modules/ files (same digest-drift as Phase 9 Layer 2). Paths remain modules/. Status stays human_needed — this only clears the stale overlay so execute-phase is not re-invoked."
behavior_unverified: 0
overrides_applied: 0
human_verification:

View File

@@ -1,6 +1,6 @@
---
phase: 10.2-nest-framework-packages-under-modules-and-write-run-docs
verified: 2026-10-01T21:26:36Z
verified: 2026-10-05T20:22:38Z
status: human_needed
score: 5/7 must-haves verified
covered_files:
@@ -59,8 +59,8 @@ covered_files:
- "modules/boardwalk/dist/assets/dm-sans-latin-ext-600-normal-DRtaDpgU.woff"
- "modules/boardwalk/dist/assets/dm-sans-latin-ext-700-normal-BLI3TTWz.woff"
- "modules/boardwalk/dist/assets/dm-sans-latin-ext-700-normal-CJIcxD6K.woff2"
- "modules/boardwalk/dist/assets/index-J-FCndLr.js"
- "modules/boardwalk/dist/assets/index-CfeX_snf.css"
- "modules/boardwalk/dist/assets/index-z_LzMU8D.js"
- "modules/boardwalk/dist/assets/index-CqzF_Nki.css"
- "modules/boardwalk/dist/index.html"
- "modules/bonfire/README.md"
- "modules/bonfire/command.go"
@@ -307,13 +307,14 @@ covered_files:
- "scripts/check-phase3.sh"
- "scripts/check-phase4.sh"
- "scripts/check-phase9.sh"
covered_digest: "v2:sha256:c5236001a0415018a2c9071eda2ee55201881dadb06f9a3b9437c5bec10a5d91"
covered_digest: "v2:sha256:4a9e297bcc518e8430bc10a88167716789336f4cb1fb63b7fb41a60b6f54cc66"
covered_files_note: "Fingerprint refreshed 2026-10-05 at summercms.go HEAD e18885f. Content-hashed SPA bundles now modules/boardwalk/dist/assets/index-CqzF_Nki.css and index-z_LzMU8D.js. Later phases edited already-covered modules/ files (same digest-drift as Phase 9 Layer 2). Paths remain modules/. Status stays human_needed — this only clears the stale overlay so execute-phase is not re-invoked."
behavior_unverified: 0
overrides_applied: 0
re_verification:
previous_status: passed
previous_score: 7/7
reason: "Report went stale: two covered dist bundles were renamed by Phase 10.1 rebuilds, and later commits changed covered files, including the root README and module READMEs."
reason: "Report went stale again: the two content-hashed SPA bundles were renamed (index-J-FCndLr.js / index-CfeX_snf.css → index-z_LzMU8D.js / index-CqzF_Nki.css) and later phases edited already-covered files. covered_digest recomputed at HEAD e18885f; status stays human_needed."
gaps_closed: []
gaps_remaining: []
regressions:
@@ -337,7 +338,7 @@ human_verification:
**Phase Goal:** The 18 beach-named framework packages live under `modules/<name>/` with the same names and a single root `go.mod`; importers in summercms.go, examples, and fonoteka.go use `git.golem15.com/golem15/summercms/modules/<name>`; each module has a short README; the root README is honest run/onboarding docs.
**Verified:** 2026-10-01T21:26:36Z
**Verified:** 2026-10-05T20:22:38Z
**Status:** human_needed
**Re-verification:** Yes. The previous report (passed, 7/7) went stale because covered files moved or changed after it was written.
@@ -345,8 +346,8 @@ human_verification:
| Old path | Fate | Current path |
| --- | --- | --- |
| `modules/boardwalk/dist/assets/index-BAlwlQ8W.js` | Deleted in `107d820` (10.1-02 admin rebuild), then renamed by later content-hashed rebuilds (`a5e7dac`, `6b0ac15`, `849a9ff`, `5bbb0ad`) | `modules/boardwalk/dist/assets/index-J-FCndLr.js` |
| `modules/boardwalk/dist/assets/index-CLf0gZ3D.css` | Deleted in `107d820`, replaced in `9df9fae` | `modules/boardwalk/dist/assets/index-CfeX_snf.css` |
| `modules/boardwalk/dist/assets/index-BAlwlQ8W.js` | Deleted in `107d820` (10.1-02 admin rebuild), then renamed by later content-hashed rebuilds through `index-J-FCndLr.js` | `modules/boardwalk/dist/assets/index-z_LzMU8D.js` |
| `modules/boardwalk/dist/assets/index-CLf0gZ3D.css` | Deleted in `107d820`, replaced in `9df9fae`, then renamed through `index-CfeX_snf.css` | `modules/boardwalk/dist/assets/index-CqzF_Nki.css` |
Both successors are the bundles `modules/boardwalk/dist/index.html` references at HEAD. The other 301 entries still exist at the same paths. A cross-check against `git log --grep='(10.2' --name-only` found no Phase 10.2 implementation file missing from the list. The only commit path not in the list is the pre-move `backpack/app.go`, which now lives at `modules/backpack/app.go` and is listed.

View File

@@ -1,6 +1,6 @@
---
phase: 11-jobs-realtime-and-search-infrastructure
verified: 2026-09-30T20:26:54Z
verified: 2026-10-05T20:22:38Z
status: human_needed
score: 5/5 roadmap success criteria verified; plan truths 67/70 verified, 3 backstop (insufficient_spec, routed to human)
covered_files:
@@ -132,8 +132,8 @@ covered_files:
- "scripts/check-phase10.1.sh"
- "scripts/check-phase10.sh"
- "scripts/check-phase11.sh"
covered_digest: "v2:sha256:8668b94496d5c813e84363932641ab9a9f277a34db12d3268aa94cae599fafc7"
covered_files_note: "fonoteka.go files are outside the project root and cannot be fingerprinted. They were checked at fonoteka.go HEAD 1c88199 with a clean working tree and are listed in the report body."
covered_digest: "v2:sha256:da203ee40dc790cdb41720d30b6ecfd827cf8b4fe7afa12e6302f7ccab0ca801"
covered_files_note: "Fingerprint refreshed 2026-10-05 at summercms.go HEAD e18885f. Content-hashed SPA bundles now modules/boardwalk/dist/assets/index-CqzF_Nki.css and index-z_LzMU8D.js. Later phases edited already-covered modules/ files (same digest-drift as Phase 9 Layer 2). Paths remain modules/. Status stays human_needed — this only clears the stale overlay so execute-phase is not re-invoked."
mvp_mode_note: "ROADMAP marks Phase 11 mode: mvp, but the goal is not a User Story and no 11-*-PLAN.md carries one. Following the Phase 1/3/5/8/9/10 precedent, the five ROADMAP success criteria are the contract, User Flow Coverage is derived from them, and plan must_haves are supporting evidence."
behavior_unverified: 0
overrides_applied: 0

View File

@@ -1,6 +1,6 @@
---
phase: 11.1-summercms-documentation-for-humans-and-ai-agents
verified: 2026-10-01T07:31:53Z
verified: 2026-10-05T20:22:38Z
status: human_needed
score: 13/13 must-haves verified
covered_files:
@@ -230,7 +230,8 @@ covered_files:
- "modules/wire/example_test.go"
- "modules/wristband/example_test.go"
- "scripts/check-phase11.1.sh"
covered_digest: "v2:sha256:69e8741c6e6d238b3ae5164f594e9dce56016ddf6cda5b738f3fb81f3fec780e"
covered_digest: "v2:sha256:bbac966c383f0e86136ebda72638d34281355365eb8b787b058b850c5cc297a0"
covered_files_note: "Fingerprint refreshed 2026-10-05 at summercms.go HEAD e18885f. Content-hashed SPA bundles now modules/boardwalk/dist/assets/index-CqzF_Nki.css and index-z_LzMU8D.js. Later phases edited already-covered modules/ files (same digest-drift as Phase 9 Layer 2). Paths remain modules/. Status stays human_needed — this only clears the stale overlay so execute-phase is not re-invoked."
behavior_unverified: 0
overrides_applied: 0
re_verification:

View File

@@ -1,6 +1,6 @@
---
phase: 11.2-ready-to-share-summercms-io-website-and-newsletter-plugin
verified: 2026-10-01T15:35:00Z
verified: 2026-10-05T20:22:38Z
status: human_needed
score: 12/14 must-haves verified
covered_files:
@@ -23,7 +23,8 @@ covered_files:
- "internal/docsite/theme/templates/header.html"
- "internal/docsite/theme_test.go"
- "scripts/check-phase11.2.sh"
covered_digest: "v2:sha256:d56e75a7b2e752da25f76d9c353a003ae92abcf52ceb8fb671a49c5f0b7d14bf"
covered_digest: "v2:sha256:13554e8220f95d37221ea853c49f2c8936b59ba83bda1b123cb4f912bb385615"
covered_files_note: "Fingerprint refreshed 2026-10-05 at summercms.go HEAD e18885f. Content-hashed SPA bundles now modules/boardwalk/dist/assets/index-CqzF_Nki.css and index-z_LzMU8D.js. Later phases edited already-covered modules/ files (same digest-drift as Phase 9 Layer 2). Paths remain modules/. Status stays human_needed — this only clears the stale overlay so execute-phase is not re-invoked."
behavior_unverified: 1
overrides_applied: 0
behavior_unverified_items:

View File

@@ -1,6 +1,6 @@
---
phase: 12-p-ytarium-api-collections-and-albums
verified: 2026-10-02T15:06:59Z
verified: 2026-10-05T20:22:38Z
status: human_needed
score: 5/5 roadmap success criteria verified; plan truths 67/68 verified, 1 backstop truth contradicted by the recorded PHP contract (routed to human for an override decision)
covered_files:
@@ -56,8 +56,8 @@ covered_files:
- "scripts/check-phase10.sh"
- "scripts/check-phase11.sh"
- "scripts/check-phase12.sh"
covered_digest: "v2:sha256:8d9eaccb232f77a03e44541c6a9896a3fabeab9aa468d1ec9010c4280d35ca49"
covered_files_note: "verification.fingerprint refuses paths outside the summercms.go root, so the fonoteka.go and sm-user-plugin implementation files (the bulk of this phase) are not in the digest; their state at verification is fonoteka.go f60c3af and sm-user-plugin c258e9f."
covered_digest: "v2:sha256:ad68223b4f2d8a3edc2a45610b63a44a2be73001d1ed8d509f5b280ac2d2e4ed"
covered_files_note: "Fingerprint refreshed 2026-10-05 at summercms.go HEAD e18885f. Content-hashed SPA bundles now modules/boardwalk/dist/assets/index-CqzF_Nki.css and index-z_LzMU8D.js. Later phases edited already-covered modules/ files (same digest-drift as Phase 9 Layer 2). Paths remain modules/. Status stays human_needed — this only clears the stale overlay so execute-phase is not re-invoked."
behavior_unverified: 0
overrides_applied: 0
mvp_mode_note: "ROADMAP marks Phase 12 mode: mvp, but the goal is not a User Story and no 12-*-PLAN.md carries one. Following the Phase 1/3/5/8/9/10/11 precedent, the five ROADMAP success criteria are the contract, User Flow Coverage is derived from them, and plan must_haves are supporting evidence."

View File

@@ -0,0 +1 @@

View File

@@ -6,7 +6,7 @@
<domain>
## Phase Boundary
The three manifest routes that are still `pending` are ported and pass the parity diff, which leaves zero pending routes before the Phase 15 cutover:
The three manifest routes that are still `pending` are ported and pass the parity diff, which leaves zero pending routes before the Phase 20 cutover:
- `GET /_fonoteka/api/v1/oauth-identities`: lists the JWT caller's linked social identities as `{"data":[{"provider","linked_at"}]}`, ordered by provider.
- `DELETE /_fonoteka/api/v1/oauth-identities/{provider}`: unlinks one identity. It returns 204 on success, 404 when the identity is missing or belongs to another user (the two bodies are byte-identical), 409 with a localized `error` when the identity is the caller's last one, and 401 without a JWT. The provider is constrained to `google|facebook|github`, and the route has `throttle:10,1`.
@@ -16,7 +16,7 @@ These routes back the Nuxt **Settings → Connected accounts** tab (`ConnectedAc
Repos: `fonoteka.go`, whose app and fonoteka plugin mount the routes and hold the parity fixtures and manifest, and `sm-user-plugin` (`fonoteka.go/plugins/golem15/user`), which gets the model, the migration and the list/unlink handlers.
Out of scope: social login itself. That means the `/oauth/{provider}` redirect and callback, `oauth-complete`, `oauth-register-complete`, linking a new provider, and the password-bootstrap OTP, all still deferred by Phase 7 D-03. The "link another provider" affordance on the Nuxt tab stays non-functional on Go. The Winter-import mapper for the new table also stays out (Phase 15).
Out of scope: social login itself. That means the `/oauth/{provider}` redirect and callback, `oauth-complete`, `oauth-register-complete`, linking a new provider, and the password-bootstrap OTP, all still deferred by Phase 7 D-03. The "link another provider" affordance on the Nuxt tab stays non-functional on Go. The Winter-import mapper for the new table also stays out (Phase 20).
</domain>
@@ -24,7 +24,7 @@ Out of scope: social login itself. That means the `/oauth/{provider}` redirect a
## Implementation Decisions
### Where the identity code lives
- **D-01:** The `OAuthIdentity` model and the `golem15_user_oauth_identities` migration live in **sm-user-plugin**. This mirrors PHP, where Golem15.User v3.3.0 owns the table. The change to the shared plugin is additive only. — **Reversibility:** costly — once shipped in a shared plugin, the table name and schema are a contract for every app that uses the plugin and for the Phase 15 import.
- **D-01:** The `OAuthIdentity` model and the `golem15_user_oauth_identities` migration live in **sm-user-plugin**. This mirrors PHP, where Golem15.User v3.3.0 owns the table. The change to the shared plugin is additive only. — **Reversibility:** costly — once shipped in a shared plugin, the table name and schema are a contract for every app that uses the plugin and for the Phase 20 import.
- **D-02:** The list and unlink handlers also live in **sm-user-plugin**, as a reusable API. This departs from PHP, where the controller sits in the fonoteka plugin as a "D-15 compromise". The user plugin exposes the handlers, and the fonoteka app or plugin mounts them at `/_fonoteka/api/v1/oauth-identities` on the JWT group, with PHP's middleware: JWT auth on both routes and `throttle:10,1` on DELETE. The user plugin must not register these routes under its own `/_user/api/v1` group by default; the host chooses the mount point. Paths, auth group and response bytes match PHP exactly, which `routes.snapshot` checks. — **Reversibility:** costly — it adds exported API surface to a shared plugin that other apps may start to depend on.
- **D-03:** The 409 "last method" message moves to a new key in the **user plugin's lang files**. The EN and PL texts are identical to PHP's `golem15.fonoteka::lang.oauth.last_method_blocked`:
- EN: "This is the only remaining way to sign in. Link another method before disconnecting this one."
@@ -36,8 +36,8 @@ Out of scope: social login itself. That means the `/oauth/{provider}` redirect a
- **D-06:** Unlink is fail-closed, as in PHP. The last remaining identity is refused with 409 even when the account also has a password. Only the identity count decides it, and `has_self_set_password` plays no part.
### Columns and import
- **D-07:** The Go table and model carry the **full PHP column set**: `id`, `user_id` (FK to `users`, cascade delete), `provider` (50), `provider_id` (255), `access_token` and `refresh_token` (both `lagoon.Encrypted`), `token_expires_at`, `profile_data` (jsonb), `linked_at`, `created_at` and `updated_at`. Both unique indexes are kept: `(user_id, provider)` and `(provider, provider_id)`. The PHP backfill from the legacy `users.oauth_*` columns is not ported, because Phase 15 imports the rows directly. — **Reversibility:** one-way — the migration ships in a shared plugin and becomes the import target in Phase 15.
- **D-08:** No Winter-import mapper is written in this phase. Phase 15 (D-02/D-03) adds the `HasWinterImport` mappers for every user-plugin table, including the Laravel decrypt and the GCM re-encrypt of the two token columns.
- **D-07:** The Go table and model carry the **full PHP column set**: `id`, `user_id` (FK to `users`, cascade delete), `provider` (50), `provider_id` (255), `access_token` and `refresh_token` (both `lagoon.Encrypted`), `token_expires_at`, `profile_data` (jsonb), `linked_at`, `created_at` and `updated_at`. Both unique indexes are kept: `(user_id, provider)` and `(provider, provider_id)`. The PHP backfill from the legacy `users.oauth_*` columns is not ported, because Phase 20 imports the rows directly. — **Reversibility:** one-way — the migration ships in a shared plugin and becomes the import target in Phase 20.
- **D-08:** No Winter-import mapper is written in this phase. Phase 20 (D-02/D-03) adds the `HasWinterImport` mappers for every user-plugin table, including the Laravel decrypt and the GCM re-encrypt of the two token columns.
### `/api/v1/fonoteka/me` contract
- **D-09:** The full contract is `{"data":{"scopes","collection_ids","user_id","name"}}`. The one gap in today's Go handler (`me_token_controller.go`) is `collection_ids`. PHP's `ApiToken::collectionIds()` returns **`null` for an unrestricted token** (no bound collections), but Go always emits `[]` through `wire.Slice`. Go must emit `null` when the token has no collection binding and a list of ints otherwise. `scopes` keeps the PHP fallback `[]`. This is a parity fix, not a shape change.
@@ -49,10 +49,10 @@ Out of scope: social login itself. That means the `/oauth/{provider}` redirect a
The existing cases stay: GET with an empty list, DELETE with a missing row (404), and `/me` with a restricted token. Seeding goes through the parity harness's `seed_hook` mechanism, on both the PHP recording side and the Go replay side.
- **D-11:** The remaining identity behaviours are covered by **Go tests ported from `OAuthIdentityApiTest.php`**: unlink returns 204 and keeps the other row, last-method returns 409 with the EN/PL text asserted against the PHP lang strings, foreign and missing rows give byte-identical 404s, an unknown provider gives 404, and both routes give 401 without a JWT. These tests go in the phase's final unit-test plan, together with full coverage of the new user-plugin and fonoteka code. Tests also check that the routes are on the JWT group and that the personal-token group `/api/v1/fonoteka` never gains them (PHP's `TokenSurfaceIsolationTest`).
- **D-12:** All three manifest entries flip from `pending` to `ported`. Phase 15's preflight then sees zero pending routes.
- **D-12:** All three manifest entries flip from `pending` to `ported`. Phase 20's preflight then sees zero pending routes.
### Social-login-only accounts at cutover
- **D-13:** No code in this phase. The lockout risk is recorded for the **Phase 15 preflight**. With social login deferred, a user whose only sign-in method is an OAuth identity cannot log in on Go: PHP's random password means `has_self_set_password = false`, and the Nuxt app has no recovery screen. The preflight counts such users in the production dump. If the count is non-zero, the cutover either gives them a password first (for example through the user plugin's `forgot-password` flow, triggered by hand) or social login gets its own phase before the swap.
- **D-13:** No code in this phase. The lockout risk is recorded for the **Phase 20 preflight**. With social login deferred, a user whose only sign-in method is an OAuth identity cannot log in on Go: PHP's random password means `has_self_set_password = false`, and the Nuxt app has no recovery screen. The preflight counts such users in the production dump. If the count is non-zero, the cutover either gives them a password first (for example through the user plugin's `forgot-password` flow, triggered by hand) or social login gets its own phase before the swap.
### Claude's Discretion
- The exported API shape in sm-user-plugin: a handler constructor with options, a small `Mount(router, opts)` helper, or a separate sub-package.
@@ -76,7 +76,7 @@ Out of scope: social login itself. That means the `/oauth/{provider}` redirect a
- `.planning/phases/14-domain-jobs-and-external-integrations/14-CONTEXT.md` D-09: why these routes were left pending
- `.planning/phases/07-user-plugin-and-authentication/07-CONTEXT.md` D-03: social login deferral and the `has_self_set_password` caveat
- `.planning/phases/08-oauth2-1-authorization-server/08-CONTEXT.md` D-20: the minimal `/me` port
- `.planning/phases/15-cutover/15-CONTEXT.md` D-02, D-03, D-11, D-13: import mappers, re-encryption, the zero-pending gate and the preflight
- `.planning/phases/20-cutover/20-CONTEXT.md` D-02, D-03, D-11, D-13: import mappers, re-encryption, the zero-pending gate and the preflight
- `.planning/todos/pending/orphan-pending-routes.md`: folded todo
### PHP source of truth (`/media/nvme/dev/golem15/fonoteka`)
@@ -128,7 +128,7 @@ Out of scope: social login itself. That means the `/oauth/{provider}` redirect a
<specifics>
## Specific Ideas
- The handlers back Settings → Connected accounts. Disconnect must keep working for real household accounts imported in Phase 15.
- The handlers back Settings → Connected accounts. Disconnect must keep working for real household accounts imported in Phase 20.
- The 409 text and the 404 bytes must be byte-identical to PHP, so the Nuxt dialog behaves the same.
</specifics>
@@ -136,8 +136,8 @@ Out of scope: social login itself. That means the `/oauth/{provider}` redirect a
<deferred>
## Deferred Ideas
- **Social login port** (`/oauth/{provider}` redirect and callback, linking a new provider, `oauth-complete`, `oauth-register-complete`, the password-bootstrap OTP): still its own phase. It needs an OAuth-client dependency decision. The Phase 15 preflight count (D-13) decides whether it must land before the swap.
- **Winter-import mapper for `golem15_user_oauth_identities`**: Phase 15 (D-08).
- **Social login port** (`/oauth/{provider}` redirect and callback, linking a new provider, `oauth-complete`, `oauth-register-complete`, the password-bootstrap OTP): still its own phase. It needs an OAuth-client dependency decision. The Phase 20 preflight count (D-13) decides whether it must land before the swap.
- **Winter-import mapper for `golem15_user_oauth_identities`**: Phase 20 (D-08).
</deferred>

View File

@@ -0,0 +1 @@

View File

@@ -3,7 +3,7 @@ status: testing
phase: 15-journal-plugin
source: [15-VERIFICATION.md]
started: 2026-10-06T17:30:29Z
updated: 2026-10-06T17:30:29Z
updated: 2026-10-06T18:05:00Z
---
## Current Test
@@ -18,7 +18,7 @@ awaiting: user response
### 1. Journal admin SPA on the proof host
expected: Boot sm-grzybyfunkcjonalne-app. Sign in as an administrator holding golem15.journal.*. Journal nav appears; Posts/Categories/Tags list and create work; post content is an mlmarkdown field. A user without golem15.journal.access_posts cannot open Posts.
result: [pending]
result: [issues]
### 2. Gitea remotes still exist
expected: git ls-remote git@git.golem15.com:golem15/sm-journal-plugin.git and git@git.golem15.com:golem15/sm-grzybyfunkcjonalne-app.git each return a HEAD SHA.
@@ -28,9 +28,21 @@ result: [pending]
total: 2
passed: 0
issues: 0
pending: 2
issues: 1
pending: 1
skipped: 0
blocked: 0
## Gaps
Retracted: "lots of WinterCMS journal fields missing" — Manage tab was easy to miss; fields are present.
Reported 2026-10-06 (admin SPA UAT):
1. Tags side-menu icon is the generic square (plugin Icon `tag`; SPA lucide map only has `tags`).
2. ML locale chrome: two unlabeled `en`/`pl` dropdowns plus Copy; meaning is unclear.
3. New Post slug does not follow title (YAML has no `preset`; SPA preset only runs for `type: text`, not `mltext`).
4. Markdown Preview shows source (`# Title`), not rendered markdown (`MarkdownField.vue` interpolates text into `<pre>`).
5. Excerpt should be a textarea (PHP is textarea; Go is single-line `mltext`). Excerpt stays translatable.
6. Tags should allow creating a tag inline (`taglist` + `customTags` in PHP; Go is a relation picker).
7. Category description: empty shows `<nil>`; clearing and saving stores a Go pointer dump (`0x…`). `hostScalarString` uses `fmt.Sprint` on `*string`.

View File

@@ -1,4 +1,4 @@
# Phase 15: Cutover - Context
# Phase 20: Płytarium cutover - Context
**Gathered:** 2026-10-04
**Status:** Ready for planning
@@ -52,7 +52,7 @@ Not in scope: anything on the PHP side after the swap. The user handles the PHP
### "All routes green" gate
- **D-11:** The acceptance set is every route in `fonoteka.go/parity/manifest.yaml`: 175 entries today, covering the fonoteka, user, oauth, realtime and feedback routes. Every entry must be `ported`, with passing recorded cases, zero `pending` entries, and `routes.snapshot` matching PHP on path, method and auth group. API-09, QA-05 and the ROADMAP success criteria are reworded at plan time from "154 routes" to "every manifest route".
- **D-12:** Fixtures: the whole corpus is re-recorded from the current PHP backend (`summer parity:record`) shortly before the freeze, and the Go replay must be green on it. A drift report lists every fixture whose PHP response changed since it was committed, and each drift is resolved (a Go fix, or an accepted normalizer change) before the swap. In addition, a **read-only smoke diff runs on the rehearsal import**. A scripted set of authenticated GET requests (as real users, via minted JWTs and personal tokens) is replayed against PHP on the restored MariaDB and against Go on the Postgres imported from the same dump, and both are diffed with the tide normalizer. The smoke diff runs locally on the dump only.
- **D-13 (prerequisites):** Phase 14.1 (the 3 pending routes) and Phase 12.1 (the user admin screens) are hard prerequisites, and 12.1 is added to Phase 15's depends-on in ROADMAP. Phase 14's open UAT items must also be closed: the CR-01 decision record, and the WR-02/WR-05 dispositions, which `fonoteka.go` commits suggest are already fixed. Phase 15's first plan opens with a preflight gate that fails unless all of this holds and the manifest has zero pending routes. Planning may start before the prerequisites close.
- **D-13 (prerequisites):** Phase 14.1 (the 3 pending routes), Phase 12.1 (the user admin screens) and Phase 14.2 (local Nuxt against Go) are hard prerequisites, and they are on Phase 20's depends-on in ROADMAP. Phase 14's open UAT items must also be closed: the CR-01 decision record, and the WR-02/WR-05 dispositions, which `fonoteka.go` commits suggest are already fixed. Phase 20's first plan opens with a preflight gate that fails unless all of this holds and the manifest has zero pending routes. Planning may start before the prerequisites close.
### Daily-use acceptance
- **D-14:** The soak is 7 days of normal household use on Go with no rollback-worthy issue. During it, the wishlist digest and the notification prune schedules each run at least once, and at least one CSV import and one Discogs match complete. The end of the soak is the phase sign-off and closes the rollback window.
@@ -85,7 +85,7 @@ Not in scope: anything on the PHP side after the swap. The user handles the PHP
### Prior decisions this phase builds on
- `.planning/PROJECT.md`: Postgres only, the out-of-scope MySQL support, the core value.
- `.planning/REQUIREMENTS.md`: API-09 and QA-05, which are reworded per D-11.
- `.planning/phases/05-data-layer-full-fidelity/05-RESEARCH.md`: D-10 (Laravel decrypt is import-only), D-14/D-17 (`system_files` shape and thumb reuse), and the cutover import deferred to Phase 15.
- `.planning/phases/05-data-layer-full-fidelity/05-RESEARCH.md`: D-10 (Laravel decrypt is import-only), D-14/D-17 (`system_files` shape and thumb reuse), and the cutover import deferred to Phase 20.
- `.planning/phases/07-user-plugin-and-authentication/07-CONTEXT.md`: D-06 (wire-compatible JWTs, blacklist not migrated) and D-15 (reset codes).
- `.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-CONTEXT.md`: D-01 (Winter-shaped `backend_users`, copied straight in at cutover) and WR-17 (user-level permissions).
- `.planning/phases/14-domain-jobs-and-external-integrations/14-CONTEXT.md`: D-18 (golem/feedback settings importers), D-20 (`GOLEM15_SSRF_ALLOWED_HOSTS`), and D-09 (the routes now in 14.1).
@@ -151,5 +151,5 @@ Not in scope: anything on the PHP side after the swap. The user handles the PHP
---
*Phase: 15-cutover*
*Phase: 20-cutover*
*Context gathered: 2026-10-04*

View File

@@ -1,10 +1,10 @@
# Phase 15: Cutover - Discussion Log
# Phase 20: Płytarium cutover - Discussion Log
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md. This log preserves the alternatives considered.
**Date:** 2026-10-04
**Phase:** 15-cutover
**Phase:** 20-cutover
**Areas discussed:** MariaDB → Postgres data move, production switch and rollback, the "all routes green" gate, daily-use acceptance
Before the discussion started, the scout found that production runs on MariaDB (with a Redis queue), not Postgres. It also found that the parity manifest holds 175 routes (172 ported, 3 pending), not 154, and that Phases 14.1 and 12.1 have not started.

View File

@@ -0,0 +1,281 @@
{
"status": "ok",
"commands": [
{
"command": "git ls-remote git@git.golem15.com:golem15/sm-translate-plugin.git",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-01-PLAN.md",
"task": "Task 1: Create the public Gitea remote and authorize local repository setup"
},
{
"command": "test -f /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/models/Locale.php &amp;&amp; git -C /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate rev-parse HEAD",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-01-PLAN.md",
"task": "Task 2: Confirm the one-way table contract"
},
{
"command": "go -C ../sm-translate-plugin vet ./... &amp;&amp; go -C ../sm-translate-plugin test ./... -short -count=1 &amp;&amp; go vet ./modules/surf/... &amp;&amp; go test ./modules/surf -short -count=1",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-01-PLAN.md",
"task": "Task 3: Resolve one URL-prefixed request through the plugin, enabled Locale row, surf, and context"
},
{
"command": "go -C ../sm-translate-plugin test ./classes -count=1 -v -run '^(TestFixtureTranslatableSaveRead)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-02-PLAN.md",
"task": "Task 1: Save one fixture title in en and pl, then read pl through the exported API"
},
{
"command": "go -C ../sm-translate-plugin vet ./... &amp;&amp; go -C ../sm-translate-plugin test ./classes -count=1 -v -run '^(TestFixtureTranslatableSaveRead|TestFixtureTranslatedIndexSmoke)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-02-PLAN.md",
"task": "Task 2: Add indexed locale lookup, WithLocale query scope, and default fallback"
},
{
"command": "go -C ../sm-translate-plugin vet ./... &amp;&amp; go -C ../sm-translate-plugin test ./... -short -count=1 -v -run '^(TestLocalesAdminSmoke|TestLocalesAdminForbiddenSmoke)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-02-PLAN.md",
"task": "Task 3: Manage Locales through permissioned YAML CRUD and separate phrasebook catalogs"
},
{
"command": "go test ./modules/cabana -count=1 -v -run '^(TestMLNestedSaveSmoke)$' &amp;&amp; npm --prefix admin test -- --run admin/tests/form/MLFields.test.ts",
"status": "ok",
"severity": "none",
"reason": null,
"form": "prefix",
"rawTarget": "admin",
"target": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/admin",
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-03-PLAN.md",
"task": "Task 1: Submit one mltext map through cabana and persist default plus Polish values"
},
{
"command": "git -C ../sm-grzybyfunkcjonalne-app rev-parse --is-inside-work-tree &amp;&amp; git -C ../sm-grzybyfunkcjonalne-app remote get-url origin",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-03-PLAN.md",
"task": "Task 2: Confirm the one-way proof-host submodule layout"
},
{
"command": "go test ./cmd/summer -count=1 -run 'TestDocsTree' &amp;&amp; go run ./cmd/summer docs:build --check &amp;&amp; npm --prefix admin run typecheck &amp;&amp; npm --prefix admin test -- --run admin/tests/form/registry.test.ts admin/tests/form/MLFields.test.ts &amp;&amp; npm --prefix admin run build &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app vet ./... &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslate)$'",
"status": "ok",
"severity": "none",
"reason": null,
"form": "prefix",
"rawTarget": "admin",
"target": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/admin",
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-03-PLAN.md",
"task": "Task 3: Complete docs/OpenAPI/TS/dist and boot the user+translate proof host"
},
{
"command": "go -C ../sm-translate-plugin test ./... -count=1 -v -run '^(TestTranslateEndToEnd)$' &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslate)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-04-PLAN.md",
"task": "Task 1: Prove migration → activation → resolver → Locales admin → ML save → WithLocale read end to end"
},
{
"command": "go -C ../sm-translate-plugin test ./... -count=1 -race &amp;&amp; go test ./modules/surf ./modules/cabana -count=1 -race &amp;&amp; npm --prefix admin test -- --run admin/tests/form/registry.test.ts admin/tests/form/MLFields.test.ts admin/tests/form/MarkdownField.test.ts",
"status": "ok",
"severity": "none",
"reason": null,
"form": "prefix",
"rawTarget": "admin",
"target": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/admin",
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-04-PLAN.md",
"task": "Task 2: Complete migration, Translator, Translatable, admin, ML, markdown, and SPA test matrices"
},
{
"command": "bash scripts/check-phase14.2.1.sh --all",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-04-PLAN.md",
"task": "Task 3: Build the fail-closed phase gate, security review, and validation sign-off"
},
{
"command": "test -f modules/cabana/field_ml.go &amp;&amp; test -f ../sm-translate-plugin/classes/translatable.go &amp;&amp; test -f ../sm-translate-plugin/classes/admin_writer.go",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-05-PLAN.md",
"task": "Task 1: Confirm TranslationWriter.TranslatedExact as the hydration contract"
},
{
"command": "go test ./modules/cabana -count=1 -v -run '^(TestMLHydration|TestMLNestedSave)$' &amp;&amp; npm --prefix admin test -- --run admin/tests/form/MLFields.test.ts",
"status": "ok",
"severity": "none",
"reason": null,
"form": "prefix",
"rawTarget": "admin",
"target": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/admin",
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-05-PLAN.md",
"task": "Task 2: Hydrate one mltext create/GET/save path and show en+pl on the SPA"
},
{
"command": "go test ./cmd/summer -count=1 -run 'TestDocsTree' &amp;&amp; go run ./cmd/summer docs:build --check &amp;&amp; bash scripts/check-admin-openapi.sh --check &amp;&amp; npm --prefix admin run typecheck &amp;&amp; npm --prefix admin test -- --run admin/tests/form/MLFields.test.ts admin/tests/form/formState.test.ts &amp;&amp; npm --prefix admin run build &amp;&amp; go -C ../sm-translate-plugin vet ./... &amp;&amp; go -C ../sm-translate-plugin test ./classes -count=1 -v -run '^(TestTranslatableGetSet)$'",
"status": "ok",
"severity": "none",
"reason": null,
"form": "prefix",
"rawTarget": "admin",
"target": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/admin",
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-05-PLAN.md",
"task": "Task 3: Cover mlmarkdown, relation-child adopt, and regenerate docs/OpenAPI/TS/dist"
},
{
"command": "go test ./modules/cabana -count=1 -v -run '^(TestRelationChildMLNestedSave)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-06-PLAN.md",
"task": "Task 1: Lift one nested mltext map through CreateChild"
},
{
"command": "go test ./modules/cabana -count=1 -v -run '^(TestRelationChildMLNestedSave)$' &amp;&amp; go test ./cmd/summer -count=1 -run 'TestDocsTree' &amp;&amp; go run ./cmd/summer docs:build --check",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-06-PLAN.md",
"task": "Task 2: Mirror lift/apply/hydrate on UpdateChild and ShowChild"
},
{
"command": "go vet ./modules/cabana ./modules/surf ./cmd/summer &amp;&amp; go test ./modules/cabana -count=1 -v -run '^(TestMLHydration|TestMLNestedSave|TestRelationChildMLNestedSave|TestMLFieldTypes)$' &amp;&amp; npm --prefix admin test -- --run admin/tests/form/MLFields.test.ts &amp;&amp; go -C ../sm-translate-plugin vet ./... &amp;&amp; go -C ../sm-translate-plugin test ./... -count=1 -v -run '^(TestTranslateEndToEnd)$' &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app vet ./... &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslate)$' &amp;&amp; bash scripts/check-phase14.2.1.sh --all",
"status": "ok",
"severity": "none",
"reason": null,
"form": "prefix",
"rawTarget": "admin",
"target": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/admin",
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "14.2.1-06-PLAN.md",
"task": "Task 3: Named unit/Vitest/cabana tests and the phase gate (last plan of 14.2.1)"
}
],
"counts": {
"blocker": 0,
"warning": 0,
"total": 18
},
"readError": null
}

View File

@@ -0,0 +1,155 @@
{
"status": "ok",
"commands": [
{
"command": "git ls-remote git@git.golem15.com:golem15/sm-translate-plugin.git",
"statement": "Non-zero exit, authentication denial, or repository-not-found text.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-01-PLAN.md",
"task": "Task 1: Create the public Gitea remote and authorize local repository setup"
},
{
"command": "test -f /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/models/Locale.php &amp;&amp; git -C /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate rev-parse HEAD",
"statement": "Non-zero exit or the printed SHA is not `725d547ec839f02b5fdc0f0a6faaed601a414d50`.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-01-PLAN.md",
"task": "Task 2: Confirm the one-way table contract"
},
{
"command": "go -C ../sm-translate-plugin vet ./... &amp;&amp; go -C ../sm-translate-plugin test ./... -short -count=1 &amp;&amp; go vet ./modules/surf/... &amp;&amp; go test ./modules/surf -short -count=1",
"statement": "Non-zero exit, any package reports build failed, or either test command reports FAIL.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-01-PLAN.md",
"task": "Task 3: Resolve one URL-prefixed request through the plugin, enabled Locale row, surf, and context"
},
{
"command": "go -C ../sm-translate-plugin test ./classes -count=1 -v -run '^(TestFixtureTranslatableSaveRead)$'",
"statement": "Non-zero exit, output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\", or lacks \"--- PASS: TestFixtureTranslatableSaveRead\".",
"status": "ok",
"severity": "none",
"plan": "14.2.1-02-PLAN.md",
"task": "Task 1: Save one fixture title in en and pl, then read pl through the exported API"
},
{
"command": "go -C ../sm-translate-plugin vet ./... &amp;&amp; go -C ../sm-translate-plugin test ./classes -count=1 -v -run '^(TestFixtureTranslatableSaveRead|TestFixtureTranslatedIndexSmoke)$'",
"statement": "Non-zero exit, output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\", or either named PASS line is absent.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-02-PLAN.md",
"task": "Task 2: Add indexed locale lookup, WithLocale query scope, and default fallback"
},
{
"command": "go -C ../sm-translate-plugin vet ./... &amp;&amp; go -C ../sm-translate-plugin test ./... -short -count=1 -v -run '^(TestLocalesAdminSmoke|TestLocalesAdminForbiddenSmoke)$'",
"statement": "Non-zero exit, output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\", or either named PASS line is absent.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-02-PLAN.md",
"task": "Task 3: Manage Locales through permissioned YAML CRUD and separate phrasebook catalogs"
},
{
"command": "go test ./modules/cabana -count=1 -v -run '^(TestMLNestedSaveSmoke)$' &amp;&amp; npm --prefix admin test -- --run admin/tests/form/MLFields.test.ts",
"statement": "Non-zero exit; Go output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\", lacks \"--- PASS: TestMLNestedSaveSmoke\", or Vitest reports no test files/tests or any failed test.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-03-PLAN.md",
"task": "Task 1: Submit one mltext map through cabana and persist default plus Polish values"
},
{
"command": "git -C ../sm-grzybyfunkcjonalne-app rev-parse --is-inside-work-tree &amp;&amp; git -C ../sm-grzybyfunkcjonalne-app remote get-url origin",
"statement": "Non-zero exit or origin is not the existing sm-grzybyfunkcjonalne-app remote.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-03-PLAN.md",
"task": "Task 2: Confirm the one-way proof-host submodule layout"
},
{
"command": "go test ./cmd/summer -count=1 -run 'TestDocsTree' &amp;&amp; go run ./cmd/summer docs:build --check &amp;&amp; npm --prefix admin run typecheck &amp;&amp; npm --prefix admin test -- --run admin/tests/form/registry.test.ts admin/tests/form/MLFields.test.ts &amp;&amp; npm --prefix admin run build &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app vet ./... &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslate)$'",
"statement": "Non-zero exit; docs checker reports stale identifiers/links/forbidden names; Vitest reports no tests or failures; build omits index.html/assets; host output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\", or lacks \"--- PASS: TestBootUserTranslate\".",
"status": "ok",
"severity": "none",
"plan": "14.2.1-03-PLAN.md",
"task": "Task 3: Complete docs/OpenAPI/TS/dist and boot the user+translate proof host"
},
{
"command": "go -C ../sm-translate-plugin test ./... -count=1 -v -run '^(TestTranslateEndToEnd)$' &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslate)$'",
"statement": "Non-zero exit; either run prints \"--- FAIL\", \"--- SKIP\", \"no tests to run\", container startup failure treated as skip, or lacks its named \"--- PASS\" line.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-04-PLAN.md",
"task": "Task 1: Prove migration → activation → resolver → Locales admin → ML save → WithLocale read end to end"
},
{
"command": "go -C ../sm-translate-plugin test ./... -count=1 -race &amp;&amp; go test ./modules/surf ./modules/cabana -count=1 -race &amp;&amp; npm --prefix admin test -- --run admin/tests/form/registry.test.ts admin/tests/form/MLFields.test.ts admin/tests/form/MarkdownField.test.ts",
"statement": "Non-zero exit; Go race detector reports a race; any package/test reports FAIL; integration tests unexpectedly SKIP in the plugin run; or Vitest reports no tests or failures.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-04-PLAN.md",
"task": "Task 2: Complete migration, Translator, Translatable, admin, ML, markdown, and SPA test matrices"
},
{
"command": "bash scripts/check-phase14.2.1.sh --all",
"statement": "Non-zero exit; any stage is absent/skipped; output contains FAIL, a Go race, no-tests-to-run, unexpected SKIP, stale generated artifacts, forbidden deferred surface, unmitigated high threat, or lacks the final `Phase 14.2.1 gate passed` line.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-04-PLAN.md",
"task": "Task 3: Build the fail-closed phase gate, security review, and validation sign-off"
},
{
"command": "test -f modules/cabana/field_ml.go &amp;&amp; test -f ../sm-translate-plugin/classes/translatable.go &amp;&amp; test -f ../sm-translate-plugin/classes/admin_writer.go",
"statement": "Non-zero exit, or any of the three paths is missing.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-05-PLAN.md",
"task": "Task 1: Confirm TranslationWriter.TranslatedExact as the hydration contract"
},
{
"command": "go test ./modules/cabana -count=1 -v -run '^(TestMLHydration|TestMLNestedSave)$' &amp;&amp; npm --prefix admin test -- --run admin/tests/form/MLFields.test.ts",
"statement": "Non-zero exit; Go output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\", lacks \"--- PASS: TestMLHydration\" or \"--- PASS: TestMLNestedSave\"; Vitest reports no test files/tests or any failed test.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-05-PLAN.md",
"task": "Task 2: Hydrate one mltext create/GET/save path and show en+pl on the SPA"
},
{
"command": "go test ./cmd/summer -count=1 -run 'TestDocsTree' &amp;&amp; go run ./cmd/summer docs:build --check &amp;&amp; bash scripts/check-admin-openapi.sh --check &amp;&amp; npm --prefix admin run typecheck &amp;&amp; npm --prefix admin test -- --run admin/tests/form/MLFields.test.ts admin/tests/form/formState.test.ts &amp;&amp; npm --prefix admin run build &amp;&amp; go -C ../sm-translate-plugin vet ./... &amp;&amp; go -C ../sm-translate-plugin test ./classes -count=1 -v -run '^(TestTranslatableGetSet)$'",
"statement": "Non-zero exit; docs checker reports stale identifiers, broken links, or a consuming-application name; OpenAPI --check reports stale committed output; Vitest reports no tests or failures; build omits index.html/assets; plugin output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\", or lacks \"--- PASS: TestTranslatableGetSet\".",
"status": "ok",
"severity": "none",
"plan": "14.2.1-05-PLAN.md",
"task": "Task 3: Cover mlmarkdown, relation-child adopt, and regenerate docs/OpenAPI/TS/dist"
},
{
"command": "go test ./modules/cabana -count=1 -v -run '^(TestRelationChildMLNestedSave)$'",
"statement": "Non-zero exit; output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\"; output lacks \"--- PASS: TestRelationChildMLNestedSave\".",
"status": "ok",
"severity": "none",
"plan": "14.2.1-06-PLAN.md",
"task": "Task 1: Lift one nested mltext map through CreateChild"
},
{
"command": "go test ./modules/cabana -count=1 -v -run '^(TestRelationChildMLNestedSave)$' &amp;&amp; go test ./cmd/summer -count=1 -run 'TestDocsTree' &amp;&amp; go run ./cmd/summer docs:build --check",
"statement": "Non-zero exit; cabana output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\", or lacks \"--- PASS: TestRelationChildMLNestedSave\"; docs checker reports stale identifiers, broken links, or a consuming-application name.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-06-PLAN.md",
"task": "Task 2: Mirror lift/apply/hydrate on UpdateChild and ShowChild"
},
{
"command": "go vet ./modules/cabana ./modules/surf ./cmd/summer &amp;&amp; go test ./modules/cabana -count=1 -v -run '^(TestMLHydration|TestMLNestedSave|TestRelationChildMLNestedSave|TestMLFieldTypes)$' &amp;&amp; npm --prefix admin test -- --run admin/tests/form/MLFields.test.ts &amp;&amp; go -C ../sm-translate-plugin vet ./... &amp;&amp; go -C ../sm-translate-plugin test ./... -count=1 -v -run '^(TestTranslateEndToEnd)$' &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app vet ./... &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslate)$' &amp;&amp; bash scripts/check-phase14.2.1.sh --all",
"statement": "Non-zero exit; any Go output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\"; output lacks \"--- PASS: TestMLHydration\", \"--- PASS: TestMLNestedSave\", \"--- PASS: TestRelationChildMLNestedSave\", \"--- PASS: TestMLFieldTypes\", \"--- PASS: TestTranslateEndToEnd\", or \"--- PASS: TestBootUserTranslate\"; Vitest reports no tests or failures; gate output lacks the exact line \"Phase 14.2.1 gate passed\" or reports a missing required test / unmapped high threat.",
"status": "ok",
"severity": "none",
"plan": "14.2.1-06-PLAN.md",
"task": "Task 3: Named unit/Vitest/cabana tests and the phase gate (last plan of 14.2.1)"
}
],
"counts": {
"blocker": 0,
"warning": 0,
"total": 18
},
"readError": null
}

View File

@@ -0,0 +1,107 @@
{
"status": "ok",
"commands": [
{
"command": "git ls-remote git@git.golem15.com:golem15/sm-journal-plugin.git &amp;&amp; git -C /media/nvme/dev/golem15/fonoteka/plugins/golem15/journal rev-parse HEAD &amp;&amp; go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestPostTranslatableSmoke)$' &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app vet ./... &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslateJournal)$'",
"statement": "Non-zero exit; PHP SHA is not 02110eb1c0c3861370b0b9b47b209a0702ac5d88; output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\"; either named PASS line is absent; journal remote is missing.",
"status": "ok",
"severity": "none",
"plan": "15-01-PLAN.md",
"task": "Task 1: Clone the plugin, persist one en/pl Post title, and boot the host with three plugins"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./updates -count=1 -v -run '^(TestJournalTables)$'",
"statement": "Non-zero exit; output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\"; lacks \"--- PASS: TestJournalTables\".",
"status": "ok",
"severity": "none",
"plan": "15-01-PLAN.md",
"task": "Task 2: Add categories, tags, pivots, settings singleton, and author_slug"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -short -count=1 &amp;&amp; python3 -c 'import pathlib,sys; t=pathlib.Path(\"../sm-journal-plugin/README.md\").read_text(); bad=[\"grzybyfunkcjonalne\",\"Płytarium\",\"fonoteka.go\",\"plytarium\"];\nsys.exit(1 if any(b.lower() in t.lower() for b in bad) else 0)' &amp;&amp; grep -F \"_journal/api/*\" ../sm-grzybyfunkcjonalne-app/config/http.yaml &amp;&amp; test -f ../sm-journal-plugin/lang/en/lang.yaml &amp;&amp; test -f ../sm-journal-plugin/lang/pl/lang.yaml",
"statement": "Non-zero exit; plugin tests FAIL; README python check exits 1; grep does not print _journal/api/*; either lang YAML is missing.",
"status": "ok",
"severity": "none",
"plan": "15-01-PLAN.md",
"task": "Task 3: Add en/pl phrasebook, neutral README, and host CORS"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestPostsFormCompiles|TestPostsAdminCreateSmoke)$'",
"statement": "Non-zero exit; output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\"; either named PASS line is absent.",
"status": "ok",
"severity": "none",
"plan": "15-02-PLAN.md",
"task": "Task 1: Create one Post through cabana with mlmarkdown and FormatHTML"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -short -count=1 &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslateJournal)$'",
"statement": "Non-zero exit; output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\"; host run lacks \"--- PASS: TestBootUserTranslateJournal\".",
"status": "ok",
"severity": "none",
"plan": "15-02-PLAN.md",
"task": "Task 2: Categories, Tags, Settings screens and remaining query guards"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestJournalCommands)$'",
"statement": "Non-zero exit; output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\"; lacks \"--- PASS: TestJournalCommands\".",
"status": "ok",
"severity": "none",
"plan": "15-02-PLAN.md",
"task": "Task 3: Register journal:export-posts and journal:import-posts plus Posts toolbar"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestJournalPublicList|TestJournalBuckets)$'",
"statement": "Non-zero exit; output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\"; either named PASS line is absent.",
"status": "ok",
"severity": "none",
"plan": "15-03-PLAN.md",
"task": "Task 1: Serve anonymous GET /_journal/api/v1/posts as a published list"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestJournalWriteUnauthenticated|TestJournal005DraftShow|TestJournalPublicCategories|TestJournalPublicTags|TestJournalFeaturedImageUnauthenticated)$'",
"statement": "Non-zero exit; output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\"; any of the named PASS lines is absent (TestJournalWriteUnauthenticated, TestJournal005DraftShow, TestJournalPublicCategories, TestJournalPublicTags, TestJournalFeaturedImageUnauthenticated).",
"status": "ok",
"severity": "none",
"plan": "15-03-PLAN.md",
"task": "Task 2: Slug show, draft 404, and backend-Bearer writes"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestJournal006MediaUpload|TestSearchGateOff|TestJournalRSS)$' &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslateJournal)$'",
"statement": "Non-zero exit; output contains \"--- FAIL\", \"--- SKIP\", or \"no tests to run\"; any of the named PASS lines is absent (TestJournal006MediaUpload, TestSearchGateOff, TestJournalRSS, TestBootUserTranslateJournal).",
"status": "ok",
"severity": "none",
"plan": "15-03-PLAN.md",
"task": "Task 3: Media upload, RSS, Typesense gate, and host route assertion"
},
{
"command": "go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestJournalEndToEnd)$' &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslateJournal)$'",
"statement": "Non-zero exit; either run prints \"--- FAIL\", \"--- SKIP\", \"no tests to run\", or container startup treated as skip; lacks its named PASS line.",
"status": "ok",
"severity": "none",
"plan": "15-04-PLAN.md",
"task": "Task 1: Prove migrate → admin save → anonymous list → Bearer write → draft 404 end to end"
},
{
"command": "go -C ../sm-journal-plugin test ./... -count=1 -v -race &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -race",
"statement": "Non-zero exit; Go race detector reports a race; any package reports FAIL; integration tests unexpectedly SKIP in the plugin run; output lacks PASS lines for TestJournalPublicCategories, TestJournalPublicTags, TestJournalRSS, and TestJournalFeaturedImageUnauthenticated.",
"status": "ok",
"severity": "none",
"plan": "15-04-PLAN.md",
"task": "Task 2: Complete PHPUnit map, migrations, YAML, FormatHTML, and search-gate tests"
},
{
"command": "bash scripts/check-phase15.sh --all",
"statement": "Non-zero exit; any stage absent or skipped; output contains FAIL, a Go race, no-tests-to-run, unexpected SKIP, unmitigated high threat, PHP tree dirty; lacks PASS evidence for TestJournalPublicCategories, TestJournalPublicTags, TestJournalRSS, or TestJournalFeaturedImageUnauthenticated; or lacks the final Phase 15 gate passed line.",
"status": "ok",
"severity": "none",
"plan": "15-04-PLAN.md",
"task": "Task 3: Phase gate, security review, and validation sign-off"
}
],
"counts": {
"blocker": 0,
"warning": 0,
"total": 12
},
"readError": null
}

View File

@@ -0,0 +1,191 @@
{
"status": "not_applicable",
"commands": [
{
"command": "git ls-remote git@git.golem15.com:golem15/sm-journal-plugin.git &amp;&amp; git -C /media/nvme/dev/golem15/fonoteka/plugins/golem15/journal rev-parse HEAD &amp;&amp; go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestPostTranslatableSmoke)$' &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app vet ./... &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslateJournal)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "15-01-PLAN.md",
"task": "Task 1: Clone the plugin, persist one en/pl Post title, and boot the host with three plugins"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./updates -count=1 -v -run '^(TestJournalTables)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "15-01-PLAN.md",
"task": "Task 2: Add categories, tags, pivots, settings singleton, and author_slug"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -short -count=1 &amp;&amp; python3 -c 'import pathlib,sys; t=pathlib.Path(\"../sm-journal-plugin/README.md\").read_text(); bad=[\"grzybyfunkcjonalne\",\"Płytarium\",\"fonoteka.go\",\"plytarium\"];\nsys.exit(1 if any(b.lower() in t.lower() for b in bad) else 0)' &amp;&amp; grep -F \"_journal/api/*\" ../sm-grzybyfunkcjonalne-app/config/http.yaml &amp;&amp; test -f ../sm-journal-plugin/lang/en/lang.yaml &amp;&amp; test -f ../sm-journal-plugin/lang/pl/lang.yaml",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "15-01-PLAN.md",
"task": "Task 3: Add en/pl phrasebook, neutral README, and host CORS"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestPostsFormCompiles|TestPostsAdminCreateSmoke)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "15-02-PLAN.md",
"task": "Task 1: Create one Post through cabana with mlmarkdown and FormatHTML"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -short -count=1 &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslateJournal)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "15-02-PLAN.md",
"task": "Task 2: Categories, Tags, Settings screens and remaining query guards"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestJournalCommands)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "15-02-PLAN.md",
"task": "Task 3: Register journal:export-posts and journal:import-posts plus Posts toolbar"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestJournalPublicList|TestJournalBuckets)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "15-03-PLAN.md",
"task": "Task 1: Serve anonymous GET /_journal/api/v1/posts as a published list"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestJournalWriteUnauthenticated|TestJournal005DraftShow|TestJournalPublicCategories|TestJournalPublicTags|TestJournalFeaturedImageUnauthenticated)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "15-03-PLAN.md",
"task": "Task 2: Slug show, draft 404, and backend-Bearer writes"
},
{
"command": "go -C ../sm-journal-plugin vet ./... &amp;&amp; go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestJournal006MediaUpload|TestSearchGateOff|TestJournalRSS)$' &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslateJournal)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "15-03-PLAN.md",
"task": "Task 3: Media upload, RSS, Typesense gate, and host route assertion"
},
{
"command": "go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestJournalEndToEnd)$' &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslateJournal)$'",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "15-04-PLAN.md",
"task": "Task 1: Prove migrate → admin save → anonymous list → Bearer write → draft 404 end to end"
},
{
"command": "go -C ../sm-journal-plugin test ./... -count=1 -v -race &amp;&amp; go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -race",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "15-04-PLAN.md",
"task": "Task 2: Complete PHPUnit map, migrations, YAML, FormatHTML, and search-gate tests"
},
{
"command": "bash scripts/check-phase15.sh --all",
"status": "not_applicable",
"severity": "none",
"reason": null,
"form": null,
"rawTarget": null,
"target": null,
"manifest": null,
"script": null,
"sentinel": false,
"base": "/media/nvme/dev/golem15/summercms.io/summercms/summercms.go",
"plan": "15-04-PLAN.md",
"task": "Task 3: Phase gate, security review, and validation sign-off"
}
],
"counts": {
"blocker": 0,
"warning": 0,
"total": 12
},
"readError": null
}

View File

@@ -0,0 +1,139 @@
# API Coverage Decision Checkpoint
> Full API Coverage by Default — Opt Out, Never Opt In. Fires when a phase
> integrates an external API / SDK / service. Most non-API phases will not fire
> it — that is the point.
## Why this exists
"We integrated the API" too often silently means "we integrated whatever the
first use case exercised." Every un-built capability is then an invisible hole,
discovered later by a user who reasonably expected it to work. The phase sealed
green because its tasks completed; nobody decided the gaps were acceptable,
because nobody enumerated them. This checkpoint makes the surface **visible and
decided** before the phase can seal.
## Detect whether this phase integrates an external API
The detector is a deterministic scan over the phase scope. It strips fenced
code blocks first, so a trigger term inside a code snippet does not fire. It
returns a typed result: `{ detected, signals[], terms }`. Run it on the phase
scope (the concatenation of this phase's ROADMAP section + the PLAN body):
```bash
SCOPE="$(cat "${PHASE_DIR}"/*-PLAN.md 2>/dev/null) $(gsd_run query roadmap.get-phase "${PHASE}" 2>/dev/null || true)"
API_COVERAGE_JSON=$(printf '%s' "$SCOPE" | node gsd-core/bin/lib/api-coverage.cjs --json 2>/dev/null) || true
[ -n "$API_COVERAGE_JSON" ] || API_COVERAGE_JSON='{"skipped":true,"reason":"probe_unavailable"}'
```
The `|| true` neutralizes the assignment's status without discarding the
detector's own payload: the detector exits **1** for a real "no integration"
verdict, so treating any non-zero exit as failure would throw away a correct
answer. Emptiness — not exit status — is what proves the probe never ran, and
the second line is the only place the fragment manufactures a payload of its
own — one that records the *absence* of a verdict rather than asserting one.
The detector's exit code and `--json` payload now distinguish a real negative
from an unexamined input (ADR-3889 Phase 3, #3907): empty/whitespace-only
`$SCOPE` or a stdin read failure emit `{"skipped":true,"reason":"no_input"|
"stdin_error"}` — no `detected` key at all. **Check for `skipped` before
reading `detected`**: a `skipped` payload is not a confirmed "no API
integration" verdict, it means the detector never examined real input. Do not
treat it as `detected:false`. Read `API_COVERAGE_JSON.detected` only when
`skipped` is absent — act on it only, do **not** pattern-match the prose
yourself.
**If `skipped` is `true`:** the detector could not establish a scope (empty
`$SCOPE`) or failed to run (stdin read error). Skip the checkpoint for this
run rather than asserting a verdict about input that was never examined; do
not raise it with the user.
**If `detected` is `false`:** this phase does not integrate an external API. Skip
the checkpoint entirely and continue planning. Do not raise it with the user.
**If `detected` is `true`:** an external-API integration is in scope. You MUST
produce a **coverage matrix** before the plan is finalized.
**If `detected` is `true` but the phase genuinely integrates no external API**
(the detector is deterministic, not infallible — confirm by re-reading the phase
scope, not by preference): do NOT fabricate a matrix row for a capability that
does not exist. Write a reasoned declaration to `${PHASE_DIR}/COVERAGE.md`
instead:
```markdown
No external API integration: <one-line reason — what the phase touches instead>.
```
The reason is required, exactly like an `OPT-OUT` reason. The seal-time gate
accepts this declaration in place of a matrix.
## Produce the coverage matrix
Enumerate the external API's full **capability surface** — the verb/endpoint/method
list (e.g. for a music service: `search`, `play`, `pause`, `skip`, `set_volume`,
`get_playlist`, `create_playlist`, `add_to_playlist`, …). For each capability
record a decision, starting from **full coverage** as the default:
| capability | decision | reason |
|---|---|---|
| `<capability-id>` | `INTEGRATE` \| `OPT-OUT` | `<one-line reason if OPT-OUT>` |
Rules:
- **`INTEGRATE` is the default.** Every capability starts as INTEGRATE; the
matrix is the *subtraction record*.
- **Every `OPT-OUT` MUST carry a one-line reason** (`not needed`, `not needed
yet`, `explicitly out of scope`, …). An opt-out without a reason is an
un-decided hole — the exact failure mode this gate exists to close.
- **A second integration against the same need** (e.g. a second platform for the
same capability) starts from the **same full-coverage baseline** as the first.
Do not carry over the first integration's opt-outs silently — re-decide each
capability for the new surface, so a first-class/fallback asymmetry cannot
accumulate.
Write the matrix to `${PHASE_DIR}/COVERAGE.md` (canonical markdown-table form):
```markdown
# API Coverage — <service>
> Full coverage by default. Opt-outs are explicit, reasoned decisions.
| capability | decision | reason |
|---|---|---|
| search | INTEGRATE | |
| playlists | INTEGRATE | |
| skip | OPT-OUT | not needed yet — tracked for follow-up phase |
```
A fenced ` ```coverage ` JSON block is also accepted for machine-generated
matrices; the markdown table is preferred (human-editable, diff-friendly).
## The seal-time gate
This checkpoint is enforced. At `verify:pre` the `api-coverage.verify-pre` gate
runs `check api-coverage.verify-pre <phase-dir>`:
- If `COVERAGE.md` exists, it is validated — every row needs a valid decision and
every `OPT-OUT` a reason. A malformed/partial matrix **blocks the seal**. A
reasoned `No external API integration: …` declaration (and no rows) passes.
- If `COVERAGE.md` is absent, the detector runs again over the phase scope. If a
strong external-API-integration signal is found, the seal is **blocked** until a
matrix is produced. If no signal is found, the phase is treated as a non-API
phase and the seal proceeds.
So: an API-integrating phase cannot seal without a decided matrix. Produce it at
plan time; do not leave it for seal time.
## Tuning the vocabulary (optional)
The trigger vocabulary is a curated, additive-only set in
`gsd-core/bin/lib/api-coverage.cjs` (`DEFAULT_API_COVERAGE_TERMS`). To widen it
for a project, override at the call site:
```bash
printf '%s' "$SCOPE" | node gsd-core/bin/lib/api-coverage.cjs --json \
--verbs integrate,wrap,connect,embed --nouns api,sdk,rest,grpc,webhook,plugin
```
The whole checkpoint is toggleable via `workflow.api_coverage_gate` in
`.planning/config.json`.

View File

@@ -0,0 +1,56 @@
# Assumption-Delta Architecture Checkpoint
> Advisory, non-blocking. Fires **only** when the phase scope shows a singular→plural / required→optional / derived→chosen transition. When it fires, it surfaces ONE identity-model question before the plan is finalized. Most phases will not fire it — that is the point.
## Why this exists
Most quietly-imported architectural debt does not come from a missing upfront design phase. It comes at the *seam*: a later phase introduces a second case (a second platform, auth method, tenant, region, source of truth) and nobody re-asks whether the original abstraction still names the right thing. The phase that adds the second case is exactly the 20-minute conversation that prevents an afternoon of later cleanup.
## Run the detector
The detector is a deterministic scan over the phase scope text. It strips fenced code blocks first, so a trigger word that appears only inside a code snippet does not fire. It returns a typed result: `{ detected, signals[], terms }`. Resolve it through the `assumption-delta scan` query (same phase-section resolver as `roadmap.get-phase`):
```bash
ASSUMPTION_DELTA_JSON=$(gsd_run query assumption-delta scan "${PHASE}" --json 2>/dev/null) || true
[ -n "$ASSUMPTION_DELTA_JSON" ] || ASSUMPTION_DELTA_JSON='{"skipped":true,"reason":"probe_unavailable"}'
```
> If the phase section cannot be resolved (no `ROADMAP.md` / unknown phase, or a section with no body), the query emits `{ "skipped": true, "reason": "phase_unresolved" }` — **not** `detected:false`. A probe that never had input does not get to assert that this phase changes no core assumption. The checkpoint does not fire either way; the difference is that a skip is now distinguishable from a real negative. Do not block on it.
>
> Optional tuning — pass `--terms <comma-list>` to replace the curated pluralization cues for this project (the `optional`/`chosen` cues keep their defaults): `gsd_run query assumption-delta scan "${PHASE}" --json --terms second,alternative,fallback`.
## Decision branch
Read `ASSUMPTION_DELTA_JSON`. Act on `detected` only — do **not** pattern-match the human prose.
**If `skipped` is `true`:** the detector never examined a phase section — it could not resolve one (`phase_unresolved`) or could not run at all (`probe_unavailable`). Skip the checkpoint for this run rather than asserting a verdict about input that was never examined; do not raise it with the user. **Check for `skipped` before reading `detected`** — a skipped payload carries no `detected` key, and treating its absence as `false` re-creates the fabrication this branch exists to prevent.
**If `detected` is `false`:** this phase does not change a core assumption. Skip the checkpoint entirely and continue planning. Do not raise it with the user.
**If `detected` is `true`:** a core assumption may have lost its monopoly. The `signals[]` array tells you which family fired:
| `kind` | What changed | The question to answer |
|---|---|---|
| `pluralization` | A second X was introduced where there was one (second platform / auth method / tenant / region / source of truth) | Does the current primary key / identity model still name the right noun? |
| `optional` | A required / `only` field became optional | Is the field still the right anchor, or has the anchor moved? |
| `chosen` | A derived value became chosen, or a constant became a parameter | Has a configuration decision become a modeling decision? |
Before finalizing the plan, answer this for the user and record the decision explicitly:
> **Promote vs. add-alongside.** The usual correct move when a generalization occurs is to **promote** the new general representation to the primary and **demote** the old specific one to a detail of one variant — *not* to add the new one alongside the still-required old one. Adding alongside silently contradicts the generalized intent (a later variant that does not fit the old primary can be stored but never confirmed as a default).
Record the outcome in the PLAN.md front matter / a `<assumption_delta_decision>` block:
- The **noun** that is now primary (the generalized identity).
- The **decision**: `promote` | `add-alongside` | `no-change`, with a one-line rationale.
- If `add-alongside`: call it out as accepted debt and note what would force a later promote.
## Optional companion: an invariant test
When `detected` is `true`, suggest (do not require) a contract/invariant test that encodes the now-generalized intent — e.g. *"every confirmed default round-trips through the primary use-path, for every supported variant."* That test goes red the instant a future phase reintroduces the singular assumption, so the regression cannot land silently. If the user accepts, add the test as a task in the plan.
## Tuning the vocabulary (optional)
The trigger vocabulary is a curated, additive-only set in `gsd-core/bin/lib/assumption-delta.cjs` (`DEFAULT_ASSUMPTION_DELTA_TERMS`). Bare "or" is intentionally excluded — it is too common in prose and would make the gate fire constantly. To widen or narrow the cues for a project, override at the call site with `--terms <comma-list>` (replaces the pluralization cues; `optional`/`chosen` keep defaults). The whole checkpoint is toggleable via `workflow.assumption_delta` in `.planning/config.json`.
This checkpoint is advisory: it informs and records; it never blocks the phase.

View File

@@ -0,0 +1,61 @@
# Schema Push Detection Gate
> Detects schema-relevant files in the phase scope and injects a mandatory `[BLOCKING]` schema push task into the plan. Prevents false-positive verification where build/types pass because TypeScript types come from config, not the live database.
Check if any files in the phase scope match schema patterns:
```bash
PHASE_SECTION=$(gsd_run query roadmap.get-phase "${PHASE}" --pick section 2>/dev/null)
```
Scan `PHASE_SECTION`, `CONTEXT.md` (if loaded), and `RESEARCH.md` (if exists) for file paths matching these ORM patterns:
| ORM | File Patterns |
|-----|--------------|
| Payload CMS | `src/collections/**/*.ts`, `src/globals/**/*.ts` |
| Prisma | `prisma/schema.prisma`, `prisma/schema/*.prisma` |
| Drizzle | `drizzle/schema.ts`, `src/db/schema.ts`, `drizzle/*.ts` |
| Supabase | `supabase/migrations/*.sql` |
| TypeORM | `src/entities/**/*.ts`, `src/migrations/**/*.ts` |
Also check if any existing PLAN.md files for this phase already reference these file patterns in `files_modified`.
**If schema-relevant files detected:**
Set `SCHEMA_PUSH_REQUIRED=true` and `SCHEMA_ORM={detected_orm}`.
Determine the push command for the detected ORM:
| ORM | Push Command | Non-TTY Workaround |
|-----|-------------|-------------------|
| Payload CMS | `npx payload migrate` | `CI=true PAYLOAD_MIGRATING=true npx payload migrate` |
| Prisma | `npx prisma db push` | `npx prisma db push --accept-data-loss` (if destructive) |
| Drizzle | `npx drizzle-kit push` | `npx drizzle-kit push` |
| Supabase | `supabase db push` | Set `SUPABASE_ACCESS_TOKEN` env var |
| TypeORM | `npx typeorm migration:run` | `npx typeorm migration:run -d src/data-source.ts` |
Inject the following into the planner prompt (step 8) as an additional constraint:
```markdown
<schema_push_requirement>
**[BLOCKING] Schema Push Required**
This phase modifies schema-relevant files ({detected_files}). The planner MUST include
a `[BLOCKING]` task that runs the database schema push command AFTER all schema file
modifications are complete but BEFORE verification.
- ORM detected: {SCHEMA_ORM}
- Push command: {push_command}
- Non-TTY workaround: {env_hint}
- If push requires interactive prompts that cannot be suppressed, flag the task for
manual intervention with `autonomous: false`
This task is mandatory — the phase CANNOT pass verification without it. Build and
type checks will pass without the push (types come from config, not the live database),
creating a false-positive verification state.
</schema_push_requirement>
```
Display: `Schema files detected ({SCHEMA_ORM}) — [BLOCKING] push task will be injected into plans`
**If no schema-relevant files detected:** Skip silently.

View File

@@ -0,0 +1 @@
Each PLAN.md must include a <threat_model> block when security enforcement is active. Use the configured ASVS level and blocking threshold from workflow.security_asvs_level and workflow.security_block_on.