diff --git a/.planning/phases/17-blog-companions/17-06-SUMMARY.md b/.planning/phases/17-blog-companions/17-06-SUMMARY.md new file mode 100644 index 0000000..7e51bc9 --- /dev/null +++ b/.planning/phases/17-blog-companions/17-06-SUMMARY.md @@ -0,0 +1,371 @@ +--- +phase: 17-blog-companions +plan: 06 +subsystem: testing +tags: [go, testing, testcontainers, postgres, vitest, node-test, gate, signpost, seaglass, cabana, sitemap, menu, faq, journal, nuxt-layers] + +requires: + - phase: 17-01 + provides: pact menu item type contracts, signpost, seaglass, cabana repeater, richeditor, dependsOn, reorder and singleton, smoke tests + - phase: 17-02 + provides: admin SPA richeditor, repeater, dependent dropdown, reorder page and singleton form with smoke specs; framework v0.1.23 + - phase: 17-03 + provides: Journal menu item types, sm-sitemap-plugin with PHP goldens and import commands + - phase: 17-04 + provides: sm-menu-plugin and sm-faq-plugin with public APIs and imports + - phase: 17-05 + provides: host wiring (TestCompanionsHost), vue-menu-layer and vue-faq-layer +provides: + - scripts/check-phase17.sh, the fail-closed phase gate over eight repositories (stages go, plugins, admin, layers, docs, postgres; --self-test, --all) + - unit tests for every Phase 17 behaviour across summercms.go, sm-journal-plugin, sm-sitemap-plugin, sm-menu-plugin, sm-faq-plugin, sm-grzybyfunkcjonalne-app, vue-menu-layer and vue-faq-layer + - fix(17-06) singleton route retries a lost first-or-create race (framework production code, unreleased) + - Journal Postgres harnesses read -short through testing.Short (17-03 deferred item 1 closed) + - filled 17-VALIDATION.md (status validated, nyquist_compliant true, wave_0_complete true) +affects: [phase 17 verification (/msd-verify-work), next framework release, next plugin and layer releases, grzybyfunkcjonalne production cutover] + +actuals: + tokens: 78502 + tasks: 4 + commits: 9 +plan_head_before: 374834b3074eb4d38e1822bde7bca97d3335a7c2 +plan_head_after: 31adf71b8209ac5395bbda5e90f0f334e0723c32 +repo_heads: + summercms.go: {before: 374834b, after: 31adf71, commits: 7, note: "rev-list 374834b..31adf71 counts 9; f622b68 and b4e2232 (docs(phase-14.1) and docs(phase-12) security verification) are another session's commits inside the range. This plan's commits: 490a4e3 91593da 6312929 40b89ad ed67e41 2274cd6 31adf71, then the SUMMARY and state commits"} + sm-journal-plugin: {before: 06d0c9b, after: 3b252f2, commits: 2} + sm-sitemap-plugin: {before: 5a260db, after: 4e55939, commits: 1} + sm-menu-plugin: {before: 33a65ad, after: 43267de, commits: 1} + sm-faq-plugin: {before: 1585f06, after: 919cf06, commits: 1} + sm-grzybyfunkcjonalne-app: {before: 6441b17, after: ceea5be, commits: 1} + vue-menu-layer: {before: d980f4c, after: 2deac88, commits: 1} + vue-faq-layer: {before: 2d62924, after: 321bc90, commits: 1} + +tech-stack: + added: [] + patterns: + - "Phase gate per repository and package: go_named DIR PKG FLAGS NAME... runs exactly the named top-level tests and requires each to pass; a skip, a miss or 'no tests to run' fails" + - "Layer tests load Nuxt composables by rewriting import.meta.server to a flag in a temporary copy next to the source, with stubbed Nuxt globals" + - "Postgres refusal tests snapshot the table (JSON) before the request and require it unchanged after" + - "Conformance error cases: documented 404/409/422 answers decoded as cabana.ErrorEnvelope with unknown fields refused and matched against admin.json" + +key-files: + created: + - scripts/check-phase17.sh + - modules/signpost/signpost_test.go + - modules/seaglass/seaglass_test.go + - modules/pact/menu_item_types_test.go + - modules/cabana/repeater_test.go + - modules/cabana/dependent_test.go + - modules/cabana/richeditor_test.go + - modules/cabana/reorder_test.go + - modules/cabana/singleton_test.go + - modules/cabana/companions_postgres_test.go + - admin/tests/form/SingletonForm.test.ts + - ../sm-journal-plugin/classes/menu_item_types_test.go + - ../sm-journal-plugin/menu_item_types_test.go + - ../sm-sitemap-plugin/classes/urlto_test.go + - ../sm-sitemap-plugin/classes/serialize_test.go + - ../sm-sitemap-plugin/classes/tiers_test.go + - ../sm-sitemap-plugin/classes/rewrite_test.go + - ../sm-sitemap-plugin/classes/manifest_test.go + - ../sm-sitemap-plugin/classes/generator_test.go + - ../sm-sitemap-plugin/models/definition_test.go + - ../sm-sitemap-plugin/import_test.go + - ../sm-sitemap-plugin/routes_test.go + - ../sm-menu-plugin/classes/links_test.go + - ../sm-menu-plugin/classes/tree_test.go + - ../sm-menu-plugin/models/menu_entry_test.go + - ../sm-menu-plugin/menu_api_test.go + - ../sm-faq-plugin/console/import_winter_test.go + - ../sm-faq-plugin/models/faq_test.go + - ../sm-faq-plugin/controllers/api/faqs_test.go + - ../sm-faq-plugin/faq_api_test.go + modified: + - modules/cabana/singleton.go + - modules/cabana/README.md + - docs/backend/admin-controllers.md + - modules/party/registry_test.go + - modules/cabana/openapi_conformance_test.go + - admin/tests/form/RichEditorField.test.ts + - admin/tests/form/RepeaterField.test.ts + - admin/tests/form/DependentDropdown.test.ts + - admin/tests/views/ReorderView.test.ts + - ../sm-journal-plugin/admin_harness_test.go + - ../sm-journal-plugin/models/post_translatable_smoke_test.go + - ../sm-journal-plugin/updates/postgres_test.go + - ../sm-grzybyfunkcjonalne-app/companions_test.go + - ../vue-menu-layer/tests/menuLinks.test.ts + - ../vue-menu-layer/tests/client.test.ts + - ../vue-faq-layer/tests/client.test.ts + - .planning/phases/17-blog-companions/17-VALIDATION.md + - .planning/phases/17-blog-companions/deferred-items.md + +key-decisions: + - "The singleton first-open race (one of two concurrent first GET .../singleton answered 500 on the unique index) is a production defect: cabana reruns the transaction once on a Postgres unique violation; fixed in its own fix(17-06) commit with the strict concurrent test, flagged for the next framework tag" + - "Docker-free cabana unit tests run in package cabana against an fstest fixture (acme.unit); route and boot tests run in cabana_test against the acme.companions fixture, compile checks through cabana.Activate without a database" + - "Layer protocol-relative URLs keep the shipped D-16 rule (an anchor like any absolute URL, never a site path, never active); the plan's 'text without href' wording is recorded as a deferred item, not changed" + - "Journal's three Postgres harnesses now read -short through flag.Parse and testing.Short, so the gate's plugins stage starts no container" + +patterns-established: + - "Every Phase 17 test name sits in one gate list; adding a test means adding its name to the stage that runs its package" + +requirements-completed: [SC-5, SC-1, SC-2, SC-3, SC-4] + +coverage: + - id: D1 + description: "Fail-closed phase gate over eight repositories with detectors proven on planted output; --all ends with 'Phase 17 gate passed'" + requirement: "SC-5" + verification: + - kind: other + ref: "bash scripts/check-phase17.sh --self-test" + status: pass + - kind: other + ref: "bash scripts/check-phase17.sh --all (Docker available, every stage PASS)" + status: pass + human_judgment: false + - id: D2 + description: "Framework unit tests: signpost activation, pattern layers and ReferenceOptions; seaglass byte stability, report and bypass corpus; pact contracts; the party hook; cabana repeater, dependent dropdown, richeditor, reorder and singleton compile and routes; conformance of the new routes' success and error answers" + requirement: "SC-2" + verification: + - kind: unit + ref: "bash scripts/check-phase17.sh go" + status: pass + - kind: integration + ref: "go test -count=1 -run '^(TestRepeaterSave|TestDependentRecheck|TestRichEditor|TestReorderPostgres|TestSingleton|TestPhase10OpenAPIConformance)$' ./modules/cabana (6 passes)" + status: pass + human_judgment: false + - id: D3 + description: "Admin SPA specs for the UI-SPEC backstops: richeditor untouched value, link popover, notices, chunk failure; repeater bounds, remove and reorder, collapsed rows; dependent dropdown states and last request wins; reorder announcements, partition switch, tree rules; singleton form" + requirement: "SC-1" + verification: + - kind: unit + ref: "bash scripts/check-phase17.sh admin (vitest 1325 passed in 88 files, 5 required files, 46 required names)" + status: pass + human_judgment: false + - id: D4 + description: "Plugin unit tests: Journal menu item types, sitemap parity (Url::to goldens, escaping, fillable, tiers, rewrite, hooks, manifest, item JSON, cap, backend URL), menu links, tree and model, FAQ decode, model and serializer; Postgres APIs, imports and beforeSave" + requirement: "SC-3" + verification: + - kind: unit + ref: "bash scripts/check-phase17.sh plugins" + status: pass + - kind: integration + ref: "bash scripts/check-phase17.sh postgres" + status: pass + human_judgment: false + - id: D5 + description: "Host wiring and layers: TestCompanionsHost with CORS preflight and sitemap headers; menu and FAQ layer link rules, clients and composable bases" + requirement: "SC-4" + verification: + - kind: integration + ref: "../sm-grzybyfunkcjonalne-app/companions_test.go#TestCompanionsHost" + status: pass + - kind: unit + ref: "bash scripts/check-phase17.sh layers (24 + 11 TAP tests)" + status: pass + human_judgment: false + - id: D6 + description: "Filled 17-VALIDATION.md: every row with task IDs, gate commands, File Exists and status; Wave 0 ticked; manual rows keep the plan 05 human check" + requirement: "SC-5" + verification: + - kind: other + ref: "grep -q '^nyquist_compliant: true' 17-VALIDATION.md && grep -c '17-0[1-6]-' 17-VALIDATION.md (21)" + status: pass + human_judgment: false + +duration: 53min +completed: 2026-10-12 +status: complete +--- + +# Phase 17 Plan 06: Unit tests and the phase gate Summary + +**One fail-closed gate (`scripts/check-phase17.sh`) runs every Phase 17 suite by name across the framework, four plugins, the host and both layers, and passes with Postgres; writing the tests found one production defect, the singleton route's first-open race, fixed in `fix(17-06)` 6312929.** + +## Performance + +- **Duration:** 53 min +- **Started:** 2026-10-12T16:58:11Z +- **Completed:** 2026-10-12T17:51:17Z +- **Tasks:** 4 of 4 (Task 1 tracer, verified end-to-end before expansion) +- **Files:** 32 test files created, 18 files modified (tests, one production file, two docs, two planning docs) + +## Accomplishments + +- **Gate:** `scripts/check-phase17.sh` with the stages go, plugins, admin, layers, docs and postgres, `--self-test` (go, vitest, TAP and required-file detectors on planted output) and `--all`. Paths are overridable (`PHASE17_ROOT`, `JOURNAL`, `SITEMAP`, `MENU`, `FAQ`, `HOST`, `MENU_LAYER`, `FAQ_LAYER`). Without Docker, `--all` prints NOT RUN postgres, and `PHASE17_REQUIRE_POSTGRES=1` turns that into a failure. +- **Framework (summercms.go):** signpost (`TestRegistryDuplicateKey`, `TestRegistryActivate`, `TestRegistryHostOverrideUnknown`, `TestRegistryPatternLayers`, `TestReferenceOptions`), seaglass (`TestSanitizeByteStable`, `TestSanitizeReport`, `TestSanitizeBypassCorpus`, `TestCheck`), pact (`TestOptionDepthOmitEmpty`, `TestMenuItemTypeContracts`), party (`TestActivateMenuItemTypesOrder`). Cabana Docker-free: `TestRepeaterCompile`, `TestRepeaterDecode`, `TestDependentDropdownCompile`, `TestDependentValues`, `TestRichEditorCompile`, `TestReorderConfigCompile`, `TestSingletonCompile`. Cabana on Postgres: `TestRepeaterSave`, `TestDependentRecheck`, `TestRichEditor`, `TestReorderPostgres`, `TestSingleton`, plus `TestPhase10OpenAPIConformance` with 11 documented error cases. +- **Admin SPA:** RichEditorField (value contract, lossy note, read-only empty, link popover allowlist and Enter, notices callout, chunk-failure fallback), RepeaterField (min/max, remove confirm only for a value, cancel, arrow keys and buttons with `repeater.moved`, collapsed rows unmount the richeditor, read-only), DependentDropdown (loading, empty naming the type, last request wins, stale stored value, 422 message, row path), ReorderView (an announcement per keyboard move, children move with their parent, skeleton and load failure, the partition switch flushes a pending save, tree rules), and the new SingletonForm spec. +- **Plugins:** Journal (`TestMenuItemTypesPatterns`, `TestMenuItemTypes`). Sitemap (`TestURLTo`, `TestXMLEscaping`, `TestFillable`, `TestTiers`, `TestRewrite`, `TestHooks`, `TestManifest`, `TestDefinitionItemJSON`, `TestURLCap`, `TestBackendURL`, `TestImportWinter`). Menu (`TestAllowedURL`, `TestMenuTree`, `TestMenuEntryModel`, `TestMenuEntryBeforeSave`, `TestMenuAPI`, `TestMenuImport`). FAQ (`TestFaqContentDecode`, `TestFaqModel`, `TestFaqSerialize`, `TestFaqAPI`, `TestFaqImport`). +- **Host and layers:** `TestCompanionsHost` now checks the CORS preflight and simple requests on `/_menu/api` and `/_faq/api`, the sitemap Cache-Control, the absence of CORS headers on the sitemap, and the XSL Content-Type. The layers get link-rule tests and composable-base tests (server base with the forwarded visitor, public base in the browser). +- **17-VALIDATION.md:** filled and validated; `nyquist_compliant: true`, `wave_0_complete: true`. + +## Task Commits + +**summercms.go** (local, not pushed) +1. **Task 1 (tracer): gate and two contract tests** - `490a4e3` (test) +2. **Task 2: framework and admin SPA tests** - `91593da` (test) +3. **Task 2: singleton race defect** - `6312929` (fix, production code) +4. **Task 3: gate lists the plugin and host suites** - `40b89ad` (test) +5. **Task 4: gate requires every layer test** - `ed67e41` (test) +6. **Task 4: validation map** - `2274cd6` (docs) +7. **Deferred items** - `31adf71` (docs) + +**Sibling repositories** (all local, nothing pushed, no tags) + +| Repository | Unpushed range | Commits | +|---|---|---| +| sm-journal-plugin | `06d0c9b..3b252f2` | `4e2aab6` test(17-06): read -short through testing.Short in the Postgres harnesses; `3b252f2` test(17-06): Journal menu item type unit tests | +| sm-sitemap-plugin | `5a260db..4e55939` | `4e55939` test(17-06): sitemap parity unit tests | +| sm-menu-plugin | `33a65ad..43267de` | `43267de` test(17-06): menu plugin unit tests | +| sm-faq-plugin | `1585f06..919cf06` | `919cf06` test(17-06): FAQ plugin unit tests | +| sm-grzybyfunkcjonalne-app | `6441b17..ceea5be` | `ceea5be` test(17-06): companions host CORS and sitemap header checks | +| vue-menu-layer | `d980f4c..2deac88` | `2deac88` test(17-06): menu layer link and client tests | +| vue-faq-layer | `2d62924..321bc90` | `321bc90` test(17-06): FAQ layer client tests | +| summercms.go | `374834b..31adf71` | the seven commits above (plus two concurrent planning commits from another session) | + +The plugin and layer commits change only tests, so they need no release. The host gitlinks still point at the pushed plugin heads; the plugin tests run from the sibling checkouts. + +**Plan metadata:** the docs commit that adds this SUMMARY, then the STATE/ROADMAP commit. + +## fix(17-06) commits: needs follow-up release + +| Commit | Repository | What | Release route | +|---|---|---|---| +| `6312929` fix(17-06): singleton route retries a lost first-or-create race | summercms.go (`modules/cabana/singleton.go`, cabana README, admin-controllers doc) | Two concurrent first `GET .../singleton` requests: the loser's insert hit the unique index and answered 500. A unique violation from `SingletonRecord` now reruns the transaction once and finds the winner's record. RED proven (the strict 6-request test failed 3 of 6 without the fix), GREEN with it. | **Needs follow-up release:** a framework patch tag after v0.1.23 (v0.1.24), then a require bump in the plugins (the sitemap definition is a singleton controller) and the host. Neither the v0.1.23 tag (plan 02) nor the UAT deploy (plan 05) contains it. | + +`4e2aab6` in sm-journal-plugin is a test-harness change (test files only) and needs no release. + +## Full gate output + +`bash scripts/check-phase17.sh --all` on 2026-10-12, Docker available (repository root shown as ``): + +``` +phase17 self-test passed +PASS self-test +go: 6 top-level tests passed in (-short -count=1 -run ^(TestSignpostSmoke|TestRegistryDuplicateKey|TestRegistryActivate|TestRegistryHostOverrideUnknown|TestRegistryPatternLayers|TestReferenceOptions)$ ./modules/signpost) +go: 5 top-level tests passed in (-short -count=1 -run ^(TestSeaglassSmoke|TestSanitizeByteStable|TestSanitizeReport|TestSanitizeBypassCorpus|TestCheck)$ ./modules/seaglass) +go: 2 top-level tests passed in (-short -count=1 -run ^(TestOptionDepthOmitEmpty|TestMenuItemTypeContracts)$ ./modules/pact) +go: 1 top-level tests passed in (-short -count=1 -run ^(TestActivateMenuItemTypesOrder)$ ./modules/party) +go: 8 top-level tests passed in (-short -count=1 -run ^(TestRepeaterCompile|TestRepeaterDecode|TestDependentDropdownCompile|TestDependentValues|TestRichEditorCompile|TestReorderConfigCompile|TestSingletonCompile|TestPhase10ConformanceInventory)$ ./modules/cabana) +phase17 go passed +PASS go +vet: /../sm-journal-plugin +vet: /../sm-sitemap-plugin +vet: /../sm-menu-plugin +vet: /../sm-faq-plugin +vet: /../sm-grzybyfunkcjonalne-app +go: 1 top-level tests passed in /../sm-journal-plugin (-short -count=1 -run ^(TestMenuItemTypesPatterns)$ ./classes) +go: 9 top-level tests passed in /../sm-sitemap-plugin (-short -count=1 -run ^(TestSitemapGolden|TestSitemapTiersConfig|TestURLTo|TestXMLEscaping|TestTiers|TestRewrite|TestHooks|TestManifest|TestURLCap)$ ./classes) +go: 2 top-level tests passed in /../sm-sitemap-plugin (-short -count=1 -run ^(TestFillable|TestDefinitionItemJSON)$ ./models) +go: 1 top-level tests passed in /../sm-sitemap-plugin (-short -count=1 -run ^(TestBackendURL)$ .) +go: 2 top-level tests passed in /../sm-menu-plugin (-short -count=1 -run ^(TestAllowedURL|TestMenuTree)$ ./classes) +go: 1 top-level tests passed in /../sm-menu-plugin (-short -count=1 -run ^(TestMenuEntryModel)$ ./models) +go: 1 top-level tests passed in /../sm-faq-plugin (-short -count=1 -run ^(TestFaqContentDecode)$ ./console) +go: 1 top-level tests passed in /../sm-faq-plugin (-short -count=1 -run ^(TestFaqModel)$ ./models) +go: 1 top-level tests passed in /../sm-faq-plugin (-short -count=1 -run ^(TestFaqSerialize)$ ./controllers/api) +go: 4 top-level tests passed in /../sm-grzybyfunkcjonalne-app (-short -count=1 -run ^(TestBootUserTranslateJournal|TestBackendURIGfadmin|TestGrzybyCommandsRegistered|TestCORS)$ .) +phase17 plugins passed +PASS plugins +vitest: 1325 passed in 88 files +phase17 admin passed +PASS admin +tap: # tests 24 # pass 24 # fail 0 in /../vue-menu-layer +tap: # tests 11 # pass 11 # fail 0 in /../vue-faq-layer +phase17 layers passed +PASS layers +go: 1 top-level tests passed in (-count=1 -run ^(TestDocsTree)$ ./cmd/summer) +docs:build: no problems found +✨ openapi-typescript 7.13.0 +🚀 /tmp/tmp.dUjURBop1p/admin.json → /tmp/tmp.dUjURBop1p/schema.d.ts [323.7ms] +npm notice run typecheck +npm notice run vue-tsc --noEmit + +(!) Some chunks are larger than 500 kB after minification. Consider: +- Using dynamic import() to code-split the application +- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks +- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit. +check-admin-dist: modules/boardwalk/dist matches a fresh build +phase17 docs passed +PASS docs +go: 11 top-level tests passed in (-timeout 20m -count=1 -run ^(TestCompanionsDependentSmoke|TestCompanionsRichEditorSmoke|TestCompanionsRepeaterSmoke|TestCompanionsReorderSmoke|TestCompanionsSingletonSmoke|TestRepeaterSave|TestDependentRecheck|TestRichEditor|TestReorderPostgres|TestSingleton|TestPhase10OpenAPIConformance)$ ./modules/cabana) +go: 2 top-level tests passed in /../sm-journal-plugin (-timeout 20m -count=1 -run ^(TestMenuItemTypesSmoke|TestMenuItemTypes)$ .) +go: 4 top-level tests passed in /../sm-sitemap-plugin (-timeout 20m -count=1 -run ^(TestSitemapTracer|TestSitemapImportSmoke|TestSitemapAdminSmoke|TestImportWinter)$ .) +go: 6 top-level tests passed in /../sm-menu-plugin (-timeout 20m -count=1 -run ^(TestMenuAdminSmoke|TestMenuImportSmoke|TestMenuAPISmoke|TestMenuEntryBeforeSave|TestMenuAPI|TestMenuImport)$ .) +go: 5 top-level tests passed in /../sm-faq-plugin (-timeout 20m -count=1 -run ^(TestFaqAdminSmoke|TestFaqImportSmoke|TestFaqAPISmoke|TestFaqAPI|TestFaqImport)$ .) +go: 1 top-level tests passed in /../sm-grzybyfunkcjonalne-app (-timeout 20m -count=1 -run ^(TestCompanionsHost)$ .) +phase17 postgres passed +PASS postgres +Phase 17 gate passed +``` + +**Postgres suites: RUN** on the local Docker daemon (testcontainers), every one green. The bundle-size note is the Vite warning already logged in 17-02 deferred item 5. + +## Decisions Made + +See `key-decisions` in the front matter. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Singleton route answered 500 to the loser of a first-open race** +- **Found during:** Task 2 (TestSingleton) +- **Issue:** Concurrent first `GET .../singleton` requests both missed the record. The second insert hit the unique index, and `lifecycleFailure` turned that into a 500 (`duplicate key value violates unique constraint`). +- **Fix:** `singletonRoute` maps a Postgres unique violation (23505) from `SingletonRecord` to an internal race error and reruns the transaction once. A second race is still a 500. The cabana README and `docs/backend/admin-controllers.md` describe the retry. +- **Files modified:** modules/cabana/singleton.go, modules/cabana/singleton_test.go, modules/cabana/README.md, docs/backend/admin-controllers.md +- **Verification:** 6 concurrent first requests all answer 200 with the same id, 10 runs in a row. Without the fix the same test failed (3 of 6 answered 500). +- **Committed in:** 6312929. Its test was committed with the fix, after the Task 2 test commit. + +**2. [Rule 3 - Blocking] Journal's TestMains started Postgres under -short** +- **Found during:** Task 3 (the plugins stage runs Journal with -short) +- **Issue:** 17-03 deferred item 1. Three harnesses scanned `os.Args` for `-test.short`, which go test never passes. +- **Fix:** `testShort()` now calls `flag.Parse()` and `testing.Short()`. +- **Files modified:** ../sm-journal-plugin/admin_harness_test.go, models/post_translatable_smoke_test.go, updates/postgres_test.go +- **Committed in:** 4e2aab6 (test files only) + +**3. [Rule 3 - Blocking] Test files placed by package** +- **Issue:** Go needs a package's internal access and its Postgres harness in the same package, so some tests sit outside the plan's file list: + - `TestMenuEntryBeforeSave` lives in `../sm-menu-plugin/menu_api_test.go`, next to the root package's Postgres harness. `models/menu_entry_test.go` holds the Docker-free `TestMenuEntryModel`. + - `TestFaqContentDecode` lives in `../sm-faq-plugin/console/import_winter_test.go`, because the decoder is internal to console. The plan named no file for it. + - `TestFaqSerialize` is new, in `controllers/api/faqs_test.go`. + - `TestBackendURL` covers the trusted forwarded proto. It is new, in `../sm-sitemap-plugin/routes_test.go`, because `backendURL` is internal to the root package. + - `TestDependentValues` and `TestMenuItemTypeContracts` are new Docker-free tests. Every one of them is in a gate list. + +**4. [Plan discrepancy, not changed] Protocol-relative menu URLs** +- **Found during:** Task 4 +- **Issue:** The plan's behaviour list says a protocol-relative URL to a foreign host renders as text without an href. Both the shipped layer and the server's `AllowedURL` (D-16) treat `//host` like any absolute URL: an anchor that is never a site path and never active. The existing layer test asserts that. +- **Resolution:** The new tests pin the shipped rule, and no test was weakened. The question is logged as 17-06 deferred item 2; changing the rule means changing the server and the layer together. + +**5. [Formatting] gofmt realigned existing lines in modules/party/registry_test.go** +- `gofmt -w` on the file, which was not gofmt-clean before, realigned a `mailPlugin` method block and a fixture map. The changes are whitespace only and went into 91593da. + +--- + +**Total deviations:** 3 auto-fixed (1 bug, 2 blocking), 1 plan discrepancy recorded, 1 formatting note. **Impact:** one production fix that needs a framework release; the rest only touch tests or how they are placed. Existing assertions all stay. + +## Issues Encountered + +- `wrapper.props('modelValue')` and the reorder fixtures needed casts for vue-tsc: the OpenAPI document types `AdminReorderNode.parent_id` as a number although null is on the wire (17-02 deferred item 3). +- The RichEditorField chunk-failure spec resets the module graph, so it loads the phrase bundle into the fresh i18n module. +- The bash heredoc turned `<` escapes into literal characters, so the FAQ serializer test compares JSON encoded without HTML escaping. + +## Known Stubs + +None. + +## User Setup Required + +None. + +## Next Phase Readiness + +- Phase 17 has all 6 plans complete and one green gate: `bash scripts/check-phase17.sh --all`, with `PHASE17_REQUIRE_POSTGRES=1` for a strict run. +- The `/msd-verify-work` human checks are still open from plan 05: header menu interaction, FAQ opening, the admin screens, and the XSL view. +- Release follow-ups: framework patch tag v0.1.24 for 6312929, then require bumps in the plugins and host. Pushing the test-only commits in the seven sibling repositories and summercms.go is waiting for human approval. + +--- +*Phase: 17-blog-companions* +*Completed: 2026-10-12* + +## Self-Check: PASSED + +All 30 created test files and the gate script exist. Every commit hash in this summary resolves in its repository: summercms.go 490a4e3 91593da 6312929 40b89ad ed67e41 2274cd6 31adf71; sm-journal-plugin 4e2aab6 3b252f2; sm-sitemap-plugin 4e55939; sm-menu-plugin 43267de; sm-faq-plugin 919cf06; host ceea5be; vue-menu-layer 2deac88; vue-faq-layer 321bc90. The acceptance criteria of all four tasks were re-run and pass, as do the plan's verify commands: the gate's `--self-test` and every stage, the six cabana Postgres passes, the ten sitemap parity passes, both layer suites, the full gate ending in "Phase 17 gate passed", and `nyquist_compliant: true`. `commits` (9) is measured from the plan ledger (`git rev-list --count 374834b..31adf71`) and includes two planning commits from a concurrent session.