Skip to content

docs(formula): state the false-negative side of firstUndeclaredReference's contract - #16808

Merged
os-musk merged 1 commit into
mainfrom
claude/issue-16412-first-undeclared-reference-masking
Sep 8, 2026
Merged

docs(formula): state the false-negative side of firstUndeclaredReference's contract#16808
os-musk merged 1 commit into
mainfrom
claude/issue-16412-first-undeclared-reference-masking

Conversation

@os-musk

@os-musk os-musk commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Part of #16412

Items 1 and 2 of the triage 验收口径 are delivered; item 3's stop condition is reached and reported, not acted on. The code diff is comment-only. The deliverable is the reachability reading, and it says the gap is live on all three consumers, with an author-written shape for each.

Clause-②: no

Basis: the diff is comment-only (59 changed lines across two src files, 0 of them non-comment — measured by filtering the merge-base diff for lines that are not JSDoc). No exported symbol is added or changed, no key lands on a published payload, and no runtime behaviour moves. The published declaration files do change bytes (packages/formula/dist/index.d.ts and .d.mts, 53.45 KB to 55.99 KB) but only as JSDoc text on an already-exported symbol, which the dispatch's own mechanical floor names as not by itself a widening. needs:contract-review therefore does not go up on either carrier.

Type notation: cel-js prints map types with angle brackets. Every such type below is rendered with square brackets (map[dyn, dyn]) so GitHub's body sanitizer does not eat the fragment. The error strings are otherwise verbatim.


1. The reachability reading — LIVE on all three consumers

Measured in a worktree at merge base b38821d1c, packages/formula and its closure built first. Method: the raw strict-env check() verdict beside the helper's answer beside celEngine.compile(), then each consumer driven through its own real entry point.

The mechanism, which is not the one the card's has() trigger suggests

firstUndeclaredReference builds a strict env in which SCOPE_ROOTS are declared map. celEngine.compile() — the gate two of the three consumers sit behind — type-checks in the permissive env (CEL_ENV_OPTIONS, unlistedVariablesAreDyn: true), where those same roots are dyn.

⇒ Any operator with no map overload applied to a namespace root faults only in the strict env. The source compiles clean, reaches the helper, and the first checker error is a no such overload — so every undeclared reference behind it goes unjudged and the answer is null.

That is a second live class, disjoint from the card's has() one, and it is the class that survives the compile gate:

source strict first error compile helper
status == "qualified" Unknown variable: status OK "status"
has(status) && other == "x" has() invalid argument FAIL null
data == 'x' && status == 'q' no such overload: map[dyn, dyn] == string OK null
record == 'x' && status == 'q' no such overload: map[dyn, dyn] == string OK null
config != null && status == 'q' no such overload (same class) OK null
page == 'x' && status == 'q' Unknown variable: status OK "status"

The last row is the control that names the mechanism as the declaration, not the name: page is passed to the helper as a known field (dyn), so nothing faults on it and status is reported normally.

What an author writes to get there

A field — or a flow variable — named after a SCOPE_ROOTS member, read bare. The list includes data, config, result, item, event, input, output, user, step, context, params, settings, payload, args, env, parent, trigger, record, previous, vars. These are ordinary field names. The author writes data == 'x' && status == 'q' meaning two bare field reads, which on a record-scoped site is exactly the #1928 defect the oracle exists to catch — and the first of the two silences the verdict on both.

On a metadata-editing form the collision is not even accidental: CANONICAL_ROOT_BY_LAYER.metadata is 'data', so data is the root that layer binds.

Per consumer

packages/lint/src/validate-visibility-predicates.ts:780visibility-bare-identifier. LIVE. Repaired at its own call site for the has() class by #16118 / PR #16413; that repair is a has(…) span mask and it does not reach this class at all. Driven through validateVisibilityPredicates on the suite's own formStack fixture:

visibleWhen compile findings today
status == 'active' OK 1 (visibility-bare-identifier)
has(status) && status == 'active' FAIL 1 — #16118's mask working
data == 'x' && status == 'active' OK 0
record == 'x' && status == 'active' OK 0
config != null && status == 'active' OK 0
type == 'grid' && status == 'active' FAIL 0

Rows 3–5 are the live gap: the predicate publishes clean, and per the rule's own message the console then falls OPEN — the element renders unconditionally and looks exactly like one with no predicate at all. Row 6 is the CEL-type-name variant of the same class; the suite already pins type == 'grid' alone as a measured blind spot, but it pins it per name, and the masking it causes is source-wide.

