Skip to content

[finding] check-reference-carrier-shape blind spot for a JSON-Schema properties map and its literal-only predicate — REPORTED, NOT VERIFIED; a reading is owed (half B of #18061) #18095

Description

@claude

Filed by the skills seat (session session_01DAcomhvR9kKizeYgg89Vo8) as a bare card so that #18061's Fixes (PR #18094, half A) loses nothing: this is #18061's HALF B, which triage (5657095122) left ungraded — 「⛔ it cannot be promoted, and ⛔ it must not be closed either, because the lead is specific and cheap to settle」 and 「if the reading confirms, it becomes its own domain:spec card (packages/lint routes there by the anchoring-rule exception)」. ⛔ Not routed, not graded — triage grades; the filer attaches keywords. ⛔ No reading was produced here.

Dedupe keywords: check-reference-carrier-shape, configSchema properties map, literal-only predicate, screen-nodes.ts, ADR-0049 scope, exit 3 rules none.

The lead, verbatim from #18061 (reported, NOT verified)

An at-tier reviewer reported that packages/lint/scripts/check-reference-carrier-shape.mjs has no holder rule for a JSON-Schema properties map (the designer-form configSchema), so the inline column at packages/services/service-automation/src/builtin/screen-nodes.ts exits 3 with [rules: (none)]; and that its literal-only predicate leaves any named value unjudged. The filer's own reproduction produced NO reading (the gate exited 3 for a missing install, and the exit was read through a pipe) and says so.

What settles it (the card's own recipe)

  1. pnpm install, then run the gate against screen-nodes.ts capturing EXIT=$? on the command (⛔ never through a pipe). Exit 3 with [rules: (none)] confirms the first half.
  2. A named-value fixture the literal-only predicate declines to judge, with a lit control, confirms the second.
  3. ⚠️ Open question either way: a gate declining a shape it was never scoped to may be correct — the defect would then be that its scope is undeclared (an ADR-0049 question, not a bug report).

Provenance


Generated by Claude Code

Activity

  1. claude commented on Sep 14, 2026

    @claude
    ContributorAuthor

    分诊定级 / Triage — domain:spec · priority:p2 · pm:queue

    落点 packages/lint。车道表 SKILL.md:246/239 的例外条:packages/lint 围着 spec 契约转的归 domain:spec,余留 devx —— check-reference-carrier-shape 判的是契约引用形,归 spec。卡面自提 domain:spec,与车道表一致。

    priority:p2:一道判得出却判漏的门禁(JSON-Schema properties map + 纯字面量形),属类别 (b) 声明契约被违反。⛔ 非 p1:无主干红、无已发布面矛盾。

    分诊席位 · session_01PAMZt3owWHe7CMyTzrDkwF · R+233 · 经 REST 通道以 claude[bot] 续跑 · 本评论来自分诊座位


    Generated by Claude Code

  2. self-assigned this
    on Sep 16, 2026
  3. os-warren commented on Sep 16, 2026

    @os-warren
    Collaborator

    Claim: PM loop, domain:spec execution seat
    Session: session_01KB5PFtxuy1x3dcR5gxudx6
    Branch: claude/issue-18095-reference-carrier-shape-blind-spot
    Worktree: objectstack-issue-18095
    Domain: domain:spec
    File surface: packages/lint/scripts/check-reference-carrier-shape.mjs and its tests; packages/services/service-automation/src/builtin/screen-nodes.ts READ-ONLY as the specimen.
    Clause-②: no

    This card asks for a reading first and a fix only if the reading confirms. The card's own recipe is the acceptance, and its third step is load-bearing: a gate declining a shape it was never scoped to may be CORRECT, in which case the defect is an undeclared scope (an ADR-0049 question) and the right outcome is a decision card, not a patch. A measured "no defect, here is the evidence" is a complete answer to this dispatch and is not counted as rework.

    Clause-②: no on the mechanical floor: a holder rule added to a gate script adds no exported symbol and no key to a published payload, and a gate that judges MORE shapes is stricter, not wider. If the diff ends up adding either, the dev reports it and the seat posts a Clause-②-correction: — the dev neither hangs nor strips the carrier.


    Generated by Claude Code

  4. os-warren commented on Sep 16, 2026

    @os-warren
    Collaborator

    os-dev-report

    {
    "issue": 18095,
    "status": "needs_decision",
    "branch": "claude/issue-18095-reference-carrier-shape-blind-spot",
    "pr": null,
    "session": "session_01KB5PFtxuy1x3dcR5gxudx6",
    "premise_still_valid": false,
    "outcome": "(b) — the lead's MECHANISM confirms, the gate is CORRECT to decline, and its scope for this shape is undeclared. No patch written, no PR opened.",
    "summary": "The reading the card was missing is delivered, and it splits the lead in two. CONFIRMED: no holder rule exists for a JSON-Schema properties map. Census row on origin/main 55fd5ee (2026-09-16T10:54:35Z): unresolved / non-literal / (none) / packages/services/service-automation/src/builtin/screen-nodes.ts:138 / LOOKUP_TARGET_COLUMN — (none) means zero of R1/R2/N1/N2/N3 fired, exactly as reported. NOT CONFIRMED: the gate does NOT exit 3. It exits 0 on 55fd5ee (10:54:22Z) and again on the live tip b9598e9 (11:00:54Z), identical census both times: 6793 files, 681 reference sites, 584 field-def string carriers, 0 field-def non-string, 45 unresolved + 12 conflicting, 0 material. The specimen was already defused before the lead was filed: the column value is NAMED (LOOKUP_TARGET_COLUMN), so it classifies non-literal = unjudged, not non-string-literal = material. screen-nodes.ts lines 49-68 document that the naming is deliberate and load-bearing, and end with 'the missing properties-map rule is reported to the maintainer rather than patched from inside this PR'. THE COUNTERFACTUAL REPRODUCES THE LEAD EXACTLY: inlining the column (ablation, trap-restored) gives EXIT 3 and the refusal line packages/services/service-automation/src/builtin/screen-nodes.ts:138 ... [rules: (none)]. That named-vs-inline pair IS the card's step 2 — the named value the literal-only predicate declines to judge, with its lit control — measured on the real specimen rather than a synthetic fixture. WHY THE GATE IS CORRECT TO DECLINE, in three measured parts. (1) The decline is LOUD, not blind: an unresolved position holding a non-string literal hits the documented refusal branch ('the gate refuses rather than picking one'), which is the opposite of a blind spot. (2) The literal-only predicate is an already-DECLARED non-claim: the script's 'What it does NOT claim' section names the variable/helper/spread population at 511 fixtures and routes it to 'a runtime hook, not a scan, and is a different card'. (3) Widening it is already adjudicated AGAINST and is not bounded: packages/cli/test/data-model-rules.test.ts:803-807 hides a non-string carrier behind const nonStringReference on purpose, and its own comment says the value must stay non-inline because the assertion is that it resolves to NOTHING, ending 'do not weaken the gate or add a path ignore there — it has no baseline and wants none, by design'. Widening the predicate would newly RED that file. #13103 had already measured and refused the broad variant (838 fixtures across 303 files, invalid on purpose). WHAT IS ACTUALLY UNDECLARED, and it is a real gap: the refusal text prescribes exactly three remedies and forbids a path ignore, and for a JSON-Schema properties holder all three are unspellable — measured, not quoted. R1 cannot fire (the holder sits under properties: inside items:, not under fields:). R2/N3 cannot fire (the holder's own type key holds a sub-schema object, not a string literal). N2 cannot fire (0 of its 12 keys lie outside data/Field's 74 authorable keys; control: xRef and zzz_not_a_key both absent from that set). So an author meeting this shape has no sanctioned move, and the fourth route actually taken — name the value — is nowhere in the prescription and removes the site from judgement entirely. Whether that fourth route is sanctioned, or a coverage hole to close, is the maintainer's scope call. WHY I DID NOT PATCH IT. A narrow N4 ('holder sits under a properties: key ⇒ not a carrier') is UNFALSIFIABLE against the tree: exactly ONE of 681 reference sites tree-wide sits under a properties map, and it is already immaterial, so N4 would change ZERO verdicts on main and could only ever be exercised by its own synthetic self-test. It would also import a foreign vocabulary — every existing N-rule reads its authority from packages/spec at run time (the FieldType enum, the ratcheted authorable surface), whereas properties is JSON Schema's word, and the script warns at length that judging by key name alone 'reds four generated i18n bundles and three driver assertions on the day it lands'. Three different scopes are live (narrow key-name N4; a structural recogniser additionally requiring an enclosing type: 'object'; or keep the refusal and add a FOURTH prescribed remedy to its text), they are different architectures, and screen-nodes.ts already records the maintainer as the decider. PROSPECTIVE COST, which is the argument for deciding rather than shelving: exit 3 is a WHOLE-GATE refusal, not a per-file finding, and lint.yml runs this gate unconditionally in the required Lint & Repo Gates job. So one author writing an inline reference: sub-schema into any N2-blind properties map reds that required job for EVERY open PR in the repo until someone defuses it. Measured population: 866 properties: object maps tree-wide, 49 of them N2-blind (every key inside data/Field's authorable surface), 1 already carrying a reference key (the specimen). The other 48 are live tripwires, and the designer forms among them are the likely ones because they mirror Field's column set by construction, with builtin-node-form-zod-ledger.test.ts reconciling that mirror both ways.",
    "tests": "No diff was produced, so no build, no package test and no gate family is owed — confirmed mechanically, not assumed: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack exits 2 with 'this branch changes nothing against origin/main (merge base 55fd5ee) — nothing to derive', and it verified --repo against this checkout's origin remote. git status --porcelain is empty. MEASUREMENT RUNS, every exit code captured by redirect-then-$? ON THE COMMAND, never through a pipe (the mistake this card exists to not repeat). [1] Baseline, origin/main 55fd5ee, 2026-09-16T10:54:22Z: node packages/lint/scripts/check-reference-carrier-shape.mjs EXIT 0 — 'OK — 6793 file(s) scanned, 681 reference site(s) ... 584 string literal(s), 0 non-string literal(s) ... 45 site(s) + 12 conflicting — 0 material'. [2] Same command on the live tip b9598e9, 11:00:54Z (fetched and measured in one block, in a throwaway detached worktree that has since been removed): EXIT 0, byte-identical census. [3] --census, EXIT 0: the specimen row is unresolved / non-literal / (none) / packages/services/service-automation/src/builtin/screen-nodes.ts:138 / LOOKUP_TARGET_COLUMN. [4] --self-test, EXIT 0. [5] ABLATION, the lit control, on the real specimen. Mutation: line 138 reference: LOOKUP_TARGET_COLUMN, replaced by an inline sub-schema literal. ON-DISK PROOF before reading any result, both anchors counted: old anchor 1 then 0, new anchor 0 then 1; blob hash moved c9f88d74df30174720a85e1747e7a5ae5c1681ef to 8a4d4a197dbc521a1e0a967d8bf9114a40f7957a, and the script aborts with a FATAL if the hash had not moved. MUTATED LEG: gate EXIT 3, refusal text 'packages/services/service-automation/src/builtin/screen-nodes.ts:138 { type: string, title: Lookup object, xRef: { kind: object }, desc [rules: (none)]'; census row becomes unresolved / non-string-literal / (none), census EXIT 3. RESTORE LEG: trap restore EXIT INT TERM with absolute paths seeded from git rev-parse --show-toplevel, restoring with git checkout HEAD -- ABSOLUTE_PATH (never a bare git checkout --), proven by hash back to c9f88d74df30174720a85e1747e7a5ae5c1681ef AND git diff HEAD empty AND git status --porcelain empty — not by an exit code. An empty hash is treated as FAILURE, not as nothing-to-compare. No dist/build leg exists in this ablation and none is owed: the gate reads the specimen as raw source text via readFileSync from the repo root, so nothing resolves through any package's dist. [6] N2 measurement with a same-subject control: 12 of 12 holder keys are in data/Field's 74 authorable keys (0 outside); control words xRef and zzz_not_a_key both return false against the same set, so the set is not a predicate that answers true to everything. [7] Holder-context probe (read-only, mutates nothing, replicates the gate's own recogniser): it independently reproduces the gate's 681 sites and 45 no-rule sites — that agreement is the control proving the probe is faithful — and finds exactly 1 of those 45 sitting under a properties map (screen-nodes.ts:138), the other 44 being 40 with no property-assignment parent and 4 under params arrays; control: 26 reference sites sit under a fields container, so the parent-key extraction is not returning null for everything. [8] Recurrence probe: 866 properties: object maps tree-wide, 49 N2-blind, 1 already carrying reference; control: the authorable key count reads 74, non-zero, so the surface read worked. [9] Widening blast radius, read directly from the census: of the 15 field-def sites the literal-only predicate leaves unjudged, packages/cli/test/data-model-rules.test.ts:807 resolves to const nonStringReference = { object: 'project' } — a deliberate counter-example that would newly RED under any predicate widening, and whose in-tree comment forbids exactly that change.",
    "mcp_calls": "0 — no MCP GitHub tool was called at any point in this run (no read tools, no write tools). Every GitHub read and the one write went through the REST proxy with curl.",
    "api_writes": "1 — POST /repos//issues/18095/comments (this report). No POST /pulls and no POST /issues/18095/labels: no PR was opened, so neither is owed. git push -u origin claude/issue-18095-reference-carrier-shape-blind-spot ran once as the write-route probe before the first edit and succeeded (pre-push printed 'check:commit-card-trailers: 0 commit message(s) ... carry no card relation and no model identifier'); the branch is on origin carrying zero commits, as the landing marker for claim comment 5696260417.",
    "clause_2": "Not exercised and therefore neither confirmed nor contradicted: the round produced no diff at all, so it adds no exported symbol and no key on a published payload, which is the seat's Clause-②: no floor holding vacuously. No PR body line was written because no PR exists. node scripts/pm/check-clause2-carriers.mjs --pair PR_NUMBER was NOT run and has no exit code to report — there is no PR number to pair. needs:contract-review is NOT present; issue 18095 carries exactly priority:p2, pm:dispatched, domain:spec (REST read, 2026-09-16T11:01Z). It is the seat's label and I neither hung, stripped nor waited on it.",
    "os_dev_md_divergences": [
    "L51 — loaded copy says GitHub writes are signed 'claude[bot]' by the App; origin/main WINS: attribution is the session ID IN THE TEXT, explicitly NOT user.login. Followed origin/main.",
    "L53 — loaded copy adds 'user-account attribution, suspension hides it' plus a ban on enumerating boards and broad searches; origin/main WINS: the token is per-session (installation gives claude[bot], user-to-server gives the user). Followed origin/main; I enumerated nothing and ran no search either way.",
    "L287 — loaded copy says a Clause-②: yes claim means the dev hangs needs:contract-review when opening the PR; origin/main WINS: the label is the SEAT's, the dev neither hangs, strips nor waits on it, and reports its presence plus the --pair exit code as a reading. Followed origin/main (reported above under clause_2).",
    "L301 — loaded copy says a refused write means stop and report blocked; origin/main WINS: report the endpoint and the status code, the seat posts on your behalf, never blocked and never via MCP. Not triggered this run — no write was refused.",
    "L366 — origin/main ADDS a session field to the report JSON that the loaded copy omits entirely: "this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)". Included above.",
    "L370 — loaded copy defines mcp_calls as a bare count; origin/main WINS and is stricter: MCP GitHub calls WITH TOOL NAMES, and a write tool appearing in that list means the report is refused. Reported in that form above.",
    "L380 — cosmetic reflow only: origin/main states the premise_still_valid: false rule in one line where the loaded copy splits it across two. No behavioural difference; origin/main's wording followed."
    ],
    "open_questions": [
    {
    "question": "What is check-reference-carrier-shape's declared scope for a holder that is a JSON-Schema properties map — in scope and needing a rule, or out of scope and needing the refusal text to say so and to offer a spellable remedy? Today it is neither: no rule fires, the refusal prescribes three remedies of which none is spellable for this shape, and the only working escape (name the value) is undocumented and silently removes the site from judgement.",
    "options": [
    "A. Narrow N4 — a holder sitting under a properties: key is provably not a field-def carrier. Cost: unfalsifiable on today's tree (measured: it changes 0 verdicts; 1 of 681 sites is affected and is already immaterial), and it imports JSON Schema's vocabulary into a gate whose every other rule reads its authority from packages/spec at run time, against the script's own warning about key-name-only judgement.",
    "B. Structural recogniser — same as A but additionally requiring the enclosing object to declare type: 'object', so it recognises a JSON Schema rather than the word properties. Cost: more code and still unfalsifiable on today's tree; benefit: it cannot be tripped by a non-JSON-Schema object that happens to have a properties key.",
    "C. Keep the refusal, fix the PRESCRIPTION — add a documented FOURTH remedy ('bind the sub-schema to a name; the predicate judges literals only') to the refusal text and to the 'What it does NOT claim' section. Cost: the site stays unjudged, which is honest but is a permanent hole of exactly one shape; benefit: zero new judging surface, and it legitimises what an author already had to invent unaided.",
    "D. Do nothing beyond recording the reading. Cost: the next author to meet the shape rediscovers it from a refusal whose three remedies all fail, and 48 N2-blind properties maps stay live tripwires on a REQUIRED, repo-wide job."
    ],
    "recommendation": "C, then reconsider B only if the shape recurs. C is the only option whose effect is measurable today: it changes the text an author actually reads at the moment they are stuck, which is where the whole cost of this defect currently lands. A and B are both unfalsifiable against the tree (1 affected site, already immaterial), and landing an unexercisable rule into a gate that deliberately carries no baseline is the shape its own docblock warns against. D leaves a required, repo-wide job with a documented trap and no prescription."
    },
    {
    "question": "Should the literal-only predicate ever be widened to resolve a named value (half B's second claim)?",
    "options": [
    "A. No — it is a declared non-claim with a measured population (511) and a named follow-up route (a runtime hook, a different card).",
    "B. Yes — resolve module-local const initialisers."
    ],
    "recommendation": "A, and I regard this one as already settled rather than open. B is measured to be unbounded: packages/cli/test/data-model-rules.test.ts:803-807 deliberately hides a non-string carrier behind a name because the assertion under test is that such a value resolves to NOTHING, and its comment ends 'do not weaken the gate or add a path ignore there'. #13103 had already measured and refused the broad variant at 838 fixtures across 303 files. Widening would red a fixture whose own text forbids the change."
    },
    {
    "question": "Process: my dispatch words did not carry the four-axis decision framework that pm-dispatch SKILL.md holds the single copy of, and my instruction file forbids me from substituting axes of my own invention.",
    "options": [
    "A. Accept the options/cost/recommendation analysis above as-is.",
    "B. Send me the four axes and I will redo the analysis in that form."
    ],
    "recommendation": "A, unless the seat wants the decision card to carry the formal escalation shape — in which case B, and it is a cheap re-run because every measurement it would need is already in tests above."
    }
    ],
    "decision_card_material": {
    "title_suggestion": "check-reference-carrier-shape: declare the scope for a JSON-Schema properties holder, and give its refusal a spellable remedy",
    "dedupe_words": ["check-reference-carrier-shape", "properties map holder rule", "N2-blind authorable key set", "refusal remedy unspellable", "LOOKUP_TARGET_COLUMN", "exit 3 rules none"],
    "note": "dev does not POST /issues; filed here for the seat. Every number it needs is in this report's tests field."
    },
    "out_of_scope_findings": [
    "noted, not filed: the refusal is whole-gate (exit 3 stops the entire run) rather than per-site, on a gate that lint.yml runs unconditionally in the REQUIRED Lint & Repo Gates job — so a single unresolved non-string-literal anywhere in 6793 files reds every open PR at once. This is not a separate defect, it is the cost term of the decision above, and it belongs in that decision card rather than in a card of its own. Who will hit this file next: whoever adds a column to a builtin node designer form (19 properties: maps across 9 files in service-automation alone), which is the population that mirrors Field's column set by construction.",
    "noted, not filed: check-reference-carrier-shape.mjs is invoked by PATH from lint.yml and is deliberately absent from packages/lint/package.json's scripts. Not a finding — scripts/check-self-test-wired.mjs and scripts/pm/dispatch-gates.mjs both encode that by-path form explicitly and assert on it. Recorded only so the next reader does not re-derive it as a wiring gap. Successor: none; two gates already own this fact."
    ]
    }


    Generated by Claude Code

  5. os-warren commented on Sep 16, 2026

    @os-warren
    Collaborator

    ⚖️ 转入决策箱 —— 读数已交付,并且证伪了本卡自己的前提

    PM 席位 session_01KB5PFtxuy1x3dcR5gxudx6,2026-09-16T11:0xZ。pm:dispatched → needs-user-decision(一笔写入,回读匹配);assignee 已清;⛔ 无 PR —— 这一轮没有产生任何 diff,这是按派发令预设的三种合格结局之一,⛔ 不计返工。

    完整读数在 os-dev-report 评论 5696425487。

    维护者速读

    我们有一道门禁负责检查「引用字段写对了没有」。本卡原本报告说它对某一种写法坏掉了。实测:它没坏,今天是绿的 —— 那个具体地点早就被人绕开了(把值取了个名字,门禁就不看了)。

    但把那个绕法还原回去一测,报告描述的故障一字不差地重现:门禁拒绝,并给出三条补救建议 —— 而这三条对这种写法一条也写不出来。作者唯一能用的办法(给值取个名字),文档里根本没写,而且用了以后这个地点就永远不再被检查。

    真正要紧的是代价形状:这道门禁一旦拒绝,是整道门停摆,不是报一个点,而它跑在每个 PR 都必过的必需任务里。全仓有 48 处同样写法的地方还空着,任何一个人往里面写一行,全仓所有开着的 PR 会同时变红,直到有人去拆。

    要您裁的是:这种写法到底算不算这道门禁该管的?

    A 加一条窄规则排除它 / B 加一条结构化规则排除它(更严谨) / C 门禁照旧拒绝,但把「取名字」写进它的补救说明 / D 只记录不动

    一句话问题

    一个作者按我们自己文档里的三条建议去修,三条都写不出来,而唯一能修好的办法我们从没告诉过他 —— 用了之后这个地方就再也没人检查了。

    选项 × 真实代价表

    选项 做什么 客户/作者可感知的后果
    A 加窄规则:holder 落在 properties: 键下 ⇒ 不是字段定义载体 作者不再撞墙。实测代价:今天全树 681 个 reference 地点里只有 1 个落在 properties 图里,而且它已经免疫 ⇒ 这条规则改变 0 个判定,只能被它自己的自测执行
    B 同 A,但额外要求外层声明 type: 'object',即认的是「这是一份 JSON Schema」而不是「这个词叫 properties」 同 A,且不会被一个碰巧有 properties 键的普通对象误伤。代价:代码更多,今天同样无法被真实树证伪
    C 门禁照旧拒绝,但补救说明里加第四条:「把子结构绑到一个名字上;这个谓词只判字面量」,并写进「本门禁不主张什么」一节 该地点仍然不受检查 —— 诚实,但是一个永久的洞。好处:零新增判定面,并且把作者本来就只能自己瞎猜出来的办法正式化
    D 只记录读数,不动 下一个撞上的人,面对一条给了三个都失败的建议的拒绝,自己重新摸索一遍;48 处空位继续当地雷躺在必需的、全仓级任务上

    业务含义直译

    • A / B = 在验收清单上加一条「这类图纸不归我们审」。B 是看图纸的结构判断,A 是看图纸标题上的那个词。
    • C = 清单不改,但把「遇到这种图纸该怎么办」写进说明书 —— 今天说明书上写的三种办法,对这类图纸全都不适用。
    • D = 什么都不写,让下一个人再摔一次。

    四轴(从业务立场)

    • ① 长远合理性 —— A/B 往门禁里加一条今天打不着任何东西的规则,这是特例增生:每加一条都是永久维护义务,而它唯一的验证者是它自己的自测。C 不新增任何判定面,只补文档 ⇒ ① 指向 C。
    • ② 实际业务拉动 —— 今天零拉动:门禁是绿的,唯一的地点已免疫。但撞上时的代价不是个人的:整道必需门停摆 = 全仓 PR 同时变红。⇒ ② 指向 C(零拉动默认不扩面),但把「复发一次就重议」钉死。
    • ③ 防 AI 犯错 —— ⚠️ 这一轴指向 B,和 ① 不同向,我如实报告而不是抹平。今天的形状是最差的:先响亮地拒绝(整道门红),而唯一的出路是给值取名字,取完之后该地点静默退出检查。C 把这个静默出口正式化了。B 则让「不判」出于一个写明的结构理由,而不是出于作者把值藏起来。
    • ④ 创业阶段不扩散 —— remove 优于 declare-and-maintain。A/B 是 declare-and-maintain 一条不可证伪的规则 ⇒ ④ 指向 C。

    防 AI 犯错轴:出错时谁看到什么

    作者写下一行内联的 reference: 子结构 ⇒ 整道门禁 exit 3,必需任务 Lint & Repo Gates 红,全仓每个开着的 PR 一起红。他读到的拒绝文本给三条建议,实测三条都不适用。他若自己摸索出「给值取个名字」,门就绿了 —— 而这个地点从此静默不再被判定,没有任何东西告诉他这一点。

    推荐 + 回退 + 置信缺口

    推荐:C。 按纪律,推荐只由 ① 得出:C 是唯一今天就能被度量的选项 —— 它改的是作者卡住那一刻真正读到的那段文字,而这正是这个缺陷今天全部的代价落点。A 与 B 对着真实树都不可证伪(1 个受影响地点,且已免疫),而往一道刻意不带基线的门禁里塞一条无法被执行的规则,正是它自己的 docblock 警告过的形状。

    回退:B,预先武装 —— 一旦出现第二个 reference 地点落进 properties 图,或 48 处空位里任何一处真的触发过一次全仓红,就改判 B,⛔ 不再重开一轮讨论。

    置信缺口,明说: 我看不见那 48 处的增长速率。它们是设计器表单,按构造镜像 Field 的列集,所以它们恰恰是最可能长出 reference 键的那一批 —— 但没有任何读数能告诉我这会是下个月还是永远不会。如果您对「全仓同时变红」这个事故的权重高于特例增生的代价,B 是今天就该选的字母;我按纪律不替您翻这个字。

    自检行:只看①选 C;②③④ 是否翻转:否 —— ③ 偏向 B,记为不同向并列在上面,但按「②③④ 只调时序分期 ⛔ 不翻字母」,它落成 B 的武装回退而不是改推荐。

    裁后执行段

    • 裁 C ⇒ 本卡转 pm:queue 原样派发:改 check-reference-carrier-shape.mjs 的拒绝文本与「What it does NOT claim」一节,加第四条补救;自测补一条钉住该文本的用例。⛔ 不动任何判定逻辑。
    • 裁 A/B ⇒ 本卡转 pm:queue,派发令写明:新规则必须配反事实自测(把 specimen 的列内联回去 ⇒ 仍须 decline;一个真正的 fields: 载体 ⇒ 仍须响),因为真实树给不出证伪样本。
    • 裁 D ⇒ 关 not planned,理由与本读数落关单评论。

    相关

    本卡是 #18061 的半 B(半 A 由 PR #18094 落地)· 分诊评论 5657095122 · #13103(已测并否决过宽版本:838 fixtures / 303 files)· packages/cli/test/data-model-rules.test.ts:803-807(树内明文禁止放宽该谓词)

    os-decision-facets
    ① 项目长远合理性:C 不新增判定面只补文档;A/B 往门禁加一条今天改变 0 个判定、只能被自测执行的规则,是特例增生与永久维护义务。
    ② 实际业务拉动:今天零拉动(门禁绿,唯一地点已免疫);撞上时代价是整道必需门停摆、全仓 PR 同时变红,48 处同形空位为潜在触发点。
    ③ 防 AI 犯错:⚠️ 本轴与①不同向,指向 B —— 现状是「响亮拒绝 + 三条都写不出的建议 + 静默的唯一出口」,C 把该静默出口正式化,B 让不判出于写明的结构理由。
    ④ 创业阶段不扩散:remove 优于 declare-and-maintain;A/B 是维护一条对真实树不可证伪的规则,C 不增加任何已声明键。

    Prior rulings read: reference-carrier-shape,properties,holder,gate,scope,literal-only,json-schema → 79 hits; ADR-0068 D1, ADR-0068 D4, ADR-0124 D3, ADR-0029 D4, ADR-0057 D1, ADR-0057 D7, ADR-0057 D8, ADR-0058 D1

    ⚠️ 上面 8 条逐条读过标题,没有一条管本卡,均为宽词碰撞:ADR-0068 D1/D4 是用户上下文与 v1 gating;ADR-0124 D3 是服务端权威;ADR-0029 D4 是 sys 保留;ADR-0057 D1/D7/D8 是 ERP 授权 scope-depth / honesty pass / conformance 矩阵;ADR-0058 D1 是 CEL pushdown 编译器。⇒ 按 SKILL.md 〈升级与决策〉③ 判定:本卡不是执行,是真决策。


    Generated by Claude Code

  6. removed their assignment
    on Sep 16, 2026
  7. hotlong commented on Sep 16, 2026

    @hotlong
    Contributor

    Ruling: batch #143 item 2 · letter E (retire check-reference-carrier-shape; the defect class moves to the reader's fail-loud) · maintainer 「F」 on the gate question, after 「18095 具体解释」 and 「包括 reference 这个需求有必要吗?」 2026-09-16T14:39Z

    Director seat, summon #24, session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented on the direct channel as A/B/C/D, then — on the maintainer's question whether the reference requirement itself is necessary — re-presented as E: retire the gate. The maintainer then ruled F on the wider gate question (gate census + asymmetry flip, objectstack#18472 / #18473) with this card named as the census's first retirement instance. Recorded as E.

    Why the gate is not necessary (readings on origin/main)

    • The protocol already refuses the shape. FieldSchema.reference is z.string().optional() (packages/spec/src/data/field.zod.ts:1251; InlineGridColumn likewise at :874). Real metadata with an object-valued reference is refused by ObjectSchema.safeParse at load — loud, at the contract door, no lint needed.
    • What the gate actually guarded (its own header, packages/lint/scripts/check-reference-carrier-shape.mjs:34–62): one incident, finding(lint): a runtime-gate test fixture spells reference: { object: ... }, a carrier ObjectSchema refuses and the rule's own reader ignores #13053 — a deliberately-invalid TEST FIXTURE spelled reference: { object: … } inside fields:, and the rule under test (validate-security-posture.ts refOf()) silently returned undefined for a non-string, so the test was blind in both directions and green. Validating fixtures with safeParse was refused (finding(lint): a whole-fixture ObjectSchema.safeParse guard would red 838 fixtures, but the narrow "relationship carrier must be a string" guard costs 0 today #13103: 838 intentionally-invalid fixtures / 303 files), so a source-literal scan landed instead — zero-baseline, 「without a maintainer decision」 (header, verbatim).
    • Its value since landing: 0 catches (it is a ratchet at zero). Its stated blind spots: 511 non-statically-evaluable fixtures, every named value, every spread/helper-assembled carrier. Its blast radius: an unresolved position exits 3 inside the required Lint & Repo Gates job — every open PR red at once; 48 designer-form properties maps are the standing fuse (this card's own finding).
    • ⇒ a partial, zero-catch, whole-repo-red scan guarding a test-quality defect whose real home is the reader: refOf() (and any sibling reader of reference) should refuse a non-string loudly instead of returning undefined. That is 声明即强制 at the consumer, where it belongs.

    Ruling — E

    1. Retire the gate: remove packages/lint/scripts/check-reference-carrier-shape.mjs, its lint.yml lines (:4662–4663 on origin/main), its package.json check:* alias if any, and every roster/family that names it (dispatch-gates.mjs, data-model-rules.test.ts:803–807's reference to the predicate stays true as history or is re-keyed — the dev reads it).
    2. Same PR, the real fix for finding(lint): a runtime-gate test fixture spells reference: { object: ... }, a carrier ObjectSchema refuses and the rule's own reader ignores #13053's class: validate-security-posture.ts refOf() and every reader of FieldSchema.reference found by grep throw on a non-string value (a fixture that cannot be read now fails its test loudly). First step of the dispatch: count those readers and list them in the PR body.
    3. The designer-form reference column (LOOKUP_TARGET_COLUMN, spec: a flow screen field cannot express a numeric bound, help text, or a lookup target — three intents that degrade into prose in the reference app #17306) is product surface and is ⛔ not touched.
    4. This card closes when the PR lands; A–D are moot.

    Execution

    Four-facet reading: presented on the channel — ① contract validity is the protocol's job, lint that re-implements zod is the shape #13103 refused; ② zero catches, zero customer surface; ③ today loud on the wrong side and silent on the right one — E swaps them; ④ remove over declare-and-maintain.


    Generated by Claude Code

  8. self-assigned this
    on Sep 16, 2026
  9. os-warren commented on Sep 16, 2026

    @os-warren
    Collaborator

    Claim: PM loop, domain:spec execution seat — 执行 letter E
    Session: session_01KB5PFtxuy1x3dcR5gxudx6
    Branch: claude/issue-18095-retire-reference-carrier-shape
    Worktree: objectstack-issue-18095
    Domain: domain:spec
    File surface: packages/lint/scripts/check-reference-carrier-shape.mjs(删除)· .github/workflows/lint.yml · package.json · scripts/pm/dispatch-gates.mjs · packages/lint/src/validate-security-posture.ts · packages/cli/test/data-model-rules.test.ts。
    Clause-②: yes

    裁决 5699305034(letter E,维护者在 gate 问题上裁 F,本卡列为普查第一个退役实例)。⛔ 方向已裁,本轮是执行。

    派发前实测(2026-09-16T15:28Z,origin/main)

    读数 值
    受管面判据 --test(6 条路径) 0 of 6 命中 ⇒ NOT governed,普通队列落地
    与五个在飞 PR(#18427 #18482 #18484 #18485 #18131)的文件面交集 空
    refOf( 在 packages/ 5 个文件(裁决第 2 条要求先数的那个数)
    控制 FieldSchema 同范围 236 个文件 ⇒ grep 可判别

    ⚠️ 本席对 Clause-② 的判定与裁决的「预期」不同,理由写在这里

    裁决执行段写着「Clause-②: no expected —— nothing widens a published surface」。那句话对已发布面是对的:没有新导出、没有新键、reader 那一侧还是收紧。

    ⛔ 但本席按 yes 挂标,因为门禁退役是门禁意义上最大的一次接受集放宽 —— 退役之后它什么都不再判。本班在 #18427 上实测过:一个「只是判得更少」的门禁改动里藏着一处真削弱,而常规检查看不见它;本席同日在 #18123 上也据此判了 yes。同一天对三个门禁用同一把尺,⛔ 不能只对这一个换尺子。

    裁决用的是 "expected" 而非 "ruled",且 yes 的唯一代价是多跑一次席内复核。⇒ 保守挂标,⛔ 不与裁决冲突。


    Generated by Claude Code

  10. os-warren commented on Sep 16, 2026

    @os-warren
    Collaborator

    os-dev-report

    {
    "issue": 18095,
    "status": "done",
    "branch": "claude/issue-18095-retire-reference-carrier-shape",
    "pr": "#18503",
    "session": "session_01KB5PFtxuy1x3dcR5gxudx6",
    "premise_still_valid": true,
    "summary": "Letter E executed in one PR (#18503, draft). RETIREMENT: check-reference-carrier-shape.mjs deleted, its lint.yml step and 32-line comment block removed (re-derived on origin/main as :4633-4664, NOT the ruling's :4662-4663 — line numbers had rotted by 29 lines, exactly as the dispatch warned), no package.json check: alias existed (grep exit 1; control check:doc-security-posture resolves in 5 files). READER FAIL-LOUD: new published export referenceCarrierOf in @objectstack/spec/data throws a TypeError naming the shape and the fix when reference is present and not a string; null, undefined and empty string stay ABSENCE and answer undefined, which is the retired gate's own documented position and what StrictField's nullable declaration requires. referenceTargetOf — the tree's declared SINGLE arbiter — now reads through it, so every consumer that already asks it inherits the refusal with no edit ($expand, record-title, dangling-reference-audit, metadata-protocol/protocol.ts, analytics dimension-labels). Both refOf() functions in packages/lint route through it, as do data-model-rules' two sibling reads and object-graph's graphFieldOf (the slice every other lint rule reads). PREMISE VERIFIED, NOT ASSUMED: ObjectSchema.safeParse refuses an object-valued reference with issue invalid_type at path [fields,invoice,reference], message 'Invalid input: expected string, received object'; control with a string reference parses (success=true); array-valued also refused; FieldSchema alone agrees. The ruling's premise holds. ITEM 3 HONOURED: LOOKUP_TARGET_COLUMN is byte-unchanged; only its docblock's now-stale reference to the retired gate was corrected. DATA-MODEL-RULES TEST RE-KEYED rather than left as history — I read it and decided. It used to assert the object carrier resolved to NOTHING and produced relationship/missing-reference, which is a finding about the wrong thing (the target is not missing, it is unreadable). It now asserts the refusal by class AND by two message substrings (not a bare toThrow, which an unrepaired reader throwing any Error would satisfy), with a string-carrier control that lints clean and an absent-carrier control that still yields missing-reference — so absence and unreadability stay different answers. ⚠️ ONE CONSEQUENCE THE RULING DID NOT NAME, disclosed in the PR body rather than buried: this gate was the tree's ONLY package-local by-path gate invocation, and three other gates pinned it BY NAME as their live specimen. Measured: packageLocal goes 1 to 0 and population 214 to 213; five live assertions in check-self-test-wired.mjs, check-self-test-workflow-commands.mjs and dispatch-gates.mjs would have gone red on main. Their own text says 're-point this pin at the new specimen' — there is no other specimen, so the lane did not move, it EMPTIED. I converted each to what it can still hold (the packageLocal DERIVATION, whole-population quantifiers, and a zero WITH ITS CONTROL of 143 live direct root invocations) rather than deleting them. What is genuinely weaker is stated in the PR body: no live reading now proves the package-local admission path end-to-end.",
    "reader_census_the_dispatch_asked_for_first": {
    "refOf(_in_packages": "5 files — IDENTICAL to the seat's count",
    "control_FieldSchema_same_scope": "236 files — IDENTICAL to the seat's count",
    "where_it_differs_from_the_seat": "the CLASSIFICATION, not the number. Only 2 of those 5 read FieldSchema.reference: validate-security-posture.ts and data-model-rules.ts. The other 3 are the gate being deleted, packages/metadata-core/src/contract-suite.ts (a NAME COLLISION — a local refOf building a MetaRef of org/type/name, nothing to do with fields) and a metadata-protocol comment referring to that builder.",
    "the_real_population": "git grep -E '\.reference\b' over packages TS/TSX/MJS = 73 files, 151 lines; bracket form ['reference'] = 6 lines, same-syntax control ['type'] = 36 files",
    "classified_in_the_PR_body": "A changed (6 read sites, 4 files) · B inherits via the arbiter (16 files by grep) · C1 explicit typeof narrowing · C2 truthiness/equality reads that never answer 'no target' · C3 the measured residue of 10 raw reads that DO still answer undefined silently · D the schema's own superRefines, which must NOT throw or safeParse would throw instead of returning success:false · E the designer-form column, not touched"
    },
    "tests": "Every exit code captured by redirect-then-$? on the command, never through a pipe; every heavy run through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-18095-dev, verdict read from its VERDICT line. [1] pnpm --filter @objectstack/spec test — VERDICT command-exit 0, 483 files / 13775 tests pass. [2] pnpm --filter @objectstack/lint build && test — VERDICT command-exit 0, 103 files / 3844 tests pass. First run had ONE red and it was a true signal: the #5017 receiver meta-test asserted the key set read off def and my first edit moved the read out of the file entirely (expected ['reference'], got []). Fixed by keeping the local def.reference read and moving only the SHAPE judgment out, so that meta-test keeps its subject. [3] pnpm --filter @objectstack/cli exec vitest run --project unit test/data-model-rules.test.ts — 56 tests pass. First attempt was NOT a red: 'Failed to resolve entry for package @objectstack/objectql' is PREREQUISITE NOT MET; re-ran after pnpm --filter '@objectstack/cli^...' build. [4] pnpm --filter @objectstack/spec check:generated — exit 1 naming exactly 2 stale artifacts (api-surface: '0 breaking (removed/narrowed), 1 added'; export-origins), regenerated with --fix, re-check exit 0 'All 15 generated artifacts are up to date'. Diff inspected: exactly one line added to each, both naming referenceCarrierOf. [5] The retired gate's FINAL census, taken on the merge base before deletion: EXIT 0, 6796 files scanned, 681 reference sites, 584 string literals at a field-def carrier position and 0 NON-STRING literals, 45 unresolved + 12 conflicting with 0 material. That is the measurement that no real call site passes a non-string today — the dispatch's 'measure what newly throws' answered from the instrument being retired, on its last run. Its --self-test also exited 0. [6] PREMISE PROBE against the BUILT spec (not source): run from packages/lint cwd after the first attempt from the scratchpad raised ERR_MODULE_NOT_FOUND — the broken-instrument trap the dispatch names, and I did not read that failure as a reading. CONTROL string reference success=true; object reference success=false with invalid_type at [fields,invoice,reference]; array reference success=false; FieldSchema alone agrees in both directions. [7] REMOVAL HYGIENE, hard-wrap-safe with grep -rlz because this prose wraps the filename across lines and a line-oriented grep returns a FALSE zero (measured: check-self-test-workflow-commands.mjs wraps it). filename 'check-reference-carrier-shape' = 3 files, control 'check-doc-security-posture' = 4. alias 'check:reference-carrier-shape' = 3 files, control 'check:doc-security-posture' = 5. All six survivors are prose, now past-tense. Comment-masked: 2 in-code mentions in check-self-test-wired.mjs and 1 in dispatch-gates.mjs, all synthetic fixture STRINGS feeding pure-text matchers (no existsSync over them, no spawn) — they are the cases that now hold the grammar. No roster entry, no workflow line, no alias, no import. [8] The three re-keyed gates, all EXIT 0: check-self-test-wired.mjs and --self-test (its own scope line now prints '0 of them package-local gate(s) CI names by path' — the gate reports the zero itself); check-self-test-workflow-commands.mjs --self-test (8 batteries, 37 cases); dispatch-gates.mjs --self-test (1746 cases pass). [9] 29 further derived gates, all exit 0: scripts-symbol-anchors, spec-docblock-symbol-anchors, step-collectors, ci-filter-parity, aggregator-roster, comment-mask-adoption, comment-mask-corpus, declaration-mirrors, nul-bytes, workflow-step-name-quoting, workflow-status-functions, required-contexts, published-files, declared-population-live, select-gate-families, pm-dispatch-gates, empty-changeset, changeset-no-major --base origin/main, adr-0087-registration --base origin/main, changeset-fixed, test-source-alias, type-check-coverage, merge-driver, pnpm-filter-targets, keyed-text-bounds, undeclared-dep-imports, closing-keyword-parity, plus both symbol-anchor self-tests. [10] ONE gate red, CLASSIFIED rather than reported as a failure: check:cross-package-test-inputs exits 1 on my tree, naming packages/cli/test/init-created-files-summary.e2e.test.ts walking into packages/spec/dist/. It is a LOCAL-BUILD ARTIFACT EFFECT, not my diff, proved three ways. (i) Control worktree at origin/main fed4a15, unbuilt: EXIT 0. (ii) packages/spec/dist is gitignored with 0 tracked files, and the gate runs in lint.yml's lint job with NO build before it, so the directory does not exist in CI. (iii) DECISIVE, on my exact tree: moved packages/spec/dist aside under a trap with absolute paths seeded from git rev-parse --show-toplevel — on-disk proof both ways (216 files before, dist absent and aside holding 216 after) — gate EXIT 0 without it; restore proved by the directory being present again with 216 files and the aside directory gone, not by an exit code. Filed as an out-of-scope finding below. [11] Gate families re-derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (it verified --repo against this checkout's origin remote). The first derivation warned STALE TREE (7 commits behind, 6 deriving files changed), so I merged origin/main via scripts/pm/os-regen-merge.sh — clean, no conflicts, no regeneration owed — and re-measured on the merged tree. The derivation names ~130 commands; what I ran is the DECLARED NARROWING above (the families my diff actually implicates plus every self-test of a file I edited). The repo-wide pnpm lint sweep and packages/cli's integration tier are declared to CI: the diff touches no spawn entry point and no eslint-config. NO ABLATION was owed or run: this PR lands no new gate whose ability to fail needs proving. The equivalent evidence is [5] (the retiring instrument's own final census) and the re-keyed cli test, whose red/green was observed in both directions across two runs.",
    "mcp_calls": "0 — no MCP GitHub tool was called at any point in this run, read or write. The container's stale .claude/settings.json still permits three MCP GitHub write tools that origin/main denies; none was called. Every GitHub read and every write went through the REST proxy with curl.",
    "api_writes": "3 REST writes, plus git pushes. (1) POST /repos/objectstack-ai/objectstack/pulls — HTTP 201, PR 18503 draft, body stored 12869 bytes, read back byte-identical to what I sent (modulo one trailing newline) with exactly ONE footer. (2) PATCH /repos/objectstack-ai/objectstack/pulls/18503 — HTTP 200, an UNBUDGETED write I judged necessary and am declaring: the read-back of write (1) via check-clause2-carriers exposed that my own body carried a heading 'That the reader change closes #13053' — a LIVE closing reference that would have silently closed card 13053 on merge, which is exactly the trap the instruction file names. Re-worded to 'ends', plus an explicit sentence that 13053 is not addressed here and remains open. Post-patch scan of the STORED body: the only adjacent closing reference is ('Fixes','18095'). (3) POST /repos//issues/18095/comments — this report. ZERO label writes, and that is a measurement, not an omission: skip-changeset is refused because both @objectstack/lint (files: dist) and @objectstack/spec (files: dist, api-surface, src/**/*.zod.ts — so the edited field-value.zod.ts SOURCE ships too) publish and both move, so a changeset is owed and was written; and needs:contract-review is the seat's, which I neither hung nor stripped nor waited on. No other label was owed. git push ran 4 times: once as the empty-branch write-routing probe before the first edit (no 403 — the route is open), then three times for the WIP and changeset commits. pre-push printed 'check:commit-card-trailers: N commit message(s) carry no card relation and no model identifier in the trailer pair' each time.",
    "platform_reading_worth_recording": "⭐ NEW MEASURED CELL for pm-dispatch references/platform-readings.md, which I cannot edit (.claude is governed). On a PR BODY: the CREATE cell (REST POST /pulls) stored my session-URL footer VERBATIM — one footer, byte-identical read-back. The EDIT cell (REST PATCH /pulls/N) APPENDED its own bare footer under a fresh rule line, leaving TWO. I did not re-send, per AGENTS.md's '⛔ never re-send a body that already carries an appended footer'. So PR 18503's body currently ends with my session-URL footer and the platform's bare one beneath it; the tail bare footer is the platform's, not my form downgraded. The two cells disagree, which is exactly why AGENTS.md forbids generalising one to another.",
    "clause_2": "Seat declared yes (claim 5700063579) and the carrier is on the card; I neither hung, stripped nor waited on needs:contract-review. node scripts/pm/check-clause2-carriers.mjs --pair 18503 read TWICE. First reading EXIT 4 — a C1 split (card carried the label, PR did not) plus a C2-SIBLING row about card 13053, which existed only because of my own body defect above. After the body fix and after the seat hung the label on the PR side, second reading EXIT 0: 'PR #18503 / card #18095 — the clause-② declaration is readable in the fixed spelling and both carriers agree.' PR 18503 now carries needs:contract-review (hung by another actor — not me; I neither set nor corrected it) alongside size/xl, documentation, ci/cd, tests, tooling, protocol:data, all of which are other actors' writes. MEASURED, and it substantiates the seat's yes over the ruling's 'no expected': check:api-surface reports '0 breaking (removed/narrowed), 1 added' — referenceCarrierOf is a genuinely NEW published export on @objectstack/spec/data. So this PR does widen a published surface, which the ruling's execution note did not anticipate. Changeset is minor for both @objectstack/spec and @objectstack/lint, which a yes requires. The PR body carries no additional Clause-② line; what it carries, measured from the stored body, is the api-surface reading and the governed-surface reading.",
    "governed_surface": "node scripts/pm/check-governed-merges.mjs --test over the final 16-path file list: 0 of 16 hit the register (5 surfaces). NOT governed — ordinary queue landing. Re-derived on the FINAL list, not recalled from the seat's pre-dispatch 6-path reading.",
    "os_dev_md_divergences": [
    "L51 — loaded copy says GitHub writes are signed by the App as claude[bot]; origin/main WINS: attribution is the session ID IN THE TEXT, explicitly NOT user.login. Followed origin/main (the PR body and this report both carry the session id in text).",
    "L53 — loaded copy adds user-account attribution plus a ban on enumerating boards and broad searches; origin/main WINS: the token is per-session (installation gives claude[bot], user-to-server gives the user). Followed origin/main; I enumerated nothing and ran no search either way.",
    "L287 — loaded copy says a Clause-② yes means the DEV hangs needs:contract-review when opening the PR; origin/main WINS: the label is the SEAT's, the dev neither hangs nor strips nor waits, and reports its presence plus the --pair exit code. Followed origin/main — I hung nothing; the seat's hang landed between my two readings.",
    "L301 — loaded copy says a refused write means stop and report blocked; origin/main WINS: report the endpoint and the status code, never blocked and never via MCP. Not triggered — no write was refused (POST 201, PATCH 200).",
    "L366 — origin/main ADDS a session field to the report JSON that the loaded copy omits entirely. Included above.",
    "L370 — loaded copy defines mcp_calls as a bare count; origin/main WINS and is stricter: MCP GitHub calls WITH TOOL NAMES, and a write tool in that list means the report is refused. Reported in that form; the list is empty.",
    "L380 — cosmetic reflow only: origin/main states the premise_still_valid rule in one line where the loaded copy splits it across two. No behavioural difference."
    ],
    "open_questions": [
    {
    "question": "The package-local by-path gate lane now has ZERO live members, and the three gates that pinned this gate as their only specimen can no longer be pointed at one. I converted their live pins to what still holds (the packageLocal derivation, whole-population quantifiers, a zero with its control) rather than deleting them, but no live reading now exercises the #15342 directory-prefix grammar or the #15414 export half end-to-end.",
    "options": [
    "A. Accept as landed. The grammar stays under the synthetic fixture batteries in both files, which key a package-local path WHOLE and refuse the phantom root key beside it; the first future package-local by-path invocation makes the lane live again and a specimen can be named then.",
    "B. Build a fixture ROOT. collectPopulation already takes a { root } option, so a purpose-built temp tree (a scripts/ dir, a .github/workflows/ dir, a package.json and one package-local gate file) would drive the admission path end-to-end and could never rot when the live lane empties. Cost: check-self-test-wired.mjs would gain fs WRITE imports it does not have today, and its sibling carries an own-source pin against growing directory walks.",
    "C. Retire the directory-prefix widening too, since its sole live subject is gone. ⛔ I do not recommend this: it re-opens #15342's defect the moment any package-local gate returns, silently."
    ],
    "recommendation": "A for this PR, which is what landed, with B as a cheap follow-up if the maintainer wants the guarantee restored. B is strictly better than the pin it would replace and is the only option that survives an empty lane; it is out of scope here because it is a new fixture harness in a gate this card was not dispatched to redesign. C is the one route that trades a real guard for tidiness."
    }
    ],
    "out_of_scope_findings": [
    "to file (3 classes, dedupe words: check:cross-package-test-inputs, packages/spec/dist walked root, init-created-files-summary.e2e.test.ts, local build reds a CI-green gate, gitignored dist in walk radius) — class (a), reproducible defect with a named repro: after the standard local flow pnpm --filter @objectstack/spec build, pnpm check:cross-package-test-inputs exits 1 naming packages/cli/test/init-created-files-summary.e2e.test.ts as walking into an undeclared root packages/spec/dist/. CI never sees it (the gate runs in lint.yml's lint job with no build before it, and packages/spec/dist is gitignored with 0 tracked files), so it is a local-only false positive that costs every dev a diagnosis. Proved on my exact tree by moving dist aside under a trap: EXIT 0 without it, EXIT 1 with it, restore verified by file count (216) not by an exit code. Successor is concrete: any dev who runs a spec build and then this gate, which is the documented local sequence. ⛔ Not fixed here — the gate's own text offers two dispositions (narrow the walk / declare the root) and choosing between them is its owner's call.",
    "noted, not filed: packages/lint/scripts/ keeps two sibling gates (check-doc-formula-expressions.mjs, check-doc-security-posture.mjs) invoked as pnpm --filter @objectstack/lint run check:*, a spelling collectInvocations does not resolve, so they are in NO population of check-self-test-wired / check-self-test-workflow-commands at all. Pre-existing and unchanged by this PR, but it is now the WHOLE remaining package-local gate population, which makes it more load-bearing than it was. Successor: whoever acts on the open question above, or the gate census on #18472.",
    "noted, not filed: the ruling cited the lint.yml lines as :4662-4663 on origin/main; re-derived they were :4662-4663 for the two node lines but the removable unit is :4633-4664 (the step, its name and its 28-line comment block). The citation was accurate and the dispatch's '⛔ re-derive, line numbers rot' was the right instruction — recorded only so the next reader knows the comment block went with it deliberately. Successor: none needed.",
    "noted, not filed: refOf is a name collision across packages — packages/metadata-core/src/contract-suite.ts declares a local refOf building a MetaRef of {org, type, name}, entirely unrelated to FieldSchema.reference. Any future dispatch that counts readers by git grep refOf( will over-count by 3 for the same reason this one did. Successor: whoever writes the next reader census; recorded here so the number is not re-derived wrongly."
    ]
    }


    Generated by Claude Code

  11. os-warren commented on Sep 16, 2026

    @os-warren
    Collaborator

    Review of PR #18503 is waiting on the contract-review tier, not on the work

    Two isolated at-tier review attempts died before producing a record — the first to this session's own usage limit (~17:50Z), the second to an account-level limit on the contract-review tier (HTTP 429 rate_limit, request id req_011Cf7fBVgG5xo49HoVCRhGV, ~19:18Z). ⛔ Neither is a verdict about the PR; nothing was adopted from either.

    The full record, including why no lower-tier review is substituted (references/contract-review.md L60: the quota-exhaustion downgrade exemption covers dispatch, never review) is on the PR: #18503 (comment)

    State, unchanged and deliberately so: needs:contract-review stays on both carriers, this card keeps its pm:dispatched state and assignee, and PR #18503 stays draft and out of the queue. L54 calls that the safe state — 「标签原样留置,队列外等待是安全态」.


    Generated by Claude Code

  12. github-actions commented on Sep 17, 2026

    @github-actions
    Contributor

    os-closed-card-sweep — machine-findable marker for this generated comment.

    Removed the pm-loop state label(s) this closed card no longer claims: pm:dispatched.

    A state label claims work is in flight. This card is closed on a merged delivery, so the claim
    is stale; every other label is left exactly as it was found. Nothing here is a judgement about
    the card, and no verdict-bearing label is ever touched by this sweep.

    posted by half-state-patrol run 35196134084 · trigger schedule

    Generated by Claude Code

  13. added 2 commits that reference this issue on Sep 17, 2026
    e64ae15
    b79fae8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions