Skip to content

fix(metadata-protocol): the per-package layer stamps a row with its own package's envelope, never another package's (#28831) - #28857

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-xqgh8b
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-xqgh8b

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #28831

Clause-②: no
Metadata-protocol: none
Skills-docs: none — no file under skills/** teaches the per-package layer or its envelope (0 hits for getMetaItemsPerPackage and per-package layer).

维护者速读

What changes

  • The graft on the per-package layer (readFlattenedMetaItems, layer: 'per-package'). After the artifact is looked up at the item's package (envelopePackageId, unchanged), perPackageLayerEnvelope keeps it only when it is that package's OWN artifact (_packageId equal to the item's bound package). Otherwise the item wears no artifact envelope and keeps its own binding.
  • The one exception keeps today's stamp: a stored row of the item's package that is the served override of the artifact's package. It is judged by isServedOverrideRowOf on the row's own package_id (never a body _packageId), at the item's bundle member, over the active rows the read already holds for the lock (lockRowsByName).
  • An item bound to no package keeps the lookup's answer, as before (a package-less row standing in for a base's slot is seated with the base's id by the merge, so it matches its own package anyway).
  • Not touched: lookupArtifactItem / registry.getArtifactItem and their fallback for every other caller; the effective list, the by-name read and the execution list (the new call is inside layer === 'per-package' only); the two readers. No new error code, no packages/spec/**, no new gate.
  • Changeset .changeset/28831-per-package-layer-own-envelope.md: patch on @objectstack/metadata-protocol, both declaration lines.

Why Clause-②: no. Measured through the real doors (step 1 below): the package export drops every _-prefixed key before it answers, and the anonymous form doors' withdrawal judgement (anonymousFormIntakeWithdrawnIn) matches by view name and reads no stamp. So the stamp this changes is on an internal layer only, and no public door's served answer moves.

Tests

New packages/objectql/src/protocol-per-package-layer-provenance.test.ts, 4 tests, on the real protocol over the real ObjectQL engine and an in-memory driver (the harness of #28828's protocol-bundle-member-base-address.test.ts, whose view control it sits beside). Legacy rows are laid down as stored before the override write gate, the way that control does. Each case asserts [columns, _packageId, _packageVersion, _provenance] row by row.

  • The withdrawal layer's read (unscoped): the base's view reads com.example.showcase · 1.0.0 · package; a dependent's row under the same name reads its own package with no artifact envelope; the pair's undeclared row reads the pair.
  • CONTROL: the pair's declared override keeps the base's envelope, beside the base's own view.
  • The export's read (scoped to each package): the dependent's layer reads the dependent, the pair's reads the pair, the base's reads the base.
  • CONTROL: the pair's scoped layer keeps the base's envelope on its declared override.
  • The form withdrawal leg at the door is not pinned separately: the door reaches the layer (step 1: a dependent's withdrawal closes /forms/contact-us, 200 → 404), but its answer reads no stamp, so no door assertion can be red on base. The layer read it asks is the first case.
Red on base, green on head; ablation
  • Base (protocol.ts from 1920e480, the new file at head, metadata-protocol rebuilt): Tests 2 failed | 2 passed (4). Both reds are the dependent's and the pair's rows reading com.example.showcase · 1.0.0 · package. The two declared-override controls are green both ways. Restored with git checkout HEAD -- protocol.ts, git diff HEAD empty, rebuilt.
  • Head: Tests 4 passed (4).
  • Ablation via node scripts/ablation-replace.mjs from committed HEAD: the graft's condition if (layer === 'per-package') { replaced with if (layer === 'per-package' && (globalThis as any).ABLATE_28831 === 1) { (the fallback restored), anchor 1 → 0, blob 17bb3db36eed → 393a0475ef93; metadata-protocol rebuilt; Tests 2 failed | 2 passed (4), the same two. Restored: blob == HEAD 17bb3db36eed, git diff HEAD empty.

Acceptance notes

  • Step 1 measurement: pm-orders/server/28831-step1-measurement.md in the project files. A temporary census probe at the graft (never committed), run over the full dogfood battery and the objectql, rest, runtime and metadata-protocol suites: fresh CRM and showcase boots 0 rows, packages/qa/** seed sites 0 rows, and 3 unit-test fixtures that lay legacy rows down on purpose. No object item reached it. No reader depends on the base's stamp, so the order's stop condition did not hold.
  • The export leg is pinned at the read the export asks (getMetaItemsPerPackage({ type, packageId })), not at the door: the door's manifest carries no _ key, so its body is the same on base and head.
  • A draft preview (previewDrafts) on the per-package layer is not covered: the exception reads active rows. Neither reader asks for drafts.

Verification

All readings at head c7b62f42 (the fix b7f9b13e, changeset 533db7df, merged once with origin/main 7f4cbc9d, a clean merge). Full pnpm turbo run build --concurrency=2 at the head: 118/118.

Package suites, typecheck, lint
  • @objectstack/metadata-protocol test: Tests 22093 passed | 26 skipped, lock VERDICT command-exit 0.
  • @objectstack/objectql test: Tests 10015 passed, command-exit 0.
  • @objectstack/rest test: Tests 6920 passed | 332 skipped, command-exit 0.
  • @objectstack/runtime test: Tests 8252 passed | 19 skipped, command-exit 0.
  • typecheck of metadata-protocol and objectql (objectql's test layer included): exit 0.
  • eslint --no-inline-config on the two touched source files: exit 0; the repo's pnpm lint command scoped to packages/metadata-protocol and packages/objectql: exit 0.
Derived gates (`dispatch-gates.mjs --commands`, 62 families)
  • All 62 exit 0 at c7b62f42; dispatch-gates --ran: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN. Record: pm-orders/server/28831-gates-ran.txt.
  • check:type-check-debt: OK, --re-measure: OK.
  • Repo-wide pnpm lint and the CI-only families are CI's; see the PR's check-runs.
Full dogfood battery (not owed: no `!` and no narrowing arm; run anyway)
  • Head c7b62f42: Test Files 4 failed | 376 passed | 3 skipped (383), Tests 4 failed | 3123 passed | 28 skipped (3155).
  • The 4 failures are main's, not this PR's. They are the 18.0 upgrade-harness files audit-key-material, audit-mail-bodies, identity-mail-read-gate and upgrade-18x-harness. Re-run alone on the head: the same 4 red. Re-run with protocol.ts checked out from origin/main and metadata-protocol rebuilt: the same 4 red (restored, git diff HEAD empty). main's own push run at 7f4cbc9d (CI run 41366661242) has every Dogfood Regression Gate shard red, with identity-mail-read-gate named in shard 2's log.
  • The battery was green (3121 passed, 0 failed) at the pre-merge base 1920e480 during the step 1 census run.

Generated by Claude Code

…wn package's envelope, never another package's by the lookup's fallback

On the per-package layer (getMetaItemsPerPackage, read by the package export
and the anonymous form doors' withdrawal layer), the #22024 envelope graft
looked up the item's artifact at its own package, and the lookup fell back to
the first code package that ships the name. A dependent package's row, or a
.custom pair's undeclared row, under a base item's name read the base's
_packageId, _packageVersion and _provenance.

The graft now keeps the artifact only when it is the item's own package's,
or when a stored row of that package is the served override of the
artifact's package (isServedOverrideRowOf on the row's package_id, at the
item's bundle member), which keeps the stamp it is served under. The shared
lookup is unchanged for its other callers.

Co-Authored-By: Claude <noreply@anthropic.com>
… own-package envelope

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 101 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 7e60dc2332c66aec63d28a154043d42178ae90ef → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 6cac95e0e76c1284843a8914bf6077a479a8381f — the merge of head c7b62f421c48a87035ec984accb905b4d4cb16e0 into base 7e60dc2332c66aec63d28a154043d42178ae90ef, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6cac95e0e76c1284843a8914bf6077a479a8381f && git checkout 6cac95e0e76c1284843a8914bf6077a479a8381f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7e60dc2332c66aec63d28a154043d42178ae90ef c7b62f421c48a87035ec984accb905b4d4cb16e0 && git checkout -B drift-repro 7e60dc2332c66aec63d28a154043d42178ae90ef && git merge --no-ff c7b62f421c48a87035ec984accb905b4d4cb16e0

node scripts/docs-audit/affected-docs.mjs --json 7e60dc2332c66aec63d28a154043d42178ae90ef

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC

席位核对 · ACCEPT · domain:server seat 1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-11-02T01:56Z

Head c7b62f42, report 6566949223 (on #28831), claim 6565336775. Clause-②: no, with no arm: no contract review is owed. The seat read the diff itself.

item reading
Shape Draft, base main, Fixes #28831. 3 files (+355 / −1): perPackageLayerEnvelope and its one call at the #22024 graft in getMetaItemsPerPackage (protocol.ts, gated layer === 'per-package'), a new test file, and a @objectstack/metadata-protocol patch changeset. Metadata-protocol: none. No export, no packages/spec/**, no gate, no new error code.
Rule On the per-package layer, a row bound to a package wears its own package's artifact envelope or none, never another package's by the lookup's fallback. A stored row that is the served override of the lender's package (isServedOverrideRowOf on the row's package_id, at the item's own bundle member) keeps the base's envelope. A package-less item keeps the lookup's answer. The shared lookup (lookupArtifactItem / registry.getArtifactItem) is untouched, so the effective read and every other caller are unchanged.
Measure Step 1: 0 rows on fresh CRM and showcase boots and in the qa seeds. Through the real doors, the export drops every _ key and the form withdrawal layer matches by view name, so no public answer changes. That confirms Clause-②: no.
Evidence CI green on head c7b62f42 (filter=latest: 35 success, 13 skipped, 0 failed). The report shows 2 red on main and 4 green on head, an ablation restored clean, the metadata-protocol, objectql, rest and runtime suites green, and 62/62 derived gates. The dev's local dogfood reds are main's (p0 #28849).
Landing 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready and auto-merge through the relay, with every check green or an expected skip.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

1 participant