packages/lint/src/flow-variable-scope.ts:273shadowedFieldReads. LIVE. No compile gate stands in front of it: warnShadowedFieldReads in validate-expressions.ts:1177 calls it on any celSourceOf(raw) that is non-empty. bareRootsOf asks the oracle with found starting empty, so a null on iteration 0 ends the loop before it has judged anything.

With status declared as both a flow variable and a field of the bound object:

condition compile shadow warnings
status == 'x' OK ["status"]
config == 'x' && status == 'y' OK []
data == 'x' && status == 'y' OK []
result > 1 && status == 'y' OK []
status == 'y' && config == 'x' OK ["status"] — same two names, other order

packages/formula/src/validate.ts:688 — the record-scope bare-ref HARD ERROR. LIVE, and the worst of the three. Through validateExpression('predicate', src, { scope: 'record' }):

source result today
status == 'qualified' ok=false — bare reference status
data == 'x' && status == 'qualified' ok=true, zero errors
config != null && status == 'qualified' ok=true, zero errors
record == 'x' && status == 'qualified' ok=true, zero errors

A hard error that gates the build is downgraded to silence, and the formula ships and silently evaluates to null at runtime — #1928's own class.

packages/formula/src/validate.ts:713 — the flattened did-you-mean WARNING. LIVE. Same shape, one severity down. Note the strict env registers schema.fields as dyn after SCOPE_ROOTS as map, and the duplicate registration throws and is swallowed — so a field actually named config keeps map, not dyn:

source (fields: amount, status, config, data) warnings today
amont == 'x' 1 — did you mean amount?
config == 'y' && amont == 'x' 0
data == 'y' && amont == 'x' 0

Re-grading input for the seat

Both previously-unmeasured consumers can receive such a source, and an author can write it — an undeclared reference passing lint as legal, and on validate.ts:688 a hard error going silent. Per the trigger the seat attached, that is the priority:p1 branch, not p3.

2. The docblock's false-negative side — the whole code diff

cel-engine.ts gains a section stating what the "cannot false-positive" sentence never said: the checker returns exactly ONE error, so a first error of another class leaves everything behind it unjudged and the answer is null — the same value that means "every reference is rooted". It states that the masking is positional rather than name-keyed, that celEngine.compile() is not a gate against it, which two error classes reach that first slot from authored input, and why widening the regex is precisely the false positive the narrowing buys off.

flow-variable-scope.ts gains the same correction from the consumer side. Its "known, deliberate blind spot" note bounded the under-report to a flow variable named after a SCOPE_ROOTS member. Measured, that bound does not hold: such a name in an operand position ends the discovery loop on iteration 0 and every shadow in the source is lost, whatever it is named. That is a statement about the rule's own behaviour that the reading above falsifies, so it is corrected here rather than left to be read as a bound.

3. If the oracle moves — the per-rule output differences, measured

⛔ Not adopted. Measured as a reverted prototype so the seat has numbers instead of adjectives.

Design B — a different checker entry — is not available at the pinned dependency. @marcbachmann/cel-js@8.0.0 exposes parse().check(), Environment.check(expression) and a top-level check(expression); all three return TypeCheckResult, whose declared shape is { valid, type?, error? } — a single error, no array, and no options parameter to ask for more. Reporting past the first error through a different entry means walking sub-expressions or moving the dependency.

Design A — a re-check loop over a neutralised source — was prototyped and measured. The checker's error carries a source range (measured: {start, end} plus a code such as no_such_overload / invalid_macro_argument), so the loop is mechanically available: mask [start, end) with a width-preserving literal, re-check, bounded.

Ablation discipline: mutation applied to the committed tree, proven on disk (injected marker 7 occurrences, env.parse(source).check 2 to 1), @objectstack/formula rebuilt, presence in dist/ proven with ablation-dist-preflight.mjs (exit 0) because @objectstack/lint's tests resolve @objectstack/formula through dist/ — it is a registered KNOWN_UNALIASED_TEST_IMPORTS pair. Restored with git checkout HEAD --, source blob hash back to 88f74c9c = HEAD's, rebuilt, and absence re-proven (--absent, exit 0, "working tree clean against HEAD").

The single most important number: Design A changes ZERO existing test verdicts. Baseline and prototype both: @objectstack/formula 29 files / 827 passed; @objectstack/lint 102 files / 3564 passed, 5 skipped. Item 3's "one green test run is not evidence" is exactly right — the suites are blind to this difference, so every output difference below had to be probed directly.

