fix(metadata-protocol): the default /diff from side is the nearest earlier version whose body differs (#20451) - #20518
Conversation
…ineages Claude-Session: https://claude.ai/code/session_01N8TPEsoJxPsdSdNKGnNGEN Co-authored-by: Claude <noreply@anthropic.com>
…rlier row whose body differs With no fromVersion, diffMetaItem walks back from the to side over the history rows it already read and stops at the first row whose body differs by diffShallow's own equality, a body-less (delete) row comparing as an empty object. An explicit version on either side is used as named. Claude-Session: https://claude.ai/code/session_01N8TPEsoJxPsdSdNKGnNGEN Co-authored-by: Claude <noreply@anthropic.com>
…ule is documented; add the changeset The response schema's JSDoc, the route's OpenAPI summary and the SDK's diffItem docblock now say what an omitted from and to mean. The pending sibling changeset's sentence about the from default is corrected for the same release. Claude-Session: https://claude.ai/code/session_01N8TPEsoJxPsdSdNKGnNGEN Co-authored-by: Claude <noreply@anthropic.com>
… version Claude-Session: https://claude.ai/code/session_01N8TPEsoJxPsdSdNKGnNGEN Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 4 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 142 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d8cf3e852f016ca7b707ceac55522cc1aacc5ea6 && git checkout d8cf3e852f016ca7b707ceac55522cc1aacc5ea6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9e9bb464170446bf993244d2b0058b935300a247 1f258bbd57a4114b1d26d532091e15b909124b91 && git checkout -B drift-repro 9e9bb464170446bf993244d2b0058b935300a247 && git merge --no-ff 1f258bbd57a4114b1d26d532091e15b909124b91
node scripts/docs-audit/affected-docs.mjs --json 9e9bb464170446bf993244d2b0058b935300a247
|
Contract reviewServed-tier: Inputs read: card #20451 (body and all eight comments: triage 5871560495, claim 5875166154, os-dev-report 5875426428, retriage request 5875470546, retriage answer 5875579209, re-claim 5877570954, os-dev-report 5879297665, seat answer 5879325435); PR #20518 body, file list (8 files, +515 −57) and the net diff against the merge base with ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
|
Dequeued at 22:42Z and re-queued at 22:44Z. The failing check was not this PR's content.
|
Fixes #20451
Clause-②: no
What changes
diffMetaItem(packages/metadata-protocol/src/protocol.ts), the defaultfromside only. With nofromVersion, the from side is now the nearest earlier history row whose body differs from the to side's, by the diff's own equality:diffShallow's three buckets not all empty, with an absent body compared as{}(the same?? {}the comparison below it uses). A body-less row (a delete's tombstone) therefore differs from any non-empty to side, and the walk stops on it (triage's answer A, 5875579209). With no earlier row that differs, the from side is absent:fromVersion: null, everything added.findoversys_metadata_history(no limit), which the function sorts byversionin memory. It adds no read and no cap. A unit pin asserts one history read on a walk path.operation_typeand adds no state column (ruling B on [finding]GET /meta/:type/:name/diffserves PENDING draft content to a member with no authoring capability: its history versions include draft saves, and it is the one draft-serving door the #20338 gate leaves open #20378, 5865708652).?from=/?to=names exactly its versions. The to-side default (the active row's own version, PR fix(metadata-protocol): the default /diff range labels its to side with the active row's own version #20443) is untouched. An explicit?to=with no?from=walks back from the named version's body.The four statements of the default rule now say the new rule, each in its own words: the
diffMetaItemdocblock,DiffMetaItemResponseSchema's JSDoc (packages/spec), the route's OpenAPI summary inrest-server.ts, and the SDK'sdiffItemdocblock (packages/client). Comment and summary text only: no schema, route or signature change.Measured on the real REST stack
The REST pins (
packages/rest/src/meta-diff-default-range-labels.test.ts, real routes and real writes over better-sqlite3:memory:) were committed first (9b308b12b) and run against the unchanged source with its closure built: 3 failed, 9 passed. After the change: 12 passed.sys_metadata_history)createA (active), v2createB (draft save), v3publishB2 → 3, empty1 → 3,labelandcolumnschanged, equal to?from=1&to=32 → 3, empty1 → 3createA, v2delete(no body), v3createA2 (draft), v4publishA23 → 4, empty2 → 4, everything added, equal to?from=2&to=4createA, v2delete(no body), v3createB (active)2 → 3, everything addedcreateNew (draft), v2publishNew1 → 2, emptynull → 2, everything addedcreate(active) onlynull → 1, everything added?from=2&to=3over the first lineage2 → 3, emptyThe unit pins in
protocol.diff-dead-history-read.test.tsrepeat these lineages over seeded rows beside the file's read-counting double. Ablation, from the committed state:node scripts/ablation-replace.mjsreplaced the walk's differ test (if (d.added.length || d.removed.length || d.changed.length) {→if (true) {, which is the old immediately-previous rule), anchor 1 → 0, bloba2d2b7686f29→dd9cffcbd1f9; the file ran 6 failed / 14 passed, exactly the six walk-dependent pins; restored, blob == HEAD andgit diff HEADempty. No build is involved: the metadata-protocol suite imports./index.jsfrom source.A pending release note corrected: needs confirmation (Check Changeset stays red)
.changeset/20397-diff-default-range-labels.md(PR #20443, not yet released) said "The defaultfromVersionis still the history version immediately before that label." This PR makes that sentence false in the same release, so it now reads: "The defaultfromVersionrule is not changed by this entry (#20451, in the same release, then moves it to the nearest earlier version whose body differs from the to side's)." One sentence, nothing else in that file.This is the DELIBERATE CORRECTION class
check-empty-changeset.mjsnames, so that gate exits 1 locally and Check Changeset will stay red on purpose. Please confirm the correction on this PR. It was outside the claim's file surface.skip-changesetis not applied and must not be.Changeset
.changeset/20451-diff-default-from-differs.md:@objectstack/metadata-protocolpatchand@objectstack/restpatch,Clause-②: no. The rest line is there because the route's OpenAPI summary is a runtime string served in the OpenAPI document. Thepackages/specJSDoc and thepackages/clientdocblock are comment-only, so they get no line, per the repo's rule that comments do not publish. All three packages are in onefixedgroup, so versions do not move differently either way.Verification (measured at
1f258bbd5, after mergingorigin/main9449512a3with a true merge commit)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 88 commands, all run, each exit code written to a file before any pipe. 87 exit 0; 1 exit 1:node scripts/check-empty-changeset.mjs --base origin/main, the release-note correction above. Also run, outside the derivation: the four roster gates whose roster sits under a changed path (check-changeset-fixed,check:meta-url-spelling,check:spec-changes,check:error-code-casing), all exit 0.dispatch-gates --ran: "88 derived famil(ies) accounted for — 88 run, 0 NOT-MEASURED".os-verify-lock: metadata-protocol 189 files passed, 3 skipped (2759 tests); rest--project local219 files passed (4181 tests); client 50 files passed (641 tests); spec--project local573 files passed (16801 tests).check:test-typecheck), client (withcheck:test-typecheck) and spec all exit 0. Both edited test files are in their tsc programs (--listFiles: 1 hit each).pnpm --filter @objectstack/spec check:generated: all 15 generated artifacts up to date, against a specdistbuilt from this tree.dispatch-gateslists as CI's own shell.Statements the census named, measured
meta-diff-default-range-labels.test.ts(this PR adds to that file), which said the from side is "the history row immediately preceding that label".docs/qa/platform-checklist/areas/studio-authoring.jsonlines 346 and 402 ("omit the params for previous-vs-current"). On that probe's lifecycle (draft save, publish, second draft save, second publish) the default range now compares the second published revision with the first (2 → 4), which is the comparison those steps describe. Before this PR it compared the second publish with its own draft save and answered "no changes".rest-server-query-number-reads.test.ts:300("from/to still mean previous-vs-current (no version members)"). It asserts only that no version member reaches the verb, which still holds. This PR does not touch that file.Acceptance notes
?from=1&to=2compares the probe's first draft save (v1) with its own publish (v2). In the draft-then-publish lifecycle those two rows carry the same body, so that range answers "no changes", before and after this PR. This is a checklist wording issue, not a product defect. No card filed; carrier: none.DiffMetaItemResponseSchema.fromVersion's.describe()reads "nullwhen that side is absent (e.g. the item had no earlier version)". It is still true, and nownullalso answers "no earlier version differs". It was left as is: a.describe()edit regenerates spec docs, and the claim limitspackages/specto comment text..changeset/20139-rest-query-number-census.mdsays "previous-vs-current on/diff" about absent parameters keeping their default. That is still true of the parameter handling. It is somebody else's pending note and is not touched.seedLineageinprotocol.diff-dead-history-read.test.tsmoved from inside the [finding]GET /meta/:type/:name/diffwith nofrom/tolabelstoVersionas the newest history row (a draft save) while it compares against the active row, so the default diff names the wrong versions #20397describeto module scope, unchanged, so theGET /meta/:type/:name/diffwith nofrom: after a draft is published, the default range compares the new version against the draft's own save and answers "no changes", never against the previous published version #20451 block shares it rather than copying it.Generated by Claude Code