Skip to content

Commit e37ea4d

Browse files
fix(spec): the published effae80 changelog entry states one undoable capture set (#19661)
Fixes #19301 Clause-②: no ## What this changes One sentence, inside the single `effae80` entry of `packages/spec/CHANGELOG.md`. Nothing else in that entry moves, and no other entry is touched. **FROM** — "`undoable` now has its anchor — the patch names exactly the fields whose prior values are captured." **TO** — "`undoable` now has its anchor — the merged write bag, `patch` UNDER the collected `params`, names exactly the fields whose prior values are captured." The replacement wording is the one the settled `ActionSchema.undoable` description already uses, so the entry, the schema description and the executor now say one thing. ## The entry was contradicting itself The single entry for `effae80: feat(spec): a row action gets the declarative single-record field write` stated BOTH capture sets, four lines apart — the sentence corrected above, against "`undoable` captures the prior values of exactly the fields written" in the same entry's executor-contract paragraph. ## Which of the two shipped — measured at `3c10091` The written set, on four independent readings: 1. `declarativeUpdateWrite` (`packages/runtime/src/action-execution.ts`) builds the write bag as `{ ...patch, ...params }`. 2. `executeDeclarativeUpdateAction` keys `undoData` off `Object.keys(data)` over that same bag, so on any params-carrying action the capture is strictly wider than `patch` names. 3. `packages/runtime/src/action-declarative-update.test.ts` pins exactly that: "`undoable: true` carries the prior values of EXACTLY the fields written". 4. The entry's own `patch` bullet, four lines above the corrected sentence, already says the static values are "merged UNDER the values `params` collects (a param of the same name wins)". So the two sentences were never two phrasings of one set, and the corrected one was wrong on the day it was written — it is not a record of behaviour that later changed. Correcting it makes the record agree with the release it describes rather than rewriting shipped history. **The entry documents 17.3.0, not 17.4.0.** It sits under the `## 17.3.0` heading; the file ships inside the current `17.4.0` tarball because `CHANGELOG.md` accumulates. The published surface is real either way — this note only keeps the version attribution straight. ## Why a PR at all, and why this shape `AGENTS.md` (Documentation Guardrails) rules the route for this exact case: a `packages/*/CHANGELOG.md` is RELEASE-OWNED and never edited in a code PR, and "Factual error in a released entry → **amend that entry in a dedicated docs-only PR**, ⛔ never an erratum in a later entry and never a rider on code changes". This is that dedicated docs-only PR: no code rides along, the amendment is in place, and no later entry carries an erratum. No mechanical gate forbids the edit. `packages/spec/CHANGELOG.md` is on no governed-surface row (Prime Directive 14 names `docs/adr/**`, `docs/NORTH-STAR.md`, `.claude/**`, `skills/**`, `AGENTS.md`, `CLAUDE.md`), and the standing red line names `content/docs/releases/`, a different path. ## Changeset — measured, not assumed `patch`, not `skip-changeset`. - `packages/spec` is published: `private` unset, version `17.4.0`. - `CHANGELOG.md` is named in its `files[]`, and `npm pack --dry-run --ignore-scripts` on the package lists 277 files with `CHANGELOG.md` among them. The bytes this PR changes ship inside the tarball. - `AGENTS.md` Post-Task Checklist step 3: `skip-changeset` "is for a diff that publishes nothing from any released package". This diff publishes changed bytes from a released package, so that label would be a false declaration. - Repo precedent agrees — prose-only corrections inside published packages take `patch` here. No labels are written by this PR: the dispatch named none, and `skip-changeset` is measured above as not applicable. ## Verification Gate families were derived in this worktree rather than carried: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `3c10091` names **61**. All 61 were run, each exit code captured to a file before any pipe, and reconciled with `--ran`: **57 exit 0, 4 NOT MEASURED**. The four are `check:dts-closure`, `check:dual-build-cjs-loads`, `check:lean-entry-closure` and `check:sourcemap-no-sources-content`. Each exited **3 — PREREQUISITE NOT MET**, because no package carries a `dist/` in this worktree. They read built output across the whole workspace; this diff contains no TypeScript, so nothing it changes can reach a `dist/`. CI's `Build Core` covers them. Package level, through the shared verify lock: - `pnpm --filter @objectstack/spec test` — exit 0, 511 files passed / 1 skipped, 14951 tests passed. - `pnpm --filter @objectstack/spec typecheck` — exit 0. - `pnpm --filter @objectstack/spec exec vitest run src/system/compliance-families-retirement.test.ts` — exit 0, 10 tests. That suite walks `CHANGELOG.md` files and skips this filename by name. `pnpm lint` narrows to nothing here, and the narrowing is measured rather than assumed: every `files:` block in `eslint.config.mjs` targets `{ts,tsx,mts,cts,js,jsx,mjs,cjs}` and no markdown processor is configured; `eslint --no-inline-config --format json` over both changed paths returns 2 file results, each carrying only "File ignored because no matching configuration was supplied.", with 0 errors and 0 rule warnings. The diff holds no file in eslint's population, so no untouched file's verdict can move. Not measured locally, with its reason: the two cross-package `action-owner-key-single-source.test.ts` suites (`packages/runtime`, `packages/objectql`) fail to resolve `@objectstack/objectql`'s entry in an unbuilt worktree. Neither can be moved by this diff by construction — their `SCANNED_EXTENSION` is `.ts`, and both `CHANGELOG.md` and `.changeset/` are ruled exclusions in their `NOT_A_STALE_MENTION` table. ## Acceptance notes Noted, not filed, and deliberately not fixed here — fixing it would turn this into a code PR, which the route above forbids: - `packages/runtime/src/action-execution.ts`, in the comment directly above the `undoData` loop, reads "EXACTLY the fields written — the patch names them". The first clause is right; the second carries the same retired patch-only phrasing, immediately above a loop that iterates the merged bag. Internal only — `@objectstack/runtime` ships `dist`, `README.md` and `CHANGELOG.md`, so `src/` never reaches the tarball. No carrier PR or person is known for that file. --- _Generated by [Claude Code](https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr)_ Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: os-support-ai <318092878+os-support-ai@users.noreply.github.com>
1 parent b971924 commit e37ea4d

2 files changed

Lines changed: 36 additions & 1 deletion

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
fix(spec): the published `effae80` changelog entry states ONE `undoable` capture set — the one that shipped (#19301)
6+
7+
Clause-②: no
8+
9+
`packages/spec` ships `CHANGELOG.md` inside its npm tarball (it is named in
10+
`files[]`), so an entry there is a published surface — the text an upgrading
11+
agent greps. The single entry for `effae80: feat(spec): a row action gets the
12+
declarative single-record field write` stated BOTH capture sets, four lines
13+
apart:
14+
15+
> `undoable` now has its anchor — the patch names exactly the fields whose prior values are captured.
16+
17+
> `undoable` captures the prior values of exactly the fields written.
18+
19+
**The second is the one that shipped.** `declarativeUpdateWrite` builds the
20+
write bag as `{ ...patch, ...params }`, and `executeDeclarativeUpdateAction`
21+
keys `undoData` off `Object.keys(data)` over that bag — so on any
22+
params-carrying action the capture is strictly wider than `patch` names. The
23+
same entry's own `patch` bullet already says the static values are "merged
24+
UNDER the values `params` collects (a param of the same name wins)". The first
25+
sentence was wrong when it was written; it is not a record of behaviour that
26+
later changed.
27+
28+
The first sentence now names the merged write bag, in the wording the settled
29+
`ActionSchema.undoable` description uses. Nothing else in the entry moves and
30+
no other entry is touched: the correction is an **amendment in place**, not an
31+
erratum in a later entry — a reader who greps the old promise lands on this
32+
entry and nowhere else.
33+
34+
- **Prose only.** No key, export, accept set, refusal, tombstone or generated
35+
artifact moves; the same author input parses the same way before and after.

‎packages/spec/CHANGELOG.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3915,7 +3915,7 @@
39153915

39163916
- `operation: 'update'` — the declarative single-record field write. One member by ruling; `'delete'` and `'custom'` are refused with the reason (a row delete is the object's own affordance; `'custom'` means "dispatch the action this def names", and on a row the action is already the action).
39173917
- `patch: Record<string, unknown>` — static field values written to the current record, merged UNDER the values `params` collects (a param of the same name wins). Passed through verbatim.
3918-
- The action's existing `params`, `visible`, `confirmText` and `undoable` keys are reused, nothing duplicated; `undoable` now has its anchor — the patch names exactly the fields whose prior values are captured.
3918+
- The action's existing `params`, `visible`, `confirmText` and `undoable` keys are reused, nothing duplicated; `undoable` now has its anchor — the merged write bag, `patch` UNDER the collected `params`, names exactly the fields whose prior values are captured.
39193919

39203920
Key shape, pinned for contract review: `operation` is a parallel key beside `type`, not a new `ActionType` member. `type` stays at its default `'script'` — the platform action route, which is where the write is performed — and answers WHERE the action dispatches; `operation` answers WHAT the platform does there. Every contradiction is refused at its own path with a prescription: any other explicit `type`, `target`, `body`, `method`, `bodyExtra`, `bodyShape`, `recordIdParam`/`recordIdField`, `onSuccess`, `opensInNewTab`/`newTabUrl` beside `operation: 'update'`; `patch` without it; `operation: 'update'` with neither `patch` nor `params`; a `list_toolbar` location (no current record). `defineStack` refuses a standalone `operation: 'update'` action that names no `objectName` (an object-embedded one is bound by the object it is written on). An inline page-element action cannot carry the keys at all.
39213921

0 commit comments

Comments
 (0)