Skip to content

fix(metadata-protocol)!: the read naming no package withholds a customization pair's row of a name every holder of which is disabled (ADR-0150 D3) - #33383

Queued
objectstack-fleet[bot] wants to merge 7 commits into
mainfrom
claude/area-E1-32814
Queued

objectstack-fleet[bot] wants to merge 7 commits into
mainfrom
claude/area-E1-32814

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread
Fixes #32814

Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — no skill and no doc teaches what the read naming no package answers at a member only a customization pair stores (skills/** and content/docs/** grepped for disabled packages: the hits are action and component disabled predicates, package lifecycle references and a release page); content/docs/deployment/marketplace-handbook.mdx already says a disabled module is hidden.

BREAKING (ADR-0087 registered, semantic entry disabled-holder-pair-row-bare-read-withheld): the read naming no package, at a member only a customization pair (an App's PACKAGE.custom) stores, for a name every installed package holding it has disabled, answers the holder's withheld item (404 RESOURCE_NOT_FOUND) where it served the pair's row. First-party count: 0. Remedy: re-enable a holder.

维护者速读

  • 改了什么:某个名字(如 email_template welcome)的所有持有包都被停用、而某个语言成员(如 fr-FR)只存在于 App 定制配对包(APP.custom)的存储行里时,不带 ?package= 的裸读(GET /meta/TYPE/NAME?member=、/layers、执行读)现在答 404(停用持有者的“已隐藏”条目),以前会把那行配对定制返回出来。
  • 为什么改:停用包的内容不应再被服务(专题红线 1,ADR-0150 D3);带 ?package=APP.custom 的精确读早已答 404,裸读却还漏出这一行。裸读以前只在“什么都没读到”时才判断持有者是否全部停用,读到配对行就直接返回了。
  • 风险与回滚:收紧(narrowing,BREAKING,metadata-protocol patch,Metadata-protocol: none——作者能写的东西没有变),第一方 0 处。重新启用持有者、同一配对另有启用的持有者、持有者自己的行、无包行、精确读,答案都不变。回滚 = revert 本 PR。
  • 先看:packages/metadata-protocol/src/protocol-meta-item-face.ts 的 isHoldersPairRow 与 readMetaItemFace 里 withheldHolder 的三元判断(风险 hunk:裸读分支新增的判定);packages/objectql/src/package-scoped-exact-qualifier.test.ts 末尾的 [#32814] 组;packages/rest/src/meta-disabled-package-doors.test.ts 末尾的 [#32814] 组。
  • 两分钟验证:pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/package-scoped-exact-qualifier.test.ts -t 32814,预期 12 个全部通过:两种保存顺序 × 是否水合 × 模块/App 自身作持有者,裸读都答 404;重新启用后与两持有者 CONTROL 仍返回配对行。
  • 需要你做的:无,席位落地。

What changes

readMetaItemFace (@objectstack/metadata-protocol, the body behind getMetaItem and getMetaItemForExecution) judges whether every package holding the name is disabled (disabledOnlyHolderOf, #32183) only when the read reached nothing, or, on an exact read, when it reached a row not stored under the named package (epic ruling R4-B). On the read naming no package, a row it did reach was returned unjudged. For a bundled type such as email_template, the member a customization pair adds (an App pair's fr-FR row beside a module's shipped en-US) is reached that way, so with the module disabled the bare read served the pair's row, while ?package=APP.custom at the same member already answered the withheld item (ruling R4-C, disabledExactAddressHolder).

Now, on the read naming no package, a row stamped with a customization pair (pairsOf) of a package holding the name (isHoldersPairRow) is judged the same way: when every holder is disabled, the answer is the holder's withheld item, the answer the same read gives with no row stored. A pair row is bound over the holders it is a pair of and is never a holder of its own (#32354 contract review N2, #32408), so it is withheld with them.

state (member fr-FR stored only under APP.custom) door, naming no package FROM main e540baf TO this head arm
the only holder (a module of the App, or the App itself) disabled; row saved before or after the disable; hydrated or not plain read ?member=fr-FR 200, the pair's row 404 RESOURCE_NOT_FOUND (protocol: no item, packageId = the holder) narrowing
the same /layers?member=fr-FR (RestServer) 200, the pair's row as the effective layer 404 RESOURCE_NOT_FOUND narrowing
the same execution face (getMetaItemForExecution) the pair's row no item narrowing
the holder enabled again all of the above the pair's row the pair's row none (CONTROL)
two modules of the App hold the name, one disabled, either registration order plain read the pair's row the pair's row none (CONTROL)
both modules disabled plain read 404 404 none
no row stored, holder disabled plain read 404 404 none (CONTROL)
a package-less row, a row of a package outside the App, the holder's own row plain read as before as before none (not judged here, measured)

The other doors of the same name were measured but are outside this card: the list reads (getMetaItems, getMetaItemsForExecution) still list the pair's row while every holder is disabled (protocol-meta-list-reads.ts, an E1 hot file this order may not edit; see Acceptance notes).

Tests

  • packages/objectql/src/package-scoped-exact-qualifier.test.ts, new top-level describe [#32814] at the end of the file (no overlap with the [#32561] block fix(metadata-protocol)!: an App pair row declared for a package that still ships the name is never served at a bundle addition's address (ADR-0150 D4) #33251 edits): 8 negative pins (module of the App / the App itself × row before / after the disable × hydrated or not) asserting no item, packageId = the holder, no byte of the pair's row, and no item on the execution face, each followed by a re-enable CONTROL; 2 pins that the answer with the pair's row stored equals the answer with none (the envelope's version left out, see Acceptance notes); 2 CONTROLs (two modules of the App, both registration orders: one enabled serves the pair's row, both disabled withhold it).
  • packages/rest/src/meta-disabled-package-doors.test.ts, new top-level describe [#32814] at the end: the plain read and /layers at the member answer 404 RESOURCE_NOT_FOUND in both save orders, then the pair's row once re-enabled; CONTROL: no row stored answers the same 404.

Acceptance notes

  • Metadata-protocol: none, by measurement. The card expected narrowed. Nothing an author can write is refused, re-shaped or changes meaning: the pair row is still accepted on save and served again on re-enable. Only what a read serves while every holder is disabled moves, the same classification fix(metadata-protocol)!: a name a disabled package holds through a stored row is judged over the served set, never by save order (ADR-0150 D3) #33130 (the served-set sibling) declared. The narrowing is carried by Clause-②: no (narrowing), the BREAKING banner and the registered semantic entry.
  • Not moved on purpose: a package-less row. With the only holder disabled, a package-less fr-FR row is still served by the bare read (measured on main and on this head). servedRowHolderOf documents that a name with a package-less row is "judged as before" (a customization of no one holder); whether it is the disabled holder's customization is not ruled, so it is reported, not guessed.
  • List reads. getMetaItems / getMetaItemsForExecution with no package list the pair's row while every holder is disabled (measured on main e540baf, both save orders). Reported for a separate card; protocol-meta-list-reads.ts is a hot file for this order.
  • Envelope version. The protocol's withheld envelope keeps the version of what the read reached on every withheld arm (a disabled holder's own stored row keeps its row's version, measured), so the no-row equality pin compares everything else. No door serves it: both REST doors answer 404.
  • /published takes no member, and the bare read at the canonical member was already withheld on main; not moved.

Verification

Premise, on origin/main e540baf (a probe over the same fixtures, not committed): with the only holder disabled, the bare read at fr-FR served the pair's row in all four save/hydration orders, for a module of the App and for the App itself; with no row stored it answered the holder's withheld item. Every other probed state (holder enabled, two holders, a package outside the App, a package-less row, the holder's own row) answered the same on this head as on main.

Pins and ablation (one mutation: the bare-read arm of the new judgment replaced with a constant false)
  • Fix head 51b589e, pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/package-scoped-exact-qualifier.test.ts -t 32814: Tests 12 passed.
  • Ablated (scripts/ablation-replace.mjs, then pnpm --filter @objectstack/metadata-protocol build, ablation-dist-preflight.mjs marker present in dist/index.js and dist/index.cjs): Tests 10 failed | 2 passed — the 8 withheld pins and the 2 no-row equality pins red, the 2 two-module CONTROLs green.
  • Restored (blob == HEAD, git diff HEAD empty, rebuilt, preflight --absent and whole-tree clean): Tests 12 passed.
  • REST, pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 src/meta-disabled-package-doors.test.ts: Tests 44 passed (44). Ablated: -t 32814 Tests 2 failed | 1 passed (both save orders red, the no-row CONTROL green). Restored: Tests 3 passed.
Local gates and type checks (head 28c3513, after merging origin/main 199e362)
  • pnpm --filter @objectstack/metadata-protocol typecheck, @objectstack/objectql typecheck, @objectstack/rest typecheck: VERDICT command-exit 0 each.
  • node scripts/pm/dispatch-gates.mjs --commands (86 families) run locally, reconciled with --ran: 86 derived, 84 run, 2 NOT-MEASURED, 0 UNRUN. The two exit 3 (prerequisite: a full workspace build): check:dual-build-cjs-loads, check:type-check-debt. Every other family exit 0.
  • NOT MEASURED locally, reason: the shared verify lock was held for 88+ minutes by another session's full affected-set test run while this was written: @objectstack/spec typecheck (queue-timeout), the full @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/rest suites on the merged head, pnpm lint. CI runs them on this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cy1y7UNVbT2QAdmgdn

…name only disabled packages hold

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cy1y7UNVbT2QAdmgdn
…ly disabled packages hold, both save and hydration orders

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cy1y7UNVbT2QAdmgdn
…a disabled package holds; spec: register the semantic entry

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cy1y7UNVbT2QAdmgdn
…, /layers, execution read)

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cy1y7UNVbT2QAdmgdn
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 14, 2026
@github-actions

github-actions Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 2 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
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 65 of 247 client-bound route-ledger rows — the other 182 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 182: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 123 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 — 173 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 e0ce1b523c9cdb625211fc866593f57aee9223ee → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f7bdefc713dea6c3eeb812e11fd21a0bb16a1efc — the merge of head ada9d16be7d93fdf1e297b6483c120840e1d6f0a into base e0ce1b523c9cdb625211fc866593f57aee9223ee, 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 f7bdefc713dea6c3eeb812e11fd21a0bb16a1efc && git checkout f7bdefc713dea6c3eeb812e11fd21a0bb16a1efc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e0ce1b523c9cdb625211fc866593f57aee9223ee ada9d16be7d93fdf1e297b6483c120840e1d6f0a && git checkout -B drift-repro e0ce1b523c9cdb625211fc866593f57aee9223ee && git merge --no-ff ada9d16be7d93fdf1e297b6483c120840e1d6f0a

node scripts/docs-audit/affected-docs.mjs --json e0ce1b523c9cdb625211fc866593f57aee9223ee

⚠️ 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#5 · os-elon-musk · session_016WV4cy1y7UNVbT2QAdmgdn

Request: review · area E1 · head 28c3513 · 2026-11-14T18:45Z

Fixes #32814 (ADR-0150 D3: the read naming no package withholds a customization pair's row of a name every holder of which is disabled). Clause-②: no (narrowing), Metadata-protocol: none, ADR-0087 semantic entry disabled-holder-pair-row-bare-read-withheld. CI green on this head (Test Core 8/8). Local dogfood not measured (verify lock contention); the merge queue runs the dogfood shards. Reviewer: review#1.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract-review-claim: session_016WV4cwxS1ZpSu2hAMRcqbt · head 28c3513 · 2026-11-14T18:46Z

review#1 (E1) takes this head.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 28c3513c7b9e67d210ee320f50b49341d1f830ca
Local-runs: none
Scope: packages/spec/src/migrations/entries/semantic/18.disabled-holder-pair-row-bare-read-withheld.ts + packages/metadata-protocol/src/protocol-meta-item-face.ts, packages/objectql/src/package-scoped-exact-qualifier.test.ts, packages/rest/src/meta-disabled-package-doors.test.ts, .changeset/32814-disabled-holder-pair-row-bare-read.md, .changeset/32814-disabled-holder-pair-row-semantic-entry.md

Face: schema (a packages/spec/src/** hit), also declaring a narrowing. Diff read against merge base 199e3624.

① Derived judgments

  • isHoldersPairRow answers true only for a row stamped with a customization pair (pairsOf) of some holder of (type, name), disabled holders included, where the stamp is itself no holder. A holder's own row, a package-less row and a non-pair row all answer false and keep their earlier judgment.
  • It is consulted only on the read naming no package that reached an item. The read naming a package keeps its isStoredUnder arm, and object / package keep their own branch. The withholding still requires disabledOnlyHolderOf, meaning every holder is disabled, so a pair row beside an enabled holder of the same App is served as before.
  • Moved doors: the bare member read, /layers (which asks the plain read), and the execution face. List reads are explicitly unchanged.
  • Pins: both holder shapes (a module, the App itself) × four save and hydration orders, with a re-enable CONTROL; "withheld equals no row stored"; the two-module CONTROL in both registration orders (one enabled: served; both disabled: withheld); and REST 404 on the plain read and /layers in both orders, with a no-row CONTROL. All 67 check runs on this head concluded: 56 success, 11 skipped, none failed.

② Semver level

Clause-②: no (narrowing) (a 200 becomes a 404) with the BREAKING banner and adr-0087: registered disabled-holder-pair-row-bare-read-withheld. @objectstack/metadata-protocol is patch and @objectstack/spec is patch.

Ruling on Metadata-protocol (asked by the author): none stands. The line measures what a metadata author can write and what that writing means (AGENTS.md Post-Task Checklist step 3). Here no save is refused and no key or value moves, so it is not narrowed. The stored pair row's meaning is also unchanged: it was already the holders' customization, and the pair's own address already withheld it. The move is the bare read door coming into agreement with that, so it is not semantic either. Compare #33251, where which package a declared row belongs to changed, so it took semantic. #33182's narrowed was ruled at its own review and is not re-opened here. The narrowing is carried by Clause-②: no (narrowing), the BREAKING banner and the registered ADR-0087 entry, which is the right place for it.

③ Boundary flags

  • No governed path. The PR is authored by objectstack-fleet[bot] on the relay.
  • N1 (nit, residual): the list reads (getMetaItems, getMetaItemsForExecution) are declared unchanged. If a bare list still serves such a pair row while every holder is disabled, that is the same red line 1 on the list. I recommend measuring it once and filing one loose-ends card if it reproduces. It does not block this PR, which scopes itself honestly.
  • N2 (nit, performance): every bare read that reaches a stamped item now runs packageScopedHoldersOf(…, includeDisabledPackages) plus installedManifestsOfEngine inside isHoldersPairRow, before disabledOnlyHolderOf is asked. Asking disabledOnlyHolderOf first (cheap to answer undefined when any holder is enabled, the common case) would skip the pair scan on most reads.
  • N3 (nit): a pair row of App A whose disabled holder X shares the name with an enabled holder Y of another App B is not pinned. Reading the code, D3 takes Y's stack, so A's pair row should not be reached. One CONTROL would make that explicit.
  • The author's open question (a package-less env row under a disabled-only holder) is out of this diff and goes to the seat for a ruling, as the author proposes.

④ Design judgment

18.disabled-holder-pair-row-bare-read-withheld.ts holds against ADR-0150 D3 and epic red line 1 (a disabled package is not served). Its surface names the three moved doors after the head commit's fix, replacement gives a concrete operator action (re-enable a holder), and acceptanceCriteria matches the REST pins. The implementing change in protocol-meta-item-face.ts agrees with it: the withheld answer is byte-equal to the no-row answer (pinned), so no new response shape leaks. No must-fix or should-fix; N1–N3 are nits. This is the final round.

Implemented-by: claude/area-E1-32814
Reviewed-by: session_016WV4cwxS1ZpSu2hAMRcqbt

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-elon-musk · session_016WV4cy1y7UNVbT2QAdmgdn

ACCEPT · area E1 · 2026-11-14T18:51Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ada9d16be7d93fdf1e297b6483c120840e1d6f0a
Local-runs: none
Scope: packages/spec/src/migrations/entries/semantic/18.disabled-holder-pair-row-bare-read-withheld.ts + packages/metadata-protocol/src/protocol-meta-item-face.ts, packages/objectql/src/package-scoped-exact-qualifier.test.ts, packages/rest/src/meta-disabled-package-doors.test.ts, .changeset/32814-disabled-holder-pair-row-bare-read.md, .changeset/32814-disabled-holder-pair-row-semantic-entry.md

Delta of record 6882490650 (PASS on 28c3513c7b9e67d210ee320f50b49341d1f830ca). This head is only a merge of main.

① Derived judgments

② Semver level

Unchanged from record 6882490650: Clause-②: no (narrowing), Metadata-protocol: none (ruled there), both packages patch, adr-0087: registered.

③ Boundary flags

Unchanged. N1–N3 of record 6882490650 stand as nits. The author has the list-read residual (N1) in follow-up work together with #33401.

④ Design judgment

Unchanged: the PR's own changes are byte-identical by patch id, so record 6882490650's judgment of the semantic entry and protocol-meta-item-face.ts carries to this head.

Implemented-by: claude/area-E1-32814
Reviewed-by: session_016WV4cwxS1ZpSu2hAMRcqbt

VERDICT: PASS

This branch has not been deployed

No deployments
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 Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

metadata-protocol: the bare read still serves an App-pair row of an email_template member whose only holder is disabled

2 participants