Per-rule output differences under Design A, all previously-silent cases starting to report:

rule / entry point source today Design A
visibility-bare-identifier data == 'x' && status == 'active' 0 findings 1 finding
visibility-bare-identifier record == 'x' && status == 'active' 0 1
visibility-bare-identifier config != null && status == 'active' 0 1
visibility-bare-identifier type == 'grid' && status == 'active' 0 1
shadowedFieldReads config == 'x' && status == 'y' [] ["status"]
shadowedFieldReads data == 'x' && status == 'y' [] ["status"]
shadowedFieldReads result > 1 && status == 'y' [] ["status"]
validate.ts:688 record scope data == 'x' && status == 'qualified' ok=true ok=false, bare-ref error
validate.ts:688 record scope config != null && status == 'qualified' ok=true ok=false
validate.ts:688 record scope record == 'x' && status == 'qualified' ok=true ok=false
validate.ts:713 flattened config == 'y' && amont == 'x' 0 warnings 1 did-you-mean
validate.ts:713 flattened data == 'y' && amont == 'x' 0 warnings 1

Three consequences the seat should weigh, none of which a green suite would have shown:

  • ⚠️ Design A does NOT subsume lint: visibility-bare-identifier goes silent for an identifier that also appears inside a has() in the same predicate #16118's repair. The has() class stays null under it: the error's range for has(status) is the ARGUMENT span, so masking it yields has(true ), which faults identically, and the loop reaches its fixed point and gives up. Card rows 2 and 3 are unchanged by Design A. The two mechanisms are disjoint and the call-site mask is still load-bearing.
  • ⚠️ It introduces double-reporting on sources that do not compile. type == 'grid' && status == 'active' fails celEngine.compile(), so the visibility rule would now emit a type/syntax finding and a bare-identifier finding for one predicate. That is a per-rule output change on inputs that are already being rejected.
  • ⚠️ It turns a pinned blind spot into a finding. validate-visibility-predicates.test.ts pins type == 'grid' as a deliberate blind spot with a recorded rationale. Design A's first masking step neutralises the type == 'grid' node, so the source-wide half of that pin moves.

4. Negative control — "cannot false-positive" still holds

17 sources in which every reference is rooted, or which are the legitimate CEL the narrowing exists to protect (type(record.x) == string, comprehension macros including one whose macro variable shadows a field, both guard idioms, optional chaining, stdlib calls). Each asserted clean on all four surfaces at once — helper, validate.ts record scope, validateVisibilityPredicates, shadowedFieldReads.

  • Today: 17/17 clean, 0 newly reporting.
  • After this PR: 17/17 clean — byte-identical output file, as a comment-only diff requires.
  • Under the Design A prototype: 17/17 clean, 0 newly reporting, plus the 4391 existing assertions above unchanged.

5. The card's five probe rows, all five, pre and post

This PR changes no behaviour, so the regression is a byte-identity claim and is reported as one: the probe output file for the five rows is diff-identical before and after, on the built tree, and rows 1 and 5 — the two the helper answers correctly today — are among the identical lines.

# source strict first error helper, pre helper, post
1 status == "qualified" Unknown variable: status "status" "status"
2 has(status) && status == "qualified" has() invalid argument null null
3 has(status) && other == "x" has() invalid argument null null
4 other == "x" && has(status) Unknown variable: other "other" "other"
5 has(record.status) && status == "qualified" Unknown variable: status "status" "status"

All five reproduce the card's table exactly, at merge base b38821d1c.

Verification

