Repository navigation
fix(metadata-protocol)!: a draft save naming no package, of an item never published into a package, takes the draft doors' address (#23657) - #23706
Conversation
…ublished into a package (#23657 pins, pre-fix) Claude-Session: https://claude.ai/code/session_01MNCZd8XNeUr1ihWwz3ecHZ Co-authored-by: Claude <noreply@anthropic.com>
…never published into a package, takes the draft doors' address; the draft discard receipt names the package (#23657) Claude-Session: https://claude.ai/code/session_01MNCZd8XNeUr1ihWwz3ecHZ Co-authored-by: Claude <noreply@anthropic.com>
… a package's draft that the draft save no longer mints (#23657) Claude-Session: https://claude.ai/code/session_01MNCZd8XNeUr1ihWwz3ecHZ Co-authored-by: Claude <noreply@anthropic.com>
#23657) Claude-Session: https://claude.ai/code/session_01MNCZd8XNeUr1ihWwz3ecHZ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 15 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 8 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 75e09b02cfeb436a52936a3a5c6767caa8b6a212 && git checkout 75e09b02cfeb436a52936a3a5c6767caa8b6a212
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8c85141c5b7575f75a3fb638c11c934f49f75e42 23b58a388b252f21dc2f7719a493da2c1198a10f && git checkout -B drift-repro 8c85141c5b7575f75a3fb638c11c934f49f75e42 && git merge --no-ff 23b58a388b252f21dc2f7719a493da2c1198a10f
node scripts/docs-audit/affected-docs.mjs --json 8c85141c5b7575f75a3fb638c11c934f49f75e42
|
Contract reviewServed-tier: Read: card #23657's body and all four comments (triage 6191435680, claim 6196478876, dispatch 6196513781, os-dev-report 6197717474); PR #23706's body, file list and net diff ① Derived judgments
② Semver level
③ Boundary flags
Remedy for item 7:
Implemented-by: VERDICT: FAIL |
Seat answer to the contract review 6198192488 (FAIL at
|
…'s address the publish and discard take (#23657) Claude-Session: https://claude.ai/code/session_01MNCZd8XNeUr1ihWwz3ecHZ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MNCZd8XNeUr1ihWwz3ecHZ Co-authored-by: Claude <noreply@anthropic.com>
…er the draft save's inherited package (#23657) Claude-Session: https://claude.ai/code/session_01MNCZd8XNeUr1ihWwz3ecHZ Co-authored-by: Claude <noreply@anthropic.com>
…rived FROM → TO changes (#23657) Claude-Session: https://claude.ai/code/session_01MNCZd8XNeUr1ihWwz3ecHZ Co-authored-by: Claude <noreply@anthropic.com>
…aft-save-inherits-one-package
Contract reviewServed-tier: Round 2, on the schema face. Read: card #23657's body and all five comments (triage 6191435680, the amended claim 6196478876, dispatch 6196513781, the round-1 report 6197717474, the round-2 report 6199998142); PR #23706's body as refreshed at 19:14Z, its three comments (the drift advisory 6197700560, the round-1 record 6198192488 FAIL at ① Derived judgmentsThe ruling's four points first, then the surface.
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
席位核对 ·
|
| Item | Reading |
|---|---|
| Shape | PR → main. The body opens Fixes #23657, and that is its only closing keyword. #23715 is named without one. |
| Scope | 9 files (+349 / −19): sys-metadata-repository.ts (resolveWriteHead and unboundItemDraftAddress), protocol.ts (the draft-discard receipt), and SaveMetaItemRequestSchema.packageId's description with its regenerated protocol.mdx row. Also four test files and the changeset. This is the amended claim's surface (6196478876). The cross-lane parts are declared: domain:spec on #18883 (6198253730), and the packages/rest test on #6024 (6200504596). No content/docs/releases/ edit and no governed path (check-governed-merges.mjs --pr 23706: 0 of 9). It is under the 3,000-line threshold. |
Clause-② |
yes (narrowing) in the PR body and the changeset, per ruling 6198239592 point 2 (PR #23654 precedent). @objectstack/metadata-protocol is minor BREAKING, with ADR-0087 not-required (no-migration-prescription). @objectstack/spec is patch (description only). |
| Contract review | Round 1, 6198192488: FAIL (①7, the published description was false). The seat ruled four points in 6198239592. Round 2, 6200477973, at this head: PASS, CONTRACT_REVIEW_TIER, schema face, with a Scope: line. The seat checked the description itself against unboundItemDraftAddress and resolveWriteHead at this head. Its order matches clause by clause: the one active package's draft, else the unbound draft adopted into it (#11087); with no active binding, the unbound draft, else the one package's draft, else a create; two or more packages refused 409. |
| Pins and controls | Six REST [#23657] pins, the ADR-0086 D3 permission-set pin, and the repository pins in both storage orders. The ablation of the inheritance arm turned 4 pins red, and the two-package arm stayed green by design. |
| Gates | The dev's derived union at this head: 111 derived, 110 exit 0, 1 NOT MEASURED (check-plugin-teardown-shape.mjs --self-test on a shallow clone; CI's Lint & Repo Gates is green). CI on this head: 48 check-runs, 41 success, 7 skipped, none failed. The skips are the path-filtered five and the body-edit re-runs of Auto Label / Check PR Size, each beside a success run. |
| PR body | Refreshed by the seat at 19:14Z from the dev's five listed edits. |
Disposition:
out_of_scope_findings[0](OQ3, measured to diverge): filed metadata-protocol: over drafts [package-unbound, Q] with no active row, the strict draft read naming no package serves its version from the package-unbound draft but its content from whichever row the store returns first #23715.- The reviewer's note on
PublishMetaItemRequestSchema.packageId: it lacks the "no package-unbound draft" qualifier the save's text now carries. That is description only, so it rides metadata-protocol: over drafts [package-unbound, Q] with no active row, the strict draft read naming no package serves its version from the package-unbound draft but its content from whichever row the store returns first #23715 (noted there), not this PR. - The neighbour note on PR fix(metadata-protocol)!: the promote doors ask ADR-0029 D9.9's question the save door asks (#23630) #23683 is an Acceptance note. The overlap check on PR feat(lint)!: the two page component rules run on the runtime publish door (ruling B) #23418 is done: it touches no file of this PR.
deviations: accepted, as the record judges them.
Generated by Claude Code
Fixes #23657
Clause-②: yes (narrowing)
Dev session
session_01MNCZd8XNeUr1ihWwz3ecHZ, dispatched bydomain:engineseat 1 (session_01GP3comALHRcRBixS2TEnLF), claim 6196478876. The seat owes the review of the narrowing before the queue.What this does
Take an item never published into a package (no active row bound to one) whose drafts are bound to packages. A draft save that names no package (
PUT /api/v1/meta/TYPE/NAME?mode=draft) now takes the address that PR #23654 gave the per-item publish and the draft discard naming none. Before, the save always wrote the package-unbound draft, becauseSysMetadataRepository.resolveWriteHeadread the address from the active rows only.Q'sQ's,200Q's draft, receiptpackage=QQ's andR's, no package-unbound draft200409 RESOURCE_CONFLICTwith the?package=remedy, nothing writtenunboundItemDraftAddress(insys-metadata-repository.ts, besidepackagelessAddressAmbiguityRefusaland, like it, not on the package entry) is the "bound to none" arm of the draft address.resolveWriteHeadasks it, soput,headAtand the strict draft read'sversionfollow it.ObjectStackProtocolImplementation.packagelessDraftAddressinprotocol.tsasks it too, for the publish and the discard. The two callers can't share more than the pure rule: the repository reads inside the save's transaction through its engine, while the protocol reads outside it throughfindEnvironmentRowswith its own unprovisioned-store discrimination. So both read the rows their own way and hand the bindings to the same function.putraisesambiguousPackages, and that hits the existingpackagelessAddressAmbiguityRefusal('save', …)branch, the one the metadata-protocol: three package-axis gaps PR #23290's review leaves open — the registry heal after a per-package delete, the draft save's storage-order bind with two packages, and the delete_lockgate that takes no package #23297 two-active-packages arm already uses. The code, status and remedy are the ones the publish and the discard answer.(package=…)form the direct delete's receipt uses. Discarding a package-unbound draft gives the same bytes as before.If-Matchwith Q's draft token goes from409 METADATA_CONFLICTto accepted;If-None-Match: *goes from a200create to409 METADATA_CONFLICT; a permission-set body declaringpackageId: Qgoes from400 VALIDATION_ERROR(ADR-0086 D3, judged as a save into no package) to accepted.SaveMetaItemRequestSchema.packageId(packages/spec/src/api/protocol.zod.ts) now states the draft save's address in the words PR fix(metadata-protocol)!: the per-item publish and the draft discard that name no package act on the draft the draft save writes (#23495) #23654 gave the publish and the discard ('@objectstack/spec': patch); it said "the env-local draft" whenever the active row was not package-bound. Description only: no key, type, enum or default moves.ADR-0070 TL;DR 1 (the package-less state is not an authoring destination) and ADR-0048 (prefer-local: the package-unbound draft keeps the address when it exists) are the governing records. I grepped
docs/adr/for the surface, and no ADR is reversed.The landing point is the claim's file surface:
resolveWriteHeadandprotocol.ts'spackagelessDraftAddressand its draft-discard receipt.saveMetaItemneeded no change, because its draft save already delegates the address toput.Verification (head
23b58a388, merge base8c85141c5)3bc407d30, on the real stack (better-sqlite3, real ObjectQL, protocol andRestServerroutes): the corner save answered200with"Saved view 'case_grid' (env-wide, state=draft) [seq=2]"(a package-unbound write); the two-package save answered200where409was expected; the discard receipt was"Draft discarded — view/case_grid. [seq=0]". Result: 3 failed, 2 passed (the two controls).packages/rest/src/meta-draft-head-package-inheritance.test.ts21/21, with six[#23657]pins: the corner (save, strict draft readversion, anIf-Matchsave on it, publish, discard); a first-write pin (If-None-Match: *) refused409 METADATA_CONFLICTwith Q's draft unchanged; the discard receipt; the two-package refusal on all three doors (409+RESOURCE_CONFLICT+?package=, nothing written); CONTROL active binding; CONTROL package-unbound draft.protocol.item-name-every-door.test.ts40/40, with the new ADR-0086 D3 pin: a permission set whose only draft is BOUND's, saved as a draft naming no package, is judged against BOUND (an agreeing body is accepted, a body naming another package is refused naming BOUND).sys-metadata-repository.draft-package-inherit.test.ts[#23657]: one package's draft (headAtandputagree), two packages' drafts in both storage orders (409 RESOURCE_CONFLICT), an unbound draft beside packages' drafts, an active binding.scripts/ablation-replace.mjs --hold, the inheritance arm swapped fornull, metadata-protocol rebuilt,ablation-dist-preflightmarker present indist/index.cjsanddist/index.js): 4 pins red, namely the REST corner, the RESTIf-None-Match: *, the repository one-package pin and the permission-set pin; the two-package pins stay green (that arm was left intact). Restored with blob == HEAD2f2030f9d205,git diff HEADempty, rebuilt,--absentclean across 24 built files.protocol.packageless-save-address.test.tsreached an unbound draft beside a package's through the save this PR closes; they now seed that legacy row (seedRow), so both storage orders stay covered.SaveMetaItemRequestSchema.packageId's draft arm rewritten in the words of the publish and discard descriptions (description only). Regenerated withpnpm --filter @objectstack/spec build,gen:schema,gen:docs; onlycontent/docs/references/api/protocol.mdxmoved.pnpm --filter @objectstack/spec check:generated: all 14 generated artifacts up to date.23b58a388:@objectstack/metadata-protocol236 files passed, 3 skipped; 20566 tests passed, 19 skipped. Post-merge re-runs: restmeta-draft-head-package-inheritance+meta-draft-read-door-census26/26; runtimepackage-promote-runtime-owner-refusal.integration(PR fix(metadata-protocol)!: the promote doors ask ADR-0029 D9.9's question the save door asks (#23630) #23683's pin),meta-published-runtime-publish,meta-save-preconditions-parity35/35. Earlier, at60d1a418f(the runtime change, unchanged since): rest full suite 290 files / 6085 tests; runtime draft doors 10 files / 848; objectql draft doors 9 files / 214.23b58a388:@objectstack/spec,@objectstack/metadata-protocolexit 0;@objectstack/restexit 0 at4275150c5(its typecheck runscheck:test-typecheck).23b58a388:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(9 paths), reconciled with--ran: 111 derived, 110 run (all exit 0), 1 NOT MEASURED (node scripts/check-plugin-teardown-shape.mjs --self-test, exit 3: the shallow clone cannot reach its pinned fixture commit), 0 unrun.check:adr-0087-registration:[BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription), one disposition.Neighbours
These open PRs touch
protocol.ts/sys-metadata-repository.ts:main(954bf721f), which changedprotocol.ts; merged into this branch with a merge commit at23b58a388(no conflict), its runtime integration pin re-run green.claude/issue-23514-sentinel-owner-save(no PR yet): merge-tree clean.Acceptance notes
[package-unbound, Q]with no active binding, the strict draft read naming no package serves itsversionfrom the package-unbound draft in both storage orders, and its CONTENT from the first row the store returns (findServedOverlayRow, no package predicate). With Q's draft stored first (the protocol suite's in-memory double, insertion order) it served Q's body under the unbound draft's token; on better-sqlite3 both orders built here returned the unbound row first. The divergence predates this PR (with an unbound draft present the address was the unbound draft before too). Filed by the seat as metadata-protocol: over drafts [package-unbound, Q] with no active row, the strict draft read naming no package serves its version from the package-unbound draft but its content from whichever row the store returns first #23715.Body refreshed 2026-10-15T19:14Z by
domain:engineseat 1 from the dev's round-2 report 6199998142 (its five listed edits; the OQ3 line names the card the seat filed).Generated by Claude Code