Skip to content

fix(devx): graduate the eight */scripts i18n-extract directories into a tsc program - #11786

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-11351-scripts-debt-graduation
Aug 24, 2026
Merged

fix(devx): graduate the eight */scripts i18n-extract directories into a tsc program#11786
os-steve merged 1 commit into
mainfrom
claude/issue-11351-scripts-debt-graduation

Conversation

@claude

@claude claude Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Fixes #11351

Sub-part (2) of the card — graduation, not a note rewrite. Each of the eight
packages gains a sibling tsconfig.scripts.json named in its typecheck
script, so scripts/i18n-extract.config.ts is compiled for real, and each
UNCHECKED_SOURCE_DEBT entry is deleted in the same change. A shrink, never a
raise: no ledger entry is added, and nothing is silenced to reach zero — no
@ts-expect-error, no skipLibCheck widening, no strict loosening.
Strictness and module semantics are inherited untouched in all eight.

Per-directory measurement — the deliverable

Compiled with the package's own config against a built dependency closure, at
4405db882. Both rootDir variants measured, because the right value is not
uniform:

directory rootDir: "." rootDir inherited shipped graduated
packages/platform-objects/scripts 0 1 x TS6059 "." yes
packages/plugins/plugin-approvals/scripts 0 0 inherited (../..) yes
packages/plugins/plugin-audit/scripts 0 0 inherited (../..) yes
packages/plugins/plugin-security/scripts 0 0 inherited (../..) yes
packages/plugins/plugin-sharing/scripts 0 1 x TS6059 "." yes
packages/plugins/plugin-webhooks/scripts 0 1 x TS6059 "." yes
packages/services/service-messaging/scripts 0 1 x TS6059 "." yes
packages/services/service-realtime/scripts 0 1 x TS6059 "." yes

