Skip to content

docs: correct nine claims the code does not deliver - #14

Merged
b2m9 merged 2 commits into
mainfrom
docs/correct-guardrail-claims
Aug 13, 2026
Merged

docs: correct nine claims the code does not deliver#14
b2m9 merged 2 commits into
mainfrom
docs/correct-guardrail-claims

Conversation

@b2m9

@b2m9 b2m9 commented Aug 13, 2026

Copy link
Copy Markdown
Owner

A follow-on from #13. Reviewing the reference-idempotence overclaim surfaced the same defect class elsewhere, so I swept the whole README and AGENTS.md. No behavior change — documentation, one comment, one test.

Nine statements were stronger than the implementation. Each was verified against running code before and after the edit.

Claim Reality
"cost scales with the delta, not with the state" a one-field delta onto a 500-field object reads 501 fields — every changed object is shallow-copied in full, ancestors included
a replace path "swaps in whatever the delta supplies without inspecting it" at items[], reconciliation validates the item before the boundary applies
replaced values "never scanned" same exception
"keyfold does not inspect untrusted data" it reads keys, values, identities and tombstones
a keyBy policy on the wrong shape "usually throws" a plain object there merges ({a:1} + {b:2}{a:1,b:2}); only an array reaches reconciliation
KeyfoldMergeError covers "base or delta data [that] violates the contract" much is undetected; a recursed cycle throws native RangeError
"a tombstone carrying patch fields throws" unsafe keys on a tombstone are ignored
DELETE_TOKEN "can no longer be stored as ordinary field data" it survives inside a replacement or an unkeyed array — as the Deletes section already said
"matching items merge in place" read as mutation

The AGENTS.md constraint was titled "Work remains delta-proportional" — the same false claim, in the file a future writer would consult to restate it. It is now named for what it actually forbids: never recursively traverse an unmentioned subtree.

Test

One new test pins the cyclic-input limit at all four positions, since the README now makes a specific promise about it: a cycle in an unmentioned branch, at a replace path, and inside an unkeyed array all pass through untouched, while one the fold recurses through raises RangeError.

Review

Three rounds with GPT-5.6-sol, and it caught me overcorrecting in each one — "carries a replacement", then "never validates", then "unsafe keys are ignored everywhere". Precise statements about these semantics are genuinely hard to write, which is the honest argument for pinning them with tests rather than prose wherever possible.

Not included: the replacement-equality premise at README:197, which belongs with #13 since that PR rewrites the adjacent line.

118 tests, vp run check green.


Summary by cubic

Corrects nine documentation claims that overstated keyfold semantics and renames an agent constraint; behavior is unchanged. Adds a test pinning the documented cycle limit.

  • Docs sweep in README.md and AGENTS.md; one new test in test/merge.test.ts; no runtime changes.
  • Cost: merges never traverse unmentioned subtrees; changed plain objects are shallow-copied across own fields; keyed reconciliation indexes delta, scans base, and builds a candidate list.
  • Replacement boundaries: replacement contents are never traversed; at items[], reconciliation still reads identity and tombstone form before the boundary applies.
  • Keyed lists: a keyBy policy engages only when the delta at that path is an array; a non-array delta merges; when the base is an array, base items are validated too.
  • Errors: KeyfoldMergeError covers only detected keyed-list protocol violations; other invalid inputs may pass; a cycle the fold recurses through raises native RangeError.
  • Deletes: unsafe keys on tombstones are ignored; DELETE_TOKEN remains data inside replacements and unkeyed arrays.
  • Wording fixes: “matching items are folded at their existing positions” (avoid mutation wording); agent constraint renamed to “Never recursively traverse an unmentioned subtree.”
  • Cycle behavior: new test confirms pass-through for cycles in unmentioned branches, at replace paths, and in unkeyed arrays; recursion into a cycle throws RangeError.

Written for commit df997fd. Summary will update on new commits.

Review in cubic

A sweep for statements stronger than the implementation, prompted by the
same defect as the reference-idempotence bullet, found nine across the
API, Deletes, Errors, Semantics and Guardrails sections.

Cost is not delta-proportional. Unmentioned subtrees are still never
traversed, which is the guarantee worth keeping, but every recursively
merged plain object whose result changes is shallow-copied across its
own fields, changed ancestors included: changing one existing field of a
five-hundred-field object reads five hundred and one. Keyed
reconciliation indexes every delta item, scans every base item and
builds a candidate list. AGENTS.md carried the claim as a constraint,
now named for what it actually forbids.

Replacement is not entirely uninspected. At `items[]` reconciliation
reads the item's identity and `$delete` before the boundary applies,
because that is what decides which item is replaced.

'keyfold does not inspect untrusted data' was false, and 'never
validates' would have been false too: it runs limited protocol checks
during reconciliation. It validates no application schema, which is the
claim that holds.

A cyclic input does not simply overflow; only a cycle the fold recurses
through does. One in an unmentioned branch, at a replace path, or in an
unkeyed array passes through, now pinned by a test.

A keyBy policy engages only when the delta at that path is an array. A
plain object there merges with a plain-object base rather than replacing
it, and reconciliation validates base items as well as delta items.

KeyfoldMergeError does not cover every contract violation. Undetected
input passes, and a recursed cycle raises a native RangeError.

A tombstone carrying an unsafe key does not throw; unsafe keys are
ignored there as everywhere.

DELETE_TOKEN remains ordinary data inside a replacement or an unkeyed
array, which the Deletes section already said.

'Matching items merge in place' read as mutation.

No behavior change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
b2m9 added a commit that referenced this pull request Aug 13, 2026
The bullet above the reference-idempotence one said replaced values
count as unchanged only when they are the very same reference. That is
the consequence, not the rule: equality is Object.is, so an equal
primitive at a replace path is unchanged without any shared reference.
Both bullets now rest on the same premise.

Found while sweeping the rest of the file for claims stronger than the
code delivers, which is #14.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 3 files

Re-trigger cubic

@b2m9
b2m9 merged commit fb57fe6 into main Aug 13, 2026
3 checks passed
@b2m9
b2m9 deleted the docs/correct-guardrail-claims branch August 13, 2026 13:34
b2m9 added a commit that referenced this pull request Aug 13, 2026
Both claims corrected in #14 were ones I first stated backwards while
reading the code, and neither had a test that would have caught it.

A keyBy policy engages only when the delta at that path is an array. I
had written that a non-array replaces the list; it does not. It follows
ordinary merge semantics, so a plain object folds onto a plain-object
base and returns it by reference when nothing changes, and replaces only
when the base is something else.

An `items[]` replacement is verbatim only after reconciliation has
decided which item it applies to. I had written that the value goes in
without inspection; a missing identity, a duplicate, or a malformed
tombstone all throw first, and a tombstone still removes its item rather
than being swapped in.

The existing tests varied the base shape at a keyed path and covered the
happy path for item replacement, so neither boundary was pinned from the
delta side.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant