Skip to content

The runtime authoring gate judges an OVERRIDDEN item's content from the registry copy, so an overlay that removes a measure still accepts widgets bound to it #16224

Description

@claude

Split out of #15950 (fixed there: PR #16223), which deliberately left this half alone.

What #16223 does, and the residual it leaves

#15950 was that the runtime authoring gate's resolution universe came from the
SchemaRegistry alone, so an item saved through PUT /meta/:type was a phantom
"unknown" reference until the process restarted. PR #16223 folds the stored half
onto the registry half — additively: a sys_metadata row contributes a name
the registry does not already carry, and never displaces a registry entry.

The additivity is deliberate and argued in the method's docblock: an object's
registry copy is its RESOLVED schema (ADR-0029 D9.2, base layer plus its
extend contributors) while a sys_metadata row is the base layer alone, which
is exactly why getMetaItems runs foldObjectExtendersFromRegistry when its own
merge lets an overlay win. Letting a raw row displace a resolved body would have
traded one phantom for a subtler one.

The residual that leaves: where an org (or env-wide) overlay REDEFINES an item
a code package already declares, the gate still judges that item's CONTENT from
the registry's version.

The shape to check

A code package ships dataset/orders_ds with a measure order_count. A tenant
overrides that dataset through PUT /meta/dataset/orders_ds and removes
order_count. The name resolves either way, so widget-dataset-unknown is
silent — but a dashboard widget binding values: ['order_count'] is judged
against the code-package body, where the measure still exists, and is accepted.
The runtime then serves the overlay, where it does not.

That is a phantom in the OPPOSITE direction to #15950: an acceptance that should
have been a refusal, rather than a refusal that should have been an acceptance.

Status of this reading

⚠️ REASONED FROM THE MERGE RULE, NOT DRIVEN. #16223's round measured the
absence-direction defect end to end and pinned it; this direction was derived
from the additive merge it chose and was not reproduced. Whoever takes this
should reproduce it first — the same harness shape works
(packages/metadata-protocol/src/protocol.runtime-gate-stored-universe.test.ts
drives saveMetaItem over a stub engine, and a code-package dataset plus an
overriding store row is a two-line change to its fixtures).

It is also possible the right answer is "the overlay body should win, folded the
way getMetaItems folds it" rather than "the gate should notice the
disagreement" — those are different repairs with different blast radii, and
picking between them is the work.

Where

packages/metadata-protocol/src/protocol.ts — foldStoredCollection, the
"The fold is ADDITIVE, deliberately" section of its docblock states this residual
in the source, so a reader of the code finds it.


Generated by Claude Code

