- PATTERNS: one conformCase per new admin route, enforced by
TestPhase10ConformanceInventory without Docker (G-16.2-1)
- VALIDATION: 16.2-07-T1 to T6 rows; the Docker-host suites now run in the
gate's postgres stage instead of the Manual-Only table
- go stage: TestPhase10ConformanceInventory (Docker-free), and the Journal
TestSiteCachePurgeConfig and TestSiteCachePurgeRequest by name
- postgres stage: TestPhase10OpenAPIConformance and the Journal
TestCategoriesAdminPurgesSiteCache and TestPostsAdminPurgesSiteCache
- layer stage: the four site cache purge tests by name
- new cache stage: playground build, purge-check, then the acme SSR smoke
on the same route-cached build
- conformCases() lists every case at package level; checkConformInventory
reports every missing, extra or duplicate key in one sorted failure
- TestPhase10ConformanceInventory runs it without a database, and
TestPhase10OpenAPIConformance runs it before the testcontainers setup
- GET /field-types decodes into cabana.FieldTypesResponse and serves the
fixture type's script; POST .../fields/{field}/upload answers 201
cabana.Envelope[cabana.FieldUploadResponse] for a multipart part file
- the fixture plugin registers conformnote and the gadget form uses it,
so create, update and show run its Decode and Load (G-16.2-1, D-13, D-18)
liftMLValues validated only object-shaped submissions, so a scalar
submitted for a translatable plugin field reached Decode as nil, a
float64 or a bool although pact promises a string; a type that asserts
a string panicked into an opaque 500. decodePluginFields now passes a
string through, turns null into the empty string and refuses any other
value with a 422 on the field before Decode runs.
The route called the type's Upload on the streamed part and only then
checked that no second part followed, so a refused request left a
public, unreferenced object in the bucket. It now reads the multipart
body whole (ReadForm, spooling past 8 MiB to a temporary file), checks
it holds exactly one file part named file, and only then calls Upload.
The remaining window (a failed record transaction after Upload, an
element that stopped waiting) is documented.
The field upload route ran only protect, so the create form of a
controller without the create toolbar button could still write files to
the public bucket. It now refuses with 403 where the save route would:
create for {id} 0, update otherwise (operationDeclared).
Fail-closed stages go, admin, editor, layer, docs and postgres with go,
vitest-JSON and TAP detectors, the editor corpus copy comparison and a
self-test that proves each fails on planted output. --all runs postgres
only when a Docker daemon answers and otherwise prints a loud NOT RUN
notice; PHASE162_REQUIRE_POSTGRES=1 makes that a failure.
- fieldTypes: one load per script URL, failed and refused scripts
reported, global styles enabled on every screen, empty list on failure
- registry, formState, FormField and ListView: plugin renderers, locale
maps, notices as literal text, unknown column types as text
- AppShell: the View site link's href, target, rel and en/pl label
- pact: FormFieldError message joining, nil pointer, funcs never serialized
- cabana: registry and option boot rules, compile (option kinds, general
and core keys, CompileForm vs CompileFormWithTypes, settings and relation
refusal), decode 422/500 split, localized and capped notices, Load,
ListValue with the request ctx, field writes, field-types and upload
routes
- TestFieldTypeSavePostgres (Docker host): create, show, untouched and
changed update, refused update, upload in and out of the record scope
- TestFieldTypeUntouched: served values skip Decode, writes and notices;
one changed locale writes and reports only that locale; canonical JSON
comparison; a create never skips
- PluginField: an element that echoes its value on connect is never
written a second time
- SUMMARY with the package report, the human reply, installed versions,
sm-journal-plugin commits and bundle size
- STATE, ROADMAP and the broken-windows ledger updated
- MLPluginField wraps PluginField in the MLField locale switcher; the
element gets the active content locale and that locale's string
- isMLField treats a translatable plugin field as a locale map in the form
state, FormView adopt and the relation child modal
- summer-upload posts the file with the admin session to the field upload
route and answers summer-upload-result {id, url} or {id, error}
- noticeLines shows meta.notices under each field as a warning status line
(text only) and in the saved toast; the stored value is reloaded
- docs/backend/field-types.md documents the full element contract;
modules/boardwalk/dist rebuilt
- translatable types lift, store and hydrate through the ML path
(isMLField); Decode and Load run per locale and notices carry the locale
- on update a value equal to the served value (Load of the stored host
value or exact translation) is neither decoded nor written
- FieldWrite, FieldWritesFromContext and WithFieldWrites expose what a save
writes to the Form hooks
- list columns may use a plugin alias and show the type's ListValue
- POST .../{id}/fields/{field}/upload hands the file to the type's Upload
(guard, X-Requested-With, permissions, field context, record scope, body
cap); OpenAPI, route inventories, README and docs updated
- loadFieldTypes fetches GET /field-types after sign-in, loads each script
once and adds the styles as global links activateStyles never disables
- PluginField creates the type's element, sets the value property and the
contract attributes, takes summer-change as the form value and never
writes back a value the element just emitted
- rendererForField and isRegisteredField route any field with an element
to PluginField in forms, previews and the save payload
- rebuilt modules/boardwalk/dist
- compileFieldTypes builds a per-boot field type registry (alias, core and
cross-plugin collisions, element prefix, assets, option keys) passed down
the compile functions; settings and relation forms refuse plugin aliases
- fields.yaml accepts a plugin alias with the general keys plus the type's
options (typeOptions, element, translatable in the schema); CheckOptions
runs at boot; CompileFormWithTypes for plugin tests
- save decodes plugin values inside the transaction (FormFieldError is a
422, other errors opaque 500) and answers meta.notices (FieldNotice) only
when a type reported something; show and save serve values through Load
- GET /field-types lists the types with versioned asset URLs; OpenAPI,
schema.d.ts, README and docs/backend/field-types.md updated
- pact.HasFormFieldTypes and pact.FormFieldType: a plugin registers admin
form field types under a short alias, with element, assets, options and
CheckOptions/Decode/Load/ListValue/Upload funcs
- FormFieldValue, FormFieldDecoded (value plus notices), FormFieldNotice,
FormFieldUpload, FormFieldUploadResult and FormFieldError
- ListValue takes the request context: func(ctx, value) string