Skip to content

packages/lint CHANGELOG:2176 claims FIELD_RULE_AMBIENT_ROOTS / FIELD_RULE_JUDGED_ROOTS are exported — src/index.ts exports neither (control: FIELD_RULE_BOUND_ROOTS is) #18169

Closed

Description

@claude

Surfaced by #17330's dev as an out_of_scope_findings entry and independently re-verified by the domain:devx execution seat before filing. ⛔ Unlabelled for domain:* and ungraded — routing and grading are triage's. ⛔ The seat did not dedupe; dedupe words at the bottom.

Class (a) — a published claim that fails when acted on

packages/lint/CHANGELOG.md:2176 — text that ships inside the npm tarball, and the text an upgrading agent greps — states:

FIELD_RULE_AMBIENT_ROOTS and FIELD_RULE_JUDGED_ROOTS are exported beside
the existing FIELD_RULE_BOUND_ROOTS

Measured against the source of truth

packages/lint/src/index.ts:53 on origin/main, the package's only export site for this family:

export { validateStackExpressions, fieldRuleRootIssue, FIELD_RULE_BOUND_ROOTS } from './validate-expressions.js';
name occurrences in src/index.ts
FIELD_RULE_BOUND_ROOTS (lit control) 1 — and it is in the export statement
FIELD_RULE_AMBIENT_ROOTS 0
FIELD_RULE_JUDGED_ROOTS 0

src/index.ts carries no star-export, so there is no second path by which either name could reach a consumer.

⇒ A consumer who writes import { FIELD_RULE_AMBIENT_ROOTS } from '@objectstack/lint' on the strength of that CHANGELOG sentence gets a resolution failure. The sentence is not merely stale; it is actionable and it fails when acted on.

⚠️ #17330's dev additionally reports the same result read off a freshly built packages/lint/dist/index.d.ts (0 occurrences, same lit control 3). This seat verified the source leg only; the dist leg is the dev's reading, recorded as such.

The remedy is constrained by AGENTS.md

Per AGENTS.md, a false CHANGELOG entry is amended in a dedicated docs-only PR — ⛔ never by an erratum in a later entry, and ⛔ never folded into a code PR. That is why this is its own card rather than a rider on #17330.

⛔ Deliberately not decided here: whether the right repair is to correct the sentence or to actually export the two names. They are different products — one fixes a false record, the other widens the published surface (and would then owe a Clause-② judgement). Whoever grades this should say which, rather than letting the implementer pick.

Acceptance

  1. The entry states only what src/index.ts actually exports, verified at the export statement, with a lit control beside the zero.
  2. Docs-only PR; ⛔ no source change riding along.
  3. If instead the decision is to export the names, that is a different card — ⛔ do not do both in one PR.

Dedupe words

FIELD_RULE_AMBIENT_ROOTS · FIELD_RULE_JUDGED_ROOTS · lint CHANGELOG export claim · #13935 changeset · published surface index.d.ts


Generated by Claude Code

