Repository navigation
fix(metadata-protocol): the strict draft read naming no package serves its content from the draft its version names - #23727
Conversation
…to the draft its version names (WIP) Claude-Session: https://claude.ai/code/session_01HTBbFnY86QT5g7BxeMo3Ub Co-authored-by: Claude <noreply@anthropic.com>
…s the draft its version names Over drafts [package-unbound, Q] with no active row, GET ?state=draft served its version from the package-unbound draft (headAt) but its content from the first draft row a package-agnostic lookup returned. The read now takes its content from packagelessDraftAddress, the address the draft save, the per-item publish and the draft discard naming no package use. Claude-Session: https://claude.ai/code/session_01HTBbFnY86QT5g7BxeMo3Ub Co-authored-by: Claude <noreply@anthropic.com>
…erves the draft the strict read serves Claude-Session: https://claude.ai/code/session_01HTBbFnY86QT5g7BxeMo3Ub Co-authored-by: Claude <noreply@anthropic.com>
…ave's no-package-unbound-draft qualifier Description only. Claude-Session: https://claude.ai/code/session_01HTBbFnY86QT5g7BxeMo3Ub Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HTBbFnY86QT5g7BxeMo3Ub Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 143 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 7865f5f582ffd104e2c1bbe51181ba6b1e1b2b51 && git checkout 7865f5f582ffd104e2c1bbe51181ba6b1e1b2b51
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6ee483cdb217e26b477e7eda9706488077e12a59 eaa4b0ed563a796fbb1722342de936f8a8ee65d6 && git checkout -B drift-repro 6ee483cdb217e26b477e7eda9706488077e12a59 && git merge --no-ff eaa4b0ed563a796fbb1722342de936f8a8ee65d6
node scripts/docs-audit/affected-docs.mjs --json 6ee483cdb217e26b477e7eda9706488077e12a59
|
Contract reviewServed-tier: Read: card #23715 (body, rider 6200550012, triage 6200989991, claim 6201610005, dispatch 6201650972, report 6202831799); PR #23727's body, file list and the net diff ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
席位核对 ·
|
| Item | Reading |
|---|---|
| Shape | PR → main. Line 1 is Fixes #23715, the only closing keyword. #23657 / PR #23706 and #23495 / PR #23654 are named without one. |
| Scope | 6 files (+292 / −13): protocol.ts (getMetaItem's draft content address through the new private strictDraftReadAddress, and servedOverlayRowCandidates / findServedOverlayRow taking packageId: null as "the package-unbound row only"); PublishMetaItemRequestSchema.packageId's description (the rider 6200550012) and its regenerated protocol.mdx row; pins in metadata-protocol and packages/rest; and the changeset. That is the claim's surface (6201610005). The cross-lane parts are declared: the spec hunk on #18883 (6201661454), the packages/rest test on #6024 (6203379542). No governed path (check-governed-merges.mjs --pr 23727: 0 of 6). Under the 3,000-line threshold. |
Clause-② |
yes, in the claim, the PR body and the changeset; @objectstack/metadata-protocol minor, @objectstack/spec patch. The review (②) reads that the diff earns no with both patch, and that yes is admissible and gate-consistent (declaring yes is never wrong, references/lanes/spec.md). The seat keeps option A as shipped. Moving to B costs a commit, a new head and a re-rendered record for a level the gates already accept. |
| Contract review | 6203538652, PASS at this head, CONTRACT_REVIEW_TIER (schema face, Scope: line). The seat checked one load-bearing claim itself. servedOverlayRowCandidates' new null arm is reached only from strictDraftReadAddress. The pre-existing callers pass a string or undefined: addressPackages is typed string | undefined, the active read passes contentPackageId conditionally, and the expansion loop passes a real package. So no existing read moves. |
| Pins and controls | Negative control on main (the in-memory double): 2 red. The read served Q's / P's body under the unbound draft's token. better-sqlite3 agreed by index order on main, so those pins are positive pins. Ablation (main's address logic restored): exactly the 2 divergence pins red; the restore is green. |
| Gates | The dev's derived union: 111 derived, 110 exit 0, 1 NOT MEASURED (check:dual-build-cjs-loads, hosted by CI's Build Core, green). CI on this head: 41 check-runs, 36 success, 5 skipped (the path-filtered roster), none failed. |
Disposition:
- open_questions: [0] A kept (above). [1] A: best-effort content on an ambiguous address. The
versionis null there and the save naming none is refused409, so noIf-Matchwrites a row other than the one read. A refusing read would be a narrowing no card has ruled, with no measured pull (startup-focus axis). It goes to Acceptance notes and is not filed. [2] A: thepreviewDraftsarm shares the address and the helper. [3], [4] accepted, as the record judges them (the dev container's own checkout; a--force-with-leaseon its own branch before the PR existed). out_of_scope_findings: the list's draft-preview merge (mergePackageAwareOverlay) was not measured. It carries no per-itemversion, so it cannot pair content with the wrong token, which was this card's harm. That goes to Acceptance notes and is not filed. The open PRs onprotocol.tsare in other regions; fix(metadata-protocol): an orphan adoption rebinds a draft view in storage only, never registering it live #23724 has landed (9bb93706f), and the queue rebuilds on it.- The rider 6200550012 is carried here and closes with this card.
Generated by Claude Code
Fixes #23715
Clause-②: yes
Dev session
session_01HTBbFnY86QT5g7BxeMo3Ub, dispatched bydomain:engineseat 1 (session_01GP3comALHRcRBixS2TEnLF), claim 6201610005. Related, not closed here: #23657 / PR #23706 (the draft save's address,unboundItemDraftAddress) and #23495 / PR #23654 (the three draft doors' address).What this does
The strict draft read that names no package (
GET /api/v1/meta/TYPE/NAME?state=draft) now takes its content from the draft itsversionnames: the draftpackagelessDraftAddressgives the draft save, the per-item publish and the draft discard naming none. Before, the content address came from the item's ACTIVE rows (packagelessReadAddress), which name no package for an item never published into one, sofindServedOverlayRowtook the first draft row the store returned.?package=)version(unchanged)[package-unbound, Q], Q's stored first (in-memory double)[package-unbound, Q], unbound stored first[package-unbound, P], P's stored firstversion: nullnullstrictDraftReadAddress(new private helper inprotocol.ts) mapspackagelessDraftAddressto a content address: the draft's binding,nullfor the package-unbound draft alone; anambiguousaddress keeps the package-unbound draft, else the best-effort first match ADR-0048 §3.3 states.servedOverlayRowCandidates/findServedOverlayRowacceptpackageId: null= the package-unbound row only. Every existing caller passes a string orundefined, so their candidate order is unchanged.previewDrafts) shared the defective address: with Q's draft stored first it servedqwhile the fixed strict read servedunbound. It now takes the same draft address (it carries noversion). The layered read (getMetaItemLayered) reads ACTIVE rows only, so it has no draft door to fix.PublishMetaItemRequestSchema.packageId's description now carries the "no package-unbound draft" qualifierSaveMetaItemRequestSchema.packageId's has (PR fix(metadata-protocol)!: a draft save naming no package, of an item never published into a package, takes the draft doors' address (#23657) #23706). Description only;content/docs/references/api/protocol.mdxregenerated bycheck:generated --fix.200before is refused now;?package=still reaches each package's draft. The write, publish and discard addresses are not touched.Verification (head
eaa4b0ed)main(9e0945cf4), in-memory double: the new[#23715]pins, run before the fix:Tests 2 failed | 4 passed—expected 'q' to be 'unbound'(Q's draft first) andexpected 'p' to be 'unbound'(two active packages, P's draft first).main, and stay green:sys_metadata's(type, name, package_id, bundle_member)index returns the NULL-bound row first, so on this store content and version agreed by index order alone. These pins are positive pins plus theIf-Matchround trip; the double is the store that diverged.scripts/ablation-replace.mjs, mutation restoringmain's address logic, the suite imports./protocol.jsfromsrc):Tests 2 failed | 55 passed (57)— exactly the two divergence pins; restore proven (blob == HEAD,git diff HEADempty).pnpm --filter @objectstack/metadata-protocol test:Test Files 236 passed | 3 skipped,Tests 20572 passed | 19 skipped.protocol.packageless-save-address.test.ts+sys-metadata-repository.draft-package-inherit.test.tsTests 81 passed;meta-draft-head-package-inheritance.test.ts+meta-draft-read-door-census.test.tsTests 30 passed(against a rebuiltmetadata-protocoldist).metadata-protocol,spec,rest, each exit 0.pnpm --filter @objectstack/spec check:generated:All 14 generated artifacts are up to dateafter--fixregeneratedcheck:docsalone.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackateaa4b0ed: 111 commands;--ranwith exit codes:111 derived, 110 run, 1 NOT-MEASURED, 0 UNRUN. NOT MEASURED:pnpm check:dual-build-cjs-loads(exit 3, PREREQUISITE NOT MET: needs every package built; CI runs it). Two first-pass exit-3 prerequisites were satisfied and re-run to exit 0:check-plugin-teardown-shape.mjs --self-test(fetched its pinned fixture commit) andspec check:skill-examples(builtclient-react).Acceptance notes
mergePackageAwareOverlay, the list's other served-row reader) was not measured for the[package-unbound, Q]shape; it carries no per-itemversion.mainmoved three commits after the branch point (rest analytics refactor, objectql, organizations); none touches this diff's files or behaviour, and GitHub reports no conflict, somainwas not merged in.protocol.tsat opening: fix(metadata-protocol)!: a save naming a package of an object its package-less definition owns is refused, with a way out into that package (ADR-0029 D9.9) #23704 (D9.9,saveMetaItem) and fix(metadata-protocol): an orphan adoption rebinds a draft view in storage only, never registering it live #23724 (orphan adoption); different regions.Generated by Claude Code