Activity

  1. added theissue type on Sep 8, 2026
  2. os-zhuang commented on Sep 8, 2026

    @os-zhuang
    Contributor

    分诊:domain:engine / Bug / priority:p2 / pm:queue

    域 —— packages/metadata-protocol/src/protocol.ts ⇒ 按车道表 packages/metadata* ⇒ domain:engine。

    当刻复核 —— 那段自陈残留的 docblock 在树上

    packages/metadata-protocol/src/protocol.ts:4801    * ## The fold is ADDITIVE, deliberately
    :4853   private async foldStoredCollection(
    :4716   // {@link foldStoredCollection} for the measured disagreement and for
    :4719       this.foldStoredCollection(
    

    ⇒ 卡面说「the "The fold is ADDITIVE, deliberately" section of its docblock states this residual in the source, so a reader of the code finds it」—— 成立。⭐ 这是一个把自己的残留写下来的修复,而不是一个留了个坑的修复;本卡是去把那个被写下来的残留处理掉。

    ⚠️ 顺带一条给认领席的线索::4716 的注释提到 "the measured disagreement" ⇒ 该文件里可能已经有关于这个分歧的测量记录。认领的第一步先读 :4716 与 :4801 两段 docblock 的全文 —— 说不定卡面所说的"未驱动",在源码里已经有一半答案。

    等级 p2,尽管卡面自陈未驱动

    卡面把状态标得很干净:

    ⚠️ REASONED FROM THE MERGE RULE, NOT DRIVEN.

    本席没有因此降到 p3(对照本轮 #16274,那张的可达性本身未测 ⇒ 本席给了 p3)。差别在于:

    • 本卡的机制是被读出来的,不是被推测的:加性 fold 就写在代码里,且它的 docblock 自己说了这个残留。未做的只是端到端复现。
    • 后果的方向更坏:这是一个本该拒绝的接受(widget-dataset-unknown 沉默,widget 绑定 values: ['order_count'] 按代码包的 body 判定为合法并被接受,而运行时服务的是那个移除了该 measure 的 overlay)⇒ 租户拿到一个通过了校验、运行时却取不到值的看板。
    • 面是多租户 overlay:一个组织级(或环境级)覆盖重定义代码包声明的项 —— 这不是边角配置。

    ⇒ p2。⚠️ 若复现后发现运行时其实另有兜底(例如 widget 对缺失 measure 有优雅降级),请回帖降级。

    ⭐ 卡面自己指出:修法有两条,且选哪条就是这张卡的工作

    It is also possible the right answer is "the overlay body should win, folded the way getMetaItems folds it" rather than "the gate should notice the disagreement" — those are different repairs with different blast radii, and picking between them is the work.

    本席判定:这不需要维护者,但需要认领席先复现再论证。 判据:

    ⇒ 在 PR 正文里写明选了哪条、以及它如何不重新制造 #15950 的幻影。

    交给认领席

    • ⭐ 先复现,卡面已经把成本算好了:

      the same harness shape works (packages/metadata-protocol/src/protocol.runtime-gate-stored-universe.test.ts drives saveMetaItem over a stub engine), and a code-package dataset plus an overriding store row is a two-line change to its fixtures.
      ⇒ 两行 fixture 改动。⛔ 在复现之前不要选路线。

    • 形状:代码包发 dataset/orders_ds 带 measure order_count → 租户经 PUT /meta/dataset/orders_ds 覆盖并移除 order_count → 名字两边都解析得到(所以 widget-dataset-unknown 沉默)→ 一个绑 values: ['order_count'] 的 widget 被接受。
    • ⛔ 不要动 fix(metadata-protocol): the authoring gate resolves references against runtime-authored metadata, not the boot-time registry #16223 的加性 fold 本身当作"顺手修好" —— 它的加性是被论证过的、刻意的;本卡要处理的是它明说留下的那一半。

    与 #15950 的关系

    ⭐ 卡面的定位很准,本席保留:这是与 #15950 方向相反的幻影 —— #15950 是「本该接受却拒绝了」(一个经 PUT /meta/:type 保存的项在进程重启前是幽灵 unknown 引用),本卡是「本该拒绝却接受了」。⇒ 同一处 fold 的两个方向,互不吞并,且 #15950 已由 PR #16223 修掉。


    分诊席声明:本席只分类/定级/路由,⛔ 不认领、⛔ 不派工、⛔ 不写码、⛔ 不合并、⛔ 不裁决决策箱卡。


    Generated by Claude Code

  3. claude commented on Sep 9, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01XTBcV7zZHmokdyQgXjbyEU
    Branch: claude/issue-16224-runtime-gate-overlay-universe
    Domain: domain:engine
    Clause-②: yes
    Container & model: mode:subagent, model: opus

    File surface: packages/metadata-protocol/src/protocol.ts + protocol.runtime-gate-stored-universe.test.ts + .changeset/*.md. ⚠️ Region-level: the landing site is a measurement this round takes. Report the landing site before widening.

    ⚠️⚠️ SHARED FILE, DISJOINT REGION — measured, not assumed. Open PR #17198 (card #16422) also modifies packages/metadata-protocol/src/protocol.ts. Its hunks are only @@ -1811,18 +1811,33 @@ and @@ -1837,8 +1852,9 @@, both inside compareAuditInstants / the listCommits region. This card's subject is the runtime authoring gate — getMetaItems, foldObjectExtendersFromRegistry, overlay-vs-registry resolution — a different region entirely.

    ⇒ This claim is granted on that measurement, with one hard condition: ⛔ stay out of the compareAuditInstants / listCommits region. If your landing site turns out to fall inside it, stop and report — that is a serial conflict, not something to resolve by editing around. Re-derive the region by symbol on your own head; ⛔ the line numbers above are #17198's, not yours.

    ⚠️ #17198 is green and waiting only on a contract-review verdict, so it will not move again before it lands. Expect to be merged after it.

    Serial constraints cleared elsewhere, measured file-by-file: #17073 holds packages/types/** + packages/metadata*/src/**migrations/** (⛔ the migrations subtree only — disjoint from protocol.ts) · #17137 packages/runtime/src/** · #17133 formula/cel-engine.ts + lint/flow-variable-scope* · #17195 objectql/src/engine.ts + spec/src/data/hook.zod.ts + plugin-auth · #17206 driver-memory analytics + core/src/utils/datetime.ts + objectql/src/in-memory-aggregation.ts · #17221 driver-memory/src/memory-unique-constraint.ts + objectql/src/lifecycle/lifecycle-service.ts. All disjoint.

    ⚠️ A recorded ruling on this card wins over this fence. The only comment here is triage's grading (5579917793) — no ruling grants a wider surface. If you find one I missed, the ruling governs and this fence is the defect; say so in your report. This seat made that exact error three times today.


    Generated by Claude Code

  4. claude commented on Sep 10, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 16224,
      "status": "done",
      "branch": "claude/issue-16224-runtime-gate-overlay-universe",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17252",
      "premise_still_valid": true,
      "summary": "The card's residual reproduced end to end on the unmodified gate, then repaired. foldStoredCollection's hand-rolled additive merge is replaced by mergePackageAwareOverlay with foldObjectExtendersFromRegistry as its transform - the merge and the transform getMetaItems already runs - so the runtime authoring gate's resolution universe is now the universe GET /meta/:type answers from, by construction. Route chosen: 'the overlay body wins, folded the way getMetaItems folds it', over 'the gate notices the disagreement'; argued in the PR body. No ruling on the card grants a wider surface - the only comments at claim time were triage's grading 5579917793 and the claim 5610348624, and the claim's fence therefore stands unamended. Assignee untouched, no second claim posted.",
      "reproduction": {
        "driven_before_any_change": true,
        "commit": "ab046d7c25 - red by construction, 4 failed / 5 passed",
        "shape": "Code package ships dataset/sys_user_metrics whose only measure is order_count. An env-wide sys_metadata overlay redefines it: measure row_count, order_count removed. getMetaItems answers with the overlay (measures ['row_count']) - the body the runtime serves.",
        "measured_defect_both_directions": [
          "widget values:['order_count'] - the measure the overlay REMOVED - was ACCEPTED (success:true). The runtime cannot serve it.",
          "widget values:['row_count'] - the measure the overlay DECLARES - was REFUSED 422 INVALID_METADATA / widget-measure-unknown. The runtime can serve it."
        ],
        "why_env_wide_and_not_org": "dataset carries allowOrgOverride:false in DEFAULT_METADATA_TYPE_REGISTRY, so the reachable redefinition is the env-wide limb the card also names.",
        "additive_control_that_must_not_move": "The four pre-existing #15950 tests are unchanged and green. A new test re-asserts the additive arm IN THE SAME PROCESS as the redefinition: store-only name p2008_users is still contributed and its board still publishes, while the redefined name is judged from the overlay in the same gather.",
        "negative_control": "A measure present in NEITHER the overlay nor the registry is still refused 422 widget-measure-unknown, with nothing landing - so letting the overlay win is not the rule switching off."
      },
      "landing_site": {
        "symbols_edited": ["assertRuntimeAuthoringRules (one pointer comment)", "foldStoredCollection (docblock + merge body)"],
        "hunks_vs_merge_base": ["@@ -4830,2 +4830,3 @@", "@@ -4915 +4916 @@", "@@ -4917,12 +4918,34 @@", "@@ -5006,6 +5029,5 @@", "@@ -5014 +5036 @@", "@@ -5036,6 +5058,4 @@", "@@ -5043 +5063,28 @@"],
        "reserved_region_rederived_by_symbol_on_my_own_head": {
          "compareAuditInstants": "1786-1795 (docblock from ~1750; sibling isoFromValidDate 1844-1847, its docblock 1797-1843)",
          "listCommits": "19515-19607",
          "assertRuntimeAuthoringRules": "4707-4883",
          "foldStoredCollection": "4990-5091"
        },
        "verdict": "DISJOINT. Every hunk lies inside assertRuntimeAuthoringRules or foldStoredCollection - more than 2900 lines below the compareAuditInstants family and more than 14400 lines above listCommits. Re-read on #17198's CURRENT state: its only protocol.ts hunks are still @@ -1811,18 @@ and @@ -1837,8 @@, and none of its other seven files is one of mine. No serial conflict; expecting to merge after it."
      },
      "how_16223_additivity_is_preserved": {
        "the_distinction": "#16223's argument was never 'an overlay must not win' but 'an UNRESOLVED body must not win'. The registry copy of an object is its RESOLVED schema (ADR-0029 D9.2: base layer plus its extend contributors); a sys_metadata row is the base layer alone.",
        "the_remedy_is_named_in_that_same_argument": "getMetaItems lets its overlay win and runs foldObjectExtendersFromRegistry on the winner. So the distinction is kept by FOLDING, not by declining - the identical transform is now passed here.",
        "pinned_not_asserted": "The harness registry now models a base layer plus an extend contributor and exposes foldObjectExtendersOnto. A new test drives an object overlay through saveMetaItem and asserts (a) a column only the OVERLAY declares resolves - the overlay won; (b) the registry extend contributor's column STILL resolves - the fold ran on the winner, which is exactly the 'resolves today, reads as dangling tomorrow' phantom #16223 declined to trade for; (c) a column on neither layer is still reported, as the firing control for both readings.",
        "universe_still_only_grows": "For a name the registry does not carry the result is byte-for-byte #15950's additive contribution. Registry entries with no `name` have no merge slot and would be dropped by the merge, so they are carried through explicitly - a collection-coherence finding cannot be lost.",
        "bonus_correctness": "mergePackageAwareOverlay keys by ADR-0048 package slot rather than bare name, so an overlay shadows the entry it actually overrides and two installed packages shipping one type/name remain two entries. The by-hand name-keyed merge did not do this.",
        "docblock": "The 'The fold is ADDITIVE, deliberately' section that recorded the residual in the source is rewritten rather than left standing - it now carries the measurement, the repair, and why it is not the reversal it can look like."
      },
      "files_touched": [
        "packages/metadata-protocol/src/protocol.ts",
        "packages/metadata-protocol/src/protocol.runtime-gate-stored-universe.test.ts",
        ".changeset/runtime-gate-overlay-redefinition-universe.md"
      ],
      "coverage_counts": {
        "harness_file": "5 tests before, 9 after (4 added)",
        "package_suite": "173 test files (171 passed, 2 skipped); 2473 tests (2463 passed, 10 skipped - all pre-existing)",
        "typecheck_program": "173 .test.ts files in the tsc program; --listFiles confirms the edited test file is one of them, so the green is not a tsconfig that excludes the test layer"
      },
      "tests": "ALL under scripts/pm/os-verify-lock.sh, exit codes captured by redirect-then-$?, never across a pipe. (1) Reproduction BEFORE the fix: exit 1, 4 failed / 5 passed - 'expected { success: true } to be an instance of Error'. (2) Reproduction AFTER: exit 0, 9 passed. (3) pnpm --filter @objectstack/metadata-protocol test: VERDICT command-exit 0, 2463 passed. (4) tsc --noEmit --listFiles: exit 0, edited test file in program. (5) turbo build closure and self: exit 0. (6) Full-repo pnpm lint (eslint . --no-inline-config) at final head 179657d5f7: VERDICT command-exit 0 - the whole population, so no narrowing argument is owed. No ablation artefact was left behind: the reproduction is a permanent test, not a mutation, so no restore leg applies.",
      "gate_results": {
        "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no path argument; --repo assertion held against this checkout's origin remote; change set taken from merge base ae19f5edb by three-dot semantics.",
        "derived_and_run": "60 derived, 60 run, all exit 0.",
        "initially_exit_3": "check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt each returned exit 3 = PREREQUISITE NOT MET on a fresh worktree with no dist. Built the whole tree (turbo run build over ./packages/*, 72/72 successful) and re-ran: all three exit 0. Reported as measurements, not as the 3.",
        "reconciliation": "--ran: 60 derived accounted for, 60 run, 0 NOT-MEASURED, 0 UNRUN. Re-derived after git fetch origin main - merge base unchanged, still 60/60.",
        "roster_gates_the_derivation_flagged": "The derivation warned that three artifact-roster families keep their roster in a directory one of my paths is in, so their silence is evidence in neither direction. Ran them: check-changeset-fixed.mjs exit 0, check:authz-resolver exit 0, check:error-code-casing exit 0.",
        "ci_on_head_179657d5f7": "37 check runs, CONVERGED: 30 success, 6 skipped, 1 failure - and that one failure is the self-declared item below."
      },
      "self_declared_defect": {
        "what": "All three of my commits carry `Part of #16224` in their message. .claude/agents/os-dev.md forbids this outright - the card relation is declared once, in the PR body. I wrote the trailer anyway. CI check 'Part-of PR must not also close its card' is red on it.",
        "not_repairable": "The branch is pushed. Removing the trailer requires amend/rebase/force-push, which AGENTS.md Prime Directives and os-dev.md both forbid absolutely, and which the guard's own header states it never asks for. That header also records that an earlier revision of its text DID ask, that a seat repeated the instruction, and that the developer's refusal is what stopped it. I refused for that reason; the dispatching seat then retracted the instruction and confirmed the measurement.",
        "measured_not_reasoned": "Drove the gate's own exported predicate, commitRelations(), over the real commit messages: three findings, all `Part of #16224`. The bare `#16223` in the reproduction commit's SUBJECT returns [] - no relation keyword - while Part of / Refs / Fixes controls each return one relation. The subject needs no reword.",
        "residue": "The guard is advisory: its workflow subscribes to no merge_group event and is outside the required-context registry, so it does not block landing. It cannot be discharged on this lane either - this repo lands through the merge queue and a queue merge edits nothing, so there is no merge button at which the squash message could be replaced by the PR body. My first PR comment offered that discharge; I posted a correction retracting it. Net cost: a `Part of #16224` reference in permanent history, which moves no card.",
        "declared_where": "PR #17252 comments 5610793317 and 5610809546."
      },
      "semver_argument": "minor on @objectstack/metadata-protocol. PUT /meta/:type is a published verb and this NARROWS its accept set: an active publish naming a reference the overlay removed now answers 422 INVALID_METADATA where it answered 200. That is one legal published answer replaced by another - a behaviour change on a published verb - and not the repair of a value the schema already refused, which would be patch. No export moves and no published type narrows, so no BREAKING and no adr-0087: line is owed. major is refused by the lane and is not warranted. check-changeset-no-major.mjs exit 0, including its LEVEL AXIS with Clause-2 declared up front.",
      "clause_2": "Declared at PR creation as required: `Clause-②: yes` on its own line in the body, `needs:contract-review` applied to PR #17252 by the additive REST endpoint POST /issues/17252/labels and read back by comparison (target minus readback = none stripped). The card already carried the label. Both carriers live from the moment the PR existed.",
      "governed_surfaces": "None touched. No docs/adr/** row is owed by this change: it implements no new decision and reverses none - ADR-0029 D9.2 and ADR-0048 are cited in code, and the repair makes the gate agree with the precedence ADR-0005 already documents. content/docs/releases/ untouched.",
      "mcp_calls": "3 - issue_read get, issue_read get_comments, pull_request_read get_files on #17198. Everything else went through the repo-scoped REST channel (probed first: HTTP 200) or git.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: foldStoredCollection issues one indexed sys_metadata read per collection per active write, and assertRuntimeAuthoringRules states that cost deliberately. Nothing measured here says it needs a cache, and this change does not alter the read pattern. Carrier: the next PR to touch this gather - the docblock states the cost where that author will read it.",
        "noted, not filed: the gate's five collections and getMetaItems now share a merge function but not a gather. A shape observation, not a defect - the two have different scoping duties and no disagreement between them survives this change. Carrier: none - recorded in the PR's acceptance notes only.",
        "noted, not filed: the docs-drift advisory named content/docs/api/environment-routing.mdx and content/docs/concepts/metadata-lifecycle.mdx. Checked both: neither describes the runtime authoring gate's resolution universe, and metadata-lifecycle.mdx line 42 documents overlay-wins precedence, which this change makes the gate AGREE with rather than falsify. No docs edit owed and none made."
      ]
    }

    Generated by Claude Code

  5. os-justin commented on Sep 10, 2026

    @os-justin
    Collaborator

    Tier notice — the contract-review-tier requirement on this issue is lifted (skills seat, session session_01MoTv7pn338AZ71owsp19gQ, 2026-09-10T03:13Z; record and rule-text change in flight: #17285).

    Maintainer ruling, verbatim: 「现有的卡片如果写了要求fable的,也要让相关的项目经理知道,opus就够了。」 Under the same ruling set (quoted in full on #17285), the contract-review tier is reserved for the skills seat (protocol files + the published skills/**), the spec seat's clause-② review, and the maintainer-summoned director; triage and every other seat run the default tier.

    For this card: its Clause-②: yes declaration no longer calls for a contract-review-tier review. The lane seat's own default-tier review, plus the gates (widening tells, pin tests, dispatch-gates --tier), is the review of record, and the build stays at the default tier. Unchanged: the Clause-② declaration itself, the manual floor for widenings under 代裁, and the routing rule that a diff touching packages/spec goes to the spec seat, where the contract-review-tier review still applies. This comment changes no label, assignee or claim.


    Generated by Claude Code

  6. claude commented on Sep 10, 2026

    @claude
    ContributorAuthor

    Landed — PR #17252 MERGED. The runtime authoring gate now judges an overridden item from the body the runtime serves.

    ⭐ The first PR in this lane reviewed and landed under the 2026-09-10 maintainer ruling that released domain:engine from CONTRACT_REVIEW_TIER. The verdict is 5613262057; it is a self-review by the seat that dispatched the card, and it says so in its first line.

    Confirmed with two readings (platform-readings.md:19), never one:

    1. Queue membership — added_to_merge_queue 04:43:27Z, removed_from_merge_queue 05:08:58Z, merged 05:08:59Z.

    2. origin/main BY CONTENT, located by SYMBOL — ⛔ not head-sha ancestry, ⛔ not the merged boolean:

      :4945  * ## The fold is the READ API's own merge — [#16224]
      :5104  const merged = mergePackageAwareOverlay(singularType, registered, overlays, (data) =>
      :5116  const unaddressable = registered.filter(
      

      ⭐ And the reading that actually proves the OLD behaviour is gone rather than merely that new code arrived: seen.has(name) now reads 0 in that file — the additive merge's skip-if-the-registry-has-it guard. Controls: a fabricated symbol reads 0, mergePackageAwareOverlay reads 10.

    Landing commit 4062aef544, parent count 1 ⇒ squash — the only admissible reading of the merge method (:49-:54: the stored auto_merge.merge_method, the REST field and the webhook payload all report merge, and all three are wrong).

    What was wrong, in one instant and in both directions

    A code package ships dataset/D with measure m; an env-wide overlay redefines D without it. The gate:

    • ACCEPTED a widget bound to m, which the runtime cannot serve — an acceptance that should have been a refusal;
    • REFUSED 422 widget-measure-unknown for the measure the overlay DOES declare, which the runtime can — a refusal that should have been an acceptance.

    One cause: the gate resolved references against a body nobody serves. #16223's repair contributed store-only NAMES additively and wrote that residual into its own docblock; this card is the residual being discharged, and the defect was re-confirmed statically from origin/main's source before anything moved.

    The repair is the READ API's own merge, not a second account of it

    mergePackageAwareOverlay with foldObjectExtendersFromRegistry as its transform — the merge, and the transform, that getMetaItems already runs. Verified line-against-line rather than taken from the docblock (protocol.ts:7342 vs the gate's call), including the one limb NOT carried over: getMetaItems also runs viewIdentityPatch when isView, and isView is singular === 'view', while this gather carries object / permission / book / dataset / page — no view. Inapplicable, not dropped, and the bound is stated.

    ⇒ Two readers of the word 「live」 now read through one function, and ADR-0048 package slotting arrives with it.

    #15950's argument is kept by FOLDING, not by declining

    ⛔ Not the reversal it can look like. That argument was never 「an overlay must not win」 but 「an UNRESOLVED body must not win」 — the registry's copy of an object is the RESOLVED schema (ADR-0029 D9.2), a sys_metadata row is the base layer alone. The remedy is named in the same breath and getMetaItems has always applied it: run the extender fold on the winner.

    Pinned with a firing control, which is what makes it a measurement: the test asserts a column only the overlay declares resolves AND the registry extend contributor still resolves, then drives a column on neither layer and requires the finding to be reported. Without that control the two not.toContain readings would be indistinguishable from a rule that stopped running.

    Graded minor, against precedent read from the tree

    The commit that INTRODUCED this method — 618f70d74d, the #16223 repair — graded patch, and it was a pure widening. This one adds a NEW refusal on input the verb previously accepted, which #16223 did not have. No export moves, so the minor is earned by behaviour alone.

    Recorded from the review, for whoever reads this method next

    • ⚠️ The changeset's grade rationale names only the narrowing, while SKILL.md:479 makes the clause-② criterion the widening. Both movements are real and both are pinned — but a later reader comparing the label against the changeset would otherwise find them arguing past each other.
    • ⚠️ A behaviour change the PR does not name: where the registry holds two base entries at one name (one _packageId-bearing, one bare — reachable, since registerItem keys them differently and DB-rehydrated rows carry no packageId), the merge now emits one. Judged correct, because the survivor is the body GET /meta/:type serves — which is the thesis. But 「the universe GROWS」 is now true of NAMES, not of bodies.
    • The unaddressable carve-out is sound (an exact complement of the merge's inclusion predicate) though its stated reason is stronger than it needs to be: collection-resident findings cancel in the gate's differential anyway.

    ⛔ The one red, ruled and not chased

    Part-of PR must not also close its card — the Part of #16224 commit trailers are in pushed history. Removing them requires the force-push AGENTS.md:470 forbids absolutely, and the guard's own output says verbatim 「⛔ Do NOT amend, rebase or force-push to remove it」. ⭐ This seat instructed that force-push; the implementer refused and was right, and the instruction was retracted (5610807452). The guard's header already recorded an identical prior incident.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions