fix(service-automation): compile the test layer with tsc, and repair the TS2341 x3 it hid - #15152
Conversation
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
…rvice-automation-typecheck # Conflicts: # scripts/check-type-check-coverage.mjs
… tree The `service-knowledge` onboarding landed on `main` between this entry's first reading and this merge, so every absolute in its provenance block (programs, pairs, packages, clean count) was a number about a tree that no longer exists. Re-taken with `--list` on the merge commit itself, all four rows plus the before/after pair, by varying only what the `typecheck` script names: no `typecheck` script absent 120 programs / 293 pairs names tsconfig.json absent 120 programs / 293 pairs names tsconfig.test PRESENT 121 programs / 302 pairs names both (the card) PRESENT 121 programs / 302 pairs before 59 of 78 packages, 120 programs, 293 pairs, 19 clean after 60 of 78 packages, 121 programs, 302 pairs, 18 clean The deltas this block actually claims (+1 package, +1 program, +9 pairs, one per dep) are unchanged; only the absolutes moved, and the block now says which merge moved them. The sibling entries' own blocks keep their own historical readings untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
Merge round — conflicted against
|
typecheck names |
entry | --list totals |
|---|---|---|
| nothing (origin/main) | absent | 120 programs / 293 pairs (59/78 packages, 19 clean) |
tsconfig.json only |
absent | 120 programs / 293 pairs (59/78, 19 clean) |
tsconfig.test.json only |
PRESENT | 121 programs / 302 pairs (60/78, 18 clean) |
| both (this PR) | PRESENT | 121 programs / 302 pairs (60/78, 18 clean) |
before 59/78 packages, 120 programs, 293 pairs, 19 clean → after 60/78, 121, 302, 18 clean, i.e. +1 package, +1 program, +9 pairs (one per dep), this entry and nothing else — the deltas the block actually claims are unchanged; only the absolutes moved. Commit c6d25b73 writes exactly these into this entry's own doc-block and says which merge moved them. The service-cluster, service-i18n and service-knowledge blocks keep their own historical readings untouched.
The DEBT ledger summary line moved for the same reason — two packages graduated where the reviewed round had one:
check-type-check-coverage: OK — 74/79 workspace packages type-checked (plus the root),
5 in the DEBT ledger (104 frozen raw errors, …), 1 exempt.
(The body above reads 6 in DEBT / 114 frozen; that was true of the pre-merge tree.)
Gates re-run on the merged head c6d25b73
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, re-derived against the post-merge diff (7 paths, merge base 919beca43) → 64 commands. Every exit code captured redirect-then-read, never across a pipe; verdict lines read rather than bare status. 62 exit 0, 2 NOT MEASURED (exit 3), 0 findings.
pnpm check:type-check-coverage— exit 0, quoted above; its--self-testleg reports 48 semantic + 68 observation + 45 re-measure + 28 built-closure + 19 auto-lowering + 18 exit-code cases holding.pnpm check:type-source-resolution— exit 0:check-type-source-resolution OK — 121 tsc program(s) across 78 packages scanned …; 60 registered as still resolving a workspace dep's types through dist/.(self-test leg OK.)pnpm --filter @objectstack/service-automation typecheck— exit 0:check:test-typecheck: OK — @objectstack/service-automation's test layer compiles under packages/services/service-automation/tsconfig.test.json; 0 file(s) / 0 error(s) / 0 pinned signature(s).pnpm --filter @objectstack/service-automation test— exit 0: 103 test files / 1225 tests passed, identical to the reviewed round.pnpm check:nul-bytes— exit 0, plus a hand scan of both edited scripts for raw control bytes: no matches.
NOT MEASURED, quoting each gate's own verdict rather than a bare status:
pnpm check:dual-build-cjs-loads— exit 3: "PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. … Runpnpm buildfirst. This is NOT a pass: nothing was measured."pnpm check:type-check-debt— exit 3: "--re-measure cannot run: 13 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk … Build the closure first, exactly as lint.yml does before this step." Its--self-testleg ran and passed before the refusal.
Both want a full-workspace build, which is CI's job rather than a per-package dependency closure — the same two NOT MEASURED results the reviewed round recorded, unchanged in kind by the merge.
One further note for the record: node scripts/check-plugin-teardown-shape.mjs --self-test exited 1 once inside the batch, refusing with "cannot read the positive control" and declining to print a verdict. Re-run unhurried on the same tree it exits 0 with all 47 cases passing, and the clone is not shallow and does hold the pinned fixture commit — a transient read under load, not a finding.
Generated by Claude Code
Resolves the third and last conflict of the #14062 family. Both siblings have landed on main (#15147 service-knowledge, #15152 service-automation) and all three touch the same two gate scripts. Resolution is "keep every entry" -- the three cards do not disagree: * scripts/check-type-check-coverage.mjs: all three graduation paragraphs kept above `DEBT` (service-storage first, so the service-knowledge paragraph's "the paragraph above ... for `metadata` and `service-storage`" back-reference still resolves), and all three DEBT entries deleted. The ledger summary line is computed at runtime from DEBT/TEST_DEBT, so no hand-edited total exists there to reconcile. * scripts/check-type-source-resolution.mjs: auto-merged; all three new registry entries present, service-cluster / service-i18n untouched. * pnpm-lock.yaml: not hand-merged. `pnpm install` over the merged manifest set leaves the auto-merged file byte-identical (hash 000bb7d...) and `pnpm install --frozen-lockfile` exits 0. The diff against main is unchanged from the reviewed one: same 20 files, same +307/-133. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
…the TS2341 x3 it hid (#15152) * wip: onboard service-automation typecheck, fix TS2341 residue * wip: onboarding gate registry entry + changeset * fix(scripts): re-measure this entry's provenance totals on the merged tree The `service-knowledge` onboarding landed on `main` between this entry's first reading and this merge, so every absolute in its provenance block (programs, pairs, packages, clean count) was a number about a tree that no longer exists. Re-taken with `--list` on the merge commit itself, all four rows plus the before/after pair, by varying only what the `typecheck` script names: no `typecheck` script absent 120 programs / 293 pairs names tsconfig.json absent 120 programs / 293 pairs names tsconfig.test PRESENT 121 programs / 302 pairs names both (the card) PRESENT 121 programs / 302 pairs before 59 of 78 packages, 120 programs, 293 pairs, 19 clean after 60 of 78 packages, 121 programs, 302 pairs, 18 clean The deltas this block actually claims (+1 package, +1 program, +9 pairs, one per dep) are unchanged; only the absolutes moved, and the block now says which merge moved them. The sibling entries' own blocks keep their own historical readings untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y --------- Co-authored-by: Claude <noreply@anthropic.com>
…e-delivery rejections (objectstack-ai#15276) * fix(runtime): type the packages-domain `protocol` service handle so undeclared request keys are compile errors (objectstack-ai#15215) * wip(runtime): type the packages-domain protocol service handle Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * wip(runtime): add the packages-domain protocol handle typing pin Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * chore(changeset): patch note for the packages-domain protocol handle typing Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * docs(permissions): re-anchor the system-context census rows moved by the typing block Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * docs(permissions): regenerate the system-context census from the merged tree Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N --------- Co-authored-by: Claude <noreply@anthropic.com> * fix(objectql): publish the record's organization on every DataEvent (objectstack-ai#15220) * fix(objectql): publish the record's organization on every DataEvent (objectstack-ai#14970) `DataEventSchema.organizationId` was declared and published by the spec half but populated by nothing, so every `data.record.*` event went out with the key absent — which the contract requires a consumer to read as "this record is behind no organization wall". `publishDataEvent` now resolves it from the row itself: the written record on `created`, the post-state on `updated`, and the by-id branch's already-read pre-image on `deleted`, so no per-event read is bought. The record's organization, never `ExecutionContext.tenantId` — that is the caller's active org, and the two diverge on exactly the system/unscoped write this key most needs to label correctly. Absence keeps one spelling: the key is omitted, never `''` (which the schema refuses outright, dropping the whole event) and never an explicit `undefined` (which survives `parse` as a present key). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ * docs(permissions): re-anchor the system-context census after the engine line shift Mechanical repair by `node scripts/check-system-context-census.mjs --fix`, the only correct writer for this table. Pure line rot: the `eventOrganizationId` helper and its threading shifted every later line in `packages/objectql/src/engine.ts`, so 14 anchors (15 citation sites — one source line is cited twice) pointed at the wrong lines. No population and no classification change: still 106 elevation read sites in 20 packages across 45 files, all anchored; 140 anchors resolve, 27 declared non-read — the same figures as before the shift. `--fix` did not refuse, and the diff is digits and nothing else (12 lines added, 12 removed, identical once digits are stripped). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ --------- Co-authored-by: Claude <noreply@anthropic.com> * ci(reaper): arm the merged-branch reaper for scheduled deletion of claude/ branches (objectstack-ai#15224) * ci(reaper): arm the merged-branch reaper for scheduled deletion of claude/ branches Flips .github/workflows/merged-branch-reaper.yml from report-only to the scheduled weekly deletion the maintainer ruled on 2026-09-04 (issue objectstack-ai#12771, decision batch objectstack-ai#30), reaffirming the 2026-08-31 ruling under the base-ref guard PR objectstack-ai#15144 landed. Deletion is a SEPARATE job (`reap`), because `permissions:` is scoped per job. `sweep` keeps `contents: read` + `pull-requests: read` and remains structurally incapable of deleting a ref; `reap` holds the only `contents: write` in the file, consumes the `reapable` list `sweep` publishes as a job output, and computes no classification of its own. Fences: - `reap` never runs on `pull_request` — the self-exercising run stays a dry run — and its `if:` is an allowlist of `schedule` plus a `workflow_dispatch` on which the operator explicitly set `dry_run: false`. - the new `dry_run` workflow_dispatch input defaults to true, so the manual path is fail-closed. - the base-ref guard, `PREFIX`, `BASE_REF`, the grace window, the schedule and the `is-ancestor` prohibition are all untouched. - the whole deletion list is printed to the run log before the first delete. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk * test(reaper): pin the deletion hand-off and fence the delete job structurally The contract harness drives the `sweep` classifier and can say nothing about the job that deletes — deletion deliberately lives outside the extracted script, so what the harness judges stays a classification rather than an action. Two additions close that gap. 1. The hand-off. `sweep` now publishes `reapable_branches`, the machine-readable half of the list it prints, and `reap` consumes that and nothing else. Scenarios G1/G2/R1 pin that the list EQUALS the reapable bucket — same members, same order — over a population carrying one branch in every bucket, and mutations M13/M14 drive both directions red (held branches leaking in; the list not published at all). 2. The fence. `reapFenceFailures()` parses the shipped YAML and asserts the delete job's structure: its `if:` excludes `pull_request` and gates `workflow_dispatch` on `inputs.dry_run == false`; it declares `contents: write` and is the ONLY job in the file that does; the top-level grant stays `contents: read`; it still `needs: sweep`. New self-test battery 6 drives six mutations of the workflow text to red, each asserting its anchor was present first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk * ci(reaper): put every excluded bucket on the run-log audit line The maintainer's ruling names the run log as the audit trail, and the notice line named three of the seven buckets — reapable, mergedElsewhere, noPr. The other four (open, closedUnmerged, grace, protectedBranch) lived only in the step summary and the uploaded artifact, so the log alone could not answer "what did it hold back, and why". Also retires two strings that stopped being true when the reaper was armed: the summary heading said "DRY RUN. Nothing was deleted." of a run that may now delete in a later job, and the notice said "Nothing was deleted" of the whole run rather than of this job. Both now speak for the `sweep` job only, which is the thing they were ever really asserting — its token grant is `contents: read` and that has not changed. No classification changed: the buckets, the guard, the grace window and the step outputs are byte-identical. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --------- Co-authored-by: Claude <noreply@anthropic.com> * fix(service-automation): compile the test layer with tsc, and repair the TS2341 x3 it hid (objectstack-ai#15152) * wip: onboard service-automation typecheck, fix TS2341 residue * wip: onboarding gate registry entry + changeset * fix(scripts): re-measure this entry's provenance totals on the merged tree The `service-knowledge` onboarding landed on `main` between this entry's first reading and this merge, so every absolute in its provenance block (programs, pairs, packages, clean count) was a number about a tree that no longer exists. Re-taken with `--list` on the merge commit itself, all four rows plus the before/after pair, by varying only what the `typecheck` script names: no `typecheck` script absent 120 programs / 293 pairs names tsconfig.json absent 120 programs / 293 pairs names tsconfig.test PRESENT 121 programs / 302 pairs names both (the card) PRESENT 121 programs / 302 pairs before 59 of 78 packages, 120 programs, 293 pairs, 19 clean after 60 of 78 packages, 121 programs, 302 pairs, 18 clean The deltas this block actually claims (+1 package, +1 program, +9 pairs, one per dep) are unchanged; only the absolutes moved, and the block now says which merge moved them. The sibling entries' own blocks keep their own historical readings untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y --------- Co-authored-by: Claude <noreply@anthropic.com> * docs(platform-objects): widen sys_email.error description to cover pre-delivery rejections `sys_email.error` was declared as "Transport error message when status=failed", but since objectstack-ai#14371 EmailService.recordRejectedMessage also writes status=failed rows for messages rejected by normalizeMessage before they reach a transport (prefixed "rejected before delivery: ..."). The declared field help was narrower than what the column actually holds. Widen the description (wording settled in triage, issue comment 5504375428) and regenerate the platform-objects i18n bundle with its own tooling (node scripts/check-i18n-bundles.mjs --write) rather than hand-editing the generated file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ * chore(platform-objects): add changeset for sys_email.error description widening Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ --------- Co-authored-by: Litant Ying <litant.dev@proton.me> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: os-sales <sales@objectstack.ai>
Fixes #15048
packages/services/service-automationhad notypecheckscript at all (its scripts werebuildandtest), so no tsc program anywhere read this package. Turbo/CI typecheck lanes skipped it silently, because a zero-matching filter run exits 0.tsuptranspiles with esbuild andvitestruns through esbuild type-stripping; neither type-checks.Clause-②: no
The route, and which exemplar it was copied from
Copied from
plugin-webhooks/service-cluster(#14181, PR #15032), not fromplugin-auth/plugin-sharing/core, because it is the structural match. The deciding property is what the BUILD config does with tests:tsconfig.jsonexcludes tests?plugin-auth,plugin-sharing**/*.test.tsinexclude)core(#14916)**/*.spec.ts,**/*.test.ts)test-typecheck-debt.json, 4 residualplugin-webhooks,service-clusterservice-automationservice-automation'stsconfig.jsonincludes the tests and always did (include: ["src"], no test exclusion), so the program that would have read them already existed and was simply never invoked. It is therefore not a package that needs an exclusion compensated for, and AGENTS.md is explicit in the other direction: "Neverexclude*.test.ts/*.spec.tsfrom a package'stsconfig.json". Sotsconfig.jsonis untouched, and the siblingtsconfig.test.jsonis the family's uniform instrument over the same files.The sibling changes module semantics only (
module: esnext,moduleResolution: bundler,lib: ES2022, matching how vitest actually executes these files). Strictness is inherited and untouched, and it declares nopaths.Measured error count, before and after
Dependency closure built first (
pnpm --filter '@objectstack/service-automation^...' build), measured at2cc4610304(origin/main):tsc --noEmit -p tsconfig.json, which already included the tests)tsc --noEmit -p tsconfig.test.json)555 files in the program, covering all 103 of the package's
src/**/*.test.ts.The two readings agree, and that agreement is the load-bearing result — same as
service-cluster's own graduation, and unlike@objectstack/core(#14916: 98 undivided → 4 after the split, nearly all TS7006 cascading from one unresolved import). This package carried no config-tier pile at all; the 3 were genuinely code-tier from the start, well inside the dispatch's "a handful, fix them properly" branch, so no stop-and-report was owed.The three errors, and why the fix is correct rather than convenient
All three were
TS2341("Property 'flows' is private and only accessible within class 'AutomationEngine'"), all insrc/nested-region-parity.test.ts(lines 95/151/180):Three tests dot-read the class's private
flowsmap (engine.flows.get(name)) directly, instead of going through its own public accessor:That accessor already exists, and it is already the idiom every other test file in this package uses (
await engine.getFlow(name)— 10+ call sites acrossengine.test.ts,canonicalize-stored-flow.test.ts,flow-cold-boot-bind.test.ts,flow-load-conversion.test.ts, etc.). The fix is therefore not a workaround: it replaces three private-internals reads with the public surface the class already offers, matching the rest of the suite — no source signature widened, no cast, no bracket-notation trick.The two test bodies that were not already
asyncwere madeasynctoawaitthe accessor. Re-ran the file after the fix: 22/22 tests pass, unchanged behavior — the assertions test the same thing, only the read reaches it through the public door.Ledger consequences
DEBTentry deleted, not lowered.check-type-check-coverage.mjscarried'@objectstack/service-automation': { errors: 3, note: 'code-tier 3 (TS2341 x3)...' }. That gate's own invariant is "covered packages must not also sit in the ledger". The entry is deleted and the graduation recorded in the file's prose. Gate now reads 5 in DEBT where it read 6 (104 frozen errors, down from 114), on the merged tree.test-typecheck-debt.jsonis created, and its absence is the zero: any error in any file here is red immediately, with no entry to be added to. ⛔ No ledger was grown in either direction.check:type-source-resolution— a re-baseline, please review it as oneThis gate goes red on this diff as expected (the card's own⚠️ block flags this), and the remedy its failure text names (
paths) is measurably the wrong one here too — same finding asservice-cluster's own re-baseline, reproduced at larger scale because this package pulls 9 workspace deps instead of 2.Provenance, measured four ways on one checkout, varying only what
typechecknames:typechecknames--listtotalstsconfig.jsononlytsconfig.test.jsononlyRow 2 is the load-bearing one:
tsconfig.jsonis always a counted program per this gate's own design (programConfigsFor's doc-block), and it already includes every test file — yet it measures clean, with zero dist-resolved workspace type imports. So the exposure is only reachable through the onboardedtsconfig.test.jsonprogram, not merely first seen there. This package also had zero pre-existing programs (notypecheckscript at all), so there is no program a dep could be laundered through — the same clean caseservice-clusterreported.Numbers stated: before 59/78 packages, 120 programs, 293 pairs, 19 clean; after 60/78, 121, 302, 18 clean — +1 package, +1 program, +9 pairs (one per dep), this entry and nothing else.
Why the entry and not
paths, measured: redirecting the 9 deps to source takes this package's test layer from 0 errors to 648 (647TS6059"not under rootDir" + 1TS6133) — all 647 of the TS6059 land in another package's source, zero in this package's ownsrc/:packages/spec/src/**379,packages/core/src/**62,packages/plugins/plugin-security/src/**60,packages/objectql/src/**49,packages/services/service-messaging/src/**41,packages/metadata-core/src/**29,packages/formula/src/**15,packages/services/service-job/src/**6,packages/drivers/driver-sql/src/**6. Billed to packages that cannot pay them down — the sameservice-cluster(#14181: 0 → 435) and PR #12570 finding, reproduced at a larger scale because this package pulls more workspace deps.Gates
All measured figures in this section are the post-merge re-run on head
c6d25b73(merge commit8aff199c, merge base919beca43); the pre-merge readings they replace were taken at6a703bf42d, and the merge-round comment on this PR records both sets side by side.Package-level, on the merged head
c6d25b73:pnpm --filter @objectstack/service-automation typecheck— exit 0:check:test-typecheck: OK — @objectstack/service-automation's test layer compiles under packages/services/service-automation/tsconfig.test.json; 0 file(s) / 0 error(s).pnpm --filter @objectstack/service-automation test— exit 0: 103 test files / 1225 tests passed.pnpm check:type-check-coverage— exit 0:check-type-check-coverage: OK — 74/79 workspace packages type-checked (plus the root), 5 in the DEBT ledger (104 frozen raw errors), 1 exempt.pnpm check:type-source-resolution— exit 0 after the re-baseline:check-type-source-resolution OK — 121 tsc program(s) across 78 packages scanned; 60 registered as still resolving a workspace dep's types through dist/.pnpm check:nul-bytes— exit 0.Dispatch-derived gate list (
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, re-derived against the post-merge diff, final headc6d25b73), each exit code captured by redirect-then-read, never across a pipe: 64 commands — 62 exit 0, 2 exit 3 (NOT MEASURED), 0 red.NOT MEASURED, quoting each gate's own verdict:
check:dual-build-cjs-loads— exit 3: "PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. … Runpnpm buildfirst. ⛔ This is NOT a pass: nothing was measured."check:type-check-debt— exit 3: "--re-measure cannot run: 13 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk. … ⛔ This is NOT a pass and NOT a finding: nothing was measured."Both read the same missing prerequisite — a full-workspace
pnpm build, which is CI's job, not a per-package build closure. Three commands (check:pm-dispatch-gates,check:query-options-erasure,check:slot-lookup) needed longer than a 90s probe budget and passed on a second, unhurried run — each does a real full-tree scan, not a hang.Both edited gate scripts' own
--self-testare in that union and hold.Declined, with reasons
packages/services/*packages with the same gap — not touched.service-storagestill lacks atypecheckscript (DEBT entry 51);service-knowledgehas since graduated onmain(tooling(services):service-knowledgehas notypecheckscript — its test layer is compiled by no tsc program (#14062 family, sibling of #14181) #15049). The family-per-card pattern is deliberate (tooling(services):service-automationhas notypecheckscript — its test layer is compiled by no tsc program (#14062 family, sibling of #14181) #15048 is one of three siblings dispatched from [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).pathsrules added — measured at +648 (647 TS6059 + 1 TS6133) in other packages' source (above).@ts-expect-error/@ts-ignoreadded;strictuntouched.packages/specnot touched (single-owner lane).content/docs/releasesedit.Changeset
.changeset/service-automation-test-tsc-program.md,patchon@objectstack/service-automation.src/**is otherwise byte-identical (only the three private-read call sites in one test file changed, same assertions), so no shipped behaviour moves; the level reflects the publishedpackage.jsongainingtypecheck/check:test-typecheckscripts and atsxdevDependency.🤖 Generated with Claude Code
https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
Generated by Claude Code