Activity

  1. os-try-charles commented on Sep 16, 2026

    @os-try-charles
    Collaborator

    Claim: PM loop round 7
    Session: session_017ef78bLdybu3AffehKkhfk
    Branch: claude/issue-18169-lint-changelog-export-claim
    Worktree: objectstack-issue-18169
    Domain: domain:devx
    File surface: packages/lint/CHANGELOG.md — that file and nothing else (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus — this call's --tier on packages/lint/CHANGELOG.md printed "no path-derived mandate: the surface hits none of the 3 declared glob(s), derived here, not recalled", so the tier is the PM's judgment call and stays at the default.
    Clause-②: no
    Thread-read: 5690478023
    Serial constraints cleared: none on this file — board read at 2026-09-16T12:18Z, immediately before this claim; all 15 open PRs had their file lists read, two touch packages/lint/ at all and neither touches CHANGELOG.md: #18420 (hotlong) → src/validate-flow-trigger-readiness.ts, and #18319 (os-warren) → four *.test.ts files. ⚠️ Named because it is adjacent, ⛔ not a constraint: #18319 touches validate-expressions.test.ts, the test file of the module that owns these three symbols — ⛔ this card does not pin its behaviour and must not edit it.

    Premise re-verified by this seat, ⛔ not inherited from the card

    Probed at 2026-09-16T12:18Z in a detached worktree at origin/main, tree e4449b4ef:

    packages/lint/src/index.ts:53 — the package's only export site for this family:

    export { validateStackExpressions, fieldRuleRootIssue, FIELD_RULE_BOUND_ROOTS } from './validate-expressions.js';
    
    name occurrences in src/index.ts
    FIELD_RULE_BOUND_ROOTS (lit control) 1, and it is inside the export statement
    FIELD_RULE_AMBIENT_ROOTS 0
    FIELD_RULE_JUDGED_ROOTS 0
    export * (a second path to a consumer) 0
    reading taken 2026-09-16T12:18Z, tree e4449b4ef

    The CHANGELOG sentence is present verbatim at the cited place. ⇒ The card holds exactly as filed.

    ⭐ And one reading the card did not take, which decides this PR's changeset question: packages/lint's files[] is ['dist', 'README.md', 'CHANGELOG.md'] — the package is not private and CHANGELOG.md ships inside the tarball. ⇒ ⚠️ editing it does move published content, so the usual docs-only reflex («skip-changeset, nothing published moves») is ⛔ not available here and the dev must decide it on a measurement rather than a habit.

    ⛔ Only ONE of the card's two products is being dispatched

    The card refuses to choose, in terms: 「⛔ Deliberately not decided here: whether the right repair is to correct the sentence or to actually export the two names … Whoever grades this should say which」. Triage (5690478023) routed and graded it but ⛔ did not answer that question. This seat answers it, and answers it narrowly:

    • ✅ Dispatched — correct the false record. It restores truth to published text, needs no floor, and is the card's own acceptance ① verbatim.
    • ⛔ NOT dispatched — export the two names. That widens the published surface, which is the manual floor: it would carry Clause-②: yes, take at least a minor, and is the maintainer's call, ⛔ not this seat's and ⛔ not the implementer's. If anyone wants it, it is a separate card the maintainer authorizes.

    ⇒ ⛔ The dev does not get to pick between them, and ⛔ must not do both in one PR — the card says so as acceptance ③.

    Fences carried into the dispatch

    PM seat domain:devx · session session_017ef78bLdybu3AffehKkhfk · round 7 · 2026-09-16T12:20Z


    Generated by Claude Code

  2. os-try-charles commented on Sep 16, 2026

    @os-try-charles
    Collaborator

    os-dev-report

    {
      "issue": 18169,
      "status": "done",
      "branch": "claude/issue-18169-lint-changelog-export-claim",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18462",
      "premise_still_valid": true,
      "summary": "Corrected the false record only; the two names were NOT exported (manual floor, maintainer's call, and the card's acceptance 3 forbids both in one PR). packages/lint/CHANGELOG.md:2176 (the 17.3.0 entry compiled from changeset 1af8286, card #13935) claimed FIELD_RULE_AMBIENT_ROOTS and FIELD_RULE_JUDGED_ROOTS 'are exported beside the existing FIELD_RULE_BOUND_ROOTS'; the paragraph now states only what src/index.ts actually exports and keeps both symbol names so the reader's grep still lands on the amended entry. A patch changeset is added, argued from files[] rather than from the docs-only reflex. Card premise re-derived by this seat and it holds, with two readings the card did not have: (a) the SECOND published subpath ./runtime carries 0 FIELD_RULE occurrences and 0 star-exports, so the entry surface is fully covered, not just '.'; (b) on the PUBLISHED @objectstack/lint@17.4.0 tarball the false sentence is present verbatim at CHANGELOG.md:2176, i.e. the defect is shipped, not merely in-tree. One correction to a number recorded in the card: #17330's dev reported FIELD_RULE_JUDGED_ROOTS at 0 occurrences in dist/index.d.ts. Measured here (both the freshly built d.ts and the published 17.4.0 one) it is 2 - both inside {@link} docblocks, NOT in the export clause. The verdict is unchanged, but a raw occurrence count on a bundled .d.ts is the wrong probe; export-clause membership is the right one, and that is what this PR's tables read. assignee was os-try-charles as dispatched; not touched. No label was applied: Clause-2 is no, the surface is not governed (governedPathsIn(diff) = [] against GOVERNED_SURFACES), and skip-changeset is refuted by measurement, so nothing was owed.",
      "tests": "No test or source file is touched by this diff, so no package test or typecheck is owed; what is owed is the gate set. GATE DERIVATION (re-derived AFTER the changeset existed, because the first derivation warned '13 families apply once this card's changeset exists'): `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` from the worktree, exit 0, --repo assertion held, change set read as 2 paths vs merge base e4449b4ef => 52 commands. ALL 52 RUN. FINAL: 50 exit 0, 2 exit 3. Pass 1 (no node_modules): 31 exit 0, 21 exit 3 (PREREQUISITE NOT MET, e.g. 'ts-parse: PREREQUISITE NOT MET - the dependency `typescript` is not installed'). Then `bash scripts/pm/os-verify-lock.sh -c 'pnpm install'` with OS_VERIFY_LOCK_SLOT=issue-18169-dev => VERDICT command-exit 0 - held the lock 8s - waited 0s. Pass 2 (21 re-run): 16 exit 0, 5 still exit 3, all naming a BUILT dist. Then `os-verify-lock.sh -c 'pnpm --workspace-concurrency=2 --filter @objectstack/lint... build'` => VERDICT command-exit 0 - held the lock 199s (3m19s) - waited 0s; 'check-dts-emitted: @objectstack/lint - 4/4 declared declaration file(s) present.' Pass 3 (5 re-run): check:docs-transcript-drift 0, check:dts-closure 0, check:sourcemap-no-sources-content 0. NOT MEASURED (exit 3, neither pass nor finding): check:dual-build-cjs-loads ('PREREQUISITE NOT MET - this gate reads built output, and some package has no dist/'), check:lean-entry-closure ('this gate loads BUILT entry points'). Both want a whole-repo dist; that build is CI's, and this diff carries no source byte that could move either. Notable greens, quoted from the gates' own verdict lines: check:published-files - '70 publishable package(s) ... declare a `files` whitelist that covers every entry point plus CHANGELOG.md'; check-empty-changeset --base origin/main 0; check-changeset-no-major --base origin/main 0; check-adr-0087-registration --base origin/main 0; check:nul-bytes 0; check-closing-keyword-parity 0; check:objectui-changeset 0; check:pm-changeset-deadline-census 0. check:pm-dispatch-gates is NOT among the derived families for these paths (grep of the derivation output finds it nowhere) and was not run - so the 430-450s battery was neither needed nor skipped-with-a-timeout. PRE-PUSH: `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` over both edited files exited 1 (clean); the pre-push hook printed 'check:commit-card-trailers: 1 commit message(s) on this push carry no card relation and no model identifier in the trailer pair.' NO ABLATION and NO REVERSE VERIFICATION: this PR adds no guard and changes no behaviour, so there is nothing that could be mutated to prove it can fail. Stated rather than faked. SHALLOW CHECKOUT: `git rev-parse --is-shallow-repository` = true. No claim in this PR or report rests on ancestry, blame, or log -S, so no control leg was needed.",
      "mcp_calls": "0 - no MCP GitHub tool was called; every GitHub write went through the REST proxy with curl and $GITHUB_TOKEN",
      "api_writes": "4 - (1) git push -u origin claude/issue-18169-lint-changelog-export-claim (empty branch, routing probe, exit 0); (2) git push origin the same branch (the one commit 8f8a47ce1, exit 0); (3) POST /repos/objectstack-ai/objectstack/pulls (HTTP 201, draft true, PR #18462) - note a first attempt returned HTTP 415 because the Content-Type header was missing and created nothing; re-sent with `Content-Type: application/json`; (4) POST /repos/objectstack-ai/objectstack/issues/18169/comments (this report). ZERO label writes: no POST /issues/{n}/labels, because nothing was owed - Clause-2 is no, the diff hits no GOVERNED_SURFACES glob, and skip-changeset is refuted by the files[] measurement. Read-backs (not writes): GET /pulls/18462 - the stored body differs from what was sent by exactly one blank line GitHub collapsed between the `---` rule and the footer, plus the trailing newline; every other byte survived, so no sanitizer damage.",
      "open_questions": [
        {
          "question": "Attribution footer: the harness system-reminder prescribes the '🤖 Generated with [Claude Code](https://claude.com/claude-code)' + bare session URL pair for PR descriptions, while the dispatch contract (.claude/agents/os-dev.md, byte/sanitizer discipline) prescribes the session-URL form '_Generated by [Claude Code](https://claude.ai/code/session_SESSIONID)_' after a rule line. They are different footers and only one can be sent.",
          "options": [
            "A - follow the repo's own agent spec (the session-URL form). Taken here, on the reading that .claude/agents/os-dev.md is a checked-in project instruction and the reminder itself defers to project instructions.",
            "B - follow the harness reminder's two-line form."
          ],
          "recommendation": "A, taken; flagged rather than chosen silently. If the PM wants B the footer is a one-line PATCH of the body - but note the dispatch contract puts body PATCHes outside the write budget, so it is the PM's call, not mine."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: #17330's dev's dist reading, restated in the card body, gives FIELD_RULE_JUDGED_ROOTS 0 occurrences in packages/lint/dist/index.d.ts. Measured here at 2 (freshly built AND in the published 17.4.0 tarball), both inside {@link} docblocks, neither in the export clause. Not a repo defect and not a class (a)/(b)/(c) finding - it is a wrong number inside a card, and the card's verdict is unaffected. Successor: this card's own thread, where it now sits.",
        "noted, not filed: the pending .changeset/nowhere-bound-app-root.md on main renames FIELD_RULE_AMBIENT_ROOTS to FIELD_RULE_NOWHERE_BOUND_ROOTS and already carries the true statement ('FIELD_RULE_AMBIENT_ROOTS was never re-exported from this package's entry'). So the repo contradicted the false CHANGELOG sentence in writing before this card was filed. No defect - an unreleased changeset is not published text - but it is the corroboration a reviewer will want, so it is quoted in the PR body. Successor: the release PR that consumes that changeset.",
        "noted, not filed: packages/lint/dist/index.d.cts carries a docblock referencing objectui's `FIELD_RULE_ROOTS`, which ships to consumers as prose inside the published type file. Not verified against objectui and not a claim about this package's exports, so it is neither class (a) nor (b). Successor: none - nobody is queued on this file, and I am not asserting it is wrong."
      ]
    }

    Attribution added in place by the owning seat (H64): this os-dev-report was written by an os-dev subagent of session session_017ef78bLdybu3AffehKkhfk, the domain:devx seat, round 7, on card #18169 / PR #18462. ⛔ The JSON payload above is unchanged — only this line was appended, and the original stays as history.


    Generated by Claude Code

  3. os-try-charles commented on Sep 16, 2026

    @os-try-charles
    Collaborator

    The one open question is discharged — ⛔ it was never a maintainer question

    The patrol's H52 row fires here because the newest os-dev-report (comment 5697631415) carries one non-empty open_questions entry while this card carries no inbox label.

    The question: the harness reminder prescribes one PR-description attribution footer while the repo's own agent spec prescribes another; only one can be sent, and the dev took the repo's (option A).

    ⛔ Not a maintainer question, and not a decision this card was dispatched on — it is a conflict between two instruction sources, and the repo's own checked-in spec settles it. Ruled, after reading the literal rather than recalling it:

    • .claude/agents/os-dev.md:398 — _Generated by [Claude Code](https://claude.ai/code/session_<id>)_ ← session-URL:创建 PR 正文用
    • .claude/agents/os-dev.md:393 binds that form to AGENTS.md's GitHub mutates body BYTES clause.

    ⇒ It is a byte-discipline rule inside a checked-in project instruction, ⛔ not a preference, and it governs anything pushed to this repository. The dev's option A stands; ⛔ no body PATCH is owed and none was made. ⭐ Flagging it instead of choosing silently was the right call — it is the same shape as this shift's commit-trailer conflict, where the PM's own dispatch had transcribed a literal the repo's gate refuses.

    ⚠️ The dev also recorded a second thing outside open_questions, and it is worth keeping where a reader will find it: it measured FIELD_RULE_JUDGED_ROOTS at 2 occurrences in dist/index.d.ts (both inside {@link} docblocks, ⛔ neither in the export clause), where this card's body restates #17330's dev's reading of 0. ⛔ This seat verified the source leg only and did not build or fetch the tarball, so the 2 is the dev's reading, recorded as theirs. ⭐ The lesson survives whoever is right: a raw occurrence count on a bundled .d.ts is the wrong probe; export-clause membership is the right one.

    ⇒ Nothing on this card is waiting on a human. Recorded here because there will be no further os-dev-report whose empty open_questions could stand the row down.

    PM seat domain:devx · session session_017ef78bLdybu3AffehKkhfk · round 8 · discharging H52 from the 13:13Z patrol · 2026-09-16T13:20Z


    Generated by Claude Code

  4. os-try-charles commented on Sep 16, 2026

    @os-try-charles
    Collaborator

    落地 —— PR #18462 已合并,两读数确认

    读数一 队列分支   队列现在是空的,本 PR 的分支已消失
    读数二 内容探针   origin/main tip 8fe5cb8e5(重新 fetch 后取),落地前 e915c190e
                      packages/lint/CHANGELOG.md                      前   后
                      "are exported beside"(那句假话)                 1 →  0
                      "Correction (2026-09-16, docs-only, #18169)"      0 →  1
                      "FIELD_RULE_NOWHERE_BOUND_ROOTS"                 0 →  1
                      "FIELD_RULE_BOUND_ROOTS"                         2 →  4
                      暗对照 zzq_nonexistent                            0 →  0
                      文件 18590 → 18602 行
    squash            8fe5cb8e51  2026-09-16T13:01Z  父数 1
    

    ⭐ 发火对照选得比本班前几次好:"are exported beside" 1 → 0 就是那句假话本身,它的消失既是发火对照也是判据 —— ⛔ 不是一个被 diff 顺带移动的主题词。

    .changeset/lint-changelog-export-claim.md 在树上(尚未被发布消费),与 changeset 自己的说法一致:已发布的 tarball 不可变,这条修订要到该包下一次发布才成为已发布文本。

    验收三条,逐条对照卡自己写下的条件

    卡的验收条件 结果
    ① 条目只陈述 src/index.ts 实际导出的东西,在 export 语句处核验,零的旁边带字面对照 ✅ index.ts:53 只导出 FIELD_RULE_BOUND_ROOTS(字面对照 1,就在 export 语句里),另两个 0,export * 0
    ② 纯文档 PR,⛔ 无源码改动搭车 ✅ diff = CHANGELOG.md + 一个 .changeset/ 条目,+25 −2,零源码字节
    ③ 若改为导出那两个名字,那是另一张卡,⛔ 不在同一个 PR 里两件都做 ✅ 两个名字没有被导出

    ⇒ 三条全满足 ⇒ 关卡。

    ⛔ 没做的那一半,以及它为什么不留在这张卡上

    卡拒绝在「改正那句话」与「真的导出那两个名字」之间选,要求定级者来定;分诊路由定级了但没答。本席答了,并且划得很窄:导出那两个名字扩大公开面 ⇒ 人工地板,会带 Clause-②: yes、至少 minor,是维护者的决定。而卡自己的验收 ③ 已经写明那是另一张卡 ⇒ ⛔ 不在本卡挂待决标签,⛔ 也不新立一张"要不要导出"的卡去替维护者提问题 —— 没有人提出过要那两个符号。要就另立,并由维护者授权。

    ⚠️ 一条要带出本卡的更正(⛔ 记为 dev 的读数,不是本席的)

    本卡正文转述 #17330 的 dev 的测量:FIELD_RULE_JUDGED_ROOTS 在 packages/lint/dist/index.d.ts 里 0 次。本卡的 dev 测得 2 次 —— 新构建的 d.ts 与已发布的 @objectstack/lint@17.4.0 tarball 都是 —— 两处都在 {@link} 文档块里,⛔ 都不在 export 子句里。结论不受影响。

    ⛔ 本席只验了源码那条腿,没构建、没拉 tarball ⇒ 那个 2 是 dev 的读数。#17330 已 closed / completed,那个数字不在任何活着的地方 ⇒ ⛔ 不另立承接卡。

    ⭐ 但教训无论谁对都成立,写在这里而不是埋在线程中段:在打包后的 .d.ts 上数出现次数是错的探针;该读的是 export 子句成员资格。 本 PR 的表读的正是后者。

    同笔清半态:pm:dispatched 摘除 · assignee 清空。

    PM seat domain:devx · session session_017ef78bLdybu3AffehKkhfk · round 8 · 2026-09-16T13:21Z


    Generated by Claude Code

  5. removed their assignment
    on Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions