Repository navigation
fix(metadata-protocol): the layered read's code layer is null for a name no package ships, before and after hydration (#21059) - #21168
Conversation
…d stored row, so a name no package ships answers a null code layer on both sides of a hydration (#21059) getMetaItemLayered's code-layer fallback read the registry's plain slot for items registered at runtime with no package. A stored row the hydrator registers lands in the same slot, so once a hydration had run the code layer was the stored body and the lock flags were derived from it, while the same read before the hydration answered a null code layer. The fallback now discards a registry answer that carries the tenant marker the hydrator already writes on every row it registers (ADR-0010 tenant provenance, read through isTenantAuthored). No new marker. A stored body with package-provenance stamps under an unshipped name is the same row: no code layer, per triage's ruling. A runtime-registered item with no package keeps its code layer. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
…ble in the engine-double-contract ledger (#21059) Generated by `node scripts/check-engine-double-contract.mjs --write`: one findOne row for protocol.layered-code-unshipped-name.test.ts, whose double is the pinned one from protocol.flow-layered-shipped-name.test.ts. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
…yered-code-null-unshipped
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 11 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 c769412419a43133262f2209552b6c359a0a3b9a && git checkout c769412419a43133262f2209552b6c359a0a3b9a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bafb8c949813c47b5a8486133acb1796f9d6eeb3 317eca662e99e79423c5982fe9791e99fec7572f && git checkout -B drift-repro bafb8c949813c47b5a8486133acb1796f9d6eeb3 && git merge --no-ff 317eca662e99e79423c5982fe9791e99fec7572f
node scripts/docs-audit/affected-docs.mjs --json bafb8c949813c47b5a8486133acb1796f9d6eeb3
|
Contract reviewServed-tier: This is the record of record for PR #21168 at Inputs:
Check-runs on Mergeability: Disclosure is kept at the card's level: doors, roles, codes and statuses. The tenant mark, the provenance flag, the package flag and the body's package-provenance stamps are named abstractly; the three layers by the method's own layer names; no request-body, header or field spelling appears and no seeding step is written. ① Derived judgments(a) The discriminator — RIGHT. The mark is the hydrator's own, on every stored row it registers under a name the fallback can reach, on nothing the loader or a package-less runtime registration produces, and the alias retry is covered.
(b) Scope — RIGHT, and the type reach is read, not assumed.
(c) The stamped corner's flags — RIGHT as landed; consistent with triage, with #5707 / #5840 and with the unchanged lock source; no affordance and no write path moves; A is within the seat's discretion for this PR.
(d) The pins — RIGHT; they hold triage's pins and the stamped corner, and they red without the fix as reported.
(e) (f) The changeset Surface inventory: no route, schema, query set, status code or exported signature changes; one method's code-layer fallback, for exactly the hydrated-stored-row population, through the three doors that read it (the layered door, the deprecated flag, the dispatcher's layered answer); the published doors untouched; one ② Semver levelThe PR body's line 2 reads
③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #21059
Clause-②: no
What this changes
The layered door (
GET /api/v1/meta/:type/:name/layers) reports three layers: the code layer, the stored layer and the effective layer. The spec's layer 1 says the code layer is null "when no artifact ships this item (it exists only as an overlay)". The method's own #5707 / #5840 rule says a layer is asserted only from a read that happened.For a name no package ships, the code layer was null only until a hydration had put the stored row into the registry's bare slot. After that, the code layer was the stored body, and the lock and provenance flags were derived from it. Below the artifact lookup, the code layer's fallback reads the bare slot. The fallback is there for items registered at runtime with no package, and hydrated rows land in the same slot.
getMetaItemLayered's code-layer fallback now ignores a registry answer that carries the tenant mark. The hydrator already writes that mark on every stored row it registers (ADR-0010 tenant provenance, read throughisTenantAuthoredfrom@objectstack/metadata-core). No new mark is added. The same rule covers the alias retry.The effective-layer binding from #21002 and
isShippedFlowNameare not edited. The stored rows are not changed.Measured: the layered door, showcase composition on a database file
The first boot reads the door with the rows stored and none of them hydrated. The second boot is cold, on the same file, after the boot hydration. Every reading answered 200.
orgpackage, package flag = showcaseorg, package flag = showcasepackage, package flag = showcasepackage, package flag = showcasepackagepackageFor every unshipped and runtime reading, lock is
none, the item is editable and deletable, and it is not resettable. The shipped name is resettable.The table was taken before the fix on a build of
origin/maine952cff578, where the dist carried no fix marker. It was taken after the fix at4a5639294d. It was taken again at the merged head317eca662e, where all 9 readings were identical to the post-fix run.Tests
packages/metadata-protocol/src/protocol.layered-code-unshipped-name.test.ts(new, 7 cases). It covers the boot hydration and the read-side hydration, the package-scoped read, the plural spelling, the stamped corner (with and without a package binding), a non-flow type (view), and both controls.packages/qa/dogfood/test/flow-unshipped-name-layered-code.dogfood.test.ts(new, 6 cases). It boots the showcase twice on one database file. It covers the layered door and the deprecated layers flag, the stamped corner's flags across hydration, and both controls.@objectstack/metadata-protocolfull suite: 198 files passed and 3 skipped (201); 2962 tests passed and 19 skipped. Typecheck exit 0, and the new pin is in the program (--listFilescount 1). Dogfoodtsc --noEmitexit 0, with the new pin in its program.Ablation. The fix was committed first.
scripts/ablation-replace.mjsreplaced the tenant-mark test with an always-false call; the anchor went from 1 hit to 0 and the replacement from 0 hits to 1.HEADandgit diff HEADempty.dist/: the package was rebuilt, andablation-dist-preflight --absentfound the guard absent from all 24 built files. 3 failed (the subject cases) and 3 passed (the hydration check and both controls). After the restore leg, the blob equalsHEAD. The package was rebuilt, the preflight found the guard present, and the tree was clean.dist/, sodist/was rebuilt from the restored source before the next run.Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat317eca662ederived 74 commands, and all 74 were run with their exit codes recorded before any pipe.--ranreports 74 derived, 74 run, 0 NOT-MEASURED and 0 UNRUN. Two first-pass results were reruns, not failures.check:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET: 8 packages outside this diff had nodist/); it exited 0 after they were built.check:query-options-erasurecrashed reading a temporary measurement file that was removed while it ran; it exited 0 on the rerun.pnpm lint(the whole repository,eslint . --no-inline-config) exited 0 with no findings at317eca662e.Acceptance notes
flow. The bootobjectlimb writes the same tenant mark, so an object no package owns also has a null code layer after hydration. The unit pin covers flow and view. Objects are not pinned separately; the objectql and rest layered-read suites above are green.readCodeLayerForCarryForwarduses the artifact lookup, then the registry's bare slot. It is called only when no stored row exists for the save's scope. This is a source reading, not measured.scripts/engine-double-contract.pinned.jsongains one generated row (--write) for the new unit pin'sfindOnedouble. That double is copied from the pinned one inprotocol.flow-layered-shipped-name.test.ts.Generated by Claude Code