diff --git a/AGENTS.md b/AGENTS.md index d7f57da..10c446e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -451,7 +451,14 @@ Defaults are baked into `defaults.zddc.yaml`; `field_codes:` ships empty (every - To add a new table type: declare a `records:` entry under the appropriate `paths:` level (or a sibling `.zddc` in the folder) with a `filename_format` referencing fields the body carries. - To inspect a record's revision history: `curl https:///.yaml?history=1 -H 'Authorization: Bearer …'`. -Source: `zddc/internal/handler/history.go`, `zddc/internal/zddc/field_codes.go`, `zddc/internal/zddc/walker.go`, `zddc/internal/zddc/cascade.go`, `zddc/internal/zddc/defaults.zddc.yaml`. Tests: `zddc/internal/handler/history_test.go`. +**Server-side only (offline gap)**: every record guarantee — audit stamping, immutable history, `filename_format` composition, `field_codes`/`locked` validation, and `folder_fields` binding — runs in zddc-server (`WriteWithHistory` + the form handlers). The tools opened offline (`file://` or the File-System-Access picker, no server) **cannot** enforce any of it: a record write needs the server. This is by design — the server is the authority — but it means folder-bound originator, composed filenames, and audit fields don't materialize for purely-offline edits. + +**Upgrading a pre-folder-binding deployment** (records created before these defaults): +- Stored `suffix:` values that carried a leading dash under the old `-A` convention now compose a doubled dash (`…0001--A`) and 422 on next edit. Strip the leading dash from `suffix:` values (`-A` → `A`); the cascade's `filename_format` supplies the separator now. +- A row whose `originator` differs from its party-folder name is silently rewritten to the folder name on the next write (the folder is the source of truth). Filenames whose originator segment disagrees with the folder will 422 until the file is renamed to match. +- Deployments that used the project-wide `phase`/`area` components already supplied a custom `form.yaml` + `.zddc` override (the prior default couldn't compose those slots otherwise), so the phase/area removal from the embedded defaults doesn't affect them. + +Source: `zddc/internal/handler/history.go`, `zddc/internal/zddc/field_codes.go`, `zddc/internal/zddc/walker.go`, `zddc/internal/zddc/cascade.go`, `zddc/internal/zddc/defaults.zddc.yaml`. Tests: `zddc/internal/handler/history_test.go`, `zddc/internal/zddc/field_codes_test.go`. ## Implementation-vs-dependency policy diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index aa3b181..f9438f0 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -470,7 +470,7 @@ app.state.subscribe((property, newValue) => { **Server-side counterpart:** `zddc/internal/handler/formhandler.go` recognizes `*.form.html` and `*.yaml.html` URLs, parses the spec, validates submissions via `zddc/internal/jsonschema/`, writes via `zddc.WriteAtomic` (plain submissions) or `zddc/internal/handler/history.go` `WriteWithHistory` (record-typed YAML — mdl rows, rsk rows, ssr.yaml). Existence of `.form.yaml` is the trigger; without it, the URL falls through to static-file serving. -**Record-vs-submission distinction.** "Records" are the three table-store types (mdl/rsk/ssr); everything else is a "submission." Records get server-stamped audit fields (`created_at`/`_by`, `updated_at`/`_by`, `revision`, `previous_sha`), an immutable per-record history at `/.history//-.`, cascade-driven filename composition (via the `records:` + `field_codes:` `.zddc` keys), per-folder field locking (e.g. type=RSK in rsk/), and folder-bound fields (`folder_fields`, e.g. originator = party-folder name). The mechanism intercepts at every write entry point — the file-API `serveFilePut` (if `isRecordPath` matches → `WriteWithHistory`, else `WriteAtomic`), the in-dir form create/update (`serveFormCreate`/`serveFormUpdate`), and the project rollup (`serveFormCreateRollup`). Each resolves the `records:` rule for the target directory and, when one with a `filename_format` applies, composes the name via the shared `recordCreatePrep` and routes through `WriteWithHistory`; non-record paths keep the historical date+email `WriteAtomic` write. The convergence means there's no back door that writes an un-stamped, un-composed record. See AGENTS.md "Records, audit, and history" for the operator surface; `zddc/internal/handler/history.go` for the orchestration. +**Record-vs-submission distinction.** "Records" are the three table-store types (mdl/rsk/ssr); everything else is a "submission." Records get server-stamped audit fields (`created_at`/`_by`, `updated_at`/`_by`, `revision`, `previous_sha`), an immutable per-record history at `/.history//-.`, cascade-driven filename composition (via the `records:` + `field_codes:` `.zddc` keys), per-folder field locking (e.g. type=RSK in rsk/), and folder-bound fields (`folder_fields`, e.g. originator = party-folder name). The mechanism intercepts at every write entry point — the file-API `serveFilePut` (if `isRecordPath` matches → `WriteWithHistory`, else `WriteAtomic`), the in-dir form create/update (`serveFormCreate`/`serveFormUpdate`), and the project rollup (`serveFormCreateRollup`). Each resolves the `records:` rule for the target directory and, when one with a `filename_format` applies, composes the name via the shared `recordCreatePrep` and routes through `WriteWithHistory`; non-record paths keep the historical date+email `WriteAtomic` write. The convergence means there's no back door that writes an un-stamped, un-composed record. All of it is server-side: the tools opened offline (`file://` / FS-Access, no server) can't enforce audit, composition, `field_codes`, or `folder_fields` — record writes need zddc-server. See AGENTS.md "Records, audit, and history" for the operator surface (incl. the offline gap and pre-folder-binding upgrade notes); `zddc/internal/handler/history.go` for the orchestration. **Round-trip philosophy:** v0 is "form-as-truth" — submission YAML is regenerated from form state on every save. Hand-edits to submission files are not preserved across re-edit→re-submit. v1 will add an opt-in "file-as-truth" mode (eemeli/yaml Document API) for forms like `.zddc` itself where users hand-edit and comments must survive.