Repository navigation
fix(metadata-protocol)!: a name two or more enabled packages hold only as stored rows, and no package ships, is refused by the read naming no package instead of served by save order (ADR-0150 D3) - #33182
Conversation
…, not per deployment (ADR-0150 child B, part 1 of the webhook family) WIP: the switch, the platform-requested auth.* names, the bundle-addition rule, the package-scoped /meta read and the send's holder-filtered bundle. Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…latform-requested auth names and the bundle-addition rule Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…rrowing Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…nd a pair's member as the package's own A read naming a dependent whose only member of a bundled name is an addition (a locale its base does not ship) was refused not-held-by-qualifier once email_template joined the package-scoped readers, and a pair's added member on its base's address was replaced by the registry's item. The qualifier now asks the holders as the caller it names, and keeps an item of a pair of the admitted package. The co-owner dogfood case asserts the ADR-0146 D2 refusal a co-owner meets first at install-local. Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…he email template's narrowing; changeset Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…inst its base Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…ule top Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…s base's address for a bundled type only Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…rowing; pin the qualifier's holder ask for the nine Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…mplate send resolves its name from the caller's package (ADR-0150 D3) The flow notify node records the package whose own flow body is armed beside its template reference; the email and inbox channels hand it to sendTemplate/renderTemplate as callerPackageId; the registry loader resolves the name from it and, for a name two packages hold, reads the resolved holder's own address. A send that names no package keeps the bare-name meaning. Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…is refused on both exact reads and listed by the upgrade report Two packages of one App share the App's customization pair, so a locale row there with no overrides declaration cannot say whose template it extends: each holder's exact read refuses it naming every holder, the list scoped to a holder leaves it out, and os migrate stored-references lists it under pairMembers. One holder, the App's own address and a row that declares its base keep being served. Pins for F1 (loader rungs, service ladder, notify node, channels, dogfood) and F2; changeset. Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…otype.at, which the package's lib target does not declare Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…) for email_template, beside api Two packages of two namespaces may now each hold one email template name, so the second package's item no longer overwrites the first's in MetadataManager's in-memory store: a read naming a package answers that package's own, and a read naming none of a two-holder name is the D3 refusal (#31527 B-shape item 1). Pins that email_template has no metadata redactor, so a save never takes the carry-forward's code-layer read for it (item 4). Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf # Conflicts: # packages/objectql/src/package-scoped-e8-readers.test.ts # packages/objectql/src/package-scoped-read.ts
…-3giljf # Conflicts: # packages/cli/src/utils/stored-package-references.ts # packages/metadata/src/package-slotted-store.ts
…es hold is no holder's own A package-less row (the environment's item, or a sys_metadata row bound to no package) of a name two or more packages hold is the customization of one holder and cannot say which (ADR-0150 D4). Each holder's exact read now refuses it 409 ambiguous-in-closure naming every holder, the list scoped to a holder (which a send made from it reads) leaves it out, and os migrate stored-references lists it under packageLessRows. One holder, the _env address and the platform-requested auth mails are unchanged. The rule sits in the exact qualifier, so it covers every type read by package (epic ruling 6761802819 on #31699). Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf # Conflicts: # packages/objectql/src/package-scoped-e8-readers.test.ts # packages/objectql/src/package-scoped-read.ts
…et (#32183) A disabled package's item is not a holder. `packageScopedHoldersOf` withholds a holder whose package is disabled (`isPackageDisabled`, the predicate `listItems` hides by), keeps the caller's own holding, and reads every holder under `includeDisabledPackages: true`, which `resolvePackageScopedName` forwards. Durable judgments read every holder: the four stored-reference backfills, the CLI stored-reference report and the picklist load audit, so a binding never moves with a reversible disable. The security catalog never re-targets a stored column that names a disabled holder, and a name only disabled packages hold is not-found there and in `holdsPlatformPermissionSet`, never the metadata service's by-name answer. The REST action rung answers a thrown package-scoped refusal as the declared 409, not `degraded`. Claude-Session: https://claude.ai/code/session_01WGufzgH2rGjWC9emeku9yp Co-authored-by: Claude <noreply@anthropic.com>
Pins, each with its BASE reading at main de141e3: - objectql: the source half (served holders, the caller's own holding, every holder on request, re-enable, a disabled base never promoting a bundle addition), the library readers including the read MCP `agent_prompt` makes, and action addressing. - Both `/meta` transports over the live `PACKAGE_SCOPED_READ_TYPES`: two holders with one disabled, only a disabled holder, a parity pin per type (the bare read and the list agree through disable and enable), and a shareable namespace's co-owners. - The REST action rung and the security catalog (no re-target of a disabled holder's column, no metadata fallback, the platform verdict). - Every durable judgment keeps every holder: the four backfills and the CLI stored-reference report. The picklist verdict (`picklistReferenceVerdict`: the load audit, the install door and a field's options) judges every holder, so a reversible disable of a dependency never refuses the next boot or empties a live field's options; the by-name `picklist` read judges the served holders. Claude-Session: https://claude.ai/code/session_01WGufzgH2rGjWC9emeku9yp Co-authored-by: Claude <noreply@anthropic.com>
Clause-② no (narrowing): one in-process library answer narrows for a name only disabled packages hold; every door move is listed FROM -> TO. Claude-Session: https://claude.ai/code/session_01WGufzgH2rGjWC9emeku9yp Co-authored-by: Claude <noreply@anthropic.com>
…eads no migration placeholder (#32183) The two label lines read `FROM → TO`, which the ADR-0087 gate takes as a migration prescription beside `not-required (no-migration-prescription)`. Each moved answer still reads as before → after; nothing is rewritten by a consumer, so the disposition stands. Claude-Session: https://claude.ai/code/session_01WGufzgH2rGjWC9emeku9yp Co-authored-by: Claude <noreply@anthropic.com>
…heir withheld item (#32183) With holders judged over the served set, the by-name read of a name only disabled packages hold resolved to nothing, and its envelope carried no package. The layered view's step 1b and `/published` ask that envelope whether a disable withheld the item, so both doors took the name as not withheld: `/layers` served the disabled package's code layer at the bare address and at `?package=` of any non-holder, and `/published` served its snapshot. The item face now answers such a name as that package's withheld item (item undefined, the envelope naming the disabled holder), the shape a disabled holder's read had, on both faces. Every door keeps the missing name's 404. Claude-Session: https://claude.ai/code/session_01WGufzgH2rGjWC9emeku9yp Co-authored-by: Claude <noreply@anthropic.com>
…ished 404s (#32183) The item face now carries part of the fix, so `@objectstack/metadata-protocol` joins the changeset. The note says the layered view and `/published` keep the missing name's 404 for a name only disabled packages hold, and lists the bare read of a name two or more disabled packages hold (409 → 404). Claude-Session: https://claude.ai/code/session_01WGufzgH2rGjWC9emeku9yp Co-authored-by: Claude <noreply@anthropic.com>
…n the action addressing pins (#32183) The file's import graph reaches `standalone-stack.ts`'s dynamic import of `@objectstack/metadata`; check:test-source-alias asks an edited test file to load it at module top, so no clocked case pays that transform. Claude-Session: https://claude.ai/code/session_01WGufzgH2rGjWC9emeku9yp Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzgH2rGjWC9emeku9yp Co-authored-by: Claude <noreply@anthropic.com>
…eview round on #32214 - notify node: a literal payload `template` string records the armed package too, as the channels read it (F-A). - the pair-row owner check is one self-contained function, pairRowOnAddress, which the exact read and the scoped list both ask: the declared base first (F-B), the App's own pair, two owners refused, a sole owner that is not one of the address's bases not served (S2). - SchemaRegistry.getPackageScopedItem skips a pair row that declares another holder as its base, so the App's address answers its own item. - stored-references hints, its module doc and the changeset say truthfully that no door binds an added member to one base today (#28777) (F-C). - changeset: Metadata-protocol narrowed; slotted set lists api, agent, skill and email_template; the package-less rule names PACKAGE_SCOPED_READ_TYPES; the pair row's sole-owner and App-slot rules. - email-service docs page: the new callerPackageId member. Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf # Conflicts: # packages/cli/src/commands/migrate/stored-references.integration.test.ts # packages/metadata/src/package-slotted-store.ts # packages/objectql/src/bare-name-one-holder.ts # packages/objectql/src/registry-bare-name-one-holder-ai-family.test.ts # packages/objectql/src/registry.ts # packages/qa/dogfood/test/bare-name-one-holder-flow-family.dogfood.test.ts # packages/services/service-automation/src/engine.ts
…holds take the served holder's stack Contract review M1 on #32327. A read naming no package, of a name an enabled and a disabled package both hold, now reads at the served holder's address: the plain read (the face), the layered read's code layer, overlay row and lock, and the layered row-ambiguity refusal, which no longer refuses a read the served holder addresses. Before, the layered code layer was the first composite in registration order (the disabled holder's when it registered first), and a legacy row bound to the disabled holder made the bare read answer 404. Pins on both transports, both registration orders: the plain read and /layers for every package-scoped type, and the plain read, /layers and /published with legacy rows bound to the disabled holder (REST harness and a booted kernel). Claude-Session: https://claude.ai/code/session_01WGufzgH2rGjWC9emeku9yp Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 4 package(s): 2 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 — 179 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 52b05f7903afc65297d89744b11dffd7e2f1769c && git checkout 52b05f7903afc65297d89744b11dffd7e2f1769c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6e1ab9b286655c5476312ae563ab531f3f36cf38 f20d249baf18db795aaaf2edb6bb601da64539f9 && git checkout -B drift-repro 6e1ab9b286655c5476312ae563ab531f3f36cf38 && git merge --no-ff f20d249baf18db795aaaf2edb6bb601da64539f9
node scripts/docs-audit/affected-docs.mjs --json 6e1ab9b286655c5476312ae563ab531f3f36cf38
|
|
review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv Contract-review-claim: session_01L9qibzosF5fZk98UHos9cv · head 5d3a6c7 · 2026-11-14T00:32Z |
|
review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv Contract reviewServed-tier: Contract review and epic red-line read of card #32607, read at 2026-11-14T00:46Z. Inputs: the card and its rulings (6820653691, 6821134180, 6836791341), the diff of this head against merge base ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentRed-line 1 read of the guard
The hand-over in both transports ( The item face now reads the name's active rows once more on every read naming no package of a by-package type that no enabled package ships at the member and that has no environment item. See S2. Findings:
Implemented-by: VERDICT: FAIL |
…tive rows, holders at the rows' grain, and only the row judgment's refusal handed over Contract review 6868755969 (review#1) and CI on 5d3a6c7: - S2 / CI (objectql protocol-meta #24371 pins): the item face reads the active rows for its served-holder judgment on its own cache key and hands them to readMetaItem (PrereadActiveRows), which takes them for the same item: one read again. - M1, M2: the no-disabled arm (storedRowHoldersRefusal) judges holders at one grain, the packages the active rows bind at the member, never united with the registry's one bare slot; a row of a pair of another known package is no holder. - S1: the refusal carries a registered-symbol marker; both transports' layered owner step hands over only that refusal and rethrows every other. - n1: pins on a bundled type at a non-canonical member (M2), M1 in both save orders before and after a reboot, S1 on REST. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4czBmkW6UeWPazkWq7L
|
review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv Contract-review-claim: session_01L9qibzosF5fZk98UHos9cv · head 2eb2f7c · 2026-11-14T02:26Z |
|
review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv Contract reviewServed-tier: Delta review of FAIL 6868755969, read at 2026-11-14T03:01Z. Inputs: the interdiff ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
The hand-over (S1). Closed. The judgment marks its own refusal under a registered symbol and both transports hand over only a marked error. The refusal beside a disabled holder and the registry's refusal are not marked and are rethrown, as on The one read (S2). Closed. The face reads the rows through the overlay cache and hands them to Red-line 2 read of the two residuals (asked in the knock). Neither is made or widened by this PR; both are
The pins. M1 runs both save orders before and after a reboot and asserts no refusal on the plain read, Findings:
M1, M2, S1 and S2 of record 6868755969 are closed on this head. No must-fix, no should-fix. Implemented-by: VERDICT: PASS |
…-loearv Second and last merge of main for #32607, at the seat's landing request (the head was 25 commits behind). Clean: protocol.ts auto-merged (#33119's hunks are elsewhere in the file). Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4czBmkW6UeWPazkWq7L
|
review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv Contract-review-claim: session_01L9qibzosF5fZk98UHos9cv · head f20d249 · 2026-11-14T04:25Z |
|
epic:#31527#2 · os-elon-musk · session_016WV4czFhtRVZNBeHdQA6PW ACCEPT · 2026-11-14T05:05Z · head
|
|
epic:#31527#2 · os-elon-musk · session_016WV4czFhtRVZNBeHdQA6PW Contract review · merge-only delta · 2026-11-14T05:12ZReviewer: Opus subagent of the seat, on the maintainer's order「你自己用 opus 审查吧」 (review seat paused) Local-runs: none (1) The interdiff
(2) main's commits that meet the PR, judged one by oneI filtered the files of all 25 commits against two groups:
The following are unchanged by Four commits meet the PR. I also read a fifth, #33174, because its title mentions a refusal.
Order after the merge: main has moved to One of those five, #33189 ( (3) Declaration and red linesDeclaration. Unchanged byte for byte: the changesets and the entry have the same blobs on both heads.
The id The PR's judgment. The meaning of each part is unchanged on the merged head: Red line 1, resolution never loosened. Holds. The arm still only refuses or leaves the read as it was. The two main commits that reach the hydrator only decline or rebind more strictly. Red line 2, ambiguity reported, never guessed. Holds as review#1 read it. Its residuals (a) and (b) are left as they were by the merge and remain carried in the body's Carried section. Red line 3, independently releasable. Holds:
CI on this head when I read it: 72 check-runs, 59 success, 13 skipped, 0 failed. (4) Findings
VERDICT: PASS |
Requested by Elon · project thread
Fixes #32607
Clause-②: yes (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — no skill and no doc teaches what the read naming no package answers for a name only stored rows of several packages hold (
skills/**andcontent/docs/**grepped: theambiguous-in-environmentmentions are about flow, queue and permission-set references, not/metareads); the refusal's own sentence names the?package=remedy.维护者速读
GET /meta/TYPE/NAME、/published、执行读)现在答 409 并点名这两个包;以前它按保存顺序返回先存的那一行。/layers本来就答 409,保持原样不动。/layers却答 409,答案取决于保存顺序,等于替调用方猜了一个包,违反史诗红线 2(歧义要报告,不许猜)。?package=的精确读、被拒的行(declined)、某包的定制配对行、依赖包给行持有的基础包追加的语言成员,答案都不变。第 2 轮按复审修了 5 处(见 Round 2)。回滚 = revert 本 PR。packages/metadata-protocol/src/protocol-meta-item-face.ts的storedRowHoldersRefusal(只按行的粒度判持有者)与readMetaItemFace(把读到的行交给readMetaItem);packages/rest/src/rest-server.ts与packages/runtime/src/domains/meta.ts各一个 catch 块(让/layers继续答写入口的 409);packages/runtime/src/domains/meta-disabled-holder-served-stack.test.ts的[#32607]组。pnpm --filter @objectstack/rest exec vitest run src/meta-disabled-package-doors.test.ts -t 32607,预期全部通过:两种注册顺序 × 两种保存顺序下裸读都是 409 点名两个包,单持有者 CONTROL 仍答 200。What changes
servedRowHolderOf(@objectstack/metadata-protocol, the row-aware served-holder judgment #32354 landed in #33130) answered only when some package was disabled. With no disabled package it returnedundefined, so the plain read fell through tofindServedOverlayRow, which takes the first row by save order, whilepackagelessLayeredReadRefusalcounted both bindings and refused 409. Same name, two doors, two answers, and one of them a guess.Now the item face (
readMetaItemFace, which the plain read,/publishedand the execution face share) asks the judgment with{ refuseWithoutDisabled: true }:shippedArtifactsOf(type, name), the setpackagelessContentPackagealready counts),storedRowHoldersRefusaljudges the holders at ONE grain: the packages the item's active rows are bound to at the member, as the read binds them (packagelessAddress), less rows the reads decline and rows of a customization pair of another package the rows or the registry name. It never unites them with the registry's holders of the name (one bare slot, the row written or hydrated last). Two or more are refused with the by-name read's own refusal (ambiguous-in-environment,details.candidates); one or none:undefined, the read as it was.readMetaItem(PrereadActiveRows), which takes them for the same item: the item face reads the item's active rows once (data reads: every GET /api/v1/data read makes five serial sys_metadata reads of the target object's own active row (about 250 ms at 50 ms RTT) #24371).en-USshipped, a dependent'sfr-FRrow) keeps its answer. The first dogfood run on3b9a447dc3caught exactly that over-reach (three metadata-protocol: an email-template locale added in a<base>.custompair is read by name but never listed or sent, so a send in that locale renders the base's (#28779 follow-up) #28802 pins,expected { status: 409 } to deeply equal { status: 404 });abd91aad9eadds the guard.packagelessLayeredReadRefusal) passes no option and is unchanged, so/layerskeeps the package-less writes' refusal (meta: with two package holders of one dashboard name, GET /meta/dashboard/X/layers silently returns one holder's overlay while package-less save and delete answer 409 #28337, ruling Q1 = A, 6820653691).objectstack.metadata-protocol.storedRowHoldersRefusal) and step 1c (packagelessLayeredReadRefusal) has a refusal for the address, hands over to it; every other error is rethrown as before.d1; no package shipsd1)GET /meta/:type/:nameRESOURCE_CONFLICT,details.candidates= E, FGET /meta/:type/:name/publishedRestServer: the row saved first; runtime dispatcher: the same package in every save order)RESOURCE_CONFLICTnaming E, FgetMetaItemForExecution)GET /meta/:type/:name/layers?package=E/?package=Fen-US, a dependent storesfr-FR), package-lessfr-FRreademail_template: base storesen-US(nobody ships it), a dependent storesfr-FR(M2): plain read,/layers, execution face atfr-FR/layersof a name two packages ship in code, each with its own stored row (S1)Both transports (
RestServer, the runtimeHttpDispatcher), both registration orders, both save orders, and on the runtime dispatcher before and after a reboot.Files:
protocol-meta-item-face.ts,protocol-meta-item-reads.tsandprotocol.ts(the hand-over of the rows read once), the A1 hunks inrest-server.tsandruntime/src/domains/meta.ts, the pins inrest/src/meta-disabled-package-doors.test.tsandruntime/src/domains/meta-disabled-holder-served-stack.test.ts, the semantic entrypackages/spec/src/migrations/entries/semantic/18.two-row-holders-bare-read-refused.ts, and two changesets (metadata-protocolminor +rest,runtimepatch, BREAKING,adr-0087: registered;specpatch for the entry).Tests
packages/rest/src/meta-disabled-package-doors.test.ts, describe[#32607] RestServer: two enabled row holders and no shipper: NARROWING over 2 registration orders × 2 save orders (plain read 409 withdetails.candidates,/layersand/published409 naming both and serving no row, execution face rejectsRESOURCE_CONFLICT/409, each?package=serves its own row); A1 (/layersof two enabled row holders leads with the writes' sentence); A1 CONTROLs (two code holders, and two served row holders beside a disabled one, keep the plain read's refusal on/layers); CONTROL (one row holder served on every bare door).packages/runtime/src/domains/meta-disabled-holder-served-stack.test.ts, describe[#32607] two enabled row holders and no shipper: the same NARROWING on a booted kernel over the runtime dispatcher, 2 registration orders × 2 save orders, before and after a reboot, plus the/layersvs/publishedsentence split; CONTROL one row holder, before and after a reboot.runtime/src/domains/meta-disabled-holder-served-stack.test.ts, describe[#32607 M1, M2] the no-disabled arm judges holders at the rows' grain: M1, a package (com.qa.rowpairbase) and its pair each storing an undeclared row, both save orders, before and after a reboot: the plain read and/publishedanswer 200 and no door refuses or names the pair, each order answering the same before and after the reboot. M2 (n1: a bundled type at a non-canonical member), base storingen-US, dependent storingfr-FR, both save orders, before and after a reboot: the plain read,/layersand the execution face atfr-FRserve the dependent's row; the base'sen-USstays the base's./publishedtakes no member selector (400VALIDATION_ERROR), so it is not asked atfr-FR.A1 CONTROL (S1): two packages ship a name in code, each with its own stored row;/layerskeeps the plain read's refusal, not the writes' sentence.rest/src/meta-layers-ambiguous-package.test.tsand the runtime twin) are untouched and green.objectql/src/protocol-meta.test.ts(red on CI at5d3a6c757c, Test Core 4/8) are green again.packages/qa/dogfood/test/email-template-dependent-unshipped-locale-read.dogfood.test.ts(metadata-protocol: an email-template locale added in a<base>.custompair is read by name but never listed or sent, so a send in that locale renders the base's (#28779 follow-up) #28802, booted showcase), which this PR leaves untouched.Premise re-measured on
main89d7bef033(this branch's tests, main's three source files): the runtime[#32607]NARROWING pins went 4 failed / 2 passed; quoted:{"success":true,"data":{… "label":"enabled row", "_packageId":"com.qa.row_alpha" …}}: expected [ 200, undefined ] to deeply equal [ 409, 'RESOURCE_CONFLICT' ](E saved first) and… "label":"third row", "_packageId":"com.qa.row_gamma" …(F saved first). The bare read still answered by save order.Ablation 1 (the item face's
{ refuseWithoutDisabled: true }replaced by an expression esbuild folds tofalse;metadata-protocolrebuilt;ablation-dist-preflightfoundrefuseWithoutDisabled: false }in 2 built files, andrefuseWithoutDisabled: true }in 0): runtime 4 failed / 18 passed of 22, REST 6 failed / 33 passed of 39, exactly the NARROWING and A1 pins. Quoted:AssertionError: {"type":"dashboard","name":"two_rows_dash","item":{… "label":"healthy row" …}}: expected [ 200, undefined ] to deeply equal [ 409, 'RESOURCE_CONFLICT' ]. Restore leg:ablation-replace --restoreblob equals HEAD,git diff HEADempty, rebuilt, preflight--absent: marker absent from all 24 built files, tree clean.Ablation 3 (the shipped-name guard:
!anyDisabledreplaced by(globalThis as any).__ABL32607B === 1;metadata-protocolrebuilt; preflight: marker present in 2 built files): the #28802 dogfood file 3 failed / 10 passed of 13, quotedAssertionError: expected { status: 409 } to deeply equal { status: 404 }(×2) andexpected { status: 409 } to deeply equal { status: 200, …(3) }. Restore leg: blob equals HEAD,git diff HEADempty, rebuilt, preflight--absent: absent from all 24 built files, tree clean.Round 2, measured before the fix (head
1ab9370442, the shared-read fix alone): M1 409 namingcom.qa.rowpairbaseandcom.qa.rowpairbase.custom; M2 409 naming the base and the dependent. Onmain89d7bef033(main's five source files put back): M2 green, and M1 answers exactly as on this head (no refusal; the row served follows the save order, see Carried).Ablation 4 (the one grain: the registry's holders united back into the holders, marker
__ABLU32607in 2 built files): runtime 3 failed / 22 passed of 25, the M1 and both M2 pins, quotedRESOURCE_CONFLICT … package "com.qa.rowpairbase"; package "com.qa.rowpairbase.custom"and… package "com.qa.mailrowaddon"; package "com.qa.mailrowbase". Restored, rebuilt,--absentclean. (A first attempt was a no-op: its replacement text contained the anchor andablation-replacerefused it; that run is not counted.)Ablation 5 (S1: the REST hand-over takes any error, marker condition dropped; src import, no build): 2 failed / 42 passed of 44, the two
A1 CONTROL (S1)pins, quoted{"error":"Name the package this layered read is for, …where the plain read's sentence was expected. Restored,git diff HEADempty.Ablation 2 (REST A1: step 1b's hand-over
return falsereplaced bythrow error; the suite imports./rest-server.js, no build): 3 failed / 39 passed of 42: the two[#32607] … A1pins and the #28337 pin (both spellings answer the package-less writes' 409 …). Restore leg: blob equals HEAD,git diff HEADempty.Acceptance notes
claude/project-thread-j3i5j5@d721d9a125and mergesorigin/mainonce (89d7bef033, merge commit3b9a447dc3). The merge takes main's landed copies of every file the old branch carried from metadata-protocol: a name held only by stored rows of two packages, one disabled, answers by save order, and /layers takes the first composite when the environment holds its own item (ADR-0150 D3) #32354'spv4nipand metadata-protocol: a pair address (?package=<base>.custom) takes its envelope from the first registered holder of the name, so it answers one package's body under another's _packageId #32501 (main's 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 and fix(metadata-protocol)!: the exact read no longer serves an undeclared App-pair row the registry binds to no package; it adopts SchemaRegistry#overlayRowBinding over the served holders (ADR-0150 D4) #33148), drops the patch copycb73bf6c, and replays the card's own seven commits (ef9fceb2…d721d9a1) onto them. Two files conflicted:protocol-meta-item-face.ts(main's served-row filter for declined and pair rows,servedAddressJudgmentand the member-selector resolution, with the card's option threaded through them) and the REST doors test (two describe blocks appended at the same place, both kept). The diff againstmainis this card's ten files only; three commits follow the merge (abd91aad9e, the shipped-name guard,5d3a6c757c, its changeset sentence, and2eb2f7c258, round 2). At the seat's landing request (the head was 25 commits behind),origin/mainwas merged a second and last time (67274acad6, merge commitf20d249baf): clean,protocol.tsauto-merged (fix(lint)!: the object_extension publish door judges a computed field an extension adds by the build's field-formula pass, against the extended object as served #33119's hunks are elsewhere in the file), and the diff againstmainis still the card's ten files, 613+/21−.Metadata-protocol: none(seat ruling 6820653691) →narrowed, per review#1's ruling on fix(lint)!: os validate and os build resolve a dashboard's dataset through the reader's declared closure, as the runtime does #33121 (a refusal of a read that served before) and the seat's re-posted claim 6866219396. Henceminor, the BREAKING banner,!and the ADR-0087 entry.?package=HOLDER.custom, metadata-protocol: after a reboot, the pair address X.custom of a name held only by stored rows of two enabled packages answers by hydration order (one 200, the other 409 naming one side) (ADR-0150 D3, red line 2) #32662);details.candidateson/layers(metadata-protocol + rest: the layered read (/meta/:type/:name/layers) of a name two packages hold answers as the plain read — 409 with details.candidates, never the first holder (ADR-0150 D3/D5; collects #32622 #32624) #32622); the REST/layersanswer for two registry holders (rest: GET /meta/:type/:name/layers of a name held by two registered packages answers 200 with the first holder instead of the 409 the plain read and the dispatcher /layers give (ADR-0150 D3, red line 2) #32624, sequenced after this card on the same chain).Round 2 (review#1 FAIL 6868755969 on
5d3a6c757c, and CI Test Core 4/8)PrereadActiveRows); objectqlprotocol-meta.test.ts94/94.storedRowHoldersRefusal); pinned as asked, red before the fix, green after; ablation 4.Carried (for the seat to route)
rows.unboundleaves the read as it was, so the plain read still takes a row by store order while/layersrefuses naming both. Not covered by this PR. Carried by metadata-protocol: a package-less row beside two packages' rows of one name (no shipper) is served by store order on the plain read while /layers refuses — follow-ups of PR #33182 (#32607) #33210 (p2, triage ruling 6870700526: red line 2 decides, refuse 409 as/layersdoes), together with review#1's n2 of 6870642184 (a row-held base's added member answers the dependent's row where a shipped base answers 404)./publishedserves X's row in both), measured identically onmain89d7bef033. It is the plain read's own pick between a base row and its pair's undeclared row, not this judgment's. Folded into metadata-protocol: after a reboot, the pair address X.custom of a name held only by stored rows of two enabled packages answers by hydration order (one 200, the other 409 naming one side) (ADR-0150 D3, red line 2) #32662 (triage ruling 6870700526).Verification
All readings on the final head
f20d249baf(the second merge ofmain), under the lock,VERDICT command-exit 0. Round 2's readings on2eb2f7c258(objectql 12863, metadata-protocol 22803, rest 8360, runtime 10599, dogfood 3905) were all green as well, and are superseded here.Affected suites and typecheck
One locked run: the dogfood closure rebuilt (
pnpm --filter '@objectstack/dogfood^...' build), then each suite in full.pnpm --filter @objectstack/objectql test: Test Files 595 passed (595); Tests 12903 passed (12903), including the data reads: every GET /api/v1/data read makes five serial sys_metadata reads of the target object's own active row (about 250 ms at 50 ms RTT) #24371 one-read pins CI's Test Core 4/8 failed on at5d3a6c757c.pnpm --filter @objectstack/metadata-protocol test: Test Files 338 passed, 7 skipped (345); Tests 22810 passed, 36 skipped (22846).pnpm --filter @objectstack/rest test: Test Files 409 passed (409); Tests 8360 passed, 332 skipped (8692).pnpm --filter @objectstack/runtime test: Test Files 597 passed (597); Tests 10624 passed, 19 skipped (10643).typecheckofmetadata-protocol,objectql,rest: exit 0.runtime: itscheck:test-typecheckran out of heap locally (the session'sNODE_OPTIONSoverrode the script's 4096 MB default, leaving 2 GB); re-run with--max-old-space-size=4096, as CI sets it: exit 0, "27 file(s) / 183 error(s) / 66 pinned signature(s) held".test:repo(vitestrepoproject) not run locally: the diff touches no file of that project; CI'sTest Coreruns it.dispatch-gates
node scripts/pm/dispatch-gates.mjs --commandsderived the same 97 commands from 10 paths vs merge base. All 97 exit 0 onf20d249baf, includingcheck:dual-build-cjs-loads,type-check-coverage,type-check-debt,dts-closure,lean-entry-closure,published-filesand speccheck:browser-reachable-entries.dispatch-gates --ran(record/mnt/project-files/epic-31527-seat2/cards/32607-thread/ran-r3.txt): "97 derived famil(ies) accounted for — 97 run, 0 NOT-MEASURED".node scripts/check-test-source-alias.mjsexit 0;node scripts/check-nul-bytes.mjsexit 0.Lint and dogfood
pnpm lint(eslint, whole repo) onf20d249baf: exit 0.Full dogfood battery on
f20d249baf(!title,Clause-②: yes (narrowing)), closure rebuilt after the merge, no shard, no path: Test Files 464 passed, 3 skipped (467) · Tests 3927 passed, 42 skipped (3969) · no failure. The #28802 file (email-template-dependent-unshipped-locale-read.dogfood.test.ts) is green in it.Round 1 for the record: the first battery on
3b9a447dc3was red in that file, 3 tests (expected { status: 409 } to deeply equal { status: 404 }), fixed inabd91aad9e.🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4czBmkW6UeWPazkWq7L