Skip to content

lint: visibility-bare-identifier goes silent for an identifier that also appears inside a has() in the same predicate #16118

Closed

Description

@os-steve

@objectstack/lint@17.3.0. Measured on objectstack-ai/hotcrm at ff43321, three injections into ONE site in src/views/lead.view.ts (views[9] · formViews.status_update_modal), same run, same command (objectstack lint --json).

The reachability fact

visibility-bare-identifier reports a bare identifier in a view visibility predicate — until the SAME identifier also appears as the argument of a has() macro anywhere in that predicate. Then the rule goes silent for it entirely, including for its unwrapped occurrence.

predicate written at that site lint finding
status == "qualified" exit 1 visibility-bare-identifier at error — CONTROL
has(status) && status == "qualified" exit 0 none
has(status) exit 0 none

Baseline for all three: passed:true · errors:0 · warnings:17 · suggestions:12 — the two silent injections moved nothing at all.

The control that makes this negative worth reading

The site is definitely walked, and the walk definitely reports there. Two OTHER injections into the byte-identical anchor at the same site fired in the same harness:

  • has(record.status) && && record.status == "qualified" gives visibility-predicate-syntax at error, exit 1, naming views[9] · formViews.status_update_modal;
  • has(record.status) && notARealFn(record.status) == "qualified" gives visibility-predicate-unknown-function at error, exit 1, same site.

So this is not "the rule cannot see that view".

Why the middle row matters — it is the shape an author actually writes

has(x) && x == "…" is the guarded spelling the totality discipline pushes authors toward. An author who correctly adds the has() guard and incorrectly forgets the record. prefix on both halves lands exactly in the silent row. The predicate then never evaluates for any record (Unknown variable: status), and on a form surface an unevaluable visibleWhen fails OPEN — the field renders, carrying its required: true into the console's submit check. That is the #688 shape, and the rule that exists to catch it is quiet for it.

The plausible cause is that a has() argument is a legitimate select target and is excluded from the bare-identifier walk — but the exclusion appears to be keyed on the identifier NAME across the whole predicate rather than on the occurrence.

⛔ Not necessarily a defect in the exclusion's intent. What it is, definitely, is a reachability fact: on this input the rule reports nothing, and the guarded-but-unprefixed spelling is the one an author is most likely to produce.

How it was found

objectstack-ai/hotcrm#1625 retires local test assertions proven duplicate of platform rules. hotcrm's test/view-predicate-dialect.test.ts carries a sweep, references only namespaces the evaluator binds, which reds on all three rows above (it reads roots through collectCelRootIdentifiers). Because the platform covers only the first row, that assertion was kept rather than retired.

Dedupe ran before filing — #13594, #13652, #12665 and #14288 read in full; none covers this. Unassigned.

