Skip to content

[finding] No plugin package has a tsc program that compiles its tests — plugin-approvals alone hides 345 type errors, and 0 of 14 are in the test-typecheck ledger #14062

Description

@claude

Found while working #13568 (approvals record-delete linkage). Out of that card's scope — recorded, not fixed. Same class as #13676 (packages/objectql) and #12542 (packages/rest, closed); this instance is the packages/plugins/** family, which no card names yet.

Measurement

packages/plugins/plugin-approvals/tsconfig.json ends with:

"include": ["src/**/*"],
"exclude": ["dist", "node_modules", "**/*.test.ts"]

and the package's typecheck script is tsc --noEmit && tsc --noEmit -p tsconfig.scripts.json — no test project. So no tsc program in this package compiles a single test file; the only thing that touches them is esbuild type-stripping under vitest, which type-checks nothing.

Measured on origin/main at e612dd0f5, by copying the package tsconfig with "exclude" reduced to ["dist", "node_modules"] and running tsc --noEmit -p against it:

345 errors

Overwhelmingly one class — openNodeRequest returns ApprovalRequestRow | ApprovalNodeAutoOutcome and test callers read .id / .pending_approvers straight off the union:

src/admin-exemption-retired.test.ts(215,16): error TS2339: Property 'pending_approvers' does not exist on type
  'ApprovalRequestRow | ApprovalNodeAutoOutcome'.

Why it is worth a card rather than a shrug

The count is not the point; the channel is. AGENTS.md and #12542 name this exact shape as how a compile-time pin becomes a phantom check: a @ts-expect-error in a file no tsc program compiles asserts nothing, and deleting it is invisible. This package has real compile-time pins — exec-context-annotation.pin.ts, manager-org-screen-parity.contract.test.ts — living in that untyped zone.

Scope: the family, not this one package

The gap is not specific to approvals:

measure count
packages under packages/plugins/ 14
…with a tsconfig.test.json 1
…with a check:test-typecheck script 0

packages/spec and packages/client both run check:test-typecheck against a tsconfig.test.json with the shrink-only test-typecheck-debt.json ledger. No plugin package is inside that instrument at all — so the ledger's own "shrink-only" guarantee says nothing about a third of the repo's runtime surface.

What this card is NOT asking for

⛔ Not "add 345 errors to test-typecheck-debt.json". That ledger is an EXACT, shrink-only ratchet and expanding it is maintainer-only (#5286). The decision this card wants is the same one #12542 answered for rest: whether the plugin family gets a checked test zone, and if so on what schedule and with what starting ledger — or whether it is deliberately left out, in which case the compile-time pins living there should be re-sited somewhere that actually compiles them.

No repro of a live defect — nothing is currently broken by this; it is a missing instrument, which is why it is filed as an observation rather than a bug.


Generated by Claude Code

Activity

  1. huangyiirene commented on Sep 1, 2026

    @huangyiirene
    Collaborator

    裁决:全家族收编 —— packages/plugins/** 进 test-typecheck 仪器族(维护者 2026-09-01,总监批 #26)

    项目总监席 · session session_01KGtaLpkW1mycWgkbSb3H6t · 维护者对本批逐字:「同意」。

    1. 收编:14 个插件包全部进入 check:test-typecheck 仪器族 —— 每包 tsconfig.test.json + 起始台账条目按各包实测数入册(shrink-only,plugin-approvals 从 345 起);
    2. @ts-expect-error 退役 pin 在 packages/spec 里是幽灵检查:tsconfig 把 **/*.test.ts 排除出唯一的 tsc --noEmit #5286 要求的维护者授权由本裁决携带:台账扩张的成本判断(起始台账 + 永久义务 + 14 包量级)已呈维护者并获准 —— 分诊席推荐的更小一支(只声明范围 + 迁两个钉)如实入册为被考虑过的备选,未采:仪器对运行时面三分之一保持沉默的空洞,比 14 份 tsconfig 的机械成本更贵;
    3. 实施序:plugin-approvals 先行(345 的实测基线),其余 13 包机械跟进,可分批 PR;⛔ 不是「修 345 个错误」—— 是让棘轮开始看见它们,修复照棘轮自身的只减纪律走;
    4. 既有编译期钉(exec-context-annotation.pin.ts、manager-org-screen-parity.contract.test.ts)随收编自动变真,⛔ 不迁址;
    5. 相邻不合卡维持:[finding] packages/objectql's tsc program excludes **/*.test.ts, so its whole test corpus is type-exercised only by esbuild type-stripping — the same shape #12542 closed for packages/rest #13676(objectql 同类)各走各的;「12 包缺 typecheck 脚本」的结转项是不同缺口,⛔ 不并入。

    状态转移(同笔)

    needs-user-decision → pm:queue;priority:p2 domain:devx finding 不动。


    Generated by Claude Code

  2. huangyiirene commented on Sep 2, 2026

    @huangyiirene
    Collaborator

    Triage bookkeeping (R+90, triage seat, session session_019kDRpB7D2XzVzkaLp57T5D), two label writes, ⛔ no re-grading:

    1. pm:blocking applied — derived from the reverse index, ⛔ not hand-judged: [finding] service-cluster tests compile under no tsc program — a TS2322 sits in memory.contract.test.ts, the package's contract witness (services-family instance of the #14062 class) #14181 (packages/services/** instance of this class) carries Blocked-by: #14062 and is open, so this card has an open downstream dependant and the derived cache should say so. It raises this card's position in the lane's selection order; the grade (p2) is untouched.
    2. finding removed — this card was graded (pm:queue + domain:devx + priority:p2) rounds ago and the label should have come off then; grading is leaving the box.

    The class decision this card carries is unchanged and is what #14181 and #14173 wait on: checked test zone vs. deliberate exclusion with pins re-sited.


    Generated by Claude Code

  3. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    Claim: domain:devx execution seat (#6023), R1 wave 7.
    Session: session_01WLJQhde67SeTccsmnBVarV (subagent dev shares this ID)
    Branch: claude/issue-14062-plugins-test-typecheck
    Worktree: ../objectstack-14062
    Domain: domain:devx · pm:queue → pm:dispatched + assignee in one label write, read back and matching. pm:blocking kept (derived cache, #14181 waits on this card).
    File surface: packages/plugins/*/tsconfig.test.json (14 new), packages/plugins/*/package.json (check:test-typecheck + gen:test-typecheck-debt + typecheck wiring), packages/plugins/*/test-typecheck-debt.json (generated by scripts/check-test-typecheck.mts --update, EXACT per-signature), and scripts/check-type-check-coverage.mjs only if its declared package population must name the new programs. ⛔ Not scripts/check-test-typecheck.mts itself unless a self-test needs a plugin-family case.
    Container & model: os-dev subagent, opus. Tier readout (dispatch-gates.mjs --tier, derived on origin/main@1dcb995f2): no path-derived mandate — floor sonnet · default opus · ceiling fable. Clause ②: NO — no contract accept/reject change, no public surface widened; the ledger expansion is maintainer-only (#5286) and the director ruling of 2026-09-01 (comment 5494659007, 「同意」) carries that authority — PR body quotes it.
    Serial constraints: none on the hot-file queue. #14173 (packages/lint, same class) and #14181 (packages/services/**) wait on this card and are ⛔ not folded in (ruling item 5).


    Generated by Claude Code

  4. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 14062,
      "status": "done",
      "branch": "claude/issue-14062-plugins-test-typecheck",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14420",
      "premise_still_valid": true,
      "summary": "All 14 packages/plugins/** packages are in the check:test-typecheck instrument: each has a tsconfig.test.json its typecheck script names, and the four measuring non-zero carry generated EXACT shrink-only starting ledgers (plugin-approvals 324/8 files, plugin-auth 94/10, plugin-sharing 3/2, knowledge-ragflow 3/1). The ten measuring zero deliberately carry NO ledger: verified reading — check:test-typecheck treats a missing ledger as empty entries and prints '0 file(s) / 0 error(s)', under which any error is red immediately with no entry to be added to, strictly stronger than a ledger holding nothing (the call plugin-security recorded for itself in #13176); gen:test-typecheck-debt is wired only where a ledger exists, so no package owns a generator whose only output would be an empty file. No test file is edited (ruling item 3). PREMISE: the card's load-bearing measurement reproduces EXACTLY — 345 by the card's own recipe at today's HEAD; the ledger reads 324 because vitest module semantics subtract 21 config-tier diagnostics (20xTS2550 lib, 1xTS1470 import.meta), both numbers recorded in the config header and the ledger _note. TWO MEASURED CORRECTIONS to the card, both in the PR body: (1) the headline 'no plugin package has a tsc program that compiles its tests' is over-general — 9 of the 14 never excluded their tests and tsc --noEmit already compiled them at zero errors; the real gap is 3 packages hiding tests behind **/*.test.ts (approvals 35, auth 87, sharing 30) plus knowledge-ragflow, silent the other way (tests in the build program but NO typecheck script existed, so nothing ran it) = 153 test files newly under a running type check; (2) exec-context-annotation.pin.ts was never a phantom pin — it is a .pin.ts, which **/*.test.ts never matched, so it was in the build program all along (control measured in ablation B: deleting a directive reds the BUILD config too). The pin ruling item 4 genuinely makes real is manager-org-screen-parity.contract.test.ts. Repo-wide sweep: NO @ts-expect-error in packages/plugins/** was actually phantom before this PR. SIDE EFFECTS, all forced by gates and documented in place: 4 graduated entries deleted from check-type-check-coverage.mjs (approvals/auth/sharing from TEST_DEBT, knowledge-ragflow from DEBT) with a block recording that none was PAID DOWN — the same errors moved to a finer per-file/per-signature instrument; a #11490 re-baseline in check-type-source-resolution.mjs meeting all three of its doc-block conditions with before/after stated in place (98 -> 111 programs, 54 -> 55 entries, 242 -> 269 pairs at merge base 79b6a22a5); 4 NOT_DRIVER_MANAGED rows in regen-artifacts.mjs; knowledge-ragflow's build tsconfig now excludes tests, because check:type-check-coverage refuses a typecheck that never invokes tsc and this card is not chartered to repair its 3 errors — ⛔ that is NOT the repo-wide 'packages missing a typecheck script' carry-over the ruling keeps separate in item 5. plugin-hono-server's config omits the lib override, measured: pinning lib ES2022 strips the DOM default its non-root-extending tsconfig relies on and manufactures 34 errors, 26 inside node_modules .d.ts.",
      "tests": "Union run at b7413a964 (git rev-parse --short HEAD from that run), workspace closure built first via `pnpm exec turbo run build --concurrency=2 --filter='./packages/*' --filter='./packages/*/*'` under the shared verify lock (VERDICT command-exit 0). ALL 14 packages `pnpm --filter @objectstack/PKG typecheck` EXIT=0, each printing its own gate verdict line, e.g. 'check:test-typecheck: OK - @objectstack/plugin-approvals's test layer compiles under packages/plugins/plugin-approvals/tsconfig.test.json; 8 file(s) / 324 error(s) / 27 pinned signature(s)'. Derived family (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, re-derived from the FINAL diff, 38 paths vs merge base 79b6a22a5, 25 families) all EXIT=0: agent-test-spelling, bash32-floor, changeset-gate-self-tests, cli-command-ids, cross-package-test-inputs, doc-authoring, dual-build-cjs-loads, entry-guard, merge-driver, objectui-changeset, override-consistency, page-declaration-shape, parse-guard, pm-half-states, pnpm-filter-targets, published-files, ratchet-remedy-authority, service-providers, slot-lookup, test-source-alias, turbo-task-graph, type-check-coverage, type-check-debt, type-source-resolution, watch-hint-literal; plus the filter-scoped `--filter @objectstack/spec run check:llms-txt`, the convention-triggered check:i18n / check:i18n-stale-fill, check:nul-bytes, check:declared-population-live. Key verdict lines quoted, not inferred from a bare exit code: 'check-type-check-coverage: OK - 68/78 workspace packages type-checked'; 'check-type-check-coverage --re-measure: OK - 23 ledger entr(ies) re-measured in 287.7s, 771 raw tsc error(s) total, none above its recorded number' with 'surplus: none - every entry sits exactly at its measurement, so any new error is red'; 'check-type-source-resolution OK - 111 tsc program(s) across 77 packages scanned; 55 registered'. dual-build-cjs-loads and type-check-debt exit 3 = PREREQUISITE NOT MET without the whole repo's dist/ - read as NOT MEASURED, then re-run green after the closure build (both reported above). Every gate's exit code captured by redirect-then-read (cmd > file 2>&1; EXIT=$?), never through a pipe. ABLATION A (the load-bearing one - a file in NO tsc program on main): target packages/plugins/plugin-approvals/src/manager-org-screen-parity.contract.test.ts, run from a COMMITTED implementation. Mutation confirmed ON DISK before any reading, anchored on the injected text: 'injected marker occurrences: 1', pre-mutation hash a010631b3d220db32d6eaa36a1ea5d23c266650c == HEAD blob, post-mutation hash d6470da16c16cb3111954d23295874471b9b781b (different, non-empty). MUTATED EXIT=1 with the exact everyday verdict: 'src/manager-org-screen-parity.contract.test.ts: 2 type error(s) in a file the ledger does not cover ... MAINTAINER-ONLY'. RESTORE leg proven by observed state, not exit code: `git checkout HEAD -- ABSPATH` (HEAD named explicitly, never a bare checkout, and the trap path absolute from git rev-parse --show-toplevel), restored hash a010631b3d220db32d6eaa36a1ea5d23c266650c == HEAD blob, `git diff HEAD -- path` EMPTY, marker occurrences 0, RESTORED EXIT=0 back at 8 files / 324 errors / 27 signatures. NO REBUILD was needed for either leg and that is stated rather than assumed: the mutated file is the package's OWN source compiled from src by this program, not a dependency reached through dist/ exports, so no dist/ marker can survive into the next run. ABLATION B (the PM-requested pin deletion, with its control): deleting a @ts-expect-error in exec-context-annotation.pin.ts - directives before 1, after 0, post hash 0f45c8617a91937fbaa8479efb1d1aabc337714c != HEAD blob 502cebae33222e7b3f7d451c9e9217ebc80b4d95 - MUTATED EXIT=1, and the CONTROL shows the same deletion also reds the BUILD config (EXIT=2, TS2322 at 135,39), which is measurement correction 2. Restored hash == HEAD blob, git diff HEAD empty, directives back to 1. LINT, narrowed with the narrowing MEASURED (all three pieces): (1) the population is ESLint's own, not my guess - passing all 38 changed paths to `eslint --no-inline-config --format json`, 35 come back carrying 'File ignored because no matching configuration was supplied.'; (2) the count is read from that JSON - the 3 files ESLint does judge (the edited scripts/*.mjs) report errorCount 0, warningCount 0; (3) invariance for untouched files - this repo runs one eslint.config.mjs which, in its own words, 'never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file', so no untouched file's verdict is a function of this diff. vitest: @objectstack/knowledge-ragflow (the only package whose BUILD tsconfig this PR edits) 1 file / 9 tests passed; no runtime source is edited anywhere in the PR, so no other suite is in the change's radius. Script self-tests for the three edited scripts, all green: check:type-check-coverage --self-test (47+65+43+28+19+18 cases), check:type-source-resolution --self-test, and check:merge-driver (= git-merge-regen --self-test, the reconciliation that reads regen-artifacts.mjs). git grep over *.test.* finds no vitest suite that references any of the three as code.",
      "mcp_calls": "0 — the whole run used REST via curl (issue body, comments, PR create, PR read-back); zero MCP GitHub tool calls",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    ACCEPT — #14062 landed. PR #14420 (head b7413a96) merged to main as ad54eb342 via the merge queue (auto-merge armed, zero rework; the auto_merge: null reading at ~06:40Z was the PR mid-queue, not disarmed).

    Close-out probes executed on origin/main (fresh fetch, throwaway worktree — not the shared checkout; ad54eb342 confirmed as an ancestor of origin/main):

    • packages/plugins/plugin-approvals/package.json:17 "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.scripts.json && pnpm check:test-typecheck" and :19 the check:test-typecheck script — the test-typecheck leg is now part of the package's own typecheck, not a separate opt-in.
    • scripts/check-type-check-coverage.mjs carries the graduation block: plugin-approvals (345), plugin-auth (94) and plugin-sharing (3) left the TEST_DEBT ledger on 2026-09-02; knowledge-ragflow (4) left DEBT; the per-package test-typecheck-debt.json ratchets read 324 / 94 / 3 / 3. Control: @objectstack/runtime still 1 hit in the ledger, so the file is read and the removals are the diff.
    • Merge touched 38 files: 14 tsconfig.test.json, scripts/check-type-source-resolution.mjs, scripts/regen-artifacts.mjs, the per-package debt files, and .changeset/plugins-test-typecheck-onboarding.md.

    pm:dispatched stripped in this same step (pm:blocking / priority:p2 / domain:devx kept — ownership is not state; pm:blocking is the triage sweep's cache and is cleared by that sweep, not here). Downstream cards that carried Blocked-by: #14062 — #14386, #14173, #14181 — are now unlocked for the next selection pass (their file surfaces get re-verified on ad54eb342 before dispatch).

    Seat: session_01WLJQhde67SeTccsmnBVarV (domain:devx, #6023).


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions