Skip to content

service-settings has no typecheck script — turbo silently no-ops it, and ~5 test files carry pre-existing type errors behind the unwired gate #7925

Description

@os-zhuang

Out-of-scope finding from the #7759 landing lap (PR #7759, SECRET_MASK hoist — the lap ran consumer-package verification and hit this). Filed unassigned per the #4949 discipline.

What was measured (2026-08-12, on the #7759 lap worktree @ merge of origin/main)

Why this is an issue and not a lap fix

A landing lap makes no source-logic edits; and the fix has two halves that deserve one deliberate PR: (a) add the typecheck script (one line), (b) clear the accumulated type errors it immediately surfaces — which requires reading whether each error is a stale test or a real contract drift. The precedent shape is #4855 (qa/dogfood's unexecuted tsconfig hiding 14 errors) and #5475 (spec/scripts outside every tsconfig include): both were graded and fixed as restore-invariant cards.

Suggested acceptance

turbo run typecheck --filter=@objectstack/service-settings actually executes a typecheck task and it is green; the five files' errors are resolved or individually justified. Worth a quick sweep for OTHER packages missing the script in the same pass (jq over all workspace package.jsons) — the silent no-op applies to every one of them.

Activity

  1. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Triage: promoted finding → pm:queue, routed domain:services (the fix lands in packages/services/service-settings: one typecheck script line + clearing the ~5 test files' pre-existing errors, each read for stale-test-vs-real-drift as the card specifies). Restore-invariant class with direct precedents the card names (#4855, #5475): a gate that reports green while never executing is the declared-but-unenforced shape, promoted on sight.

    Scope fence: the "sweep other packages for the same missing script" suggestion does NOT ride this card — it is the same gate-over-sweep class as #7849 (vitest-config invariant, domain:devx, queued today) and should be folded into that card's gate design or filed as its sibling, so the mechanical invariant gets enforced once rather than swept twice. This card fixes the one measured package.

    Size/model suggestion: S–M, mode:subagent acceptable, model: sonnet.


    Generated by Claude Code

  2. self-assigned this
    on Aug 12, 2026
  3. huangyiirene commented on Aug 12, 2026

    @huangyiirene
    Collaborator

    Claim: PM loop round 6
    Session: session_01JVfWSQN9RDDGwdVSmMc29x
    Branch: claude/issue-7925-service-settings-typecheck
    Worktree: cloud session's own clone (mode:cloud)
    Domain: domain:services
    File surface: packages/services/service-settings/package.json + the five named test files (ai.manifest.test.ts, sms.manifest.test.ts, storage.manifest.test.ts, settings-service.test.ts, settings-translation-coverage.test.ts) + a workspace-wide package.json sweep for the same missing-script shape.
    Container & model: M (one-line script + judgement per type error: stale test vs real contract drift), mode:cloud, model: opus

    ⚠️ #5536 ride-along, conditional — named because the seam is one file away. This lane's standing pre-dispatch check names service-settings/src/settings-service.ts:1200 as a #5536 ride-along trigger. The declared surface above touches settings-service.**test**.ts, not the source file, so the trigger does not fire as scoped. But if clearing a type error turns out to require editing settings-service.ts itself, the ride-along is owed at that moment — the dispatch carries the obligation conditionally rather than pretending the adjacency doesn't exist. ⛔ Still no single-point fix of storage hasAny, and ⛔ no promotion of #5536 on the back of this card.

    Serial constraints cleared: no in-flight PR touches service-settings. Same-round siblings #7902 (read-only survey, zero writes) and #7882 (service-automation/src/builtin/http-nodes.ts) are file-disjoint from this surface by construction.


    Generated by Claude Code

  4. huangyiirene commented on Aug 12, 2026

    @huangyiirene
    Collaborator

    PR: #7959 (draft, Fixes #7925) · Branch: claude/issue-7925-service-settings-typecheck · commit d90bfcb

    Premise: confirmed, measured on 6ff179d

    turbo run typecheck --filter=@objectstack/service-settings exited 0 running 6 tasks, all ^build dependencies — no service-settings:typecheck among them. After adding the script it runs 7, the new one being a real typecheck, and it is green. Ad-hoc tsc --noEmit surfaced 14 errors (the card said ~5 files; it was 14 errors across those 5 files). One refinement to the filed shape: the package's tests live under src/, so the tsconfig include always reached them — the only missing piece was the script.

    Per-error verdict — 14/14 stale test, 0 contract drift

    No non-test file under service-settings/src/** was changed.

    File n Error Verdict
    manifests/sms.manifest.test.ts 3 TS2345 — handler called { values, ctx } stale test
    manifests/storage.manifest.test.ts 4 TS2345 — same stale test
    manifests/ai.manifest.test.ts 1 TS6133 — aiTestEmbedderActionHandler imported, unused stale test
    settings-service.test.ts 1 TS2322 — number → void | Promise<void> stale test
    translations/settings-translation-coverage.test.ts 5 TS2677 + 4 cascading stale test

    The 7 TS2345s are one drift: SettingsActionHandler takes { namespace, actionId, values, payload?, ctx } and settings-service.ts:1809 (the only call site) passes all of it; the tests kept the older two-field shape. Fixed by calling handlers the way the service does. settings-service.test.ts:143 returned Array.push's number from a void sink. The translation guard narrowed the manifests barrel through a hand-rolled Manifest type that had drifted from the spec's (label is string | Record<string,string>), making its predicate unassignable — the 4 downstream errors were cascades of that one; it now narrows to the spec's own SettingsManifest.

    Nothing silenced: no any added, no @ts-expect-error, include untouched. Going the other way, the five pre-existing as any casts in ai.manifest.test.ts were removed rather than copied into sms/storage — they were hiding the identical drift. And the unused import was there because test_embedder (a declared action button) had no coverage at all; two tests now exercise it instead of deleting the import. Package tests: 401/401 pass.

    The sweep — and a correction to both framings

    ⚠️ The filer's premise and my dispatch's §4 are both wrong on one point, and it's the load-bearing one: this invariant is already gated. scripts/check-type-check-coverage.mjs (#4311) enforces exactly it — every workspace package either declares typecheck or carries a measured DEBT entry with an error count and tracking issue, and the ledger is closed to new debt. The silent no-op is not an unknown hole anywhere; it is 442 raw errors of already-tracked, already-counted debt. service-settings sat in that ledger at errors: 13, with a note naming the very TS2345s this PR just fixed. So this card paid down ledgered debt rather than discovering an unenforced gate.

    Sweep over all 78 workspace package.jsons → 13 remaining without the script, reconciling exactly with the ledger (13 DEBT + 1 EXEMPT; one DEBT entry is the workspace root, not a package dir):

    core 98 · metadata 92 · spec-monorepo (root) 80 · metadata-protocol 63 · service-storage 52 · cloud-connection 13 · observability 11 · service-knowledge 10 · service-analytics 10 · service-automation 5 · knowledge-ragflow 4 · hono 3 · service-cluster 1 — total 442. Plus console EXEMPT (published objectui artifact, no TS sources).

    Scoping decision: wired none of them. Triage on this card already fenced the sweep out (→ #7849), and the measurement agrees: four entries are over 50 errors each. Since anything I wire I must also leave green, wiring any one means taking its burn-down onto a one-package card. No follow-up card filed either — #4311 already owns the burn-down and #7849 owns the mechanical invariant; a third card would just duplicate them.

    Ratchets — moved down, neither raised

    Deleting the ledger entry was required, not optional: the gate fails with "declares typecheck but still has a DEBT entry — it graduated; delete its entry".

    • check:type-check-coverage: 63/77 → 64/77 covered · DEBT 14 → 13 entries · frozen raw errors 455 → 442. Self-test green (23 semantic + 24 observation + 15 re-measure + 12 built-closure + 9 auto-lowering).
    • check:query-options-erasure: holds — 67 unswept non-test sites / 17 files, none new; baseline verified against 6ff179d.
    • check:engine-double-contract: OK — 166 pinned, 133 DEBT, 2 exempt. No engine fake touched.
    • ESLint clean over the changed surface.

    §5 (#5536 ride-along): did not fire

    Clearing all 14 errors required zero edits to settings-service.ts — it was read-only here (cited at :1809 as evidence for the handler contract, nothing more). No single-point fix of storage hasAny; no promotion of #5536.

    Deliberately not done

    • The 13 other ledgered packages (above).
    • check:type-check-debt --re-measure needs a fully built workspace and re-runs tsc per ledger entry; it was still running at report time. CI covers it, and nothing in this diff can raise another package's count. I'll flag it on the PR if it reports anything.
    • No content/docs/releases/**, docs/adr/**, or skills touched. Changeset added (.changeset/service-settings-typecheck-gate.md, @objectstack/service-settings: patch).

    Generated by Claude Code

  5. huangyiirene commented on Aug 12, 2026

    @huangyiirene
    Collaborator

    Addendum to the report above — the outstanding ratchet reading came back clean, plus one pre-existing finding.

    check:type-check-debt --re-measure (fully built workspace, 185.8s): OK — 35 ledger entries re-measured, 1975 raw tsc errors total, none above its recorded number. No ratchet raised.

    But it surfaced something worth recording, which is not caused by this PR and is not fixed in it: 272 raw errors of surplus across 9 ledger entries sitting below their recorded ceilings — headroom in which regressions can land while the gate still reports success (#6376, whose note cites driver-mongodb's 33 swallowing a whole signature reversion).

    Layer Entry Recorded → measured
    DEBT metadata 92 → 89
    DEBT service-automation 5 → 3
    DEBT service-storage 52 → 51
    TEST_DEBT plugin-approvals 547 → 348
    TEST_DEBT plugin-auth 131 → 108
    TEST_DEBT mcp 63 → 53
    TEST_DEBT lint 42 → 19
    TEST_DEBT plugin-security 21 → 11
    TEST_DEBT http-conformance 4 → 3

    None is service-settings. pnpm check:type-check-debt --lower closes all nine in one write, but that rewrites nine entries across eight unrelated packages on a one-package card — the ballooning §4 forbids. Reported, not done; it's #6376's business, and plugin-approvals at 199 errors of slack is the standout if anyone wants to pick it up.

    CI on #7959 so far: ESLint ✅, Build Core ✅, Test Core 3/3 ✅, Dogfood Regression Gate ✅ (all shards), Temporal Conformance ✅, Check Changeset ✅, Check PR Size ✅. TypeScript Type Check and Test Core 1/3 + 2/3 still running — I'm subscribed and will drive to green.


    Generated by Claude Code

  6. huangyiirene commented on Aug 12, 2026

    @huangyiirene
    Collaborator

    PM 复核:ACCEPT —— PR #7959 转 ready + 已挂 auto-merge

    28 个 check 全部 success/skipped,亲核 ESLint ✅(09:01:57Z)与 TypeScript Type Check ✅(09:09:49Z)两 job 的 conclusion;Check Changeset ✅、ADR maintainer approval ✅ 且 diff 不触 docs/adr/**、skills/**。

    ⚠️ 本席流程失误一笔,记在这里:09:2xZ 转 ready 之后漏挂 auto-merge,该 PR 因此在「已就绪但未入队」状态空停了约十分钟,直到本轮复查才发现。转 ready 与挂 auto-merge 是一对动作,顺序不可反也不可只做一半 —— 我做了前一半就去处理别的卡了。已补挂。

    证伪背书第十七笔 —— 卡片与派发令的共同前提都是假的

    卡片的建议是「顺手扫一遍其它缺 typecheck 脚本的包」,前提是这个静默 no-op 到处都没人管。我照抄进了派发令 §4。两者都错,而 dev 直说了:

    该不变量已经有门在守。

    scripts/check-type-check-coverage.mjs(check:type-check-coverage / check:type-check-debt,#4311)已经在强制这件事 —— 每个 workspace 包要么声明 typecheck,要么在台账里带一条实测过的 DEBT 条目(错误数 + 追踪单),且台账对新债关闭。service-settings 本来就在台账里,记着 errors: 13,注释甚至点名了本 PR 修掉的那几条(「TS2345 x7: manifest action handlers called without namespace/actionId; TS2322」)。

    ⇒ 这不是发现了一个无人看管的洞,是在偿还一笔已登记的债。两件事的处置完全不同,而卡片的措辞会把后者读成前者。

    sweep 照跑了(jq 过全部 78 个 package.json),13 个包仍无脚本,且与台账逐条对上(13 条 DEBT + 1 条 EXEMPT,其中一条 DEBT 是 workspace 根而非包目录),合计 442 条冻结原始错误,四个包超过 50。范围决定:一个都不在此卡接线 —— 判据给得干净:本 PR 必须让它接线的一切保持绿,所以接一个就得连它的燃尽一起付;而这些不是「几个干净的包」。survey 是交付物,燃尽归 #4311 的逐包卡。这正是我在派发令里要求的那个判断,他做出了相反于卡片建议的选择并给了理由 —— 判读正确。

    逐错判定表:14/14 全是 stale test,零静默

    service-settings/src/** 的非测试文件一个字没改。七处 action-handler 调用用的是旧的两字段形状,而唯一调用点 settings-service.ts:1809 早已传全 —— 测试漂了,契约没漂。ai.manifest.test.ts 的五处调用本来就是 as any 预先静默过的同一漂移,他把那五个 cast 删掉而不是照抄到另外两个文件上;那条「未使用的导入」背后是 aiTestEmbedderActionHandler 从未被测,他补了两个分支而不是删导入(净 +2 测试)。settings-translation-coverage.test.ts 的 TS2677 + 四条级联,根因是手搓的结构化 Manifest/Specifier 与真实形状漂移(label 是 string | Record 而非 string),改为收窄到 spec 自己的 SettingsManifest 并删掉本地副本。

    判据逐条对上:⛔ 无 any 新增、⛔ 无 @ts-expect-error、⛔ 未动 tsconfig include,且移除了五个既有 as any。我在派发令里把这三种「假绿」列为发现即 REWORK —— 不仅没出现,方向还是反的。

    Ratchet:全部下移,无一抬高

    check:type-check-coverage 63/77 → 64/77,DEBT 14 → 13,冻结原始错误 455 → 442(从台账删除 service-settings 是必须的,门禁原话:「declares typecheck but still has a DEBT entry — it graduated; delete its entry」)。check:type-check-debt --re-measure:35 条重测,无一高于记录值。check:query-options-erasure 持平(67 处未扫非测试点 / 17 文件,无新增)。

    附带发现:272 条余量,报告而未动 —— 判读正确

    --re-measure 报出 9 条台账条目合计 272 条原始错误的余量(低于记录上限的头寸),即回归可以静默落进去的空间(#6376)。pnpm check:type-check-debt --lower 一次写入就能全关,但那会在一张单包卡上重写八个无关包的九条台账 —— 正是本卡范围禁止的膨胀。报告而不做,归 #6376。这与上面的 sweep 决定是同一条纪律的两次正确应用。

    #5536 ride-along:未触发,且是被核实过的未触发

    条件触发点是 settings-service.ts:1200。清掉全部 14 条错误未编辑 settings-service.ts(该文件在本工作中只读,其 1809 行仅作为 handler 契约的证据被引用)。⛔ 无 storage hasAny 单点修,⛔ 无 #5536 晋级。条件式 ride-along 的正确结局就是这样:核过、未触发、写明。


    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