--- phase: 14.2.1-translate-plugin plan: 03 type: execute wave: 3 depends_on: ["14.2.1-02"] files_modified: - modules/cabana/form_schema.go - modules/cabana/crud.go - modules/cabana/field_ml.go - modules/cabana/field_markdown.go - modules/cabana/ml_smoke_test.go - modules/cabana/README.md - docs/backend/forms.md - docs/backend/admin-controllers.md - admin/src/components/form/registry.ts - admin/src/components/form/formState.ts - admin/src/components/form/fields/MarkdownField.vue - admin/src/components/form/fields/MLTextField.vue - admin/src/components/form/fields/MLMarkdownField.vue - admin/tests/form/registry.test.ts - admin/tests/form/MLFields.test.ts - admin/openapi/admin.json - admin/src/api/schema.d.ts - modules/boardwalk/dist/ - ../sm-translate-plugin/classes/admin_writer.go - ../sm-translate-plugin/plugin.go - ../sm-grzybyfunkcjonalne-app/go.mod - ../sm-grzybyfunkcjonalne-app/go.work - ../sm-grzybyfunkcjonalne-app/summer.yaml - ../sm-grzybyfunkcjonalne-app/.gitmodules - ../sm-grzybyfunkcjonalne-app/main.go - ../sm-grzybyfunkcjonalne-app/plugins.gen.go - ../sm-grzybyfunkcjonalne-app/boot_test.go autonomous: false requirements: [D-06, D-09, D-12, D-13, D-14, D-15, D-16, D-17] must_haves: truths: - "D-06: cabana accepts `markdown`, `mltext`, and `mlmarkdown`; `mlmarkdown` composes the same markdown primitive with locale switching." - "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." - "The SPA exposes one locale selector per ML field, switches all ML controls together, supports copy-from-locale, and sends every locale as `Record`." - "Markdown preview uses goldmark without unsafe HTML and cannot execute translated raw HTML/script/event-handler/javascript content." - "Cabana README, forms/admin docs, OpenAPI, generated TS types, and committed `modules/boardwalk/dist/` are regenerated in the same change." - "D-13/D-14/D-15/D-16/D-17: `sm-grzybyfunkcjonalne-app` mounts user and translate as submodules, uses go.work/local replaces, and boots both compiled plugins." artifacts: - path: "modules/cabana/field_ml.go" provides: "ML nested-value lift and TranslationWriter bridge" contains: "TranslationWriter" - path: "admin/src/components/form/fields/MLMarkdownField.vue" provides: "locale-aware markdown source editor" contains: "modelValue" - path: "docs/backend/forms.md" provides: "verified field-type documentation" contains: "mlmarkdown" - path: "../sm-grzybyfunkcjonalne-app/summer.yaml" provides: "compiled user+translate plugin list" contains: "golem15.translate" - path: "../sm-grzybyfunkcjonalne-app/boot_test.go" provides: "proof-host boot smoke" contains: "TestBootUserTranslate" key_links: - from: "modules/cabana/crud.go" to: "modules/cabana/field_ml.go" via: "ML values are lifted before ProjectWritableFields" pattern: "liftMLValues" - from: "../sm-translate-plugin/classes/admin_writer.go" to: "../sm-translate-plugin/classes/translatable.go" via: "published cabana TranslationWriter delegates each locale to SetTranslated" pattern: "SetTranslated" - from: "../sm-grzybyfunkcjonalne-app/summer.yaml" to: "../sm-grzybyfunkcjonalne-app/plugins.gen.go" via: "summer build generates blank imports for both submodules" pattern: "sm-translate-plugin" --- Add cabana's markdown and multilingual fields end to end, preserve nested locale writes, regenerate every public/generated artifact, and boot the user+translate proof host. Purpose: prove a Journal-shaped form can submit localized text/markdown through the generic admin stack without a Node extension or dropped nested JSON. Output: framework schema/save/UI/docs/OpenAPI/dist changes, translate writer adapter, and the D-13 proof-host layout. @~/.codex/gsd-core/workflows/execute-plan.md @~/.codex/gsd-core/templates/summary.md @.planning/phases/14.2.1-translate-plugin/14.2.1-02-SUMMARY.md @.planning/phases/14.2.1-translate-plugin/14.2.1-CONTEXT.md @.planning/phases/14.2.1-translate-plugin/14.2.1-RESEARCH.md @.planning/phases/14.2.1-translate-plugin/14.2.1-PATTERNS.md @modules/cabana/form_schema.go @modules/cabana/crud.go @modules/cabana/field_permission.go @admin/src/components/form/registry.ts @admin/src/components/form/formState.ts @../sm-bm-app/summer.yaml @../sm-bm-app/go.work ## Spec-less probe fallback The phase has no mapped requirement IDs, so no speculative requirement probes are generated. D-06/D-09/D-12/D-13/D-14/D-15/D-16/D-17 and RESEARCH's cabana/host validation rows are the acceptance source. ## Artifacts this phase produces - Cabana field types `markdown`, `mltext`, `mlmarkdown`, `TranslationWriter`, nested-map lifting, and safe `RenderMarkdown`. - Vue `MarkdownField`, `MLTextField`, `MLMarkdownField`, synchronized selector/copy behavior, and registry/form-state support. - Updated cabana README and backend docs; regenerated `admin/openapi/admin.json`, `admin/src/api/schema.d.ts`, and `modules/boardwalk/dist/`. - Translate plugin's cabana writer adapter published at Boot. - Proof host source layout with user and translate submodules, local workspace/replaces, generated plugin list, and `TestBootUserTranslate`. Task 1: Submit one mltext map through cabana and persist default plus Polish values modules/cabana/form_schema.go, modules/cabana/crud.go, modules/cabana/field_ml.go, modules/cabana/field_markdown.go, modules/cabana/ml_smoke_test.go, admin/src/components/form/registry.ts, admin/src/components/form/formState.ts, admin/src/components/form/fields/MarkdownField.vue, admin/src/components/form/fields/MLTextField.vue, admin/src/components/form/fields/MLMarkdownField.vue, admin/tests/form/MLFields.test.ts, ../sm-translate-plugin/classes/admin_writer.go, ../sm-translate-plugin/plugin.go modules/cabana/form_schema.go (formFieldTypes and compileFieldNode), modules/cabana/crud.go (save, ProjectWritableFields, BindWritableFields, scalarFormField), modules/cabana/field_permission.go (liftPermissionValues), modules/postcard/templates.go (goldmark safe path), admin/src/components/form/registry.ts, admin/src/components/form/formState.ts, admin/src/components/form/fields/TextField.vue, admin/src/components/form/fields/TextareaField.vue, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/traits/MLControl.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/traits/mlcontrol/partials/_locale_selector.htm, ../sm-translate-plugin/classes/translatable.go Register exactly `markdown`, `mltext`, and `mlmarkdown` as fail-loud schema types without adding unnecessary YAML keys. Define a framework-owned cabana `TranslationWriter` service contract so cabana never imports the translate plugin; implement and publish its adapter from sm-translate-plugin. In CRUD save, identify declared ML fields and lift each `map[locale]text` before `ProjectWritableFields` evaluates nested values. Reject non-string values, undeclared locales, and extra field names with validation errors. Fill only the default-locale scalar into the host model, then call the writer for non-default values inside the same lagoon/cabana transaction and after controller permission/query scoping has succeeded. Never expose a public translate-write endpoint. Implement `RenderMarkdown` using the already-pinned goldmark without unsafe HTML; reject unsafe output patterns consistently with postcard. `MarkdownField` edits source and may preview only sanitized output. Build ML components around a `Record` value: active locale editor, selector, copy-from-locale, and a shared form event so changing one selector changes all ML controls. `mlmarkdown` composes `MarkdownField`, not a duplicate parser/editor. Ensure formState sends nested ML records. Create one focused Go smoke fixture using the Plan 02 writer adapter and a cabana save body `{title:{en,pl}}`. It must prove the nested map survives projection, English reaches the host field, and Polish reaches attribute JSON. Add a focused SPA smoke asserting registry resolution and emitted nested values; full edge/coverage tests remain Plan 04. go test ./modules/cabana -count=1 -v -run '^(TestMLNestedSaveSmoke)$' && npm --prefix admin test -- --run admin/tests/form/MLFields.test.ts Non-zero exit; Go output contains "--- FAIL", "--- SKIP", or "no tests to run", lacks "--- PASS: TestMLNestedSaveSmoke", or Vitest reports no test files/tests or any failed test. - A compiled schema accepts all three exact field types and still rejects unknown types. - ML map lifting occurs before `ProjectWritableFields`; generic nested maps remain rejected/dropped by existing rules. - Default locale fills the host scalar and is not duplicated in attributes; Polish persists through TranslationWriter. - Writer execution occurs only after host-controller authorization/scoping and inside the save transaction. - Raw script/iframe/event-handler/javascript markdown cannot become executable preview HTML. - SPA payload contains all locale keys, and `mlmarkdown` reuses `MarkdownField`. A real cabana save carries one multilingual title from Vue-shaped JSON through projection and transactional persistence without dropping or broadening the write. Task 2: Confirm the one-way proof-host submodule layout ../sm-grzybyfunkcjonalne-app/.gitmodules, ../sm-grzybyfunkcjonalne-app/go.work, ../sm-grzybyfunkcjonalne-app/go.mod, ../sm-grzybyfunkcjonalne-app/summer.yaml .planning/phases/14.2.1-translate-plugin/14.2.1-CONTEXT.md (D-13 through D-17), .planning/phases/14.2.1-translate-plugin/14.2.1-PATTERNS.md (Proof host), ../sm-bm-app/.gitmodules, ../sm-bm-app/go.work, ../sm-bm-app/go.mod, ../sm-bm-app/summer.yaml Record the D-13 host layout before adding gitlinks. The selected contract is the existing `sm-grzybyfunkcjonalne-app` repository with `plugins/golem15/user` and `plugins/golem15/translate` submodules plus go.work and local replace entries, following sm-bm-app. Journal is deliberately absent until Phase 15. Which durable proof-host layout should receive the translate gitlink? Changing submodule paths after downstream clones and Phase 15 Journal mounts requires coordinated gitlink, workspace, replace, and deployment changes. git -C ../sm-grzybyfunkcjonalne-app rev-parse --is-inside-work-tree && git -C ../sm-grzybyfunkcjonalne-app remote get-url origin Non-zero exit or origin is not the existing sm-grzybyfunkcjonalne-app remote. - The selection is recorded in the summary. - Task 3 proceeds only with `grzyby-submodules`; another selection stops as a D-13/D-16 conflict. Select `grzyby-submodules` to implement the locked host, or an alternative to stop. Task 3: Complete docs/OpenAPI/TS/dist and boot the user+translate proof host The committed user/translate gitlink paths become the Phase 15 host layout; moving them later requires coordinated submodule and workspace migration. Task 2 selected `grzyby-submodules`, and the Plan 02 translate commit is reachable from its Gitea remote. modules/cabana/README.md, docs/backend/forms.md, docs/backend/admin-controllers.md, admin/tests/form/registry.test.ts, admin/tests/form/MLFields.test.ts, admin/openapi/admin.json, admin/src/api/schema.d.ts, modules/boardwalk/dist/, ../sm-grzybyfunkcjonalne-app/go.mod, ../sm-grzybyfunkcjonalne-app/go.work, ../sm-grzybyfunkcjonalne-app/summer.yaml, ../sm-grzybyfunkcjonalne-app/.gitmodules, ../sm-grzybyfunkcjonalne-app/main.go, ../sm-grzybyfunkcjonalne-app/plugins.gen.go, ../sm-grzybyfunkcjonalne-app/boot_test.go modules/cabana/README.md, docs/backend/forms.md, docs/backend/admin-controllers.md, modules/cabana/openapi_conformance_test.go, admin/package.json, admin/tests/form/registry.test.ts, ../sm-bm-app/go.mod, ../sm-bm-app/go.work, ../sm-bm-app/summer.yaml, ../sm-bm-app/.gitmodules, ../sm-bm-app/main.go, ../sm-bm-app/plugins.gen.go, .planning/phases/14.2.1-translate-plugin/14.2.1-RESEARCH.md (Docs/OpenAPI/TS/dist and Proof-host layout) Update cabana README and backend forms documentation for `markdown`, `mltext`, `mlmarkdown`, nested locale payloads, TranslationWriter, safe markdown, and fail-loud behavior. Update admin-controller docs only where activation/save semantics changed. All examples use neutral blog/acme names. Remove the stale claim that markdown is unavailable. Regenerate admin OpenAPI from the established command, regenerate TypeScript schema with `npm --prefix admin run gen:api`, run the admin build, and copy the resulting Vite output to committed `modules/boardwalk/dist/` using the existing boardwalk build workflow. Do not hand-edit generated JSON, d.ts, or dist assets. Extend registry tests for all three field types. In the already-cloned D-14 host, use the sm-bm-app layout: module `git.golem15.com/golem15/sm-grzybyfunkcjonalne-app`, framework replace `../summercms.go`, submodules at `plugins/golem15/user` and `plugins/golem15/translate`, workspace uses and host replaces for each, and summer.yaml entries for only `golem15.user` and `golem15.translate`. Add submodules from their Gitea remotes, never copy runtime mirrors. Generate main.go/plugins.gen.go with `summer build`. Add `TestBootUserTranslate` that activates the real two plugins and verifies the Locales admin controller is registered; the fixture save/read proof remains the plugin integration smoke from Plans 02/03 rather than a third production plugin. go test ./cmd/summer -count=1 -run 'TestDocsTree' && go run ./cmd/summer docs:build --check && npm --prefix admin run typecheck && npm --prefix admin test -- --run admin/tests/form/registry.test.ts admin/tests/form/MLFields.test.ts && npm --prefix admin run build && go -C ../sm-grzybyfunkcjonalne-app vet ./... && go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslate)$' Non-zero exit; docs checker reports stale identifiers/links/forbidden names; Vitest reports no tests or failures; build omits index.html/assets; host output contains "--- FAIL", "--- SKIP", or "no tests to run", or lacks "--- PASS: TestBootUserTranslate". - README/docs identifiers resolve and contain no consuming-application name. - OpenAPI, schema.d.ts, and boardwalk dist are regenerated and tracked; no generated file is hand-edited. - Registry resolves markdown/mltext/mlmarkdown and unknown ml types still fail boot. - Host `.gitmodules`, `go.work`, `go.mod`, and `summer.yaml` use exact D-16 paths/module IDs. - Generated plugin list imports sm-user-plugin and sm-translate-plugin; Journal is absent. - Host smoke activates both plugins and sees the Locales admin controller. The framework ships typed multilingual controls and the intended host boots user+translate from durable submodules with generated admin artifacts in sync. ## Trust Boundaries | Boundary | Description | |----------|-------------| | Admin browser → cabana CRUD | Untrusted nested multilingual JSON crosses authenticated controller boundary | | Translated markdown → preview DOM | Stored content can carry active HTML payloads | | Cabana → plugin TranslationWriter | Framework delegates scoped writes to an optional plugin service | | Host gitlinks → compiled binary | Remote plugin commits become trusted build inputs | ## STRIDE Threat Register | Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | |-----------|----------|-----------|----------|-------------|-----------------| | T-14.2.1-09 | Tampering | ML nested request | high | mitigate | Lift only declared ML fields; require string locale map; validate locales; execute after host authorization | | T-14.2.1-10 | Elevation of Privilege | TranslationWriter | high | mitigate | No standalone endpoint; writer runs inside permissioned/scoped host save transaction | | T-14.2.1-11 | Tampering | mass assignment | high | mitigate | Default scalar only enters writable host field; locale map never reaches generic Fill | | T-14.2.1-12 | Tampering | markdown preview | high | mitigate | Goldmark safe mode plus unsafe output rejection and CSP-compatible rendering | | T-14.2.1-13 | Tampering | host submodule provenance | medium | mitigate | Exact Gitea remotes and committed gitlinks; generated plugin imports | | T-14.2.1-SC | Tampering | npm packages | high | mitigate | No package installation or version change; existing exact pins only | ASVS L1: all high threats are mitigated; Plan 04 adds fail-when-broken evidence. Run the Task 3 combined gate. Confirm `git status --short` in all three repositories contains only intended tracked source/generated artifacts and gitlink updates. - ML nested saves persist both locales without widening mass assignment. - Markdown and multilingual controls compile, test, and appear in generated API/types/dist. - Docs checks pass with application-neutral examples. - Proof host boots real user+translate plugins and exposes Locales admin. Create `.planning/phases/14.2.1-translate-plugin/14.2.1-03-SUMMARY.md` when done.