All eight measure 0, so all eight graduate and no note needed rewriting. The
counts the deleted notes carried (3 x TS2883, 1 for platform-objects) were
already stale: #10868 drove them to zero by annotating the nine configs'
default export, verified here by content on origin/main rather than from
the closed issue — all nine carry const config: ObjectStackDefinition = defineStack({.

rootDir is deliberately not uniform. plugin-approvals, plugin-audit and
plugin-security already widen it to ../.. in tsconfig.json to carry a
paths redirect of a sibling package to source, so the inherited value already
contains scripts/. Overriding it to "." also measures 0 today and is still
wrong: it would re-narrow the root below the redirected source, so the first
script here that reaches that sibling would report TS6059 about the check
rather than about the code.

packages/spec/tsconfig.scripts.json is the precedent for the idea, not the
shape to copy.
Its allowImportingTsExtensions, module: esnext,
moduleResolution: bundler, DOM lib and exclude are argued in its own
header as things that package needs; none of the eight needs any of them,
because these configs already spell their relative imports with .js. The
shape copied here is the minimal one — packages/objectql (#10756) and
packages/plugins/plugin-auth (#10869).

Non-vacuity, both directions

The gate sees each graduated directory — all eight, each its own control.
Per directory: append an exported const … : number = 'not a number', confirm
the mutation on disk (marker grep -c 0 → 1 and sha256 changed), run that
package's own typecheck script, restore, confirm sha256 byte-identical and
marker back to 0. All eight went red with error TS2322 on
scripts/i18n-extract.config.ts, exit status 2. All eight restored
byte-identical. The whole harness ran under trap … EXIT INT TERM.

The main program really was blind — the paired control. Same mutation, same
tree, two programs: tsc --noEmit (the config that existed before this PR)
reports 0; tsc --noEmit -p tsconfig.scripts.json reports 1. Run for
one package of each rootDir variant (plugin-sharing, plugin-audit). That is
what makes deleting the entries earned rather than asserted.

The ledger shrank by exactly the number graduated. Same command, pristine
origin/main worktree at 5cb62d88b vs this branch, diffed:

- source layer: 9 directory(ies) ... in 9 ledgered entr(y/ies) ... (9 files ...)
+ source layer: 1 directory(ies) ... in 1 ledgered entr(y/ies) ... (1 files ...)

Every other line of the summary is byte-identical — 65/78 covered, 13 DEBT / 436
frozen errors, test layer 19 packages / 1066 files / 1463 errors, generated
layer unchanged. The one surviving entry is packages/cli/test, which is
untouched.

A deletion without the repair is refused. Measured, not asserted: with
plugin-approvals' tsconfig.scripts.json removed and its typecheck reverted
while its entry stays deleted, the gate goes red naming exactly
packages/plugins/plugin-approvals/scripts. The one outcome that would make
this PR worse than doing nothing is mechanically unreachable.

packages/services/service-storage is deliberately out of scope

It is the ninth config's package and it is in no line of the ledger, for a
structural reason: SOURCES_COVERED only asks its question of a package that
declares a typecheck script, and service-storage declares none. It is covered
instead by DEBT['@objectstack/service-storage'], which records 51 errors
— so giving it a typecheck script is a 51-error burn-down, not a one-line
graduation. Wiring one that ran only tsconfig.scripts.json would be worse
than leaving it: COVERED would start passing on a script that never reads
src, and RECONCILED would then force out a 51-error DEBT entry whose
errors are all still there. It graduates with that entry, not before it. This
is recorded in the ledger header so the next author does not re-derive it.

Gates

Derived from the real change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (17 paths at 4405db882), then run. Verdicts
quoted from each gate's own output, exit codes captured before any pipe:

  • check:type-check-coverageOK — 65/78 workspace packages type-checked
  • check:type-check-debtOK — 32 ledger entr(ies) re-measured in 238.3s, 1898 raw tsc error(s) total, none above its recorded number
  • check:published-files, check:type-source-resolution, check:test-source-alias, check:slot-lookup, check:override-consistency, check:parse-guard, check:entry-guard, check:pnpm-filter-targets, check:cross-package-test-inputs, check:agent-test-spelling — all exit 0
  • check:i18nOK (9 package(s) — all bundles in sync, no undeclared authoring keys). Convention-triggered (this change touches packages owning an i18n-extract.config.ts); its first run reported PREREQUISITE NOT MET — the workspace CLI is not built, which is a refusal to measure, not a verdict, and it passes once @objectstack/cli is built.
  • check:nul-bytesOK (scanned 6580 text file(s) ... no raw ASCII control bytes)
  • The eight packages' own typecheck — all green, with tsc --noEmit && tsc --noEmit -p tsconfig.scripts.json echoed 8/8 in the log, so this is a real run and not a zero-match filter.

check:type-check-debt reports one informational surplus — plugin-approvals
TEST_DEBT records 348, tsc now reports 347. Not this PR's: that number was
frozen on 2026-07-31 (#4324) and the package has taken several test-touching
commits since, and it is already filed as #11497. Not lowered here — that is
that card's call, and lowering needs its own attribution.

No changeset: this releases nothing (CI-internal gate script, dev-only
tsconfigs, scripts.typecheck fields; files is ["dist", …] in all eight, so
nothing new ships). Same shape as #10876, which graduated packages/objectql
without one. skip-changeset applied.

Generated by Claude Code


Generated by Claude Code

…c program

Each of the eight packages gains a sibling `tsconfig.scripts.json` named in
its `typecheck` script, so `scripts/i18n-extract.config.ts` is compiled for
real, and its `UNCHECKED_SOURCE_DEBT` entry is deleted in the same change --
the shrink the ledger's own RECONCILED direction forces.

All eight measure 0 errors. The notes they carried recorded a TS2883 count
that #10868 had already driven to zero by annotating the nine configs'
`default` export; #10868 could not graduate them, because a directory in no
tsc program does not graduate by itself.

`rootDir` is deliberately not uniform: five packages inherit `rootDir: "src"`
and widen it to `"."`; plugin-approvals, plugin-audit and plugin-security
already widen it to `"../.."` to carry a `paths` redirect of a sibling
package to source, so the inherited value already contains `scripts/` and
overriding it would re-narrow the root below the redirected source.

`packages/services/service-storage` is deliberately left out: it declares no
`typecheck` script and is covered by a 51-error DEBT entry, so wiring one is
a burn-down rather than a graduation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 24, 2026
@github-actions github-actions Bot added size/m dependencies Pull requests that update a dependency file labels Aug 24, 2026

Copy link
Copy Markdown
Collaborator

ACCEPT. Verified by content on origin/main, not by reading the report back.

Non-vacuity 4 is the one that makes this PR safe, and it is the one I asked for

I wrote in the dispatch:

A ledger entry deleted without the directory actually entering a tsc program is the one outcome that would make this PR worse than doing nothing.

You made that outcome mechanically unreachable: removing plugin-approvals' tsconfig.scripts.json and reverting its typecheck while the entry stays deleted reds the gate, naming exactly packages/plugins/plugin-approvals/scripts. That is not a check that the change is correct — it is a check that the change cannot be half-done later, which is a stronger property and not one I asked for by name.

And non-vacuity 2 is what makes the eight deletions earned rather than asserted: same mutation, same tree, tsc --noEmit (the pre-PR config) reports 0 while tsc --noEmit -p tsconfig.scripts.json reports 1, run for one package of each rootDir variant. Without that paired control, "the new config is green" is equally consistent with the new config seeing nothing at all — which is precisely the defect class this repo keeps finding.

Non-vacuity 3 closes it from the ledger side: BASE vs branch, the only differing line is the source-layer count 9 → 1, every other figure byte-identical (65/78 covered, 13 DEBT / 436 frozen, test layer 19 pkgs / 1066 files / 1463 errors). A consolidation that moved a number it should not have would show up there, and none did.

All four Zone 2 assumptions came back with an answer

#10868 landed — verified independently: 9 of 9 i18n-extract.config.ts files carry const config: ObjectStackDefinition = defineStack(. Not one missing.

packages/spec is NOT a copyable precedent — my Zone 2 worry was right and you confirmed it by reading. Its tsconfig.scripts.json is a bespoke config for a bespoke surface: 29 scripts/**/*.test.ts that vitest genuinely runs, plus ~48 files that are the gates (build-schemas.ts producing every published JSON Schema, build-openapi.ts, check-liveness.mts, check-strictness-ledger.mts). Copying its allowImportingTsExtensions / esnext / bundler / DOM lib / exclude eight times would have propagated settings that exist for that package's reasons. Taking the minimal objectql + plugin-auth shape instead is the right read. The card cited spec as the supported repair and it is — as a pattern, not as a file to copy.

service-storage is a scope trap, and your evidence is stronger than my lean. I said leave it out and make the case if it is genuinely one line. You found the actual hazard: it carries a package-level DEBT entry, so wiring a typecheck script there is a burn-down, not a wiring change — and a scripts-only one would let RECONCILED force out that 51-error entry. That is a mechanism, not a size estimate, and it turns "probably out of scope" into "actively harmful". I would have accepted the one-line case if you had made it; you found the reason not to.

The count is eight — confirmed on today's main: 8 */scripts entries, with packages/cli/test as the survivor you name.

⚠️ My own instrument, again: my ledger extraction read 25 entries because the range over-captured past UNCHECKED_SOURCE_DEBT into neighbouring structures (next-env.d.ts, .next/types are plainly not it). Fourth time today the same class — a range extractor whose terminator does not match the declaration's actual shape. The */scripts = 8 and the named survivor are what I am relying on, and both match.

The per-directory table answered a question the card could not

rootDir='.' → 0 errors for all eight. rootDir inherited → 0 for plugin-approvals / plugin-audit / plugin-security (their tsconfig.json already widens to ../.. for a paths redirect) and 1 × TS6059 for the other five. So the shipped configs override rootDir only where it is needed and let three inherit — a per-package decision made on a per-package measurement, which is exactly what the card's warning ("some of these packages may surface other errors once really compiled") was asking for. All eight qualified, so no fallback to sub-part (1) and no note rewritten — sub-part (2) made (1) moot, as the card predicted.

Both gate refusals resolved rather than read as verdictscheck:i18n's PREREQUISITE NOT MET — the workspace CLI is not built, and check:type-check-debt's refusal naming @objectstack/service-knowledge as having no built type entry point. Building the one named package rather than the repo is the proportionate response, and "no local scope was narrowed away: every gate the derivation named was run to a real verdict" is the claim I check for and rarely see stated.

#11497: right call. You searched before filing, found it already carries the plugin-approvals TEST_DEBT −1 (with runtime −1), and did not file a duplicate — and did not lower the number either, since that is #11497's call and a lowering needs its own attribution. Confirming it is not this PR's (frozen 2026-07-31 in #4324, test-layer headline byte-identical between BASE and branch) is the part that makes leaving it alone defensible rather than lazy.

Flipping to ready. Arming once every check is green.

⚠️ Note on timing: the shared identity's GraphQL quota is currently exhausted for enable_pr_auto_merge (recorded on #11742) — issue_write and comments still work, arming does not. This PR may sit ready-but-unarmed briefly; a check-in retries it.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 8 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 8 changed file(s) yielded no anchor (packages/platform-objects/tsconfig.scripts.json, packages/plugins/plugin-approvals/tsconfig.scripts.json, packages/plugins/plugin-audit/tsconfig.scripts.json, …) — pages documenting those are invisible to this run

Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8b41bcd85160ac1c475d26b2968d74f84954bbfapackageMentionDocs.

@os-steve
os-steve marked this pull request as ready for review August 24, 2026 16:40
@os-steve
os-steve added this pull request to the merge queue Aug 24, 2026
Merged via the queue into main with commit cb954f1 Aug 24, 2026
36 checks passed
@os-steve
os-steve deleted the claude/issue-11351-scripts-debt-graduation branch August 24, 2026 17:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] The eight */scripts UNCHECKED_SOURCE_DEBT entries are graduation candidates once #10868 lands — and their notes still say "3 x TS2883"

1 participant