Files
summercms/.planning/phases/14.2.1-translate-plugin/14.2.1-VERIFICATION.md
2026-10-06 16:40:06 +02:00

303 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 14.2.1-translate-plugin
verified: 2026-10-06T14:40:00Z
status: human_needed
score: 16/16 must-haves verified
covered_files:
- .planning/phases/14.2.1-translate-plugin/14.2.1-01-PLAN.md
- .planning/phases/14.2.1-translate-plugin/14.2.1-01-SUMMARY.md
- .planning/phases/14.2.1-translate-plugin/14.2.1-02-PLAN.md
- .planning/phases/14.2.1-translate-plugin/14.2.1-02-SUMMARY.md
- .planning/phases/14.2.1-translate-plugin/14.2.1-03-PLAN.md
- .planning/phases/14.2.1-translate-plugin/14.2.1-03-SUMMARY.md
- .planning/phases/14.2.1-translate-plugin/14.2.1-04-PLAN.md
- .planning/phases/14.2.1-translate-plugin/14.2.1-04-SUMMARY.md
- .planning/phases/14.2.1-translate-plugin/14.2.1-05-PLAN.md
- .planning/phases/14.2.1-translate-plugin/14.2.1-05-SUMMARY.md
- .planning/phases/14.2.1-translate-plugin/14.2.1-06-PLAN.md
- .planning/phases/14.2.1-translate-plugin/14.2.1-06-SUMMARY.md
- admin/src/api/schema.d.ts
- admin/src/components/form/fields/MLMarkdownField.vue
- admin/src/components/form/fields/MLTextField.vue
- admin/src/components/form/formContext.ts
- admin/src/components/form/formState.ts
- admin/src/components/form/mlLocale.ts
- admin/src/components/form/registry.ts
- admin/src/components/relation/RelationChildModal.vue
- admin/src/views/FormView.vue
- admin/tests/form/MLFields.test.ts
- admin/tests/form/formState.test.ts
- docs/backend/admin-controllers.md
- docs/backend/forms.md
- modules/cabana/README.md
- modules/cabana/crud.go
- modules/cabana/field_markdown.go
- modules/cabana/field_ml.go
- modules/cabana/form_schema.go
- modules/cabana/http.go
- modules/cabana/ml_smoke_test.go
- modules/cabana/ml_test.go
- modules/cabana/openapi_conformance_test.go
- modules/cabana/relation.go
- modules/cabana/relation_child.go
- modules/cabana/relation_child_ml_test.go
- modules/cabana/schema_types.go
- modules/surf/locale_from_principal.go
- modules/surf/locale_resolver.go
- modules/surf/router.go
- scripts/check-phase14.2.1.sh
covered_digest: "v2:sha256:e8c9c72cd5252f44d6193fa658ea95663432905c4a061fd228887f46a07c4402"
covered_files_note: "verification.fingerprint covers only paths under the summercms.go root. Sibling implementation read this pass: sm-translate-plugin b3e0e26 (plugin.go, Locale/Attribute/Index models, five golem15_translate_* migrations, Translator, Translatable, TranslatedExact, Locales admin, AdminWriter, integration/admin/migration tests); sm-grzybyfunkcjonalne-app 1860ad6 (summer.yaml, plugins.gen.go, go.work, gitlinks, boot_test.go). Host translate gitlink is b3e0e26. PHP pin /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate HEAD 725d547ec839f02b5fdc0f0a6faaed601a414d50, clean tree, not edited."
behavior_unverified: 0
overrides_applied: 0
re_verification:
previous_status: gaps_found
previous_score: 14/16
gaps_closed:
- "Cabana markdown/mltext/mlmarkdown compose; nested locale writes are not dropped"
- "The SPA exposes one locale selector per ML field, switches all ML controls together, supports copy-from-locale, and sends every locale as Record<string,string>"
gaps_remaining: []
regressions: []
advisory: []
human_verification:
- test: "Boot the proof host and open Locales in the admin SPA"
expected: "Sign in as an administrator holding golem15.translate.manage_locales; English then Polski appear in sort_order; name/enabled can be edited; a user without the permission cannot open the controller"
why_human: "Needs a running host, admin login, and visual confirmation of the Locales screen. Harvested from 14.2.1-04-PLAN.md and 14.2.1-06-PLAN.md human-checks. Automated tests cover HTTP 403/list order, not the SPA chrome."
- test: "With the proof host and admin SPA, open a Journal-shaped form with mltext when one is available"
expected: "Create lists en and pl (not a single English box); one locale selector per ML field; switching one selector switches the others; save/reload still shows both locales"
why_human: "Harvested from 14.2.1-06-PLAN.md. Vitest proves seed/selector/broadcast/copy-from against provided ['en','pl'], not a running SPA against schema meta from the live writer. The proof host has no production mltext YAML (only test-harness fields); do not invent a pass. Relation-child ML UAT can wait for Phase 15 if this host has no child ML form."
---
# Phase 14.2.1: Translate plugin Verification Report
**Phase Goal:** Golem15.Translate is ported to Go as `sm-translate-plugin` so Journal (Phase 15) can keep translatable fields. Lean core only: Locale model and Locales admin, Translatable API, cabana `mltext`/`mlmarkdown`, and PHP Translator locale resolution (URL prefix / session / cookie / default).
**Verified:** 2026-10-06T14:40:00Z
**Status:** human_needed
**Re-verification:** Yes — after gap closure (plans 05 CR-01 and 06 WR-02)
**MVP note:** ROADMAP does not mark this phase `mode: mvp`. User Flow Coverage is derived from ROADMAP success criteria and CONTEXT D-17.
Prior report (2026-10-06T12:48:00Z) was `gaps_found` 14/16. CR-01 and WR-02 are closed in source. All 16 must-have truths are now VERIFIED. Status is `human_needed` because harvested SPA UAT remains; it is not a pass.
## User Flow Coverage
| Step | Expected | Evidence | Status |
|------|----------|----------|--------|
| Proof host boots user+translate | `party.Activate` loads `golem15.user` then `golem15.translate`; Locales controller and `manage_locales` are registered; submodules are gitlinks | `sm-grzybyfunkcjonalne-app/plugins.gen.go`, `summer.yaml`, gitlink `b3e0e26`; `TestBootUserTranslate` exists | ✓ |
| Operators manage locales | YAML CRUD gated by exactly `golem15.translate.manage_locales`; seed en then pl; no Messages admin | `controllers/locales.go` `RequiredPermissions`; production Permissions() is only `manage_locales` | ✓ API; SPA list/edit is human |
| Fixture saves and reads en+pl through Translatable / WithLocale | Default on host columns; pl in `golem15_translate_attributes`; missing pl falls back | `classes/translatable.go`; `Translated` still does D-11; `TranslatedExact` does not | ✓ API |
| Journal-shaped admin form is one screen with a locale switch | Enabled locales on create/load/save; nested `{en,pl}` posted and rehydrated | `FormMeta.EnabledLocales`; `hydrateMLRecord` + `TranslatedExact`; SPA `FORM_ENABLED_LOCALES`; `TestMLHydration` / Vitest create+GET; live SPA UAT still human | ✓ code; ⏳ SPA UAT |
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
| --- | --- | --- | --- |
| 1 | Plugin repo exists with `golem15_translate_*` schema (locales, attributes, indexes, empty messages table), Locale model, en/pl seed, and context-safe Translator | ✓ VERIFIED | Module `git.golem15.com/golem15/sm-translate-plugin` HEAD `b3e0e26`, ID `golem15.translate`. Five gormigrate files create `golem15_translate_locales\|attributes\|indexes\|messages`. `Locale.TableName()` is `golem15_translate_locales`. `--php` / `--layout` / `--forbidden` PASS this pass. `Translator.Locale` reads `towel.Locale(ctx)` only. |
| 2 | Translatable API (`Translatable()`, `WithLocale`, get/set, default-locale fallback) and permissioned Locales admin work; a fixture model saves and reads en+pl | ✓ VERIFIED | `classes.Translatable` plus `Translated` / `SetTranslated` / `WithLocale`. Locales permission is exactly `golem15.translate.manage_locales`. `Translated` still applies D-11; admin hydration uses `TranslatedExact`. |
| 3 | Cabana `markdown` / `mltext` / `mlmarkdown` compose | ✓ VERIFIED | `formFieldTypes` includes all three (`form_schema.go:29`). `MLMarkdownField.vue` renders `MarkdownField`. `TestMLFieldTypes` PASS this pass (`go test ./modules/cabana -count=1 -run 'TestMLFieldTypes$'`). Registry tests register the three types. |
| 4 | Nested locale writes are not dropped | ✓ VERIFIED | CR-01/WR-02 closed. Host `CRUDService.save` still `liftMLValues` then `applyMLTranslations` then `hydrateMLRecord` (`crud.go:600`, `:688`, `:709`). `CreateChild`/`UpdateChild` lift on `in.Body` **before** `fillChild` (`relation_child.go:168-172`, `:330-334`) and apply after PK (`:183`, `:345`). `ShowChild` hydrates (`:296`). `TestMLNestedSave` PASS; `TestMLHydration` PASS (create/show `{en,pl}`; english-only `pl: ""`); `TestRelationChildMLNestedSave` PASS (create/update/show + `de` 422 with no insert). SPA create seeds `{[code]: ""}`; `mergeMLValue` keeps siblings on a GET string. |
| 5 | Docs, OpenAPI, TS types and `boardwalk` `dist/` update in the same change | ✓ VERIFIED | `docs/backend/forms.md`, `docs/backend/admin-controllers.md`, `modules/cabana/README.md` document `EnabledLocales`, `TranslatedExact`, `hydrateMLRecord`, and child lift. `schema.d.ts` has `enabledLocales?: string[]`. `boardwalk/dist/assets/index-B08bxLq5.js` contains `mltext`/`mlmarkdown`. Gate `--forbidden` found no consuming-application names. |
| 6 | Proof host `sm-grzybyfunkcjonalne-app` boots with user+translate; unit tests are the last plan | ✓ VERIFIED | Host HEAD `1860ad6`. `summer.yaml` lists user then translate; gitlink `plugins/golem15/translate` → `b3e0e26`. Plan 06 is the dedicated named-test/gate close of the gap round; plan 04 remains the original last-of-phase test plan. `--layout` PASS this pass. |
| 7 | D-01/D-02/D-03/D-04: implementation is derived only from the read-only PHP tree at SHA `725d547ec839f02b5fdc0f0a6faaed601a414d50`; no PHP file changes | ✓ VERIFIED | `bash scripts/check-phase14.2.1.sh --php` this pass: `phase14.2.1 php passed (725d547…)`. Porcelain empty. |
| 8 | D-07/D-12: resolver selects URL prefix, valid user preferred_locale, remembered `golem15.translate.locale`, cookie-gated Accept-Language, then default; surf stores only a validated code on context | ✓ VERIFIED | `Translator.Resolve` order still URL → preferred → remembered → AL (unless manual flag) → default (`translator.go:75-103`). Surf `router.go:712-714` writes `towel.WithLocale` from `resolver.Resolve`. |
| 9 | KERN-07: no package-level mutable current locale; `Translator.Locale(ctx)` and `towel.WithLocale` carry request state | ✓ VERIFIED | No package `currentLocale`. `Translator.Locale` reads context then `defaultCode`. `Rewrite` clones the request. |
| 10 | D-15/D-16: module is `git.golem15.com/golem15/sm-translate-plugin`, package `translate`, plugin ID `golem15.translate`, sibling checkout | ✓ VERIFIED | `Plugin.ID()`; remote `git@git.golem15.com:golem15/sm-translate-plugin.git`; path `/media/nvme/dev/golem15/summercms.io/summercms/sm-translate-plugin`. Host submodule `plugins/golem15/translate` mode `160000`. |
| 11 | D-10: default-locale values remain in host columns; each non-default locale is one JSON object row in `golem15_translate_attributes`; indexed values are mirrored to `golem15_translate_indexes` | ✓ VERIFIED | `SetTranslated` still writes default via host `Update`; non-default upserts one attribute JSON object. `hydrateMLRecord` does not change that write path. |
| 12 | D-05: Locales is ordinary cabana CRUD, permission-gated by exactly `golem15.translate.manage_locales`; no Messages controller, permission, or navigation | ✓ VERIFIED | `RequiredPermissions` returns only `PermissionManageLocales`. `Permissions()` is only `manage_locales`. `--forbidden` PASS (no production `manage_messages`). |
| 13 | Locale form writes only code/name/is_enabled; `is_default` and `sort_order` cannot be mass-assigned; permissioned make-default preserves disabled/default lifecycle guards | ✓ VERIFIED | `Fillable` is `code,name,is_enabled`. WR-01 concurrent two-default race remains untested (advisory, not a gap). |
| 14 | The SPA exposes one locale selector per ML field, switches all ML controls together, supports copy-from-locale, and sends every locale as `Record<string,string>` | ✓ VERIFIED | CR-01 closed. `MLTextField`/`MLMarkdownField` inject `FORM_ENABLED_LOCALES` and list those codes (`locales` computed is no longer `Object.keys` / `['en']`). `FormView` provides `schemaEnabledLocales(schema.meta)`; `RelationChildModal` re-provides the parent inject. Create `initialValues(fields, enabledLocales)` seeds `{[code]: ""}`. `adopt` uses `mergeMLValue`. `editablePayload` sends `localeRecord`. Vitest this pass: `npx vitest run tests/form/MLFields.test.ts tests/form/formState.test.ts` — 42 passed, including create seed listing en+pl, GET-string merge keeping `pl`, one selector per field, broadcast switch, copy-from, empty-pl payload. Live SPA UAT is still human (below). |
| 15 | Markdown preview uses goldmark without unsafe HTML and cannot execute translated raw HTML/script/event-handler/javascript content | ✓ VERIFIED | `RenderMarkdown` uses `goldmark.New()`; no `html.WithUnsafe`. SPA preview is text interpolation, no `v-html=`. |
| 16 | Nested locale maps are lifted before `ProjectWritableFields` drops maps; default locale fills the host field and non-default locales reach the translate writer inside the host save transaction | ✓ VERIFIED | Host path unchanged (`crud.go:600` then `:688` inside `s.transaction`). Child path now mirrors it on `tx` (`relation_child.go`). `TestMLNestedSave` and `TestRelationChildMLNestedSave` both assert unlifted maps empty at projection, English on host, pl via writer, `de` rejected. |
**Score:** 16/16 truths verified (0 present, behavior-unverified)
### Advisory (New Scope, Unevidenced)
None. WR-01 (makeDefault race), WR-03 (enabled `backend` prefix), and IN-02/IN-03/IN-04 were out of the gap-closure contract, were not in the prior `gaps:` list, and were not re-opened. Evidence gate: not blocking.
### Required Artifacts
gsd-tools `verify.artifacts` still does not emit per-file JSON for sibling `../sm-translate-plugin/...` paths. Manual three-level check:
| Artifact | Expected | Status | Details |
| -------- | -------- | ------ | ------- |
| `../sm-translate-plugin/plugin.go` | Compiled plugin, Resolver + TranslationWriter publish | ✓ EXISTS + SUBSTANTIVE + WIRED | Boot publishes `surf.LocaleResolver` and `cabana.TranslationWriter`. |
| `../sm-translate-plugin/updates/202610060001_create_golem15_translate_locales.go` | Squashed Locale schema | ✓ EXISTS + SUBSTANTIVE | `golem15_translate_locales`. |
| `../sm-translate-plugin/updates/202610060004_create_golem15_translate_messages.go` | Empty compatibility Messages table | ✓ EXISTS + SUBSTANTIVE | Created; no admin. |
| `../sm-translate-plugin/classes/translator.go` | Context-only Translator | ✓ EXISTS + SUBSTANTIVE + WIRED | Published from Boot; surf stores result on context. |
| `../sm-translate-plugin/classes/translatable.go` | Translatable contracts | ✓ EXISTS + SUBSTANTIVE + WIRED | `Translated` (D-11) and `TranslatedExact` (no fallback). |
| `../sm-translate-plugin/classes/admin_writer.go` | Plugin adapter including TranslatedExact | ✓ EXISTS + SUBSTANTIVE + WIRED | `AdminWriter.TranslatedExact` delegates to `classes.TranslatedExact`; `var _ cabana.TranslationWriter`. |
| `../sm-translate-plugin/controllers/locales.go` | Permissioned Locales admin | ✓ EXISTS + SUBSTANTIVE + WIRED | `RequiredPermissions`; registered via `admin.go`. |
| `modules/surf/locale_resolver.go` | Optional resolver contract | ✓ EXISTS + SUBSTANTIVE + WIRED | Used in `router.go`. |
| `modules/cabana/schema_types.go` | `FormMeta.EnabledLocales` | ✓ EXISTS + SUBSTANTIVE + WIRED | `json:"enabledLocales,omitempty"`. |
| `modules/cabana/field_ml.go` | Lift + hydrate + TranslationWriter | ✓ EXISTS + SUBSTANTIVE + WIRED | `hydrateMLRecord` writes `""` when `TranslatedExact` `ok` is false. Wired from CRUD Show/save and relation-child create/update/show. Not used from list projection. |
| `modules/cabana/relation.go` | `RelationService.writer` | ✓ EXISTS + SUBSTANTIVE + WIRED | Same unexported Lookup as `CRUDService`. |
| `modules/cabana/relation_child.go` | lift before fillChild, apply after PK, hydrate | ✓ EXISTS + SUBSTANTIVE + WIRED | WR-02 closed. |
| `modules/cabana/relation_child_ml_test.go` | Named child nested-ML proof | ✓ EXISTS + SUBSTANTIVE | `TestRelationChildMLNestedSave` PASS this pass. |
| `admin/src/components/form/formContext.ts` | `FORM_ENABLED_LOCALES` | ✓ EXISTS + SUBSTANTIVE + WIRED | Injected by `FormView`; re-provided by `RelationChildModal`. |
| `admin/src/components/form/fields/MLMarkdownField.vue` | Locale-aware markdown editor | ✓ EXISTS + SUBSTANTIVE + WIRED | Options from schema inject, not value keys. |
| `admin/src/components/form/fields/MLTextField.vue` | Locale-aware text editor | ✓ EXISTS + SUBSTANTIVE + WIRED | Same. |
| `admin/src/views/FormView.vue` | adopt merges hydrated maps | ✓ EXISTS + SUBSTANTIVE + WIRED | `mergeMLValue`; create seeds via `initialValues`. |
| `docs/backend/forms.md` | Field-type docs | ✓ EXISTS + SUBSTANTIVE | Documents EnabledLocales, exact hydration, child lift. |
| `../sm-grzybyfunkcjonalne-app/summer.yaml` | user+translate plugin list | ✓ EXISTS + SUBSTANTIVE + WIRED | Generates `plugins.gen.go`. |
| `scripts/check-phase14.2.1.sh` | Fail-closed phase gate | ✓ EXISTS + SUBSTANTIVE | `FRAMEWORK_REQUIRE` includes `TestMLHydration` and `TestRelationChildMLNestedSave`. Echoes `Phase 14.2.1 gate passed`. Cheap stages PASS this pass. |
| `.planning/phases/14.2.1-translate-plugin/14.2.1-SECURITY-REVIEW.md` | ASVS L1 threat evidence | ✓ EXISTS + SUBSTANTIVE | T-14.2.1-19/22/23 mapped to named tests. |
**Artifacts:** fully verified at exists + substantive + wired. Prior hollow SPA locale options are gone.
### Key Link Verification
| From | To | Via | Status | Details |
| ---- | --- | --- | ------ | ------- |
| `plugin.go` Boot | `modules/surf/locale_resolver.go` | `Publish[surf.LocaleResolver]` | ✓ WIRED | Translator implements the interface. |
| `modules/surf/router.go` | `modules/towel` | `towel.WithLocale(resolver.Resolve(...))` | ✓ WIRED | Lines 712-714. |
| `translator.go` | `models/locale.go` | `is_enabled` query in `enabledCodes` | ✓ WIRED | Candidates must match enabled rows. |
| `translatable.go` | `golem15_translate_attributes` | `SetTranslated` upsert JSON | ✓ WIRED | `attribute_data`. |
| `translatable.go` | `golem15_translate_indexes` | indexed upsert | ✓ WIRED | `TranslatableIndexes`. |
| `controllers/locales.go` | `models/locale.go` | cabana CRUD + hooks | ✓ WIRED | `RequiredPermissions`. |
| `modules/cabana/http.go` | `modules/cabana/schema_types.go` | `enabledContentLocales` → `FormMeta.EnabledLocales` | ✓ WIRED | `formSchema` copies codes onto `view.Meta` and envelope; `relationSchema` envelope meta; list schemas do not call the helper. |
| `modules/cabana/crud.go` | `modules/cabana/field_ml.go` | `liftMLValues` / `hydrateMLRecord` | ✓ WIRED | ShowRecord and save. |
| `modules/cabana/http.go` | `modules/cabana/relation.go` | `relations()` `Lookup[TranslationWriter]` | ✓ WIRED | Matches `crud()`. |
| `modules/cabana/relation_child.go` | `modules/cabana/field_ml.go` | `liftMLValues` then `applyMLTranslations` then `hydrateMLRecord` | ✓ WIRED | WR-02. |
| `classes/admin_writer.go` | `classes/translatable.go` | `TranslatedExact` / `SetTranslated` | ✓ WIRED | Exact path never calls `Translated` (D-11). |
| `admin/src/views/FormView.vue` | ML field components | `FORM_ENABLED_LOCALES` | ✓ WIRED | Inject drives selector options. |
| `FormView.adopt` / `RelationChildModal.adopt` | nested ML maps | `mergeMLValue` | ✓ WIRED | Map overlays seed; host string becomes first enabled code and keeps siblings. |
| `summer.yaml` | `plugins.gen.go` | `summer build` blank imports | ✓ WIRED | Both submodule modules. |
**Wiring:** 14/14 connections verified (prior 5 NOT WIRED links are now WIRED).
### Data-Flow Trace (Level 4)
| Artifact | Data Variable | Source | Produces Real Data | Status |
| -------- | ------------- | ------ | ------------------ | ------ |
| Locales list/form | locale rows | GORM `golem15_translate_locales` via cabana CRUD | Yes | ✓ FLOWING |
| Translator.Resolve | request locale | enabled Locale rows + URL/cookie/AL | Yes, validated codes | ✓ FLOWING |
| `Translated` / `SetTranslated` | field text | host column or `attribute_data` JSON | Yes | ✓ FLOWING |
| Cabana ML save (HTTP) | nested locale map | request body → lift → host scalar + writer | Yes | ✓ FLOWING |
| Cabana GET/Show ML field | `data[field]` | `hydrateMLRecord` after `projectFullRecord`: default from host column, others from `TranslatedExact` | Locale map; missing pl is `""` | ✓ FLOWING |
| Relation-child create/update/show | child ML field | same lift/apply/hydrate on `tx` | Yes (`TestRelationChildMLNestedSave`) | ✓ FLOWING |
| Form schema meta | `enabledLocales` | `TranslationWriter.EnabledLocales` | Enabled codes when writer published | ✓ FLOWING |
| SPA ML locale `<select>` | `locales` | `FORM_ENABLED_LOCALES` from schema meta | Schema codes, not value keys | ✓ FLOWING |
| SPA create ML value | `initialValues` | `{[code]: ""}` for every enabled locale | Seeded from schema | ✓ FLOWING |
| Markdown preview | preview pane | interpolated source text | Real source; not `RenderMarkdown` HTML | ✓ FLOWING (text) |
### Behavioral Spot-Checks
| Behavior | Command | Result | Status |
| -------- | ------- | ------ | ------ |
| PHP pin SHA + clean tree | `bash scripts/check-phase14.2.1.sh --php` | `phase14.2.1 php passed (725d547…)` | ✓ PASS |
| Host/plugin layout | `--layout` | `phase14.2.1 layout passed` | ✓ PASS |
| Forbidden Winter names / AutoMigrate / v-html / app names | `--forbidden` | `phase14.2.1 forbidden passed` | ✓ PASS |
| Gate detector fail-closed | `--self-test` | `phase14.2.1 self-test passed` | ✓ PASS |
| Security review IDs | `--security` | `phase14.2.1 security passed` | ✓ PASS |
| Cabana ML types | `go test ./modules/cabana -count=1 -run 'TestMLFieldTypes$'` | PASS 0.086s | ✓ PASS |
| Nested host save | `go test ./modules/cabana -count=1 -run 'TestMLNestedSave$'` | PASS 4.371s | ✓ PASS |
| Hydration without D-11 | `go test ./modules/cabana -count=1 -run 'TestMLHydration$'` | PASS 4.586s; english-only `pl: ""` | ✓ PASS |
| Relation-child nested ML | `go test ./modules/cabana -count=1 -run 'TestRelationChildMLNestedSave$'` | PASS 4.301s | ✓ PASS |
| SPA ML create/GET/selector | `npx vitest run tests/form/MLFields.test.ts tests/form/formState.test.ts` | 2 files, 42 tests passed | ✓ PASS |
| Full gate `--all` | not re-run (cabana `-race` ~30m) | Script still ends with `Phase 14.2.1 gate passed`; `FRAMEWORK_REQUIRE` includes the two named tests that passed this pass. Plan 06 SUMMARY claimed `--all` printed that line. | ? SKIP |
### Probe Execution
No `scripts/*/tests/probe-*.sh`. The phase gate is `scripts/check-phase14.2.1.sh`. Cheap stages PASS this pass (table above). `--plugin` / `--framework` / `--admin` / `--host` / `--all` were not re-executed here; named tests that the gate requires for CR-01/WR-02 passed in this process.
### Requirements Coverage
No REQUIREMENTS.md IDs map to this phase (`Requirements: TBD` in ROADMAP; grep of REQUIREMENTS.md for `Phase 14.2.1` is empty). CONTEXT D-01..D-17 are the contract. Plans 01–04 declare D-01..D-17; plans 05–06 cite D-06 and D-17 for gap closure. Every ID is accounted for.
| Requirement | Source Plan | Description | Status | Evidence |
| ----------- | ---------- | ----------- | ------ | -------- |
| D-01 | 01, 04 | PHP pin `725d547` | ✓ SATISFIED | `--php` PASS |
| D-02 | 01, 04 | Freeze at that SHA | ✓ SATISFIED | Gate compares HEAD to the pin |
| D-03 | 01, 04 | Read frozen tree at fonoteka path | ✓ SATISFIED | Path exists, SHA matches |
| D-04 | 01, 04 | Do not edit PHP | ✓ SATISFIED | Clean porcelain |
| D-05 | 02, 04 | Lean Locale + Locales admin only | ✓ SATISFIED | No Messages controller/permission/nav |
| D-06 | 03, 04, 05, 06 | `mltext`/`mlmarkdown`; Journal form stays one screen with a locale switch | ✓ SATISFIED (code) | Field types compile; schema `EnabledLocales`; SPA inject; child lift. Live SPA UAT remains human. |
| D-07 | 01, 04 | URL / session / cookie / default, not header-only | ✓ SATISFIED | `Translator.Resolve` order |
| D-08 | 01, 02, 04 | Seed en+pl, not de | ✓ SATISFIED | Seed SQL |
| D-09 | 02, 03, 04 | `Translatable() []string` | ✓ SATISFIED | Interface + fixture models |
| D-10 | 01, 02, 04 | Go tables `golem15_translate_*` | ✓ SATISFIED | Production DDL; `--forbidden` refuses Winter names |
| D-11 | 02, 04 | Missing translation falls back to default | ✓ SATISFIED | `Translated` still falls back. Admin GET uses `TranslatedExact` and does **not** apply D-11 (`hydrateMLRecord` writes `""` when `ok` is false; `TestMLHydration` english-only `pl: ""`). |
| D-12 | 01, 02, 03, 04 | Min API: WithLocale, get/set, Translator current locale | ✓ SATISFIED | Exported funcs + context Locale |
| D-13 | 03, 04 | Host is `sm-grzybyfunkcjonalne-app` with user+translate | ✓ SATISFIED | `plugins.gen.go` |
| D-14 | 03, 04 | Local host checkout path | ✓ SATISFIED | Sibling directory exists |
| D-15 | 01, 03, 04 | Public Gitea remote + module path | ✓ SATISFIED | `git remote -v` |
| D-16 | 01, 03, 04 | Sibling plugin checkout; host submodule + go.work replace | ✓ SATISFIED | gitlink 160000 `b3e0e26` |
| D-17 | 02, 03, 04, 05, 06 | App boots; Locales admin works; fixture saves/reads en+pl through ML fields and WithLocale | ✓ SATISFIED (code) | Boot + API fixture + permissioned Locales HTTP + hydrated admin maps + relation-child nested save. SPA one-screen UAT is human. |
**Coverage:** 17/17 requirements satisfied in code (D-06/D-17 SPA chrome still needs human).
### Decision Coverage
gsd-tools `check.decision-coverage-verify`: `{ honoured: 17, total: 17, not_honored: [] }` — “All trackable CONTEXT.md decisions are honored by shipped artifacts.” Non-blocking substring gate. D-06 is now honored as a working one-screen switch in source (selectors read schema locales), not merely as artifact presence.
### Test Quality Audit
| Test File | Linked Req | Active | Skipped | Circular | Assertion Level | Verdict |
|-----------|-----------|--------|---------|----------|-----------------|---------|
| `updates/migrations_test.go` | D-08/D-10 | yes | skip only without Docker | no | value | OK |
| `classes/translator_resolve_test.go` | D-07 | yes | no | no | behavioral | OK |
| `classes/translatable_test.go` | D-09/D-11/D-12 | yes | no | no | value | OK |
| `locales_admin_test.go` | D-05 | yes | no | no | behavioral | OK |
| `integration_test.go` | D-17 API | yes | no | no | behavioral HTTP | OK |
| `modules/cabana/ml_test.go` | D-06 types / nested lift / hydration | yes | no | no | behavioral | OK — `TestMLHydration` proves missing pl is empty |
| `modules/cabana/relation_child_ml_test.go` | D-06/D-17 child | yes | no | no | behavioral | OK |
| `admin/tests/form/MLFields.test.ts` | D-06 SPA | yes | no | no | behavioral on create seed, GET string, and maps | OK (prior INSUFFICIENT closed) |
| `admin/tests/form/formState.test.ts` | D-06 seed/adopt | yes | no | no | value | OK |
**Disabled tests on requirements:** 0.
**Circular patterns detected:** 0.
**Insufficient assertions:** 0 (create `{}` / GET string coverage now present).
### Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
| ---- | ---- | ------- | -------- | ------ |
| (none in gap-closure files) | — | TBD/FIXME/XXX | — | Debt-marker scan clean |
| `../sm-translate-plugin/controllers/locales.go` | makeDefault | Two unordered Updates | 📋 Advisory | WR-01; out of this closure; sequential guards hold |
| `../sm-translate-plugin/models/locale.go` | code index | Not UNIQUE | 📋 Advisory | WR-03; out of this closure |
**Anti-patterns:** prior CR-01/WR-02 blockers are gone. Remaining notes are advisory, not blockers (evidence gate).
Debt-marker scan on phase-touched production files this pass: no `TBD`/`FIXME`/`XXX`.
### Human Verification Required
Automated CR-01/WR-02 gaps are closed. SPA UAT is not invented as a pass.
### 1. Locales admin in the proof-host SPA
**Test:** Boot `sm-grzybyfunkcjonalne-app`; sign in as an administrator holding `golem15.translate.manage_locales`; open Locales; confirm English then Polski; edit name/enabled; confirm a user without the permission cannot open the controller.
**Expected:** List order en then pl; permitted fields save; forbidden user is 403/hidden.
**Why human:** Needs running host + admin login. Harvested from 14.2.1-04-PLAN.md and 14.2.1-06-PLAN.md.
### 2. Journal-shaped mltext form (when available)
**Test:** With the executor-started proof host and admin SPA, when a Journal-shaped form with mltext is available, confirm one locale selector per ML field listing en and pl on create (not a single English box), that switching one selector switches the others, and that save/reload still shows both locales.
**Expected:** Create lists en and pl; selectors switch together; save/reload keeps both locales.
**Why human:** Harvested from 14.2.1-06-PLAN.md. Vitest mounts with injected `['en','pl']`; it does not boot the SPA against live schema meta. The proof host has **no production mltext YAML** (only `admin_harness_test.go` fixture fields). Do not invent a pass. Relation-child ML UAT can wait for Phase 15 if this host has no child ML form.
## Gaps Summary
No blocking gaps. Prior CR-01 (admin ML locale switch) and WR-02 (relation-child nested ML maps) are closed in source with named tests that passed this pass.
Human verification items remain; overall status is `human_needed`, not `passed`.
## Verification Metadata
**Verification approach:** Goal-backward re-verification (prior must-haves reused; failed items full 3-level; passed items existence + sanity). SUMMARY.md used only as a file-list hint.
**Must-haves source:** Prior VERIFICATION.md (ROADMAP Phase 14.2.1 Success Criteria + 14.2.1-01..04 PLAN frontmatter; 05/06 restated the two failed truths)
**Automated checks:** cheap gate stages 5 passed / 0 failed; `TestMLFieldTypes` PASS; `TestMLNestedSave` PASS; `TestMLHydration` PASS; `TestRelationChildMLNestedSave` PASS; Vitest 42/42 PASS
**Human checks required:** 2 (Locales SPA; Journal-shaped mltext when available — relation-child UAT waits for Phase 15)
**Total verification time:** ~25 min
**CR-01 / WR-02:** independently re-read in source; REVIEW.md (status: clean) not rubber-stamped
**winter_translate_* in Go production DDL:** absent
**Phase gate phrase:** `scripts/check-phase14.2.1.sh` still prints `Phase 14.2.1 gate passed`; `--all` not re-run this pass
---
_Verified: 2026-10-06T14:40:00Z_
_Verifier: the agent (gsd-verifier)_