docs(14.2.1): create phase plan
This commit is contained in:
@@ -830,7 +830,7 @@ Plans:
|
|||||||
Plans:
|
Plans:
|
||||||
- [ ] TBD (run $gsd-plan-phase 14.2 to break down)
|
- [ ] TBD (run $gsd-plan-phase 14.2 to break down)
|
||||||
|
|
||||||
### Phase 14.2.1: Translate plugin (INSERTED) — complete 2026-10-06
|
### Phase 14.2.1: Translate plugin (INSERTED)
|
||||||
|
|
||||||
**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).
|
**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).
|
||||||
**Requirements**: TBD
|
**Requirements**: TBD
|
||||||
@@ -843,7 +843,7 @@ Plans:
|
|||||||
3. Cabana `markdown`/`mltext`/`mlmarkdown` compose; nested locale writes are not dropped; docs, OpenAPI, TS types and `boardwalk` `dist/` update in the same change.
|
3. Cabana `markdown`/`mltext`/`mlmarkdown` compose; nested locale writes are not dropped; docs, OpenAPI, TS types and `boardwalk` `dist/` update in the same change.
|
||||||
4. Proof host `sm-grzybyfunkcjonalne-app` boots with user+translate; unit tests are the last plan.
|
4. Proof host `sm-grzybyfunkcjonalne-app` boots with user+translate; unit tests are the last plan.
|
||||||
|
|
||||||
**Plans:** 4/4 plans executed
|
**Plans:** 4/6 plans executed
|
||||||
|
|
||||||
Plans:
|
Plans:
|
||||||
|
|
||||||
@@ -859,6 +859,16 @@ Plans:
|
|||||||
**Wave 4** *(blocked on Wave 3 completion)*
|
**Wave 4** *(blocked on Wave 3 completion)*
|
||||||
- [x] 14.2.1-04-PLAN.md — Unit/integration tests last, phase gate, security review
|
- [x] 14.2.1-04-PLAN.md — Unit/integration tests last, phase gate, security review
|
||||||
|
|
||||||
|
**Wave 5** *(gap closure, blocked on Wave 4 completion)*
|
||||||
|
- [ ] 14.2.1-05-PLAN.md — Hydrate enabled locales on admin ML forms (CR-01)
|
||||||
|
|
||||||
|
**Wave 6** *(gap closure, blocked on Wave 5 completion)*
|
||||||
|
- [ ] 14.2.1-06-PLAN.md — Lift ML maps on relation-child saves (WR-02); named tests last
|
||||||
|
|
||||||
|
**Cross-cutting constraints:**
|
||||||
|
- Nested locale writes are not dropped on the admin form path and on relation-child saves
|
||||||
|
- The SPA lists every enabled locale, switches all ML controls together, and sends `Record<string,string>`
|
||||||
|
|
||||||
### Phase 15: Journal plugin
|
### Phase 15: Journal plugin
|
||||||
|
|
||||||
**Goal:** The Golem15 Journal plugin is ported to Go as `sm-journal-plugin` and mounts in a host application the same way `sm-user-plugin` does, so a blog can run on SummerCMS without the PHP plugin.
|
**Goal:** The Golem15 Journal plugin is ported to Go as `sm-journal-plugin` and mounts in a host application the same way `sm-user-plugin` does, so a blog can run on SummerCMS without the PHP plugin.
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
gsd_state_version: "1.0"
|
gsd_state_version: "1.0"
|
||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
current_phase: 14.2.1
|
current_phase: 14.2.1
|
||||||
current_phase_name: Translate plugin
|
current_phase_name: translate-plugin
|
||||||
status: verifying
|
status: verifying
|
||||||
stopped_at: Completed 14.2.1-04-PLAN.md
|
stopped_at: Completed 14.2.1-04-PLAN.md
|
||||||
last_updated: "2026-10-06T12:21:36Z"
|
last_updated: "2026-10-06T13:18:47.315Z"
|
||||||
last_activity: 2026-10-06
|
last_activity: 2026-10-06
|
||||||
last_activity_desc: 14.2.1-04 tests, phase gate, and security review
|
last_activity_desc: 14.2.1-04 tests, phase gate, and security review
|
||||||
state_head: ba6e436
|
state_head: 249b4d10cac25d64f987d719fdb581bb08c0aac0
|
||||||
progress:
|
progress:
|
||||||
total_phases: 26
|
total_phases: 26
|
||||||
completed_phases: 12
|
completed_phases: 12
|
||||||
total_plans: 129
|
total_plans: 131
|
||||||
completed_plans: 129
|
completed_plans: 129
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
---
|
---
|
||||||
@@ -28,10 +28,10 @@ See: .planning/PROJECT.md (updated 2026-09-16)
|
|||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 14.2.1 (Translate plugin) — VERIFYING
|
Phase: 14.2.1 (translate-plugin) — READY TO EXECUTE
|
||||||
Plan: 4 of 4 complete
|
Plan: 4 of 4 complete
|
||||||
Status: Ready for phase verification / UAT
|
Status: Verification gaps — CR-01 admin ML locale switch; do not mark complete
|
||||||
Last activity: 2026-10-06 — End-to-end tests, matrices, fail-closed gate, security review
|
Last activity: 2026-10-06 — 14.2.1-VERIFICATION.md status gaps_found (14/16)
|
||||||
|
|
||||||
Progress: [██████████] 100%
|
Progress: [██████████] 100%
|
||||||
|
|
||||||
|
|||||||
265
.planning/phases/14.2.1-translate-plugin/14.2.1-05-PLAN.md
Normal file
265
.planning/phases/14.2.1-translate-plugin/14.2.1-05-PLAN.md
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
---
|
||||||
|
phase: 14.2.1-translate-plugin
|
||||||
|
plan: 05
|
||||||
|
type: execute
|
||||||
|
wave: 5
|
||||||
|
depends_on: ["14.2.1-04"]
|
||||||
|
files_modified:
|
||||||
|
- modules/cabana/schema_types.go
|
||||||
|
- modules/cabana/field_ml.go
|
||||||
|
- modules/cabana/crud.go
|
||||||
|
- modules/cabana/http.go
|
||||||
|
- modules/cabana/ml_test.go
|
||||||
|
- modules/cabana/ml_smoke_test.go
|
||||||
|
- modules/cabana/README.md
|
||||||
|
- docs/backend/forms.md
|
||||||
|
- docs/backend/admin-controllers.md
|
||||||
|
- admin/src/components/form/formContext.ts
|
||||||
|
- admin/src/components/form/formState.ts
|
||||||
|
- admin/src/views/FormView.vue
|
||||||
|
- admin/src/components/form/fields/MLTextField.vue
|
||||||
|
- admin/src/components/form/fields/MLMarkdownField.vue
|
||||||
|
- admin/src/components/relation/RelationChildModal.vue
|
||||||
|
- admin/tests/form/MLFields.test.ts
|
||||||
|
- admin/tests/form/formState.test.ts
|
||||||
|
- admin/openapi/admin.json
|
||||||
|
- admin/src/api/schema.d.ts
|
||||||
|
- modules/boardwalk/dist/
|
||||||
|
- ../sm-translate-plugin/classes/admin_writer.go
|
||||||
|
autonomous: false
|
||||||
|
gap_closure: true
|
||||||
|
requirements: [D-06, D-17]
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "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>"
|
||||||
|
artifacts:
|
||||||
|
- path: "modules/cabana/schema_types.go"
|
||||||
|
provides: "form schema meta lists enabled content locales"
|
||||||
|
contains: "EnabledLocales"
|
||||||
|
- path: "modules/cabana/field_ml.go"
|
||||||
|
provides: "TranslationWriter.TranslatedExact and hydrateMLRecord for Show/save maps"
|
||||||
|
contains: "TranslatedExact"
|
||||||
|
- path: "modules/cabana/field_ml.go"
|
||||||
|
provides: "GET/save ML field expansion without D-11 fallback"
|
||||||
|
contains: "hydrateMLRecord"
|
||||||
|
- path: "admin/src/components/form/formContext.ts"
|
||||||
|
provides: "form-wide enabled locale list for ML controls"
|
||||||
|
contains: "FORM_ENABLED_LOCALES"
|
||||||
|
- path: "admin/src/views/FormView.vue"
|
||||||
|
provides: "adopt merges hydrated ML maps instead of overwriting with a host scalar"
|
||||||
|
contains: "adopt"
|
||||||
|
- path: "admin/src/components/form/formState.ts"
|
||||||
|
provides: "create seed maps keyed by every enabled locale"
|
||||||
|
contains: "initialValues"
|
||||||
|
- path: "../sm-translate-plugin/classes/admin_writer.go"
|
||||||
|
provides: "plugin adapter for cabana TranslatedExact"
|
||||||
|
contains: "TranslatedExact"
|
||||||
|
key_links:
|
||||||
|
- from: "modules/cabana/http.go"
|
||||||
|
to: "modules/cabana/schema_types.go"
|
||||||
|
via: "protect()'d formSchema copies TranslationWriter.EnabledLocales onto FormMeta"
|
||||||
|
pattern: "EnabledLocales"
|
||||||
|
- from: "modules/cabana/crud.go"
|
||||||
|
to: "modules/cabana/field_ml.go"
|
||||||
|
via: "ShowRecord and save hydrate ML fields after projectFullRecord"
|
||||||
|
pattern: "hydrateMLRecord"
|
||||||
|
- from: "../sm-translate-plugin/classes/admin_writer.go"
|
||||||
|
to: "../sm-translate-plugin/classes/translatable.go"
|
||||||
|
via: "AdminWriter.TranslatedExact delegates to classes.TranslatedExact"
|
||||||
|
pattern: "TranslatedExact"
|
||||||
|
- from: "admin/src/views/FormView.vue"
|
||||||
|
to: "admin/src/components/form/fields/MLTextField.vue"
|
||||||
|
via: "FORM_ENABLED_LOCALES inject drives selector options, not value keys"
|
||||||
|
pattern: "FORM_ENABLED_LOCALES"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Close CR-01 so D-06's one-screen locale switch and D-17's fixture save/read of en+pl through ML fields work on the admin form path: schema meta carries enabled locales, create seeds empty maps, GET/save hydrate via TranslatedExact, FormView.adopt merges maps, and SPA selectors read schema locales.
|
||||||
|
|
||||||
|
Purpose: Journal-shaped create/update must list every enabled locale before the administrator types, survive GET of a host scalar, and POST `{en, pl}` instead of `{en}` only.
|
||||||
|
Output: additive `FormMeta.EnabledLocales`, `TranslationWriter.TranslatedExact`, `hydrateMLRecord`, SPA inject/seed/adopt, regenerated OpenAPI/TS/dist, and named hydration smoke.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@~/.codex/gsd-core/workflows/execute-plan.md
|
||||||
|
@~/.codex/gsd-core/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/phases/14.2.1-translate-plugin/14.2.1-04-SUMMARY.md
|
||||||
|
@.planning/phases/14.2.1-translate-plugin/14.2.1-REVIEW.md
|
||||||
|
@.planning/phases/14.2.1-translate-plugin/14.2.1-VERIFICATION.md
|
||||||
|
@.planning/phases/14.2.1-translate-plugin/14.2.1-CONTEXT.md
|
||||||
|
@modules/cabana/field_ml.go
|
||||||
|
@modules/cabana/schema_types.go
|
||||||
|
@modules/cabana/crud.go
|
||||||
|
@modules/cabana/http.go
|
||||||
|
@admin/src/components/form/fields/MLTextField.vue
|
||||||
|
@admin/src/views/FormView.vue
|
||||||
|
@../sm-translate-plugin/classes/admin_writer.go
|
||||||
|
@../sm-translate-plugin/classes/translatable.go
|
||||||
|
</context>
|
||||||
|
|
||||||
|
## Spec-less probe fallback
|
||||||
|
|
||||||
|
The phase has no mapped requirement IDs, so no speculative requirement probes are generated. Edge coverage is CR-01 and the two failed VERIFICATION truths (D-06 / D-17 admin form path).
|
||||||
|
|
||||||
|
## Gap-closure source audit
|
||||||
|
|
||||||
|
Audited only the failed VERIFICATION truths and REVIEW CR-01. Already-verified D-01..D-05 and D-07..D-16 are EXCLUDED.
|
||||||
|
|
||||||
|
| Decision | Source | Status | Why this plan |
|
||||||
|
|----------|--------|--------|---------------|
|
||||||
|
| D-06 | CONTEXT.md; REVIEW CR-01; VERIFICATION truths 4 and 14 | NOT WIRED on create/GET/adopt | Selector options come from `Object.keys(value)` else a single English fallback; create seeds `{}`; GET is a host scalar |
|
||||||
|
| D-17 | CONTEXT.md; VERIFICATION user-flow row for Journal-shaped form | NOT WIRED on the SPA form path | HTTP POST of a pre-built `{en,pl}` map still works; the form Journal will hit never produces that body |
|
||||||
|
|
||||||
|
No new gormigrate file: runtime hydration and schema meta only. Skip Prisma/Payload schema-push.
|
||||||
|
|
||||||
|
No new HTTP routes. `FormMeta.enabledLocales` is an additive field on the existing form-schema response. OpenAPI/TS regen is in Task 3.
|
||||||
|
|
||||||
|
## Artifacts this phase produces
|
||||||
|
|
||||||
|
- `cabana.FormMeta.EnabledLocales` (`json:"enabledLocales,omitempty"`) filled from `TranslationWriter.EnabledLocales` on protect()'d `formSchema` (and copied onto that handler's envelope meta).
|
||||||
|
- `cabana.TranslationWriter.TranslatedExact` plus `hydrateMLRecord`, called after `projectFullRecord` from `CRUDService.ShowRecord` and `CRUDService.save` so GET/save data for declared `mltext`/`mlmarkdown` fields is `map[locale]string` (default from the host column; other codes from exact stored values; missing non-default codes become empty strings, never D-11 fallback).
|
||||||
|
- SPA `FORM_ENABLED_LOCALES`, `initialValues` seed `{[code]: ""}` for every enabled code, `FormView.adopt` / `RelationChildModal.adopt` merge of ML maps (string host values become the default-locale entry; they never wipe sibling locales), `MLTextField` / `MLMarkdownField` locale lists from the inject.
|
||||||
|
- Regenerated `admin/openapi/admin.json`, `admin/src/api/schema.d.ts`, and `modules/boardwalk/dist/`.
|
||||||
|
- Named `TestMLHydration`; Vitest create-empty and GET-scalar fixtures; `TestMLNestedSave` projected `title` is the hydrated map.
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="checkpoint:decision" gate="blocking-human">
|
||||||
|
<name>Task 1: Confirm TranslationWriter.TranslatedExact as the hydration contract</name>
|
||||||
|
<files>modules/cabana/field_ml.go, ../sm-translate-plugin/classes/admin_writer.go, ../sm-translate-plugin/classes/translatable.go</files>
|
||||||
|
<read_first>.planning/phases/14.2.1-translate-plugin/14.2.1-REVIEW.md (CR-01 Fix), .planning/phases/14.2.1-translate-plugin/14.2.1-CONTEXT.md (D-06, D-11, D-17), modules/cabana/field_ml.go (TranslationWriter), ../sm-translate-plugin/classes/translatable.go (TranslatedExact), ../sm-translate-plugin/classes/admin_writer.go</read_first>
|
||||||
|
<action>Record the hydration read contract before expanding the published cabana interface. Cabana must not import the translate plugin. classes.TranslatedExact already exists in the plugin and must not apply D-11 fallback. The locked CR-01 fix is a method on TranslationWriter so hydrateMLRecord stays on the same published adapter as DefaultLocale, EnabledLocales, and WriteTranslated. The only in-tree implementers are AdminWriter, recordingWriter, and boomWriter (embeds recordingWriter).</action>
|
||||||
|
<decision>How should cabana read exact translations for admin GET/save hydration?</decision>
|
||||||
|
<context>Adding a method to cabana.TranslationWriter is a compile break for every published adapter. An optional second interface is more reversible but splits the CR-01 contract. Importing the plugin from cabana is forbidden.</context>
|
||||||
|
<options>
|
||||||
|
<option id="writer-exact">
|
||||||
|
<name>Add TranslatedExact to cabana.TranslationWriter; AdminWriter delegates to classes.TranslatedExact</name>
|
||||||
|
<pros>Matches CR-01; one Lookup; recordingWriter stays a single mock.</pros>
|
||||||
|
<cons>Every TranslationWriter implementer must add the method (one-way for the published interface).</cons>
|
||||||
|
</option>
|
||||||
|
<option id="optional-reader">
|
||||||
|
<name>Add a separate optional exact-reader interface that cabana type-asserts</name>
|
||||||
|
<pros>Existing WriteTranslated-only adapters keep compiling.</pros>
|
||||||
|
<cons>Diverges from CR-01; hydration silently no-ops if the adapter is incomplete.</cons>
|
||||||
|
</option>
|
||||||
|
<option id="import-plugin">
|
||||||
|
<name>Call classes.TranslatedExact from cabana</name>
|
||||||
|
<pros>No interface change.</pros>
|
||||||
|
<cons>Violates compiled-plugin isolation; cabana would import sm-translate-plugin.</cons>
|
||||||
|
</option>
|
||||||
|
</options>
|
||||||
|
<verify>
|
||||||
|
<automated>test -f modules/cabana/field_ml.go && test -f ../sm-translate-plugin/classes/translatable.go && test -f ../sm-translate-plugin/classes/admin_writer.go</automated>
|
||||||
|
<fails_when>Non-zero exit, or any of the three paths is missing.</fails_when>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- The selection is recorded in the summary.
|
||||||
|
- Task 2 proceeds only with `writer-exact`; another selection stops as a CR-01 / D-06 conflict.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<resume-signal>Select `writer-exact` to implement CR-01 hydration, or an alternative to stop.</resume-signal>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="tracer">
|
||||||
|
<name>Task 2: Hydrate one mltext create/GET/save path and show en+pl on the SPA</name>
|
||||||
|
<reversibility rating="one-way">Adding TranslatedExact to the published TranslationWriter interface is a compile break for every adapter.</reversibility>
|
||||||
|
<precondition>Task 1 selected `writer-exact`.</precondition>
|
||||||
|
<files>modules/cabana/schema_types.go, modules/cabana/field_ml.go, modules/cabana/crud.go, modules/cabana/http.go, modules/cabana/ml_test.go, modules/cabana/ml_smoke_test.go, admin/src/components/form/formContext.ts, admin/src/components/form/formState.ts, admin/src/views/FormView.vue, admin/src/components/form/fields/MLTextField.vue, admin/tests/form/MLFields.test.ts, ../sm-translate-plugin/classes/admin_writer.go</files>
|
||||||
|
<read_first>modules/cabana/schema_types.go (FormMeta), modules/cabana/field_ml.go (TranslationWriter, liftMLValues), modules/cabana/crud.go (CRUDService.writer, save, ShowRecord, projectFullRecord, projectRecord), modules/cabana/http.go (formSchema, crud Lookup), admin/src/components/form/formContext.ts (FORM_LOCALE), admin/src/components/form/formState.ts (initialValues), admin/src/views/FormView.vue (load, adopt, provide), admin/src/components/form/fields/MLTextField.vue (locales computed), admin/src/components/form/mlLocale.ts (localeRecord), ../sm-translate-plugin/classes/admin_writer.go, ../sm-translate-plugin/classes/translatable.go (TranslatedExact), .planning/phases/14.2.1-translate-plugin/14.2.1-REVIEW.md (CR-01)</read_first>
|
||||||
|
<action>Implement D-06/D-17 hydration for one mltext field end to end.
|
||||||
|
|
||||||
|
On FormMeta in schema_types.go add EnabledLocales as a string slice with json enabledLocales,omitempty so list schemas that reuse FormMeta stay clean. Do not set it inside FormSchema.Localize (that path is cache/no-DB). In http.go formSchema, after protect and Localize, Lookup TranslationWriter the same way crud() does; when present, assign writer.EnabledLocales onto view.Meta.EnabledLocales and also copy the slice onto the WriteData envelope meta next to locale. Leave list_schema.go Meta as locale-only.
|
||||||
|
|
||||||
|
Add TranslationWriter.TranslatedExact(ctx, model, field, locale) returning string, bool, error. Document that ok is false when the non-default key is missing and that the method must not apply D-11 fallback. Implement it on recordingWriter from stored attrs (default locale reads the host column via the model; missing keys return ok false). Implement AdminWriter.TranslatedExact by delegating to classes.TranslatedExact and converting the stored value to string; keep var _ cabana.TranslationWriter = AdminWriter{}.
|
||||||
|
|
||||||
|
Add hydrateMLRecord in field_ml.go. After projectFullRecord in ShowRecord and at the end of save (CreateRecord/UpdateRecord path), for each declared mltext/mlmarkdown field whose context allows the current op, replace the host scalar in RecordResult.Data with a map that contains every EnabledLocales code: default from the projected host column, others from TranslatedExact, missing non-default codes as empty string. Skip when writer is nil. Do not call hydrateMLRecord from list row projection. Do not introduce a public translate-read HTTP route.
|
||||||
|
|
||||||
|
Update TestMLNestedSave so the projected title after Create is the hydrated map containing Hello and Witaj, while the host column remains the English scalar and Polish still reaches recordingWriter.attrs. Add TestMLHydration that creates an mltext map, ShowRecord-loads it, and asserts both locales; a second case with only English stored must still list pl as empty, not English.
|
||||||
|
|
||||||
|
In formContext.ts add FORM_ENABLED_LOCALES as an InjectionKey of a readonly string-array ref, next to FORM_LOCALE. FormView provides it from schema.meta.enabledLocales (empty array when omitted). Change initialValues to take the enabled locale list and seed mltext/mlmarkdown as a map of each code to empty string; FormView create calls it with that list. Extract a small merge helper in formState.ts used by adopt: for ML fields, start from the seeded empty map, overlay localeRecord of the incoming value, and if the incoming value is a string put it on the default/first enabled locale instead of replacing the whole field. Never assign a bare string onto an ML field. FormView.adopt uses that merge; password fields still clear after save.
|
||||||
|
|
||||||
|
MLTextField locales computed reads FORM_ENABLED_LOCALES. When the inject is non-empty, that list is the selector options (D-06). Do not derive options from Object.keys of the value and do not fall back to a hardcoded English-only list. Existing Vitest mounts must provide FORM_ENABLED_LOCALES with en and pl via global provide. Add a Vitest case that starts from initialValues of an mltext field with locales en,pl (empty maps, two selector options) and a case that adopt-merges a GET host string onto that seed and still shows both locales so copy-from and a typed Polish value survive.
|
||||||
|
|
||||||
|
Do not change fillChild (WR-02 is Plan 06). Do not add a gormigrate file.</action>
|
||||||
|
<verify>
|
||||||
|
<automated>go test ./modules/cabana -count=1 -v -run '^(TestMLHydration|TestMLNestedSave)$' && npm --prefix admin test -- --run admin/tests/form/MLFields.test.ts</automated>
|
||||||
|
<fails_when>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.</fails_when>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- FormMeta JSON includes enabledLocales when the writer is published; omitempty leaves list schemas without the key.
|
||||||
|
- formSchema fills EnabledLocales from TranslationWriter.EnabledLocales after Lookup; Localize remains cache-only.
|
||||||
|
- Show/save RecordResult.Data for a declared mltext field is a locale map; host column stays the default scalar; missing pl is empty, not D-11 English.
|
||||||
|
- TestMLNestedSave still proves unlifted maps never reach ProjectWritableFields and that de is rejected; projected title after Create is the hydrated map.
|
||||||
|
- Create initialValues for mltext is `{en: "", pl: ""}` when those codes are enabled, not `{}`.
|
||||||
|
- FormView.adopt of a host string does not drop sibling locale keys; password fields still empty after save.
|
||||||
|
- MLTextField selector options equal FORM_ENABLED_LOCALES, including on an empty create value.
|
||||||
|
- Cabana never imports the translate plugin; AdminWriter still satisfies TranslationWriter.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>A Journal-shaped mltext create lists en and pl, GET returns both codes, and the SPA can send Record<string,string> for every enabled locale (D-06, D-17).</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Cover mlmarkdown, relation-child adopt, and regenerate docs/OpenAPI/TS/dist</name>
|
||||||
|
<files>admin/src/components/form/fields/MLMarkdownField.vue, admin/src/components/relation/RelationChildModal.vue, admin/src/components/form/formState.ts, admin/tests/form/formState.test.ts, admin/tests/form/MLFields.test.ts, modules/cabana/http.go, modules/cabana/README.md, docs/backend/forms.md, docs/backend/admin-controllers.md, admin/openapi/admin.json, admin/src/api/schema.d.ts, modules/boardwalk/dist/</files>
|
||||||
|
<read_first>admin/src/components/form/fields/MLMarkdownField.vue, admin/src/components/relation/RelationChildModal.vue (adopt, initialValues, provide), admin/tests/form/formState.test.ts, modules/cabana/http.go (relationSchema, formSchema envelope meta), modules/cabana/README.md (TranslationWriter row), docs/backend/forms.md (Markdown and multilingual fields), docs/backend/admin-controllers.md, admin/package.json, scripts/check-admin-openapi.sh, scripts/check-admin-dist.sh</read_first>
|
||||||
|
<action>Apply the same locale inject to MLMarkdownField as MLTextField (D-06 compose). RelationChildModal create must call initialValues with FORM_ENABLED_LOCALES (parent FormView provide is visible to the modal; re-provide the same inject if the modal already re-provides FORM_VALUES). RelationChildModal.adopt must use the same ML merge helper as FormView.adopt. Pivot initialValues in RelationPickerModal may pass an empty locale list; pivot forms are not ML. Update formState.test.ts callers of initialValues.
|
||||||
|
|
||||||
|
Copy enabledLocales onto relationSchema WriteData envelope meta the same way formSchema copies locale, using the same Lookup, so a child form that only sees the relation envelope still receives the list. Do not attach EnabledLocales to list schema responses.
|
||||||
|
|
||||||
|
Document FormMeta.EnabledLocales, GET/save map hydration via TranslatedExact, create seed maps, and adopt merge in cabana README and docs/backend/forms.md. Mention the GET shape in admin-controllers.md only where the record payload is described. Neutral blog/acme names only. Every identifier named in README/docs must exist in the package (EnabledLocales, TranslatedExact, hydrateMLRecord, FORM_ENABLED_LOCALES is SPA-only and must not be claimed as a Go identifier).
|
||||||
|
|
||||||
|
Regenerate admin OpenAPI with scripts/check-admin-openapi.sh (no args) so cabana.FormMeta in admin/src/api/schema.d.ts includes enabledLocales. Do not hand-edit admin.json or schema.d.ts. Run npm --prefix admin run build and copy Vite output to committed modules/boardwalk/dist/ using the existing boardwalk workflow (scripts/check-admin-dist.sh). Extend MLFields tests so mlmarkdown create-empty and GET-string merge match mltext. No npm package install or pin change (T-14.2.1-SC).</action>
|
||||||
|
<verify>
|
||||||
|
<automated>go test ./cmd/summer -count=1 -run 'TestDocsTree' && go run ./cmd/summer docs:build --check && bash scripts/check-admin-openapi.sh --check && npm --prefix admin run typecheck && npm --prefix admin test -- --run admin/tests/form/MLFields.test.ts admin/tests/form/formState.test.ts && npm --prefix admin run build && go -C ../sm-translate-plugin vet ./... && go -C ../sm-translate-plugin test ./classes -count=1 -v -run '^(TestTranslatableGetSet)$'</automated>
|
||||||
|
<fails_when>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".</fails_when>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- MLMarkdownField selector options come from FORM_ENABLED_LOCALES; it still composes MarkdownField.
|
||||||
|
- RelationChildModal create seeds ML maps and adopt merges GET maps/strings the same way FormView does.
|
||||||
|
- cabana.FormMeta in schema.d.ts includes enabledLocales; boardwalk dist matches the fresh admin build.
|
||||||
|
- README/docs name EnabledLocales and TranslatedExact, describe D-06/D-17 hydration, and name no consuming application.
|
||||||
|
- AdminWriter.TranslatedExact compiles and existing TranslatedExact plugin tests still pass.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>Enabled locales are a first-class form-schema contract, GET/save round-trip the nested map, and generated admin artifacts stay in sync.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| Admin browser → form schema / record GET | Authenticated admin receives every enabled locale's stored value for declared ML fields |
|
||||||
|
| Cabana → TranslationWriter.TranslatedExact | Framework reads exact translations only through the published adapter |
|
||||||
|
| SPA adopt → in-memory form values | A scalar GET must not wipe sibling locale keys the administrator can still edit |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||||
|
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||||
|
| T-14.2.1-19 | Information Disclosure | hydrateMLRecord | high | mitigate | Hydrate only declared mltext/mlmarkdown on Show/save record payloads after protect/loadRecord; never list rows; TranslatedExact skips D-11 so unpublished empty values stay empty |
|
||||||
|
| T-14.2.1-20 | Information Disclosure | FormMeta.EnabledLocales | medium | mitigate | Fill only on protect()'d formSchema/relationSchema; omitempty on list Meta |
|
||||||
|
| T-14.2.1-21 | Tampering | FormView.adopt | medium | mitigate | Merge helper applies only to ML fields; server/seed maps win; password fields still clear |
|
||||||
|
| T-14.2.1-SC | Tampering | npm packages | high | mitigate | No package installation or version change; existing exact pins only |
|
||||||
|
|
||||||
|
ASVS L1: high threats are mitigated in this plan; Plan 06 adds named relation-child evidence and gate IDs.
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Run the Task 3 combined gate. Confirm `git status --short` in summercms.go and sm-translate-plugin contains only intended tracked source and generated admin artifacts. Do not commit.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- D-06: create/GET/update admin forms list every enabled locale on each ML control and switch them together.
|
||||||
|
- D-17: hydrated GET/save carries en and pl through ML fields; empty non-default values stay empty.
|
||||||
|
- OpenAPI, schema.d.ts, and boardwalk dist include FormMeta.enabledLocales.
|
||||||
|
- CR-01 is closed; WR-02 remains Plan 06.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/14.2.1-translate-plugin/14.2.1-05-SUMMARY.md` when done.
|
||||||
|
</output>
|
||||||
232
.planning/phases/14.2.1-translate-plugin/14.2.1-06-PLAN.md
Normal file
232
.planning/phases/14.2.1-translate-plugin/14.2.1-06-PLAN.md
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
---
|
||||||
|
phase: 14.2.1-translate-plugin
|
||||||
|
plan: 06
|
||||||
|
type: execute
|
||||||
|
wave: 6
|
||||||
|
depends_on: ["14.2.1-05"]
|
||||||
|
files_modified:
|
||||||
|
- modules/cabana/relation.go
|
||||||
|
- modules/cabana/relation_child.go
|
||||||
|
- modules/cabana/http.go
|
||||||
|
- modules/cabana/relation_child_ml_test.go
|
||||||
|
- modules/cabana/ml_test.go
|
||||||
|
- modules/cabana/README.md
|
||||||
|
- docs/backend/forms.md
|
||||||
|
- admin/tests/form/MLFields.test.ts
|
||||||
|
- scripts/check-phase14.2.1.sh
|
||||||
|
- .planning/phases/14.2.1-translate-plugin/14.2.1-SECURITY-REVIEW.md
|
||||||
|
autonomous: true
|
||||||
|
gap_closure: true
|
||||||
|
requirements: [D-06, D-17]
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "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>"
|
||||||
|
artifacts:
|
||||||
|
- path: "modules/cabana/relation.go"
|
||||||
|
provides: "RelationService carries the same TranslationWriter as CRUDService"
|
||||||
|
contains: "writer"
|
||||||
|
- path: "modules/cabana/http.go"
|
||||||
|
provides: "relations() Lookup of TranslationWriter matches crud()"
|
||||||
|
contains: "TranslationWriter"
|
||||||
|
- path: "modules/cabana/relation_child.go"
|
||||||
|
provides: "CreateChild/UpdateChild lift ML maps before fillChild and apply after PK"
|
||||||
|
contains: "liftMLValues"
|
||||||
|
- path: "modules/cabana/relation_child.go"
|
||||||
|
provides: "ShowChild/CreateChild/UpdateChild hydrate ML maps like CRUD save"
|
||||||
|
contains: "hydrateMLRecord"
|
||||||
|
- path: "modules/cabana/relation_child_ml_test.go"
|
||||||
|
provides: "named relation-child nested ML save/read proof"
|
||||||
|
contains: "TestRelationChildMLNestedSave"
|
||||||
|
- path: "scripts/check-phase14.2.1.sh"
|
||||||
|
provides: "gate requires hydration and relation-child ML tests plus new high threat IDs"
|
||||||
|
contains: "TestRelationChildMLNestedSave"
|
||||||
|
key_links:
|
||||||
|
- from: "modules/cabana/http.go"
|
||||||
|
to: "modules/cabana/relation.go"
|
||||||
|
via: "relations() copies Lookup[TranslationWriter] onto RelationService.writer"
|
||||||
|
pattern: "Lookup[TranslationWriter]"
|
||||||
|
- from: "modules/cabana/relation_child.go"
|
||||||
|
to: "modules/cabana/field_ml.go"
|
||||||
|
via: "CreateChild/UpdateChild call liftMLValues then applyMLTranslations like CRUDService.save"
|
||||||
|
pattern: "applyMLTranslations"
|
||||||
|
- from: "modules/cabana/relation_child.go"
|
||||||
|
to: "modules/cabana/field_ml.go"
|
||||||
|
via: "child Show/save hydrate through hydrateMLRecord"
|
||||||
|
pattern: "hydrateMLRecord"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Close WR-02 so D-06/D-17 nested locale writes are not dropped on relation-child saves: RelationService looks up TranslationWriter, lifts maps before fillChild the way CRUDService.save does, applies translations after the child PK, hydrates ShowChild, and lands the phase's remaining named tests.
|
||||||
|
|
||||||
|
Purpose: a relation-manager child form with mltext/mlmarkdown must persist English on the host column and Polish through the writer, then GET the hydrated map, matching host CRUD.
|
||||||
|
Output: RelationService.writer wiring, CreateChild/UpdateChild/ShowChild ML lift+apply+hydrate, TestRelationChildMLNestedSave, Vitest/gate named tests as the last tasks of Phase 14.2.1.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@~/.codex/gsd-core/workflows/execute-plan.md
|
||||||
|
@~/.codex/gsd-core/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/phases/14.2.1-translate-plugin/14.2.1-05-SUMMARY.md
|
||||||
|
@.planning/phases/14.2.1-translate-plugin/14.2.1-REVIEW.md
|
||||||
|
@.planning/phases/14.2.1-translate-plugin/14.2.1-VERIFICATION.md
|
||||||
|
@.planning/phases/14.2.1-translate-plugin/14.2.1-CONTEXT.md
|
||||||
|
@modules/cabana/relation.go
|
||||||
|
@modules/cabana/relation_child.go
|
||||||
|
@modules/cabana/http.go
|
||||||
|
@modules/cabana/crud.go
|
||||||
|
@modules/cabana/field_ml.go
|
||||||
|
@modules/cabana/ml_test.go
|
||||||
|
@modules/cabana/ml_smoke_test.go
|
||||||
|
</context>
|
||||||
|
|
||||||
|
## Spec-less probe fallback
|
||||||
|
|
||||||
|
The phase has no mapped requirement IDs, so no speculative requirement probes are generated. Edge coverage is WR-02 and the failed nested-write truth (D-06 / D-17 on relation-child).
|
||||||
|
|
||||||
|
## Gap-closure source audit
|
||||||
|
|
||||||
|
Audited only WR-02 and the failed nested-write truth. Plan 05 CR-01 hydration and already-verified D-01..D-05, D-07..D-16 are EXCLUDED from re-planning here; Plan 06 consumes hydrateMLRecord / TranslatedExact from 05.
|
||||||
|
|
||||||
|
| Decision | Source | Status | Why this plan |
|
||||||
|
|----------|--------|--------|---------------|
|
||||||
|
| D-06 | CONTEXT.md; REVIEW WR-02; VERIFICATION truth 4 | NOT WIRED on fillChild | projectOperation drops nested maps; no liftMLValues / applyMLTranslations |
|
||||||
|
| D-17 | CONTEXT.md; VERIFICATION missing "Relation-child saves lift nested ML maps" | NOT WIRED | Phase 15 related records would silently lose translations |
|
||||||
|
|
||||||
|
No new gormigrate file: same runtime lift/apply as CRUDService.save. Skip Prisma/Payload schema-push.
|
||||||
|
|
||||||
|
No new HTTP routes. Child create/update/show already exist; this plan hydrates those bodies. OpenAPI regen is not required unless Task 1 accidentally changes a documented type (it should not).
|
||||||
|
|
||||||
|
## Artifacts this phase produces
|
||||||
|
|
||||||
|
- `RelationService.writer TranslationWriter` and `relations()` Lookup matching `crud()`.
|
||||||
|
- `CreateChild` / `UpdateChild`: `liftMLValues` on `in.Body` with op create/update and `cr.child` as the compiled form, then `fillChild` seeing host scalars, then `applyMLTranslations` after `Create`/`Save` so the child has a primary key, inside the existing child transaction.
|
||||||
|
- `ShowChild` / child save results: `hydrateMLRecord` after `projectFullRecord` using `s.writer` and `cr.child` / `cr.childForm()`.
|
||||||
|
- Named tests `TestRelationChildMLNestedSave`, remaining Vitest create/GET assertions, `FRAMEWORK_REQUIRE` entries, and SECURITY-REVIEW rows for T-14.2.1-19..23.
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="tracer">
|
||||||
|
<name>Task 1: Lift one nested mltext map through CreateChild</name>
|
||||||
|
<files>modules/cabana/relation.go, modules/cabana/http.go, modules/cabana/relation_child.go, modules/cabana/relation_child_ml_test.go, modules/cabana/ml_smoke_test.go</files>
|
||||||
|
<read_first>modules/cabana/relation.go (RelationService), modules/cabana/http.go (relations, crud Lookup), modules/cabana/relation_child.go (fillChild, CreateChild, projectFullRecord), modules/cabana/field_ml.go (liftMLValues, applyMLTranslations, hydrateMLRecord), modules/cabana/crud.go (save ML sequence), modules/cabana/ml_test.go (TestMLNestedSave, mlCompiled, mlPost), modules/cabana/ml_smoke_test.go (recordingWriter), .planning/phases/14.2.1-translate-plugin/14.2.1-REVIEW.md (WR-02)</read_first>
|
||||||
|
<action>Close WR-02 on the create-child tracer (D-06 / D-17 nested writes).
|
||||||
|
|
||||||
|
Add writer TranslationWriter to RelationService next to bucket/tr, documented as the same optional plugin adapter CRUDService uses. In http.go relations(), after constructing RelationService, Lookup TranslationWriter exactly as crud() does (s.app.Lookup[TranslationWriter]) and assign it. Do not add a new route. deferred.go RelationService literals used only for pivot/file bind do not call fillChild; leave them writer-less unless a compile-time struct literal requires the new field (zero value nil is correct).
|
||||||
|
|
||||||
|
In CreateChild, before fillChild, call liftMLValues(ctx, cr.child, in.Body, "create", s.writer, tx) using the transaction already opened (so default/enabled codes match apply). Then fillChild as today (projectOperation + Fill on scalars). After tx.Create(child) succeeds and the child has a primary key, call applyMLTranslations(ctx, tx, s.writer, child, translations) before commitChildFiles / AfterCreate. After projectFullRecord, call hydrateMLRecord with s.writer and cr.child so the create response is the same map shape as CRUDService.save (Plan 05). Validation errors from lift (undeclared locale, non-string, missing default, nil writer with a nested map) remain 422 ValidationError and must abort before Fill.
|
||||||
|
|
||||||
|
Add modules/cabana/relation_child_ml_test.go in package cabana. Fixture: a parent model plus a hasMany child whose manage form declares mltext title, compiled like mlCompiled, with recordingWriter {en, pl}. Construct RelationService{DB: db, writer: writer} and call CreateChild with Body title `{en: Hello, pl: Witaj}`. Assert ProjectWritableFields on the unlifted copy is empty of nested maps, host child.Title is Hello, writer.attrs pl title is Witaj, default locale is not duplicated, and the returned RecordResult.Data title is the hydrated map containing both locales. A second CreateChild with locale de must 422 and must not insert a child row. Name the test TestRelationChildMLNestedSave.
|
||||||
|
|
||||||
|
Reuse recordingWriter; add TranslatedExact on it only if Plan 05 did not (it must already exist). Do not implement WR-01/WR-03. No new migration.</action>
|
||||||
|
<verify>
|
||||||
|
<automated>go test ./modules/cabana -count=1 -v -run '^(TestRelationChildMLNestedSave)$'</automated>
|
||||||
|
<fails_when>Non-zero exit; output contains "--- FAIL", "--- SKIP", or "no tests to run"; output lacks "--- PASS: TestRelationChildMLNestedSave".</fails_when>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- relations() assigns Lookup[TranslationWriter] onto RelationService.writer; no new HTTP endpoint exists.
|
||||||
|
- CreateChild lifts declared ML maps before fillChild/projectOperation; generic nested maps on non-ML fields remain dropped.
|
||||||
|
- Default locale fills the child host column; Polish reaches TranslationWriter inside the child transaction after PK.
|
||||||
|
- Undeclared locale de is 422 and creates no row.
|
||||||
|
- CreateChild response data for title is the hydrated locale map from Plan 05 hydrateMLRecord.
|
||||||
|
- TestRelationChildMLNestedSave is the named proof.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>A relation-child create carries one multilingual title from Vue-shaped JSON through lift, Fill, writer, and hydrated response (D-17).</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Mirror lift/apply/hydrate on UpdateChild and ShowChild</name>
|
||||||
|
<files>modules/cabana/relation_child.go, modules/cabana/relation_child_ml_test.go, modules/cabana/README.md, docs/backend/forms.md</files>
|
||||||
|
<read_first>modules/cabana/relation_child.go (UpdateChild, ShowChild, fillChild), modules/cabana/field_ml.go (hydrateMLRecord), modules/cabana/README.md, docs/backend/forms.md (Markdown and multilingual fields)</read_first>
|
||||||
|
<action>Use the same D-06/D-17 contract on update and GET child.
|
||||||
|
|
||||||
|
UpdateChild: liftMLValues on in.Body with op update, cr.child, s.writer, and the open tx before fillChild; applyMLTranslations after Save when the child PK is present; hydrateMLRecord after projectFullRecord. ShowChild: after projectFullRecord on cr.childForm(), call hydrateMLRecord with s.writer so GET of a child is a locale map, not the host scalar. Writer-nil nested maps still 422 on update the same way as host CRUD. Authorization remains loadParent/loadChild 404-not-403; do not run the writer before those load checks.
|
||||||
|
|
||||||
|
Extend TestRelationChildMLNestedSave (subtests are fine) to UpdateChild a stored child with a new Polish string and ShowChild asserting the hydrated map. Keep host English on the column.
|
||||||
|
|
||||||
|
Document in cabana README and docs/backend/forms.md that relation-child create/update use the same lift/apply/hydrate path as controller save, still with no standalone translate-write route. Neutral blog/acme names. Do not name a consuming application. RelationService.writer is unexported; document the behaviour on TranslationWriter / child records, not a new exported type unless you export it (do not export writer).</action>
|
||||||
|
<verify>
|
||||||
|
<automated>go test ./modules/cabana -count=1 -v -run '^(TestRelationChildMLNestedSave)$' && go test ./cmd/summer -count=1 -run 'TestDocsTree' && go run ./cmd/summer docs:build --check</automated>
|
||||||
|
<fails_when>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.</fails_when>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- UpdateChild persists nested ML maps the same way CreateChild does.
|
||||||
|
- ShowChild returns hydrated locale maps for declared ML fields when a writer is present.
|
||||||
|
- Writer still runs only after parent/child scope checks and after the child row has a PK.
|
||||||
|
- README/docs identifiers resolve; no consuming-application name.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>Relation-child GET/update match host CRUD ML hydration and nested writes (WR-02 closed).</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Named unit/Vitest/cabana tests and the phase gate (last plan of 14.2.1)</name>
|
||||||
|
<files>modules/cabana/ml_test.go, modules/cabana/relation_child_ml_test.go, admin/tests/form/MLFields.test.ts, scripts/check-phase14.2.1.sh, .planning/phases/14.2.1-translate-plugin/14.2.1-SECURITY-REVIEW.md</files>
|
||||||
|
<read_first>modules/cabana/ml_test.go (TestMLNestedSave projection), admin/tests/form/MLFields.test.ts, scripts/check-phase14.2.1.sh (FRAMEWORK_REQUIRE, HIGH_THREATS, ALL_THREATS, --admin), .planning/phases/14.2.1-translate-plugin/14.2.1-SECURITY-REVIEW.md, .planning/phases/14.2.1-translate-plugin/14.2.1-VERIFICATION.md (failed truths)</read_first>
|
||||||
|
<action>This is the last plan of the phase: named tests last (lean-mode rule). Do not add product features here.
|
||||||
|
|
||||||
|
Confirm TestMLNestedSave still expects the hydrated title map from Plan 05 and still proves lift-before-projection plus de rejection. Confirm TestMLHydration still passes. Confirm TestRelationChildMLNestedSave covers create, update, show, and de 422.
|
||||||
|
|
||||||
|
In MLFields.test.ts keep Plan 05 create-empty and GET-string cases and assert: one locale selector per ML field, broadcastMLLocale still switches mltext and mlmarkdown together, copy-from-locale works, and editablePayload sends every enabled locale as Record<string,string> including empty pl. Mounts provide FORM_ENABLED_LOCALES.
|
||||||
|
|
||||||
|
Add TestMLHydration and TestRelationChildMLNestedSave to FRAMEWORK_REQUIRE in scripts/check-phase14.2.1.sh. Add T-14.2.1-19, T-14.2.1-22, and T-14.2.1-23 to HIGH_THREATS and ALL_THREATS; add T-14.2.1-20 and T-14.2.1-21 to ALL_THREATS. Keep T-14.2.1-SC. Do not reuse T-14.2.1-01..18.
|
||||||
|
|
||||||
|
Append SECURITY-REVIEW rows for T-14.2.1-19 (hydrate only declared ML record fields, no D-11, no list hydration), T-14.2.1-22 (fillChild lift validates locales like save), T-14.2.1-23 (RelationService writer Lookup, no new route), plus medium T-14.2.1-20/21, each with source citation and the named test that fails if the mitigation is removed. No npm install (T-14.2.1-SC).
|
||||||
|
|
||||||
|
Run go vet and go test in summercms.go, the plugin, and the host. Run admin vitest and the phase gate.</action>
|
||||||
|
<verify>
|
||||||
|
<automated>go vet ./modules/cabana ./modules/surf ./cmd/summer && go test ./modules/cabana -count=1 -v -run '^(TestMLHydration|TestMLNestedSave|TestRelationChildMLNestedSave|TestMLFieldTypes)$' && npm --prefix admin test -- --run admin/tests/form/MLFields.test.ts && go -C ../sm-translate-plugin vet ./... && go -C ../sm-translate-plugin test ./... -count=1 -v -run '^(TestTranslateEndToEnd)$' && go -C ../sm-grzybyfunkcjonalne-app vet ./... && go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslate)$' && bash scripts/check-phase14.2.1.sh --all</automated>
|
||||||
|
<fails_when>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.</fails_when>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- FRAMEWORK_REQUIRE includes TestMLHydration and TestRelationChildMLNestedSave.
|
||||||
|
- Vitest proves create-empty, GET-scalar merge, synchronized selectors, copy-from, and full-locale payloads (failed SPA truth).
|
||||||
|
- Cabana tests prove host CRUD and relation-child nested maps are not dropped (failed nested-write truth, including WR-02).
|
||||||
|
- SECURITY-REVIEW maps T-14.2.1-19 through T-14.2.1-23 with named-test evidence; T-14.2.1-SC unchanged; IDs 01-18 are not reused.
|
||||||
|
- `bash scripts/check-phase14.2.1.sh --all` prints `Phase 14.2.1 gate passed`.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>Phase 14.2.1 unit/Vitest/cabana named tests and the fail-closed gate are green with CR-01 and WR-02 closed.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| Admin browser → relation child create/update | Untrusted nested multilingual JSON crosses the parent-scoped child write |
|
||||||
|
| Cabana RelationService → TranslationWriter | Same published adapter as host CRUD; no extra privilege surface |
|
||||||
|
| Test/gate → production claims | A skipped or missing named test must not close WR-02 |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||||
|
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||||
|
| T-14.2.1-22 | Tampering | fillChild / CreateChild / UpdateChild | high | mitigate | liftMLValues before projectOperation; same locale/string/default validation as CRUDService.save; 422 before Fill |
|
||||||
|
| T-14.2.1-23 | Elevation of Privilege | RelationService.writer | high | mitigate | Lookup only inside existing protect()'d child routes; apply after loadParent/loadChild and after child PK; no new endpoint |
|
||||||
|
| T-14.2.1-19 | Information Disclosure | ShowChild hydrateMLRecord | high | mitigate | Same Plan 05 helper; hydrate only declared ML fields on the child record payload |
|
||||||
|
| T-14.2.1-SC | Tampering | npm packages | high | mitigate | No package installation or version change |
|
||||||
|
|
||||||
|
ASVS L1: high threats are mitigated with named tests in Task 3. Reserved T-14.2.1-SC is unchanged.
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
`bash scripts/check-phase14.2.1.sh --all` is the authoritative final command and must end with `Phase 14.2.1 gate passed`.
|
||||||
|
|
||||||
|
<human-check>
|
||||||
|
With the executor-started proof host and admin SPA, sign in as an administrator holding `golem15.translate.manage_locales`; open Locales; confirm English and Polski appear in order. 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. Relation-child ML UAT can wait for Phase 15 if this host has no child ML form yet.
|
||||||
|
</human-check>
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- WR-02 closed: relation-child create/update lift nested ML maps; ShowChild hydrates them.
|
||||||
|
- Both failed VERIFICATION truths have named Go and Vitest evidence.
|
||||||
|
- D-06 and D-17 are cited and covered on host CRUD (Plan 05) and relation-child (this plan).
|
||||||
|
- Phase gate passed; high threats T-14.2.1-19..23 mapped; no new migration or npm pin.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/14.2.1-translate-plugin/14.2.1-06-SUMMARY.md` when done.
|
||||||
|
</output>
|
||||||
@@ -88,16 +88,31 @@
|
|||||||
"name": "OAuth identities and fonoteka me routes (INSERTED)",
|
"name": "OAuth identities and fonoteka me routes (INSERTED)",
|
||||||
"status": "complete"
|
"status": "complete"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"number": "14.2",
|
||||||
|
"name": "Local Fonoteka frontend on local SummerCMS backend (INSERTED)",
|
||||||
|
"status": "pending"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"number": "15",
|
"number": "15",
|
||||||
"name": "Cutover",
|
"name": "Journal plugin",
|
||||||
|
"status": "pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"number": "16",
|
||||||
|
"name": "grzybyfunkcjonalne.pl on reusable blog views",
|
||||||
|
"status": "pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"number": "20",
|
||||||
|
"name": "Płytarium cutover",
|
||||||
"status": "pending"
|
"status": "pending"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"next": {
|
"next": {
|
||||||
"command": "/gsd:progress --next",
|
"command": "/gsd:progress --next",
|
||||||
"label": "Advance to the next step (plan phase 11)",
|
"label": "Advance to the next step (verify)",
|
||||||
"reason": "Phase 11 of 22 — needs a plan"
|
"reason": "Phase 14.2.1 of 26 · ready to verify"
|
||||||
},
|
},
|
||||||
"updated_at": "2026-10-05T19:48:22.022Z"
|
"updated_at": "2026-10-06T13:18:47.344Z"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user