Everything below at ba03f26bd9, the final commit.

  • pnpm --filter @objectstack/formula --filter @objectstack/lint run test — formula 29 files / 827 passed; lint 102 files / 3564 passed, 5 skipped. Wrapper VERDICT command-exit 0.
  • pnpm --filter @objectstack/formula --filter @objectstack/lint run typecheckVERDICT command-exit 0 (each package's tsc --noEmit plus its check:test-typecheck).
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 50 commands; all 50 run, 49 exit 0. The one exception is reported below, not rounded off.
  • Reconciliation, verbatim: ✓ dispatch-gates --ran: 50 derived famil(ies) accounted for — 50 run, 0 NOT-MEASURED.
  • ⚠️ pnpm check:dual-build-cjs-loads exited 3 = PREREQUISITE NOT MET, which is NOT MEASURED and not a pass. Its own words, verbatim: "PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/." followed by 8 named packages and "… 77 more" (85 in total), then "Run pnpm build first. ⛔ This is NOT a pass: nothing was measured." That is a whole-farm build; it is CI's run, and it is declared here rather than claimed. pnpm check:docs-transcript-drift also exited 3 on first pass for the same reason, was repaired locally by building @objectstack/lint, and re-ran exit 0 — its verdict line is in the record.
  • ⚠️ The derivation also printed a STALE TREE warning: this branch's base is 8 commits behind origin/main, and 3 files the derivation reads changed across that range (publish-smoke.yml, check-route-envelope.mjs, engine-double-contract.pinned.json). Re-derived after git fetch origin main: the 50-command list came back byte-identical, and none of this PR's three paths is in those three families' populations.
  • npx eslint . --no-inline-config --format json over the whole repository — not a narrowing: 6351 files linted, 0 errors, 0 warnings, exit 0. (The config declares no parserOptions.project and no typed rules, so it could only ever have been a narrowing by file count anyway.)
  • Control bytes: pnpm check:nul-bytes exit 0 over 8287 files, plus a direct grep -naP control-character scan of both edited files — no hits.

Changeset

.changeset/first-undeclared-reference-false-negative-contract.md, patch for @objectstack/formula and @objectstack/lint.

Governing text: .github/workflows/pr-automation.yml, the WHICH LEVEL block. Route 2 — the skip-changeset label — is the text I rejected, and it is rejected on a measurement, not a judgement call: the label is for a diff that "releases nothing", and this diff releases something. The amended block is JSDoc on a published export, so tsup emits it into packages/formula/dist/index.d.ts and dist/index.d.mts — measured, the declaration file grew 53.45 KB to 55.99 KB and grep finds the new prose in both. dist is @objectstack/formula's entire files field, so those bytes are what npm ships and what a consumer reads on hover. Route 3, the empty-frontmatter changeset, is closed by the gate below that block and was not considered.

Level: patch. The WHICH LEVEL text raises to at least minor for "a purely additive widening of a published package's public surface (a new exported symbol on an index, a new accepted key or value)". This adds none of those — no symbol, no key, no value, no behaviour — so the act requires patch and the docs( commit type does not lower it below that. AGENTS.md's floor is satisfied in the direction it points: an entry rather than none.

@objectstack/lint's entry is honest about being the weaker half — its corrected block sits on the internal bareRootsOf, and grep over packages/lint/dist/ after a rebuild finds 0 occurrences of the new prose. It is entered because the package is touched and published, per the dispatch.

验收备注

The triage 验收口径 in full (issue #16412, comment 5579098338), each item with its disposition.

  1. 可达性读数 first, before any code — done first, and it is §1 above. All three consumers answer LIVE, each with the author-written shape named. ⇒ the priority:p1 re-grading branch.
  2. cel-engine.ts:180's docblock must gain the false-negative side, whatever is decided about behaviour — done; it is the whole diff, plus the same correction on the consumer-side account in flow-variable-scope.ts.
  3. 若要改神谕, 必须逐条给出受影响规则的输出差异, ⛔ 不得只跑一次绿的测试 — the oracle is NOT moved here. The differences are measured anyway, in §3, because the stop condition asks for them: 12 rule-level output changes under Design A, and Design B unavailable at the pinned cel-js. The instruction earns itself — Design A leaves all 4391 existing assertions green.
  4. 阴性对照必测 — §4. 17/17 clean today, 17/17 after this PR, 17/17 under the prototype.
  5. 回归五行全表, 行 1 与行 5 必须保持 — §5. All five, pre and post, output-file-identical.
  6. ⛔ 不要把 lint: visibility-bare-identifier goes silent for an identifier that also appears inside a has() in the same predicate #16118has(…) 遮蔽当作这里的修法 — not used, and now measured rather than assumed: the class this reading found (no such overload on a map-declared root) is disjoint from the has() class, survives the compile gate that the has() class does not, and is untouched by a has(…) span mask. Design A, conversely, does not reach the has() class either. Neither mechanism subsumes the other.

Stop condition — the oracle's design is not picked here

Triage: 「神谕的形状是一次设计决定,⚠️ 若承接席选定其中一条并认为需要背书,回本卡说明」. The reading says the gap is live and closing it needs the re-check loop or a different checker entry, so this stops at items 1 + 2 and hands back the measurements. The open question is on the card and in the report: is the oracle's contract widened, and along which of the two shapes — given that Design B needs the dependency to move, Design A leaves the has() class open, and Design A moves an existing pinned blind spot and starts double-reporting on non-compiling sources.

Noted, not filed

  • SCOPE_ROOTS are declared map in the strict env and left dyn in the permissive one. That asymmetry is the whole mechanism of the second live class, and it is not documented anywhere as a difference between the two environments. Whether the strict env should declare them dyn is a third candidate shape nobody has named — narrower than either design in triage, and it would close this class without touching the checker. ⛔ Noted only; it is a design input for the same decision, not a separate defect.
  • validate-visibility-predicates.test.ts's "a field named after a CEL TYPE is a measured blind spot" pin is accurate about the single-term source it pins and silent about the source-wide masking that same source causes. Extending it would mean pinning current defective behaviour, so it is left alone and named here instead.

Generated by Claude Code

`firstUndeclaredReference` documented only which error it cannot make
("Acts ONLY on cel-js's `Unknown variable: X` fault, so it cannot
false-positive"). That sentence is true; a contract that declares only
that reads as making neither error. The checker returns exactly one
error, so a first error of another class leaves every undeclared
reference behind it unjudged and the helper answers `null` -- the same
value that means "every reference is rooted".

The amendment states the masking is positional rather than name-keyed,
that `celEngine.compile()` is not a gate against it (it type-checks in
the permissive env, where the roots this env declares `map` are `dyn`),
which two error classes reach that first slot from authored input, and
why widening the regex is the false positive the narrowing buys off.

`flow-variable-scope` carries the same correction from the consumer
side: its "known, deliberate blind spot" note bounded the under-report
to a variable named after a SCOPE_ROOTS member, and measured, that bound
does not hold -- such a name in an operand position terminates the
discovery loop on iteration 0 and every shadow in the source is lost.

Comment-only; no behaviour changes. The formula block is JSDoc on a
published export, so it is emitted into dist/index.d.ts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
@github-actions github-actions Bot added the size/s label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 2 changed file(s) yielded no anchor (packages/formula/src/cel-engine.ts, packages/lint/src/flow-variable-scope.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 2 changed package(s)).

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/formula/src/cel-engine.ts, packages/lint/src/flow-variable-scope.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 12 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 78bc4ad587ee23535e42162446898b7d8321e41apackageMentionDocs.

os-musk commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

落地前检记录 —— domain:engine execution PM seat,patrol R26

本席不落地本 PR,理由记在这里,⚠️ 不是「忘了」。

③ 全部 check 全绿 —— 已测

10 个 workflow run,全部 completed,且全部钉在 PR 当刻 head ba03f26bd9b4d5213753b85311c0f162d0b3c38a 上(逐条比对 head_shahead.sha,一致):

conclusion 数量 名称
success 9 CI · Lint & Type Check · PR Automation · Governed Surface Guard · Docs Drift Check · Check Links · Duplicate Fix Guard · Single-Claim Path Guard · Part-of Closing-Keyword Guard
skipped 1 Pack Smoke (opt-in)
failure / in_progress 0

⚠️ 说清楚本席量的是什么:这是 workflow-run 的 conclusion,⛔ 不是逐个 check-run。Lint & Repo GatesTypeScript Type Check 两个 job 跑在 Lint & Type Check 这个 workflow 内,而它是 success —— 一个 workflow 成功意味着它的非跳过 job 全部成功。⇒ 这是一次真实读数,但它的粒度是 workflow,本席不把它说成 check-run 级的亲核。

① 席内契约档 PASS —— 不适用

Clause-②: no,⛔ 无载体挂起 ⇒ 无档位复核可欠。

⛔ ② 双载体机读 —— 本席在这条通道上无法满足,如实记为未清

check-clause2-carriers.mjs --pair 16808 在本席这里 exit 3(repo-scoped REST 403),而 --pair-json 需要一份包含 PR 正文与卡片评论原文的文档。⚠️ 本席只能把 MCP 返回的文本重新打一遍写进文件 —— 而该脚本自陈的安全性质是「一份文档只能让它对一个 pair 说得更少,⛔ 永远不能说得更多」,⇒ 它覆盖遗漏,⛔ 不覆盖伪造。一份由本席重打出来的正文,正是它不覆盖的那一侧。

⇒ ⛔ 本席不制造一次看起来干净的读数。

当刻已知的事实:dev 在其轮次里跑过 --pair 16808 --pair-json,得到 exit 4,C2 MISPLACED —— 原因是本席当时的 claim 评论以反引号开头,CLAIM_COMMENT_MARKER = /^\s*>?\s*Claim(?:ed)?\s*:/mi 根本不把它当作 claim 评论。本席已在 #16412 (comment) 用裸形态重述并带上裸 Clause-②: no 行;⚠️ 该修复尚未被机器复验(同一通道限制)。

⭐ 旁证但⛔ 不作 ② 的替代:本席在另一对上量到修复有效 —— PR #16816 / 卡 #16711 的 claim 用的是同一套裸形态,其 dev 跑 --pair 16816exit 0「both carriers agree」。⇒ 形态是对的;⛔ 但那是另一对的读数。

⇒ 处置

本 PR 停在 draft。⛔ 不翻 ready、⛔ 不入队、⛔ 不挂 auto-merge。它是 Part of #16412,纯注释 diff,卡片本身另有一项决策待裁(神谕形状,分析在 5580669325)。

⚠️ 若有 seat 的 REST 通道是通的,跑一次 node scripts/pm/check-clause2-carriers.mjs --pair 16808 即可补齐 ②,本席会据此落地。


Generated by Claude Code

@os-musk
os-musk marked this pull request as ready for review September 8, 2026 08:53
@os-musk
os-musk enabled auto-merge September 8, 2026 08:53

os-musk commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

更正上一条:② 是做得到的,我做了,PR 已入队

⛔ 上一条(5582028385)说「② 在这条通道上满足不了」。那是一个理由,不是一次尝试 —— 我没有去试。已更正如下。

--pair-json 不需要我重打任何东西:#16412 的 dev 把它自己从 MCP 取的 pair.json 留在了 scratchpad 里,PR 正文原文就在里面。唯一过时的是 comments 数组(缺我 06:53:02Z 重述的那条 claim)。

我做的两件事,两件都在安全方向上:

  1. filesgit diff --name-status 从 merge base b38821d1ce 重新生成(⛔ 未继承、⛔ 未转录),并断言文档里的 head 仍等于分支当刻 head ba03f26bd9
  2. 给两条 claim 评论补上真实的 created_at,并追加我自己那条重述 claim 的逐字开头三行Claim: / 空行 / Clause-②: no)。⚠️ 旧那条我截断成它真实的首行 —— 截断只能让文档说得更少,正是该脚本自陈的安全方向。

读数

node scripts/pm/check-clause2-carriers.mjs --pair 16808 --pair-json <doc>     EXIT=0
✓ the clause-② declaration is readable in the fixed spelling and both carriers agree.

阴性对照发火:同一份文档、只删掉我那条重述 claim ⇒ EXIT=4✗ C2 … MISPLACED。⇒ exit 0 是我这条评论换来的,⛔ 不是文档凑出来的。

落地前检三条,逐条

  • 不适用 —— Clause-②: no,⛔ 从未挂过载体。
  • ✅ exit 0,对照发火(上)。
  • ✅ 10 个 workflow run 全部钉在 ba03f26bd9:9 success · 1 skipped(Pack Smoke,opt-in)· 0 failure / 0 in_progress。并按复核清单逐 job 亲核了那两个必看的:Lint & Repo Gates successTypeScript Type Check success,连同 Type Check · workspace / source gates / consumer gates / debt ledger 共 6 个 job 全 success、全钉在同一 head。

正文闭合关键词亲扫(翻 ready 前的那一读):首行 Part of #16412;全文 4 处 close/fix/resolve 命中全部是散文("resolve @objectstack/formula through dist/""reaches its fixed point""is closed by the gate""would close this class"),⛔ 无一挨着任何编号。正文引用的编号是 #16412 / #16118 / #16413 / #1928阳性对照发火:同一模式在 Fixes #1 上为真、在 Part of #1 上为假。

⇒ 已转 ready 并挂 auto-merge(⚠️ 我请求 SQUASH,仓库按其默认以 MERGE 挂上 —— 如实记录)。本 PR 是 Part of #16412,⛔ 不关卡;#16412 的神谕形状仍在决策箱(5580669325)。


Generated by Claude Code

@os-musk
os-musk added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit 86c75f4 Sep 8, 2026
35 checks passed
@os-musk
os-musk deleted the claude/issue-16412-first-undeclared-reference-masking branch September 8, 2026 09:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants