Skip to content

check-type-source-resolution recommends paths, but paths is unavailable when the consuming program's rootDir excludes the dep — the message names no remaining self-serve option #16000

Closed

Description

@claude

Filed by the PM dispatch loop from a measured finding by the #14646 round (PR #15978). ⛔ Unassigned and ungraded — domain:*, type and priority are triage's.

⚠️ This card is about the gate's GUIDANCE, not its verdict. The round that hit this judged the underlying design working-as-designed and I agree: the ratchets steered it to the correct outcome (don't take the dependency). What cost a full cycle was that the remedy the gate's own message names leads to a second, unmentioned wall.

What happened

check-type-source-resolution refused a new dist-resolved type import: @objectstack/runtime → @objectstack/service-realtime (via tsconfig.test.json). Its message offers two remedies and refuses a third:

  • Add the paths rules to the package's tsconfig.json — the default remedy it names first.
  • Re-baseline, if the (via …) names a program this change ONBOARDED (settled by the registry doc-block).
  • ⛔ Widening the registry entry — refused, the registry is shrink-only.

The round measured its case correctly as the already-counted-program case (packages/runtime's typecheck script names tsconfig.test.json byte-identically before and after the diff), so the re-baseline limb was closed and widening was refused. That left paths — the remedy the message names.

paths then fails, for a reason the message does not mention. Measured: it cleared check-type-source-resolution (exit 0, registry unwidened) and then made pnpm --filter @objectstack/runtime typecheck exit 1 with 13 TS6059, billing @objectstack/service-realtime's file graph into packages/runtime's test-typecheck ledger. All 13 were TS6059; none was a code error. That is precisely the PR #12570 shape the message warns about — but it warns about it only under the re-baseline limb, not under the paths remedy it recommends.

The mechanism: paths pulls another package's source into a program whose rootDir is ./src, which tsconfig.test.json's own header states it will not widen. And that ledger's only other remedy is maintainer-only.

⇒ For a package whose test layer is already onboarded and re-baselined, a NEW cross-package source dep in its test program has no self-serve remedy: the registry refuses the widening, paths bills TS6059 into a ledger the other package cannot see, and both the ledger expansion and the rootDir widening are closed to a dev seat.

What this card asks for

Not a design change. Make the closed door visible at the point of refusal, so the next seat does not spend a cycle discovering it:

  • check-type-source-resolution's message should say that paths is not available when the consuming program's rootDir excludes the dependency's source, and name the TS6059-into-the-ledger outcome — the same warning it already gives under the re-baseline limb.
  • State the remaining option plainly: don't take the dependency (reach the subject through in-package source, as the round did), or escalate, because ledger expansion and rootDir widening are maintainer-only.

⚠️ Verify the claim above before writing it into a message: it is one round's measurement on one package. ⛔ A message that overstates the closure would be worse than the current silence. Reproduce the paths → TS6059 leg first.

Prior art / refs

#15978 (the PR that hit it, and its round's measurement) · #12570 (the billing shape the message already cites) · #14646 (the card) · the tsconfig.test.json header stating it will not widen rootDir · the shrink-only registry doc-block


Generated by Claude Code

Activity

  1. os-zhuang commented on Sep 6, 2026

    @os-zhuang
    Contributor

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

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

    ⭐ 我在闸门自己的文件头里找到了它「已经知道」这件事的证据

    卡说 paths 会撞第二堵墙、而消息不提。实测 scripts/check-type-source-resolution.mjs 的头注:

    :27   //   without a `paths` block   `tsc --noEmit` -> exit 0, CLEAN
    :28   //   with a `paths` block      src/additional-domains.fixtures.ts(35,3): error
    

    ⇒ ⭐ 这个闸门的头注里已经写着「加了 paths 会报错」这个对照,而它给作者的消息却把 paths 列为首选补救。⇒ 知识在文件里,只是不在出口处。

    ⚠️ 我没有复现那 13 条 TS6059(需要真实构建 @objectstack/runtime 的 test 程序)。⇒ 卡的那一腿按其标注采信;⛔ 而卡自己也要求先重跑:「Reproduce the paths → TS6059 leg first.」

    定级 p2

    • 实际代价已发生:卡说「What cost a full cycle was that the remedy the gate's own message names leads to a second, unmentioned wall」——一整个周期,而且是在一条被闸门主动推荐的路上;
    • ⭐ 更贵的是那条路的终点:卡测得对一个「test 层已 onboard 且已 re-baseline」的包,新的跨包源码依赖在它的 test 程序里没有任何自助补救——registry 拒绝加宽(shrink-only)、paths 把 TS6059 计进另一个包看不见的账本、而账本扩张与 rootDir 加宽都是 maintainer-only。

    ⇒ 一个把作者引向死路、且死路尽头是「只能找维护者」的消息。p2。

    不给 p1:⛔ 闸门的判决是对的——卡自己也说「the ratchets steered it to the correct outcome (don't take the dependency)」,我同意。坏的是指路,不是判决。

    车道 domain:devx / 定型 documentation + tooling

    落点 scripts/check-type-source-resolution.mjs 的消息文本 ⇒ 车道表 scripts/ gates 在 devx 行。改的是文案但改在 gate 里 ⇒ 两个标签都挂。

    交付物:只改消息,⛔ 不改设计

    卡自己划得很清楚,我加重:

    Not a design change. Make the closed door visible at the point of refusal, so the next seat does not spend a cycle discovering it。

    两句话:

    1. paths 在消费程序的 rootDir 排除依赖源码时不可用,并点名 TS6059-计进账本 这个后果 —— ⭐ 这句话闸门已经在 re-baseline 那一支下说过了,只是没在 paths 这一支下说;
    2. 剩下的选项直说:不要取这个依赖(照那一轮的做法,从包内源码触达目标),或者上报,因为账本扩张与 rootDir 加宽是 maintainer-only。

    ⚠️ 一条硬前置,卡写了,我列为必做

    ⚠️ Verify the claim above before writing it into a message: it is one round's measurement on one package. ⛔ A message that overstates the closure would be worse than the current silence.

    ⇒ ⭐ 这条自律是对的:一条夸大了封闭程度的消息,会让下一个作者在本来有路的情况下放弃。⛔ 先重跑 paths → TS6059 那一腿,再落文案。


    Generated by Claude Code

  2. claude commented on Sep 6, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01Vbw3RPgdtqesx4azk9SbW8
    Branch: claude/issue-16000-type-source-resolution-paths-guidance
    Worktree: objectstack-issue-16000
    Domain: domain:devx
    File surface: scripts/check-type-source-resolution.mjs (the refusal message text and its self-test pins only; ⛔ no change to what the gate judges) (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus — dispatch-gates.mjs --tier: no path-derived mandate; default tier because the card's hard precondition is a reproduction (the paths → TS6059 leg on packages/runtime's test program) and a message that overstates the closure is worse than the current silence
    Clause-②: no
    Serial constraints cleared: no open PR touches scripts/check-type-source-resolution.mjs (all open PRs' file lists read 07:1xZ; the only tsconfig-adjacent open PR is #16215 on packages/plugins/organizations/tsconfig.test.json, off this surface). Premise on origin/main @ 1ebde495: the gate's own header at :27-28 already records the with-paths red, and :279-281 states "paths is additionally the WRONG tool, measured on PR #12570" — the knowledge is in the file, not at the exit; the refusal text still leads with the paths remedy. This card changes the message, not the verdict.


    Generated by Claude Code

  3. claude commented on Sep 6, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 16000,
      "status": "done",
      "branch": "claude/issue-16000-type-source-resolution-paths-guidance",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16257",
      "premise_still_valid": true,
      "summary": "The card's premise held under re-measurement and the message now carries the closed door. check-type-source-resolution's NEW-dist-resolved-import refusal names `paths` first; that remedy has a precondition it never stated - the consuming program's rootDir must contain the dependency's source - and past it every remaining route is maintainer-only, so the message recommended a path and then named no remaining self-serve option. Added to the remedy paragraph, after the existing re-baseline limb: the precondition, the TS6059-into-the-test-typecheck-ledger outcome when it fails (the PR #12570 shape, reached from the `paths` limb rather than the re-baseline one), and the remaining option stated plainly - do not take the dependency (reach the subject through in-package source), or escalate. Written as a CONDITION and never as a blanket closure, per the card's own warning that overstating it would be worse than the current silence. Message only: the verdict, the shrink-only registry and every tsconfig*.json are untouched, and the three load-bearing literals are intact - the refusing PREDICATION 'widening the registry entry is not the fix', the SHRINK-ONLY testimony and the ONBOARDED exception - so check-ratchet-remedy-authority still classifies this gate as `refused`. Both new negations are bound to their verbs ('Never widen that ledger', 'never widen a rootDir'), so every offer the message makes is still refused and the gate did not become an unmarked ratchet-expanding offer. Two self-test pins added in the existing 'the registry, audited in BOTH directions' battery (the text names TS6059; the text names the maintainer-only closure) and that battery's floor raised 10 to 12 - tightened, never loosened. Dispatch's assignee note: the field was already set to baozhoutao by the PM's dispatch and I did not write it; the newest Claim: (5557630524) names this branch.",
      "tests": "REPRODUCTION (the card's hard precondition, Zone 2 A1) - PASSED, premise CONFIRMED. Dependency closure built first under the shared lock: `pnpm --filter '@objectstack/runtime^...' build` -> VERDICT command-exit 0 (so not a PREREQUISITE NOT MET reading). Then, in one trap-guarded script (trap RESTORE EXIT INT TERM, absolute paths seeded from git rev-parse --show-toplevel): LEG 0, pristine tree, tsc --noEmit -p packages/runtime/tsconfig.test.json -> exit 2, 191 errors, 0 x TS6059 (the 191 matches the count tsconfig.test.json's own header records for this program). MUTATION, exactly the gate's own recommended remedy: a `paths` pair for @objectstack/service-realtime pointing at that package's SOURCE, added to packages/runtime/tsconfig.json (the file the message names, inherited by tsconfig.test.json), plus one new cross-package `import type` in packages/runtime/src/discovery-realtime-channel.pin.test.ts - the shape #15978 backed out of. ON-DISK LANDING PROVEN before either reading (editor exit codes prove nothing): grep -c on both touched files (paths key 4 occurrences, import marker 1) and git hash-object showing both blobs moved off their HEAD hashes. LEG 1, with `paths` -> exit 2, 204 errors, 13 x TS6059. +13 exactly, ALL of them TS6059, and NOT ONE new code error: every other code's count is byte-identical across the legs (TS18048 x91, TS18046 x27, TS2339 x17, TS2493 x15, TS2345 x10, TS6133 x6, TS2322 x6, TS7006 x4, TS2554 x4, TS2353 x4 ...). All 13 name packages/services/service-realtime/src/** (index.ts, objects/index.ts, realtime-service-plugin.ts, translations/index.ts), billed into packages/runtime's test-typecheck ledger, which service-realtime cannot see. Their presence is also the positive control that the paths rule really resolved through the extends chain - tsc could not report those files at all had it not admitted them. RESTORATION PROVEN, not assumed: git checkout HEAD -- (the two paths), then both git hash-object values compared equal to the HEAD blob hashes captured before the mutation (1294309a... and 5768284f...), with a scoped `git diff HEAD -- (those two paths)` of 0 bytes. Nothing from the reproduction is committed; the PR's file list is exactly one file. The card's measured 13 x TS6059 and zero code errors is confirmed independently on this branch. GATES: all 31 derived commands exit 0 at HEAD a8cc3cb55, exit codes captured before any pipe (redirect first, then EXIT=$?), reconciled by `node scripts/pm/dispatch-gates.mjs --changed --ran ...` -> '31 derived famil(ies) accounted for - 31 run, 0 NOT-MEASURED' (exit 0). The whole derived set was re-run on the final HEAD after the last commit, so every reading cites one sha. NO ABLATION of the implementation was performed and none is claimed: this diff adds prose to a failure string and two self-test pins; the pins themselves are the falsifiability evidence - each asserts on the planted violation the battery already constructs, and each fails independently if its half of the sentence is dropped. The reproduction above is the measurement the card asked for, and it is a mutation/restore pair with both legs proven on disk.",
      "mcp_calls": "0 - every GitHub read and write went through repo-scoped REST (probed first: issue read HTTP 200, empty-branch push accepted); no MCP GitHub tool was called at any point, including the dedup read, which the PM supplied as an existing fact",
      "gates": {
        "head_sha": "a8cc3cb55",
        "derivation": "node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack at 4ebde563b (merged tree); reconciled with --ran",
        "reconciliation": "dispatch-gates --ran: 31 derived famil(ies) accounted for - 31 run, 0 NOT-MEASURED, 0 UNRUN (exit 0)",
        "all_31_exit_0": true,
        "load_bearing_verdict_lines": [
          {
            "command": "pnpm check:type-source-resolution",
            "verdict_line": "check-type-source-resolution --self-test OK / check-type-source-resolution OK - 125 tsc program(s) across 78 packages scanned (every tsconfig*.json each package's typecheck script names, #11490); 61 registered as still resolving a workspace dep's types through dist/",
            "exit": 0
          },
          {
            "command": "pnpm check:ratchet-remedy-authority",
            "verdict_line": "OK  check-ratchet-remedy-authority: 213 scripts swept; 14 mark the expanding remedy MAINTAINER-ONLY, 6 turn it down outright, 193 hand out no ratchet-expanding remedy. Control corpus: 30 hand-classified scripts, set-equality audited both ways.",
            "exit": 0,
            "note": "this file's hand-classified verdict is `refused`; it still holds, so the cross-gate contract in Zone 1 ruling 3 is intact"
          },
          {
            "command": "node scripts/check-type-source-resolution.mjs --self-test",
            "verdict_line": "check-type-source-resolution --self-test OK",
            "exit": 0
          },
          {
            "command": "pnpm check:pm-dispatch-gates",
            "verdict_line": "os-verify-lock: VERDICT command-exit 0 (held the lock 650s, waited 213s)",
            "exit": 0,
            "note": "run under scripts/pm/os-verify-lock.sh, slot issue-16000"
          },
          {
            "command": "pnpm check:nul-bytes",
            "verdict_line": "check-nul-bytes: OK (scanned 7959 text file(s); no raw ASCII control bytes)",
            "exit": 0
          },
          {
            "command": "node scripts/check-self-test-wired.mjs",
            "verdict_line": "check-self-test-wired: every one of the 179 script(s) CI runs that ship a --self-test has that self-test run by CI",
            "exit": 0
          },
          {
            "command": "node scripts/check-comment-mask-corpus.mjs",
            "verdict_line": "comment-mask corpus sweep: 6198 files, 0 disagree, 0 unparseable",
            "exit": 0
          }
        ],
        "exit_code_capture": "every command redirected to its own log file first, then EXIT=$? captured; never read across a pipe"
      },
      "files_changed": [
        "scripts/check-type-source-resolution.mjs"
      ],
      "files_changed_method": "git diff --name-only origin/main...HEAD (three-dot). The two-dot `git diff --name-only 1ebde4953` reads 21 files because origin/main was merged into this branch mid-run (it had moved 5 commits and dispatch-gates refused to derive from the stale tree); the PR's own file list confirms exactly one file. Expected one, got one.",
      "line_budget": "n/a",
      "deviations": [
        "Merged origin/main (8647c87b2) into the branch mid-run. dispatch-gates.mjs refused the stale tree by name: 3 of the files its families are derived FROM had changed across the range, so the pre-merge derivation was 'a well-formed answer about a tree nobody is on'. Re-derived clean after the merge. The merge was conflict-free, moved no lockfile and left no os-regen-pending deferral.",
        "The shared verify lock returned exit 99 (queue-timeout, NOT MEASURED) three times before acquiring - roughly 20 minutes across resumed slot 'issue-16000'. Each interval was spent on lock-free work (reading the gate and its cross-gate pin, writing the message edit and the self-test pins, running the unlocked check:* gates), never on idling, and the slot was re-presented under the same name each time so the arrival stamp carried. No reading was taken from an exit-99 call.",
        "pnpm check:pm-dispatch-gates exceeded the container's foreground cap on its first invocation and was moved to the background by the harness. It was NOT left there: this turn blocked in-foreground on an until-loop over its log until its own VERDICT line appeared - 'VERDICT command-exit 0, held the lock 650s'. Measured, not inferred from silence.",
        "The reproduction's paths rule was written into packages/runtime/tsconfig.json (the file the gate's message names) and reached the test program through `extends`. That the inheritance really resolved is not assumed: the 13 TS6059 name service-realtime source files, which the program could only have admitted through the mapping.",
        "@objectstack/service-realtime is not a declared dependency of @objectstack/runtime and was not installed into its node_modules for the reproduction. It did not need to be: a matching `paths` rule supersedes node resolution for tsc, and the diagnostics prove the source was admitted. Recorded because it is the one respect in which the mutation is narrower than #15978's real diff.",
        "3 families matched by path take a value from the workflow environment ($RUNNER_TEMP) and are NOT MEASURED locally by dispatch-gates' own account: check-cross-package-test-inputs --union-into/--changed, check-shard-attestation --emit, check-test-completeness. They are CI's. A further 10 families declare a population too wide to place and 41 artifact-roster families score `silent` as a fact about a list rather than about these paths - neither group is read as a clearance.",
        "Repo-wide `pnpm lint` was NOT run and no narrowing is claimed for it: it is a repo-scale scan CI owns, and this diff is one script file with no new identifiers.",
        "No changeset: no publish surface. `skip-changeset` applied at PR open by additive REST write (POST .../issues/16257/labels) and read back - the label set reads ['skip-changeset'], nothing stripped."
      ],
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  4. claude commented on Sep 6, 2026

    @claude
    ContributorAuthor

    ACCEPT — PR #16257 (head a8cc3cb55, read 2026-09-06T08:23Z)

    Review checklist (read from GitHub, not from the report):

    • Shape: draft, targets main, body first line Fixes #16000; merge should close the card (message + pins are the whole deliverable). No other issue number sits next to a closing keyword (build(rest): give the package a test-layer tsc program and ledger its 37 errors #12570, fix(discovery): stop advertising a realtime service with no mounted surface, and define a subscribable channel once #15978, feat(organizations): bring the multi-organization runtime back to open core — the org-scoping registrar ships open, the licence gate stays in cloud (ADR-0132) #16215 appear once each, non-closing).
    • Scope: 1 file, scripts/check-type-source-resolution.mjs (+53/−2). NOT governed (predicate 0/1). Tooling-only ⇒ skip-changeset present. No content/docs/releases/ change. No other open PR in the lane's file map touches this script.
    • Diff vs rulings: the addition sits inside the existing paths remedy paragraph, after the re-baseline limb, and is written as a condition (rootDir must contain the dependency's source), never a blanket closure — the card's own warning. It names the diagnostic an author meets (TS6059 billed to the consuming package's test-typecheck ledger) and the remaining option (do not take the dependency, or escalate). The load-bearing literals for check-ratchet-remedy-authority are untouched (the refusing predication, SHRINK-ONLY, ONBOARDED), and both new negations are verb-bound (Never widen that ledger, never widen a rootDir), so the file's hand-classified refused verdict stands — dev ran that gate, exit 0, and CI's Lint & Repo Gates re-runs it. Two self-test pins in the audited in BOTH directions battery; its floor moved 10 → 12 (tightened).
    • Reproduction accepted as the card's precondition: mutation/restore pair with both legs proven on disk — 191 errors / 0×TS6059 pristine vs 204 / 13×TS6059 with the gate's own paths remedy applied to packages/runtime/tsconfig.json, every other code's count byte-identical, restoration verified by blob hash. Nothing from it is committed (file list = 1).
    • Gates: 31/31 derived commands exit 0 at a8cc3cb55, reconciled by dispatch-gates --ran (0 NOT-MEASURED); check:pm-dispatch-gates measured under the lock (held 650s, waited 213s, after three exit-99 queue timeouts spent on lock-free work — disclosed). CI at head: 19 success / 11 skipped / 3 in progress. Flip waits for Lint & Repo Gates, TypeScript Type Check and Test Core (1/6) to read completed/success on their own rows.
    • Process disclosures accepted: origin/main merged mid-run because dispatch-gates refused the stale tree by name (conflict-free, no regen debt); the report's two-dot diff caveat is answered by the PR's own one-file list.

    Landing plan: flip point armed; on all-green → ready → auto-merge (squash); on MERGED → content probe on origin/main (firing: TS6059 and maintainer-only in the failure text of scripts/check-type-source-resolution.mjs; negative control: the battery floor line reads 12, not 10), strip pm:dispatched, clear assignee.


    Generated by Claude Code

  5. claude commented on Sep 6, 2026

    @claude
    ContributorAuthor

    LANDED — PM seat domain:devx @ objectstack, session session_01Vbw3RPgdtqesx4azk9SbW8, 2026-09-06T09:20Z.

    PR #16257 (Fixes #16000) MERGED 2026-09-06T09:16:13Z as 1b5bc6bdd on main via the merge queue (queued 08:36:39Z). Content probe on re-fetched origin/main dfb7a0d8e (scripts/check-type-source-resolution.mjs, git grep counts): TS6059 = 12, maintainer-only = 2 — the paths precondition and the closed-door sentence are in the failure text; negative control: the audited in BOTH directions battery floor reads 12 (was 10 before the merge). Card closed by the merge; pm:dispatched stripped and assignee cleared in this stroke.


    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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions