Consolidate edit-history bookkeeping under the single reserved .zddc.d/
sidecar (where tokens + access logs already live), instead of its own
top-level .history/ dot-name:
- history.go: record + text history now write/read <dir>/.zddc.d/history/<stem>/
(was <dir>/.history/<stem>/). Const renamed .history → .zddc.d/history and
unexported (the only external user was the dispatch carve-out). The history
VIEWER endpoints (<record>.yaml?history=1, <file>?history=…) read it
server-side, so they keep working for anyone with read on the live file;
the raw store is bookkeeping, blocked by the existing dot-prefix guard.
- main.go: drop the .history GET carve-out (b9ebee7) — superseded; history is
reached via the viewer, not raw browsing. Reword the guard comment to
"reserve .zddc.d/ bookkeeping" (Part B will replace the blanket block with a
.zddc.d/ admin-fence).
- Delete dead .devshell references (the dev-shell was dropped from the chart):
guard comment, paths.go comment, test fixtures/cases (→ .zddc.d), and docs.
This is Part A of the approved plan: ship history in its permanent home so we
never migrate it twice. Tests updated to the new paths; the obsolete
TestDispatchHistoryReadCarveOut is removed (raw-block covered by
TestDispatchHidesDotPrefixedSegments, viewer by mdhistory_test).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The in-dir form create/update (serveFormCreate/serveFormUpdate) wrote
records with plain WriteAtomic + date+email naming — no audit stamping,
no filename composition, no field_codes/folder_fields. So "+ Add row"
from a per-party mdl/rsk table produced un-stamped, mis-named rows that
the tables tool's own PUT-update path (which composes) would then 422
on. Only PUT and the project rollup honored the record machinery.
Now every record-write entry point converges on WriteWithHistory:
- Extract the shared field_defaults + folder_fields + row-assign +
compose step into recordCreatePrep (history.go); the rollup uses it
too, replacing its inline copy.
- serveFormCreate: when a records: rule with a filename_format applies
in the target dir, compose the name + route through WriteWithHistory;
otherwise keep the generic date+email submission write.
- serveFormUpdate: route through WriteWithHistory unconditionally — it
stamps/historizes records and plain-writes non-records. Editing a
tracking-number component in place now 422s (identity is the
filename; renames are delete+create).
- Drop originator from required: in the per-party mdl/rsk forms and mark
it readOnly, matching the rollup forms — it's server-derived from the
party folder, so a create needn't send it.
Docs (AGENTS.md, ARCHITECTURE.md) updated for the converged wire
surface. Tests: in-dir record create composes + stamps audit +
folder-binds originator; in-dir update bumps revision and rejects an
in-place component edit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two coupled cleanups so the baked-in defaults reflect the actual
convention instead of leaking one project's choices into every
deployment:
- Drop the project-wide phase/area components from the default
filename_format, form schemas, and table columns. They must be
all-on or all-off across a project to keep filenames lexically
consistent, so the simplest default omits them; operators re-enable
via the commented-out templates + a .zddc filename_format override.
Teaching comments (incl. a field_codes: example) now ride along in
defaults.zddc.yaml, which `show-defaults` dumps verbatim.
- Separate suffix from sequence with a template hyphen
({sequence}-{suffix?}); stored suffix is now just the part marker
(A, 01) with no leading dash.
- New records: key `folder_fields: {field: parent-distance}` binds a
body field to an ancestor folder name. The default mdl/rsk records
bind originator to the party folder (distance 1) — the folder is the
sole source of truth. The server overwrites the body value before
validation + composition (WriteWithHistory and the rollup create
path), and the form renderer marks the field read-only and pre-fills
it. Rollup forms drop originator from required (server derives it
from the selected party).
Tests: folder-binding overwrite + wrong-originator-filename 422, and a
form-render readOnly/prefill assertion; existing record tests realigned
so the party folder name equals the originator.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds history_test.go with eight cases exercising the record-write
orchestration path:
- CreateStampsAuditFields: PUT to a fresh mdl path → audit fields
injected; response echoes the stamped YAML; no history dir yet.
- UpdateIncrementsRevisionAndArchivesPrior: second PUT archives
the prior bytes under .history/<base>/<ts>-<sha8>.yaml, bumps
revision, preserves created_*, chains previous_sha.
- ConflictPreservesHistory: 412 from stale If-Match leaves the live
file untouched and writes NO history entry (the failed write must
be a true no-op).
- ClientAuditFieldsStripped: client-supplied created_by / revision
are silently overwritten by server values — anti-forgery test.
- FilenameMismatch: URL says ...-0002 but body composes to ...-0001
→ 422.
- LockedFieldRejected: posting type=SPC to an rsk row → 422 with
/type error (rsk/ locks type=RSK via cascade).
- SSRHistoryAtPartyLevel: writes to archive/<party>/ssr.yaml put
history at archive/<party>/.history/ssr/, NOT at
archive/.history/<party>/.
- RollupCreate_AssignsRowAndComposesFilename: three POSTs to
/project/rsk/form.html in two table-scope groups demonstrate the
server picks up filename_format + row_field+row_scope_fields from
the cascade, auto-assigns sequence row numbers per group, and
composes the canonical filename.
Bug fix surfaced by the first test: composeFilename was eliding TWO
separators around an optional placeholder when one was correct.
"ACM-{phase?}-PRJ" with phase="" was producing "ACMPRJ" instead of
"ACM-PRJ". Now drops only the trailing separator from output and
lets the next iteration emit the connector.
Default-project-{mdl,rsk}.form.yaml updated: project-rollup MDL +
RSK schemas gained the six readOnly audit fields and the project-
rsk schema picked up the full table-tracking component shape (+
row) plus an enum-locked type=RSK. The required: list no longer
includes type for rsk schemas — the cascade's field_defaults
injects it after schema validation, and requiring it would 422
well-behaved clients.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>