Activity

  1. added
    bugSomething isn't working
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    pm:queueReady for the PM dispatch loop
    domain:devxDomain lane — seat card indexed by label:pm:seat
    on Sep 6, 2026
  2. os-zhuang commented on Sep 6, 2026

    @os-zhuang
    Contributor

    分诊 · domain:devx / bug / tooling / priority:p2 / pm:queue

    分诊席位。⛔ 不认领、不派发、不写代码、不合并。origin/main @ 932acc3d,2026-09-06T03:22Z。

    车道 domain:devx

    visibility-bare-identifier 落在 packages/lint/src/validate-visibility-predicates.ts(由 packages/lint/src/predicate-rhs-position.ts:16 指名,authoring-rules.ts:1027 记录它以 error 发射)⇒ domain:devx。⛔ 不是 domain:ui——被检查的是视图谓词,改的代码是 linter。

    ⭐ 这张卡的控制设计值得单独表扬,我不复测

    它把「零」做成了不可能被误读的形状:同一个站点(views[9] · formViews.status_update_modal)、字节相同的锚点、同一次运行里,两条别的注入起火:

    has(record.status) && && record.status == "qualified"        → visibility-predicate-syntax, error, exit 1
    has(record.status) && notARealFn(record.status) == "…"       → visibility-predicate-unknown-function, error, exit 1
    

    ⇒ 「规则看不见那个 view」这个解释被直接排除。⭐ 这比「同文件另一个符号起火」更强一档——它是同一个站点、同一个锚点上的控制。

    定型 bug,不是 enhancement

    规则已经存在且已声明要报裸标识符;它在一个子集上不报。⇒ 声明 ≠ 强制,恢复方向 ⇒ bug。

    ⚠️ 卡自己留了余地:「⛔ Not necessarily a defect in the exclusion's intent」。我同意 intent 可能是对的(has() 的参数是合法 select 目标,该排除),⛔ 但排除的粒度是错的——卡推测的机制我认为就是答案:

    the exclusion appears to be keyed on the identifier NAME across the whole predicate rather than on the occurrence.

    ⇒ 修法是把排除从「名字」改到「出现位置」,⛔ 不是取消排除。这仍然是 bug:一个按名字生效的排除,会让同一个谓词里未被包裹的那一次出现一起免检。

    优先级 p2

    三条叠加,方向比 #16119 更坏:

    1. 静默的那一行正是作者最可能写出的形状。 has(x) && x == "…" 是 totality 纪律推着作者走的带守卫拼法;一个正确加了 has() 却在两处都忘了 record. 前缀的作者,恰好落进静默行。
    2. ⭐ 失败方向是 fail OPEN。 谓词对任何记录都不求值(Unknown variable: status),而表单面上不可求值的 visibleWhen 渲染该字段,把它的 required: true 一并带进 console 的提交检查。⇒ 用户被一个本不该出现的必填字段挡住,或者相反——一个本该隐藏的字段暴露出来。这就是卡引的 Add patch version changeset #688 形状。
    3. 规则存在的理由正是抓这个,而它对这个静默。

    不给 p1:需要作者写错前缀才触发,且没有数据损坏或越权。

    ⚠️ 与 #16119 的对照值得记一笔:那张是 fail closed(丢访问权),这张是 fail open(该藏的没藏 / 该判的没判)。⇒ 两张都是 p2,但如果只能先做一张,做这张——fail open 的错误方向更贵。

    给接卡人的两条

    1. ⭐ 修完请把三行都做成 pin,包括那条 has(status) 单独出现的(第三行)。⚠️ 第三行是真的该静默(只有一个 has() 参数,没有裸出现),所以它是修复后必须保持绿的负控制。⛔ 只 pin 中间那行,下一次重构可能把排除整个删掉而测试仍绿。
    2. ⛔ 不要顺手退掉 hotcrm 的本地 sweep。 test/view-predicate-dialect.test.ts 的 references only namespaces the evaluator binds(走 collectCelRootIdentifiers)在三行上全红,而平台只覆盖第一行——所以 objectstack-ai/hotcrm#1625 保留了它。本卡落地后可以重新评估,⛔ 但那是 hotcrm 的决定。

    去重

    卡自陈读全了 #13594、#13652、#12665、#14288,均不覆盖。⚠️ 本席位未做穷举枚举(search_issues 的 in:body 在本仓对确实存在的词返回过 0,我不拿它的零当缺席证据)⇒ 采信卡的近邻判断。


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 6, 2026
  4. claude commented on Sep 6, 2026

    @claude
    Contributor

    Claim: PM seat domain:devx @ objectstack (#6023), session session_01Vbw3RPgdtqesx4azk9SbW8, 2026-09-06T19:42Z — dispatching to an os-dev subagent (mode:subagent) on branch claude/issue-16118-visibility-bare-identifier-has-occurrence. pm:queue → pm:dispatched, assignee = the shared account (identity is this session ID + that branch). The dev inherits both and posts no second claim.

    Clause-②: no


    Generated by Claude Code

  5. claude commented on Sep 6, 2026

    @claude
    Contributor

    os-dev-report
    {
    "issue": 16118,
    "status": "done",
    "branch": "claude/issue-16118-visibility-bare-identifier-has-occurrence",
    "pr": "#16413",
    "head_sha": "efd084c428c0e6ff7985648a9ff7f532c5c42d3d",
    "premise_still_valid": true,
    "summary": "The reachability fact reproduces exactly as filed, but the CAUSE the card and the triage hypothesised is falsified. There is no name-keyed has() exclusion in the rule: namespaceRoots only declares receivers (a.b, a?.b, a['b'], a.exists(...)), a has() argument is not a receiver, and validate-visibility-predicates.ts had no has() handling of any kind. The silence lives in the shared oracle: firstUndeclaredReference reads the FIRST error cel-js's checker reports and acts only on 'Unknown variable: X'; a bare has(x) fails that check with 'has() invalid argument', a different class, and everything behind it in the same predicate went unjudged. Measured: has(status) && other == "x" is also silent, so the masking is POSITIONAL, not name-keyed, and 'other == "x" && has(status)' reports 'other' with the arms swapped. Fix: maskHasCalls(source, ast) replaces each has(...) call with a same-width 'true' literal, using the canonical AST's own start/end spans, before the source reaches the checker. That excludes the ARGUMENT occurrence (the exclusion the triage kept, now per-occurrence) and leaves no foreign first error to mask the rest. The rewrite only deletes source, so it can remove a finding and never invent one; the rest of the predicate reaches the checker byte-identical, so comprehension-macro variables keep the checker's verdict; a span it cannot locate exactly turns the whole rewrite off. Existing rule id and 'error' severity only; no new rule, no severity change, no change to the module's other rules. The upstream oracle masking is out of scope here and is filed as #16412. Assignee was already set by the PM's dispatch (the shared account) and was NOT written by this session.",
    "tests": "RED-FIRST (tests-only commit 56e62c6, before any source change): pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/validate-visibility-predicates.test.ts -t 'does not silence an unwrapped occurrence' => EXIT=1, 'Tests 2 failed | 6 passed | 144 skipped (152)'; the two reds are 'row 2 - the SAME identifier unwrapped beside has(status) is reported' and 'a bare has() does not mask a DIFFERENT name behind it either'. Row 1 (status == "qualified", must fire) and row 3 (has(status) alone, must stay silent) were among the 6 greens, so both controls behave on main. | GREEN after the fix: same file 'Test Files 1 passed (1) / Tests 152 passed (152)'; the four dispatch-named suites (validate-visibility-predicates, validate-predicate-path-refs, runtime-gate, authoring-rule-input-tier) 'Test Files 4 passed (4) / Tests 266 passed (266)'. | LOCKED runs, verdict lines read from the wrapper, never a bare $?: 'pnpm --filter @objectstack/lint test' -> 100 files, 'Tests 3425 passed | 5 skipped (3430)', VERDICT command-exit 0 (held 48s); 'pnpm --filter @objectstack/lint typecheck' -> VERDICT command-exit 0 (held 17s, check:test-typecheck OK); 'pnpm --filter @objectstack/metadata-protocol test' -> 'Tests 2414 passed | 10 skipped', VERDICT command-exit 0 (held 130s); 'pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2' -> 'Tests 2453 passed | 6 expected fail', VERDICT command-exit 0 (held 243s). CLI integration tier declared to CI: the diff touches no tier-integration file, no bin/ entry, no spawn helper. | ABLATION (implementation committed FIRST at 2847e4a, then mutated in place, trap 'restore' EXIT INT TERM, absolute paths): on-disk proof before reading any colour - 'before: with-mask count = 1 ; without-mask count = 0', 'after mutation: with-mask count = 0 ; without-mask count = 1', mutated blob cd7360ff8f22dd3792723515bffee0c886673885 vs HEAD blob 5fdac4f82049e3524096cd40f793c3547c3905bb (differ, so the edit really landed). Ablated run: 'Tests 2 failed | 6 passed | 144 skipped (152)' - exactly row 2 and the different-name row red, rows 1 and 3 and the five other pins green. Direction observed = TURNS RED, as predicted. Restore leg: 'git checkout HEAD -- ABSOLUTE_PATH', proven by hash equality AND empty 'git diff HEAD' - 'RESTORED ok (hash 5fdac4f82049e3524096cd40f793c3547c3905bb, git diff HEAD empty)'. No rebuild/dist leg is owed: the suite imports the mutated module by relative path inside packages/lint/src, so it resolves to SOURCE, not through a dependency's exports into dist/; ablation-dist-preflight does not apply to this subject. The lint dist WAS rebuilt separately for the CLI-level controls below. | POSITIVE CONTROL on a real app (same trap + blob-hash discipline): examples/app-showcase task.view.ts:368 swapped from record.priority == 'urgent' to has(priority) && priority == 'urgent'; 'pnpm --filter @objectstack/example-showcase validate' -> exit 1 with 'rule: visibility-bare-identifier at views[0].formViews.edit.sections[0].fields[6]'. Restored: hash 660ca568f0f6222cd4eb1decde5475ca4907cd6b, git diff HEAD empty. Unmutated, all four example apps validate at exit 0 (showcase, crm, todo, multi-package) - the fix surfaces no pre-existing defect in the examples. | GATES: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 54 commands for the 3-path change set; all 54 run, each exit code captured BEFORE any pipe. 51 green on the first pass. Three needed a prerequisite and are measurements, not passes, until it was met: 'pnpm check:docs-transcript-drift' EXIT=3 PREREQUISITE NOT MET (lint not built) -> EXIT=0 after 'pnpm --filter @objectstack/lint... build'; 'pnpm check:dual-build-cjs-loads' EXIT=3 PREREQUISITE NOT MET (74+ packages had no dist) -> EXIT=0 after the full packages build; 'pnpm check:type-check-debt' EXIT=3 under my --max-old-space-size=4096 (tsc OOM, 'refusing to record 0') -> EXIT=0 at its own documented 6144 ceiling, '5 ledger entries re-measured in 71.8s, 55 raw tsc errors total, none above its recorded number'. Reconciliation: 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran gates.txt' -> '54 derived, 54 run, 0 NOT-MEASURED, 0 UNRUN'. | REPO-WIDE ESLINT: 'pnpm lint' (eslint . --no-inline-config) EXIT=0 at head efd084c - the full union, run after the final commit, so no narrowing is claimed. | BUILDS through the lock: 'pnpm --filter @objectstack/lint^... build' VERDICT command-exit 0; 'pnpm --filter @objectstack/lint... build' VERDICT command-exit 0; 'pnpm exec turbo run build --filter=./packages/* --filter=./packages// --concurrency=2' -> 71/71 successful, VERDICT command-exit 0. | SCOPE NOTE: turbo ls --affected reports 59 packages, but that number is not usable here - the .changeset/.md file ALONE (a root-package change) already yields the same 59, measured with TURBO_SCM_BASE=HEAD. The structural answer is the 54 downstream consumers of @objectstack/lint (pnpm --filter '...@objectstack/lint', prefix form = downstream), i.e. effectively the whole workspace; CI runs the farm. | BYTE HYGIENE: grep -naP over the diff for control characters - no hits; pnpm check:nul-bytes EXIT=0. PR body read back after creation: byte-identical as a strict prefix, platform appended its own 90-byte session-URL footer (the documented POST /pulls form). Finding issue #16412 body read back: byte-identical, 58-byte footer appended.",
    "mcp_calls": "0 - the whole run used the container's REST channel (repo-scoped probe returned 200) plus git; no mcp__github__
    call was made. Reads: issue #16118, comment 5556556954, its comment list, and all 8 pages of open issues for the dedupe. Writes: POST /issues (the finding), POST /pulls (the draft PR), POST /issues/16118/comments (this report).",
    "open_questions": [
    {
    "question": "A bare has(x) - has() applied to something that is not a select - is REFUSED by the canonical checker with 'has() invalid argument', yet parseCelToAst returns an AST for it, so visibility-predicate-syntax does not own it either and no rule in this family reports it. Row 3 of the card (has(status) alone) is therefore silent because nothing judges that source, not because a rule decided it was acceptable. The triage pinned row 3 as the negative control that must stay green, and this PR keeps its verdict byte-for-byte unchanged. Should some rule own a predicate the canonical checker refuses but the parser accepts?",
    "options": [
    "A - leave it: row 3 stays silent, the checker's refusal is nobody's verdict on this surface. Zero risk, and the fail-open predicate keeps shipping.",
    "B - widen visibility-predicate-syntax to consult check() as well as parse, so any source the canonical front end refuses gets exactly one finding whichever stage refused it. Same one-finding-per-broken-predicate property the module already argues for, but it moves a rule the PM's scope decision explicitly held.",
    "C - a new rule id for check-stage refusals, leaving both existing rules byte-identical."
    ],
    "recommendation": "Not mine to pick inside this card - the triage reserved row 3's verdict and the PM's scope decision holds the other rules. Reported with the citation as the dispatch's assumption 2 directs. If asked to analyse it on the four axes, B looks strongest on 'defends against AI writing metadata apps wrong' (a predicate that cannot evaluate is refused loudly at publish, which is contract-first) and on 'long-term project soundness' (one oracle, one finding per broken predicate, no third rule id to keep aligned); it is weakest on 'startup-stage no requirement sprawl' only in that it touches a rule nobody asked about. It needs its own card and its own measurement of what real apps write."
    },
    {
    "question": "The card's stated mechanism ('the exclusion appears to be keyed on the identifier NAME across the whole predicate') and the triage's endorsement of it ('卡推测的机制我认为就是答案') are both wrong on measurement - the masking is positional and hits unrelated names too. The fix serves the triage's INTENT ('修法是把排除从「名字」改到「出现位置」,⛔ 不是取消排除') exactly, and the three pins it asked for are in place. Flagging the divergence rather than quietly reinterpreting it.",
    "options": [
    "A - accept as implemented: the intent is served, the exclusion is kept and is now per-occurrence, and the true mechanism is documented in the code, the PR and card #16412.",
    "B - re-triage before merge, on the grounds that a falsified mechanism could change the priority or the scope."
    ],
    "recommendation": "A. The reachability fact, the fail-open direction and the p2 reasoning are all unaffected by the correction - if anything the positional masking is slightly worse than the card assumed, since it also hides unrelated names. Nothing about the ruling's intent depends on the mechanism being name-keyed."
    }
    ],
    "out_of_scope_findings": [
    "filed as #16412: formula - firstUndeclaredReference returns null for every undeclared reference behind a first checker error of another class (measured; consumers flow-variable-scope.ts:273 and validate.ts:688 / :713 read the same helper and are unmeasured against this shape). Unassigned, no labels - a concrete defect, left for PM triage. Dedupe: all 678 open non-PR issues enumerated through the REST list endpoint (8 pages, page sizes 100x7 + 11, reconciled against open_issues_count 709) and grepped locally; no duplicate."
    ]
    }


    Generated by Claude Code

  6. claude commented on Sep 6, 2026

    @claude
    Contributor

    ACCEPT — PR #16413 (head efd084c42, read 2026-09-06T20:34Z) — PM seat domain:devx @ objectstack (#6023), session session_01Vbw3RPgdtqesx4azk9SbW8. Reviewed against the three-dot diff origin/main...claude/issue-16118-visibility-bare-identifier-has-occurrence (3 files, +165/−2), every changed line read; the report 5561980604 is the increment I checked, not the evidence.

    • PR shape: draft, base main, first line Fixes #16118; full-body keyword scan reads that one binding and nothing else (the finding is spelled "filed as formula: firstUndeclaredReference returns null for every undeclared reference behind a first checker error of another class #16412, unassigned" with no keyword adjacent; related cards as "card …"). Three commits (tests-first, fix, changeset), no card-relation trailer in any — clean under RULE 2. No model identifiers.
    • Scope: exactly the declared surface — packages/lint/src/validate-visibility-predicates.ts (+86/−2), its test (+68), .changeset/visibility-bare-identifier-has-occurrence.md (@objectstack/lint patch, published package, user's words, states the behaviour change and the fail-OPEN reason). check-governed-merges.mjs --test on this file list ⇒ NOT governed. No content/docs/releases/ edit.
    • Triage constraint conformance (5556556954): ① the exclusion a has() argument earns is kept and is now per OCCURRENCE — implemented as maskHasCalls: every has(…) call is replaced by a same-width true literal from the canonical AST's own spans before the source reaches the shared checker, so the argument occurrence is excluded and every other occurrence is judged as if no guard were written; a span that cannot be located exactly turns the rewrite off (coverage lost, never a wrong span). ② all three rows pinned — row 1 the firing control, row 2 the fix, row 3 (has(status) alone) the negative control that stays silent — plus the prefixed-guard/bare-use, bare-guard/prefixed-use, several-bare-guards, and comprehension-variable-behind-a-guard shapes. ③ hotcrm untouched.
    • Mechanism corrected, on measurement (open question 2 — A, accepted): the card's and the triage's hypothesis (a NAME-keyed has() exclusion) is falsified — the rule had no has() handling at all; the silence lives in the shared oracle firstUndeclaredReference (packages/formula/src/cel-engine.ts), which acts only on the FIRST checker error and only when it is Unknown variable: X; a bare has(x) fails with has() invalid argument and that different-class first error masked every undeclared reference behind it — the dev's pin has(status) && overdue == true (a DIFFERENT name, now reported) is what tells the two mechanisms apart, and it is the strongest evidence in the PR. The triage's intent (per-occurrence exclusion, not removal) is served exactly; the reachability fact, the fail-open direction and the p2 reasoning are unaffected. The rewrite direction is safe by construction (it only deletes source, so it can remove a finding and never invent one) — verified in the hunk.
    • Open question 1 (a bare has(x) is refused by the canonical checker but accepted by the parser, so no rule in the family owns it and row 3 is silent because nothing judges it): left as A for this card — row 3's verdict is byte-for-byte unchanged, as the triage reserved it; whether visibility-predicate-syntax should consult the checker (B) or a new id should own check-stage refusals (C) is a design question outside this card's scope — it goes to triage as a finding if the dev's formula: firstUndeclaredReference returns null for every undeclared reference behind a first checker error of another class #16412 does not already cover the surface (it covers the oracle, not this). Recorded here; not filed twice.
    • Tests / evidence: red-first on a tests-only commit (56e62c627): exactly row 2 and the different-name row red, rows 1/3 green; fix ⇒ 152/152 in the file, 266/266 across the four neighbouring suites; owner suite 100 files / 3425 through the lock, typecheck 0; dependents (metadata-protocol 2414, CLI unit 2453) green; ablation removes the mask and reds exactly the same two pins, restore proven by blob hash + empty git diff HEAD; examples measured: all four example apps validate at exit 0 unmutated, and an injected guarded-but-unprefixed predicate in the showcase's task view reds validate at views[0].formViews.edit.sections[0].fields[6] — the anti-vacuity control; 54/54 dispatch-gates commands reconciled by --ran, three exit-3 first reads (unbuilt lint, no dist, OOM below the documented 6144 ceiling) re-measured to 0 and disclosed; whole-repo eslint 0. CI at head: every job in_progress at 20:33Z (started ~20:29Z).
    • Out of scope, filed: formula: firstUndeclaredReference returns null for every undeclared reference behind a first checker error of another class #16412 (the oracle's first-error masking; two other consumers named as unmeasured), unrouted for triage. Dedupe over all 678 open non-PR issues via the REST list, reconciled against the count.
    • API reads git could have answered: none — 0 MCP calls, REST + git throughout.

    Landing plan: flip point armed 21:00Z; flip ready when every row at efd084c42 reads completed/success on its own line (each Test Core (k/6), Lint & Repo Gates, TypeScript Type Check, Dogfood Regression Gate (k/3), Build Core, Temporal Conformance, Check Changeset) → auto-merge → probe added_to_merge_queue (expect the shard-5 lap). On MERGED → probe on origin/main: maskHasCalls ≥1 and hasCallSpans ≥1 in the rule file, row 3 in its test ≥1; negative control: firstUndeclaredReference(source, (the unmasked call form) = 0 in the rule file; changeset present; card closes by Fixes, strip pm:dispatched, clear assignee.


    Generated by Claude Code

  7. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 6, 2026
  8. claude commented on Sep 6, 2026

    @claude
    Contributor

    LANDED — PR #16413 merged into main at 2026-09-06T21:42:58Z as 60ff091a9 via the merge queue on its second group run (34060488115: every Test Core (k/6) row completed/success on its own line, shard 5 at 26m54s). Card closed by Fixes #16118.

    The first group run (34059823700, 21:02Z) was an honest red, not this PR's: Test Core (5/6) hit the 30-minute wall at 30m15s and the fixed aggregate read failure; the queue regrouped rather than removed (queue note 5562367385). 40 minutes queued for a one-rule lint patch — the stale timings dataset's cost, #16173 the lever.

    Landing probe on origin/main @ 0ea5f9d9f (21:51Z) — git merge-base --is-ancestor 60ff091a9 origin/main ⇒ true.

    • Firing: maskHasCalls = 3 and hasCallSpans = 4 in packages/lint/src/validate-visibility-predicates.ts; row 3 = 1 in its test; .changeset/visibility-bare-identifier-has-occurrence.md present.
    • Negative control: the unmasked call form firstUndeclaredReference(source, in the rule file = 0 on the same grep.

    For the record: the card's mechanism (a name-keyed has() exclusion) was falsified on measurement — the silence was the shared oracle's first-error masking, fixed here at this rule's call site by masking has(…) per occurrence; the oracle itself is #16412 (triage's; domain:engine by the lane table). A bare has(x) refused by the checker but accepted by the parser is owned by no rule — recorded on the ACCEPT (5561996154), not filed twice.

    Labels: pm:dispatched stripped (four-step write, read back); assignee cleared.


    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

    bugSomething isn't workingdomain:devxDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions