build(rest): give the package a test-layer tsc program and ledger its 37 errors - #12570
Merged
Merged
Conversation
… 37 errors `packages/rest/tsconfig.json` excluded `**/*.test.ts` and `typecheck` was `tsc --noEmit` against that config, so no tsc program compiled any of the package's 149 test files and both `@ts-expect-error` directives in that layer were phantom checks. Adopts the #5286 sibling route: `tsconfig.test.json` with vitest-matching module semantics and untouched strictness, named by the `typecheck` script through the shared `check-test-typecheck.mts` gate. No test file is edited; the 37 errors the program finds (13 files) are recorded per file in `test-typecheck-debt.json`, EXACT and shrink-only. Two mechanical consequences, both required by gates rather than chosen: `@objectstack/rest` graduates out of TEST_DEBT (155 raw, re-measured 37), and its `check:type-source-resolution` registry entry gains the six workspace deps the new program reaches, with the program-set numbers stated in place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-litant
marked this pull request as ready for review
August 26, 2026 11:25
This was referenced Aug 26, 2026
This was referenced Aug 28, 2026
This was referenced Sep 1, 2026
baozhoutao
pushed a commit
that referenced
this pull request
Sep 2, 2026
…sc program check:type-check-coverage's isUncheckedSourceCandidate skipped depth === 0 (the package root) unconditionally, so a package-root .ts file was invisible to SOURCES_COVERED regardless of content. This is why #13284's driver-memory / plugin-hono-server manifests went unchecked for as long as they did. Per the triage on #14386 (comment 5504408509), this admits depth === 0 only for a declared, exact-name allowlist (ROOT_SOURCE_FILES, currently just objectstack.config.ts) rather than every package-root file -- the wider 104-file question stays explicitly unresolved. The uncheckedByDir aggregation now keys a root-level file at '.' (which posix.join collapses to the package's own directory) instead of the rel.slice(0, -1) garbage key the old indexOf('/') === -1 arithmetic produced. The three sites the widened predicate then surfaces (plugin-auth, plugin-security, service-i18n) are put into a program: widened include on the existing sibling noEmit programs for the first two, a new sibling tsconfig.typecheck.json (following the driver-memory shape from #13284) for service-i18n, which had none to widen. Putting service-i18n's manifest into a program onboards that package's first tsc program to reach the bare @objectstack/spec specifier (src/ only ever imports subpaths), which check:type-source-resolution correctly flagged. Repaired via that gate's own documented onboarding-limb registry re-baseline (the sanctioned tool for a dep reached only through a newly-onboarded program -- paths is measured wrong for this shape on PR #12570), with --list before/after numbers stated in place. Fixes #14386
os-sales
pushed a commit
that referenced
this pull request
Sep 3, 2026
… for the onboarded test program Onboarding a `tsconfig.test.json` moves this package's tsc PROGRAM SET, which `check:type-source-resolution` judges per program. `service-cluster` had NO `typecheck` script before, so it ran zero counted programs; both deps the gate now reports are reached only through the program this change added. Taken on the onboarding limb the registry's own doc-block opens, on its three stated terms. Provenance measured four ways by varying only what `typecheck` names: absent with no script, absent naming `tsconfig.json` alone, PRESENT naming `tsconfig.test.json`. The build program carries no dist-resolved workspace type import at all, so the exposure is only reachable through the onboarded program rather than merely first seen there. `--list` before 57/78 packages, 118 programs, 288 pairs; after 58/78, 119, 290. +1 package, +1 program, +2 pairs -- this entry and nothing else. `paths` was measured rather than argued and is the wrong tool here: redirecting the two deps to source takes the test layer from 0 errors to 435, all TS6059 and all in `packages/spec/src` and `packages/core/src` -- another package's diagnostics billed to one that cannot pay them down (PR #12570, #8021). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
hotlong
pushed a commit
that referenced
this pull request
Sep 3, 2026
…st tsc program (#15004) `check:type-source-resolution` went red on the pin: `@objectstack/runtime`, `@objectstack/objectql` and `@objectstack/plugin-security` were newly reached by `tsconfig.test.json` and resolved through each package's `exports` map to `dist/index.d.ts` — a build artifact. For a pin whose whole subject is the readers the reader program is about to CHANGE, a type verdict about the last `pnpm build` is the vacuous-green direction that gate exists to refuse. Fixed the way the gate prescribes for a dep exposed through an EXISTING program: `paths` rules, never a widening of its shrink-only registry. Three star-less (exact-match) rules, so `@objectstack/objectql/core` keeps resolving through the package's own `exports` map untouched. Measured, because the registry's own notes record `paths` billing other packages' diagnostics into the consumer's ledger (PR #12570): with the workspace closure built, `pnpm --filter @objectstack/cli check:test-typecheck` reports 3 files / 28 errors / 6 pinned signatures — byte for byte the pre-existing `test-typecheck-debt.json`. Zero new diagnostics. Also corrects the pin header's reverse-verification count to the measured 24 and records the `effect: 'writes'` -> `effect: 'pure'` row, a loss no presence-check would have found. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
This was referenced Sep 4, 2026
Merged
This was referenced Sep 5, 2026
os-warren
pushed a commit
that referenced
this pull request
Sep 5, 2026
The sibling gate `check:type-source-resolution` is red on the same specifier, and it is red at the PR head `b65fafc81` as well -- measured with both files of the alias fix-up reverted in place under a trap, then restored byte-identical. It was invisible only because `Lint & Repo Gates` halts at step 130 (`Test-source alias gate`) and skips 131-148; this gate is step 132, so unblocking 130 walks the job straight into it. Same defect class, same specifier, same round-1 import (`celEngine`), and the remedy the gate dictates: ONE `paths` rule for the bare name in the package's `tsconfig.json`, copied in shape from the `@objectstack/service-messaging` rule already beside it. `@objectstack/formula` publishes no subpath in its `exports`, so a subpath rule would match nothing on disk and fall back to node resolution. `tsconfig.test.json` inherits this map rather than declaring its own, which is what carries the rule to the test layer that reaches the specifier. Measured rather than assumed, because #12570 records this exact rule billing another package's source diagnostics into this ledger: - `check:type-source-resolution` 0 -- "125 tsc program(s) across 78 packages scanned; 61 registered as still resolving a workspace dep's types through `dist/`". - `pnpm --filter @objectstack/plugin-auth typecheck` 0, with `check:test-typecheck` reporting the ledger UNCHANGED at 10 file(s) / 94 error(s) / 23 pinned signature(s) -- nothing was added to it. - `tsc -p tsconfig.test.json --listFiles`: 15 `packages/formula/src/*.ts` files now in the program, 0 files under `packages/formula/dist/`, program 1243 -> 1257 files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
baozhoutao
pushed a commit
that referenced
this pull request
Sep 6, 2026
… the closure at the point of refusal The refusal names `paths` first. That remedy has a precondition it never stated: the consuming program's `rootDir` has to contain the dependency's source. Where it does not, tsc admits the dep's file graph and then reports TS6059 for all of it, billed to the consuming package's test-typecheck ledger — the PR #12570 shape, reached from the `paths` limb instead of the re-baseline one. Both the ledger and `rootDir` are maintainer-only, so at that point the author has no self-serve remedy and the message named none. The knowledge was already in this file — the header's with-`paths` red and the PR #12570 reading over the registry — and nowhere in the text an author reads. Written as a CONDITION, never a blanket closure: `paths` stays correct wherever the program's `rootDir` does contain the source, and a message overstating the closure would be worse than the current silence. The refusing sentence, the ⛔ SHRINK-ONLY testimony and the ONBOARDED exception are untouched, so `check-ratchet-remedy-authority` still classifies this gate as `refused`. Two self-test pins added for the new halves and the battery floor raised 10 -> 12 to match. Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8 Co-authored-by: Claude <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 9, 2026
… TS2322 it hid (objectstack-ai#15032) * fix(service-cluster): compile the test layer with tsc, repair the TS2322 it hid `packages/services/service-cluster` had no `typecheck` script at all, so no tsc program read the package: turbo/CI typecheck lanes skipped it silently (a zero-matching filter run exits 0), while tsup and vitest both type-STRIP. Its `tsconfig.json` does include the tests and always did — the program existed and was never invoked. That hid a TS2322 in `src/memory/memory.contract.test.ts`, the package's contract witness: a concise arrow body passed as a `PubSubHandler` returns `Array.prototype.push`'s `number` where the contract declares `void | Promise<void>`, and the void-return relaxation does not forgive a UNION target. Fixed with a block body — the handler is side-effect-only by contract. The spec contract is untouched. Wired by the route objectstack-ai#14062 settled for `packages/plugins/**`: a sibling `tsconfig.test.json` changing module semantics only (strictness inherited, untouched), named by a new `typecheck` script through `check:test-typecheck`. Measured 1/1 errors before (build semantics / new config — they agree, so no config-tier pile), 0/0 after, over a 410-file program covering all 7 test files. No `test-typecheck-debt.json` is added; its absence is the zero. The package's `DEBT` entry in `scripts/check-type-check-coverage.mjs` is deleted rather than lowered, which is the graduation that gate's own invariant requires. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8 * fix(service-cluster): re-baseline the type-source-resolution registry for the onboarded test program Onboarding a `tsconfig.test.json` moves this package's tsc PROGRAM SET, which `check:type-source-resolution` judges per program. `service-cluster` had NO `typecheck` script before, so it ran zero counted programs; both deps the gate now reports are reached only through the program this change added. Taken on the onboarding limb the registry's own doc-block opens, on its three stated terms. Provenance measured four ways by varying only what `typecheck` names: absent with no script, absent naming `tsconfig.json` alone, PRESENT naming `tsconfig.test.json`. The build program carries no dist-resolved workspace type import at all, so the exposure is only reachable through the onboarded program rather than merely first seen there. `--list` before 57/78 packages, 118 programs, 288 pairs; after 58/78, 119, 290. +1 package, +1 program, +2 pairs -- this entry and nothing else. `paths` was measured rather than argued and is the wrong tool here: redirecting the two deps to source takes the test layer from 0 errors to 435, all TS6059 and all in `packages/spec/src` and `packages/core/src` -- another package's diagnostics billed to one that cannot pay them down (PR objectstack-ai#12570, objectstack-ai#8021). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8 --------- Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 9, 2026
… through every entry path with the flattened collections absent (objectstack-ai#15039) * test(cli): the option-B acceptance pin — a two-package fixture booted through every entry path (objectstack-ai#15004) Reader program 1/4 of the ADR-0130 D4 option-B ruling (objectstack-ai#14512 comment 5528589044). Option B removes the flattened top-level collections from a multi-package artifact so `packages[]` carries each definition once; the ruled order is readers first, emitter last, and the failure mode that order exists to contain is a reader nobody enumerated — silent, because the collection is simply absent and nothing throws. This commit adds the pin that makes that loud, and nothing else: three new test files, zero production files. - `test/fixtures/option-b-collection-zoo.ts` — two ordinary `defineStack` packages carrying one member of every collection family, composed with `manifest: 'preserve'`, in both shapes. The key set separating the shapes is DERIVED from `ObjectStackDefinitionSchema` ∩ `AssembledPackageBodySchema` rather than transcribed, so a collection family added later joins the probe automatically. - `test/fixtures/option-b-reader-probe.ts` — the instrument. Every row calls a reader this repo ships, or boots a real kernel carrying the real `AppPlugin` and reports what that plugin handed to a subsystem. No row reads `bundle.<collection>` and calls it a measurement. - `test/option-b-reader-acceptance.pin.test.ts` — the pin. `OPTION_B_LOSSES` records exactly which subsystems lose their collection today; the assertion is set equality, shrink-only, audited in both directions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m * test(cli): resolve the pin's three workspace deps to SOURCE in the test tsc program (objectstack-ai#15004) `check:type-source-resolution` went red on the pin: `@objectstack/runtime`, `@objectstack/objectql` and `@objectstack/plugin-security` were newly reached by `tsconfig.test.json` and resolved through each package's `exports` map to `dist/index.d.ts` — a build artifact. For a pin whose whole subject is the readers the reader program is about to CHANGE, a type verdict about the last `pnpm build` is the vacuous-green direction that gate exists to refuse. Fixed the way the gate prescribes for a dep exposed through an EXISTING program: `paths` rules, never a widening of its shrink-only registry. Three star-less (exact-match) rules, so `@objectstack/objectql/core` keeps resolving through the package's own `exports` map untouched. Measured, because the registry's own notes record `paths` billing other packages' diagnostics into the consumer's ledger (PR objectstack-ai#12570): with the workspace closure built, `pnpm --filter @objectstack/cli check:test-typecheck` reports 3 files / 28 errors / 6 pinned signatures — byte for byte the pre-existing `test-typecheck-debt.json`. Zero new diagnostics. Also corrects the pin header's reverse-verification count to the measured 24 and records the `effect: 'writes'` -> `effect: 'pure'` row, a loss no presence-check would have found. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m --------- Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 9, 2026
…, not the better-auth role scalar (objectstack-ai#15948) * fix(plugin-auth): session payload positions[] carries the security positions, not auth roles `customSession` built `user.positions` from a hand-rolled union — the better-auth `sys_user.role` scalar split on commas, the active membership mapped to `org_*`, and `platform_admin` — and read nothing from `sys_user_position`, the ADR-0057 D4 table that is the source of truth for custom positions. The Console binds that array straight through as the CEL root `current_user` (objectui `expressionUser.ts`: `positions: user.positions ?? []`), so an `action.visible` narrowed by a business position answered FALSE for everyone, including its holder — silently, because the root and the key were both bound and CEL raised nothing. `EvalUserSchema` had declared the opposite all along: `positions` is "built-in identity names + position names", exposed to "every predicate surface ... with an identical shape" so a predicate "evaluates identically wherever it is written". The payload was violating a declared contract, not merely carrying an ambiguous name. The documented example survived because `org_admin` sits on both axes — the one name that could not reveal the split. The derivation is deleted rather than repaired: `resolve-authz-context.ts` states that every entry point must resolve authorization through it and never re-read the `sys_*` grant tables itself, and this callback was doing exactly what that forbids. It now asks `resolveUserAuthzGrants` — the same authority `/auth/me/permissions` is served from and every server-side evaluator resolves `ExecutionContext.positions` through — scoped to the session's active organization. `isPlatformAdmin` is derived from that array (ADR-0068 D2 defines it as an alias of `'platform_admin' in positions`), so one authority answers both and cannot disagree with itself. The better-auth role scalar is not lost: `user.role` stays on the payload verbatim, which is the ADR-0090 D3 documented exception for third-party schema. Fails closed and warns on an unreadable grant store, matching what both halves of the old derivation already did silently. Refs objectstack-ai#15136 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * test(plugin-auth): migrate the four fixtures that pinned the removed auth-axis derivation They asserted `positions[]` WAS the better-auth `sys_user.role` scalar split on commas — the derivation the objectstack-ai#15136 ruling removed — so they are fixtures to migrate, not evidence against it. Each now asserts the scalar is absent from the array while remaining untouched on the payload, which is the half of ADR-0068 D2 that did not change. One case is added for the axis the payload was missing entirely: a `sys_user_position` assignment reaching `positions[]`. Also declares in `packages/spec` which axis `positions` is, and that the better-auth role scalar is not it, with the regenerated reference page. Part of objectstack-ai#15136 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * docs(spec): register the ADR-0087 semantic entry for the positions[] meaning change A meaning change with no key move: nothing here can be found by grepping for a removed spelling, and there is no schema rejection to catch a stale reader, so the ledger row is the only channel that reaches an upgrader. The acceptance criteria name the discriminator the defect itself hid behind — `org_admin` sits on both axes and cannot tell them apart — and require the check be made against a real session by a name that exists on one side only. Part of objectstack-ai#15136 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * fix(plugin-auth)!: derive isPlatformAdmin from the posture RUNG, never from a name in positions[] Contract review found a privilege-escalation path this PR would have introduced. `positions.includes('platform_admin')` is the exact form `resolve-authz-context.ts` forbids at `hasPlatformAdminStanding`: an ADR-0057 D4 `sys_user_position` row may spell that very name, and a platform-RBAC assignment is not the ADR-0068 D2 capability grant. That read was defensible while `positions[]` carried the auth axis, where nothing a tenant writes could put the word there. Moving it to the security axis moved the string into a space a tenant can WRITE: `sys_user_position` is `apiEnabled`, a tenant-level admin passes the ADR-0090 D12 gate outright, and a delegate passes `assertAssignmentWrite`'s `boundSets.every(...)` vacuously for a position carrying no position-bound set. Measured on the real pipeline: a plain org member with such a row got `isPlatformAdmin: true` and passed `judgePlatformAdmin` — the `/admin/*` mount gate — while the rung said false. Pre-fix all three answered false, so the disagreement was introduced here. Both readers of the old "name implies rung" equivalence are re-bound: - `auth-manager.ts` derives the alias from `grants.posture === 'PLATFORM_ADMIN'` on the same envelope, which is byte-for-byte what `hasPlatformAdminStanding` returns, so payload, predicate and gate cannot disagree. - `platform-admin-gate.ts` drops its positions leg. That line is pre-existing, but this PR is what makes it dangerous, so it is in scope here. Its fixture (`admin-ban-endpoints.test.ts`) is migrated per the ruling, which named that file's cases as fixtures to migrate, and gains a case refusing the escalation shape. New pin `session-platform-admin-rung-agreement.test.ts` drives a real session carrying a D4 row spelled `platform_admin` and requires all three predicates to agree on false, with a genuine unscoped `admin_full_access` grant as the control that they agree on true. It states its own population: it does NOT cover the validity-window or catalogue shapes, which are the consolidation suite's — that suite was green throughout the window this escalation was live, which is the lesson. Part of objectstack-ai#15136 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * docs(changeset): carve out the no-active-org membership projection, and correct the role-word attribution Two corrections the contract review's standing guard asked for. The changeset claimed "membership-derived names are unaffected" without qualification. Measured: for a session carrying no active organization the resolver projects every membership, so those names are now ADDED where the old derivation contributed none — `[]` becomes `[org_admin, everyone]`. Pinned rather than merely asserted; the first attempt at that pin failed because the default fixture DOES stamp an active org, which is a different branch. The deviation rationale attributed the identifier ban to `check:role-word`. That gate ratchets the reserved word in docs prose; the identifier ban over authored metadata lives in `packages/lint`. A TypeScript payload key trips neither mechanically until documented, so the ADR-level prohibition is what rules — the conclusion is unchanged, the wording was wrong. Also names the new three-way-agreement pin beside PIN 6, and scopes PIN 3's reference to the session's active organization: unscoped, it agreed only because the fixture has one org, and a scoping regression would have passed it. Part of objectstack-ai#15136 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * test(plugin-auth): migrate the /admin/sso/* bridge fixtures off the removed positions leg Same species as the fixture the ruling named: the platform-admin session shape stood for a real payload back when the gate admitted on the `platform_admin` NAME. It carries the rung-derived alias now, because that is what the gate reads. `positions` keeps the name, since a genuine platform admin really does have it projected — which is exactly why the name alone cannot be the signal. Adds the escalation shape as a refused case on all four bridges. Without it, restoring the array leg would reopen these operator routes and every existing case would still pass. Part of objectstack-ai#15136 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * fix(plugin-auth): widen the customSession dataEngine double to its seam's arity `Type Check · workspace` (run 33978981028) failed `@objectstack/plugin-auth#typecheck` on one ARRIVED signature: TS2554 "Expected 1 arguments, but got 2" at src/auth-manager.test.ts:3543, where the `sys_user_position` case overrides `makeDataEngine`'s `find` and delegates `inner(object, q)` to a double declared one-parameter, `vi.fn(async (object: string) => ...)`. CHOSE (a) widen the double; NOT (b) drop the second argument at the delegation. The double stands in for `IDataEngine.find(objectName, query?, options?)` (packages/spec/src/contracts/data-engine.ts:259), and every production read that reaches this fake goes through `resolve-authz-context.ts` `tryFind`, which always calls `ql.find(object, { where, limit, context })` — two arguments, never one. (b) would also have typechecked, by making the delegation call `inner(object)`. That is worse: it fixes the compiler by teaching the double a call shape production never produces. `find` here is a `vi.fn`, so its recorded calls are assertable, and the tenant-scoped `context` `tryFind` threads is exactly the kind of claim a future test would assert over `engine.find.mock.calls` — against a shape that cannot occur. A delegation that silently drops an argument the seam passes is how a double stops expressing the condition it exists to test. Scope: this one type error. No production file touched — the round-2 Clause-(2) contract review's design stands unmodified. Nothing added to test-typecheck-debt.json (MAINTAINER-ONLY, ratchets down only); the gate reports the ledger unchanged at 10 file(s) / 94 error(s) / 23 pinned signature(s). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * test(plugin-auth): satisfy check:test-source-alias on the positions-axis pin `check:test-source-alias` is the only step making `Lint & Repo Gates` red on this PR: job 101340631783 fails at step 130 and skips steps 131-148, so the merge-driver gate that reds other PRs on this seat never runs here. Base-branch control: the same gate exits 0 on `origin/main` f7db8f4, and the test file is absent from that ref, so the redness is this PR's. Both findings are remedied exactly as the gate's own text dictates. 1. `session-positions-security-axis.test.ts:235` paid `import('@objectstack/core')` inside an `it()` body -- a CLOCKED window, and `@objectstack/core` is one of the specifiers this package still resolves through `dist/`. A module-top side-effect import pays that transform during collection, which vitest clocks against nothing. The dynamic call stays exactly where it is, and no timeout was widened -- widening relocates the cliff to the next heavier shard. 2. `@objectstack/formula` was a NEW unaliased artifact import for this package (`celEngine`, the real CEL engine the pins evaluate `current_user.positions` with). One anchored entry in the package's `vitest.config.ts` resolves it to `packages/formula/src/index.ts`, which exists in the checkout. `KNOWN_UNALIASED_TEST_IMPORTS` is shrink-only, so no registry row was added or widened; plugin-auth's existing entry is untouched. Verified: `check:test-source-alias` --self-test and the real run both exit 0, the real run reporting the same counts as the base-branch control (72 packages scanned / 61 registered / 49 published subpaths). `pnpm --filter @objectstack/plugin-auth test` still 102 files / 2142 tests passed, and the package's full `typecheck` task is green with its test-typecheck ledger unchanged at 10 file(s) / 94 error(s) / 23 pinned signature(s). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * test(plugin-auth): resolve @objectstack/formula's TYPES to source too The sibling gate `check:type-source-resolution` is red on the same specifier, and it is red at the PR head `b65fafc81` as well -- measured with both files of the alias fix-up reverted in place under a trap, then restored byte-identical. It was invisible only because `Lint & Repo Gates` halts at step 130 (`Test-source alias gate`) and skips 131-148; this gate is step 132, so unblocking 130 walks the job straight into it. Same defect class, same specifier, same round-1 import (`celEngine`), and the remedy the gate dictates: ONE `paths` rule for the bare name in the package's `tsconfig.json`, copied in shape from the `@objectstack/service-messaging` rule already beside it. `@objectstack/formula` publishes no subpath in its `exports`, so a subpath rule would match nothing on disk and fall back to node resolution. `tsconfig.test.json` inherits this map rather than declaring its own, which is what carries the rule to the test layer that reaches the specifier. Measured rather than assumed, because objectstack-ai#12570 records this exact rule billing another package's source diagnostics into this ledger: - `check:type-source-resolution` 0 -- "125 tsc program(s) across 78 packages scanned; 61 registered as still resolving a workspace dep's types through `dist/`". - `pnpm --filter @objectstack/plugin-auth typecheck` 0, with `check:test-typecheck` reporting the ledger UNCHANGED at 10 file(s) / 94 error(s) / 23 pinned signature(s) -- nothing was added to it. - `tsc -p tsconfig.test.json --listFiles`: 15 `packages/formula/src/*.ts` files now in the program, 0 files under `packages/formula/dist/`, program 1243 -> 1257 files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y --------- Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 9, 2026
… the maintainer-only closure at the point of refusal (objectstack-ai#16257) * docs(check-type-source-resolution): name the `paths` precondition and the closure at the point of refusal The refusal names `paths` first. That remedy has a precondition it never stated: the consuming program's `rootDir` has to contain the dependency's source. Where it does not, tsc admits the dep's file graph and then reports TS6059 for all of it, billed to the consuming package's test-typecheck ledger — the PR objectstack-ai#12570 shape, reached from the `paths` limb instead of the re-baseline one. Both the ledger and `rootDir` are maintainer-only, so at that point the author has no self-serve remedy and the message named none. The knowledge was already in this file — the header's with-`paths` red and the PR objectstack-ai#12570 reading over the registry — and nowhere in the text an author reads. Written as a CONDITION, never a blanket closure: `paths` stays correct wherever the program's `rootDir` does contain the source, and a message overstating the closure would be worse than the current silence. The refusing sentence, the ⛔ SHRINK-ONLY testimony and the ONBOARDED exception are untouched, so `check-ratchet-remedy-authority` still classifies this gate as `refused`. Two self-test pins added for the new halves and the battery floor raised 10 -> 12 to match. Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8 Co-authored-by: Claude <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(check-type-source-resolution): record the re-measured `paths` -> TS6059 leg Measured on this branch rather than carried over from the filing: the runtime test program goes 191 -> 204 errors under the `paths` remedy, +13, all TS6059 in `packages/services/service-realtime/src/**`, no new code error. Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8 Co-authored-by: Claude <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #12542
packages/rest/tsconfig.jsonexcludes**/*.spec.tsand**/*.test.ts, and thepackage's
typecheckscript wastsc --noEmitagainst that config and nothingelse. So no tsc program compiled a single test file in the package — the
shape AGENTS.md names twice ("never exclude
*.test.ts", "a@ts-expect-errorin a file no tsc program compiles is a phantom check"), sitting live in one of
the repo's largest packages.
Premise, re-derived rather than inherited
At
5fbd58e0d(my branch base; the card measured at52a982388):include/exclude["src/**/*"]/["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]typecheckscripttsc --noEmit, that program and nothing elsegit ls-tree -r --name-only HEAD -- packages/rest | grep -cE '\.test\.ts$').spec.tsfiles**/*.spec.tshalf of the exclusion covers nothingsrc/?includetsconfig.test.json/test-typecheck-debt.jsoncheck:test-typecheckwiredThe card's numbers hold exactly, at both shas. Only the
.spec.tsline is anaddition: the exclusion's second glob was already inert.
What landed — an adoption, not an invention
packages/rest/tsconfig.test.jsonfollowspackages/client's andpackages/spec's landed shape: module semantics only, pluslib, withstrictness inherited and untouched. It is named by the
typecheckscriptthrough the shared
scripts/check-test-typecheck.mtsgate, because a config noscript invokes is exactly the phantom this change is about:
tsxjoinsdevDependenciesfor the same reasonpackages/clientandpackages/speccarry it — the shared gate runs through it.What the program INHERITED, and did not re-declare
packages/rest/tsconfig.jsonalready carries two#9960settings, bothload-bearing, and this file deliberately re-declares neither:
rootDir: ".."— already widened topackages/there, as a consequenceof the
pathsrule rather than a preference. So the TS6059 pile that forcedpackages/client's sibling config to widenrootDirdoes not arise here; itwas paid for already.
paths: { "@objectstack/metadata-protocol": [".../src/index.ts"] }— achild that declared its own
pathswould replace this map rather thanmerge into it, silently sending that specifier back to
dist/. This filedeclares no
pathsat all, so the rule stands and 22 of the producer's sourcefiles are in the test program exactly as they are in the build one.
The number nobody had (ruling 5), measured at
5fbd58e0d37 errors across 13 files, with the workspace closure built first
(
pnpm --filter '@objectstack/rest^...' build, exit 0).src/export-integration.test.tsNeither stop-and-report fork fires: 37 is above the "small,
client-scale"threshold and nowhere near "enormous", and it is not one producer-side defect
wearing many files' clothes — five codes across 13 files, the largest single
file holding 17.
Ruling 4's question, answered for all 37: would this error exist on
origin/mainif the program had always been there? Yes, for every one. Notest file is edited by this PR; the program differs from the build config only
in module semantics and
lib, and each of the 37 sits on a line that predatesthis branch. None is caused by the change, so all 37 are ledger entries in
packages/rest/test-typecheck-debt.json— EXACT and shrink-only.Why the 155 in TEST_DEBT became 37
Not a rescale — the old entry predicted it. 121 of its 155 were TS2835 plus the
implicit-any pile TS2835 causes, and 16 more were one
Array.prototype.atmessage against a
libolder than es2022. This package is"type": "module",so NodeNext was compiling extensionless relative imports as unresolvable ESM and
every symbol they named became
any. Under vitest-matching semantics TS2835x72 goes to 0, TS2550 x16 to 0, TS7006 x49 to 4. What is left is a different
shape, and it grew in one place while collapsing in four: TS18048 x13 is a class
the 155 never contained at all — "possibly undefined" reads that only become
visible once the imports above them resolve to real types. Sizing this off the
TS2835 line alone would have said "155 − 121 = 34" and been wrong in both
directions. That is the #8612 lesson, measured a second time.
No test file is edited (ruling 3), and the #12421 coupling
Zero test files are touched: the deliverable is the program plus the measured
ledger. PR #12421 holds
packages/rest/src/rest.test.ts, which carries 4 ofthe 37 and therefore has a ledger entry — which collides with nothing, where an
edit would have been a serial breach.
ledger is EXACT and shrink-only, so once #12421 merges: an error it adds to
rest.test.tsreds the ledger on its run ("the debt GREW"); an error itremoves reds the entry as stale until re-recorded ("the debt SHRANK"). Both
are the pin working, and both land on the change that caused them.
Both
@ts-expect-errordirectives in this layer — insrc/rest.test.tsandsrc/rest-api-plugin-slot-lookups.test.ts— were phantom checks that evaluatednever. Under this program neither reports TS2578, so both are live and each is
suppressing a real error.
Anti-vacuity: the program can say no (ruling 6)
Membership.
tsc -p tsconfig.test.json --listFilesputs 149 ofpackages/rest/src/**/*.test.tsin the program — the exact census — out of 489files total.
Ablation. A real type error planted in
src/analytics-routes.test.ts, anunledgered file, so the everyday verdict is what gets exercised:
The gate exits non-zero and names the file. The restore ran from a trap on
EXIT/INT/TERMagainst absolute paths, usedgit checkout HEAD -- path(never the bare form, which restores from a polluted index), and is proven by
blob hash — never by an exit code. It reports 2 errors, not 1: the second is
noUnusedLocalson the planted binding, which is itself evidence that inheritedstrictness is live. No build or
dist/is involved on either leg — tsc readsthese sources directly — so there is no artifact staleness to preflight.
Two mechanical consequences, both required by gates rather than chosen
1.
@objectstack/restgraduates out of TEST_DEBT. Once thetypecheckscript names the sibling config,
check:type-check-coveragereportshas a TEST_DEBT entry but ... no longer hides its tests -- it graduated. Theentry is deleted and a graduation note recorded in its place, per the file's own
convention. Headline moves: 19 → 18 packages hiding tests, 1110 → 965
hidden files, 1461 → 1306 frozen errors — exactly −1 package, −149 files,
−155 errors.
2.
check:type-source-resolutionsees a new program, and this is the onejudgement call in the PR — flagged rather than buried. Since #11490 that gate's
population is every
tsconfig*.jsonatypecheckscript names, so the newprogram joins it and reports six workspace deps the build program never reached:
driver-sql,metadata,plugin-hono-server,plugin-security,service-analytics,service-datasource— all six viatsconfig.test.json.I recorded them in that package's registry entry, with the program-set numbers
stated in place as the registry's own rule requires: before, at
5fbd58e0d,--listreported 93 programs / 77 packages / 54 entries / 233 pairs; after,94 / 77 / 54 / 239. +1 program, +0 entries, +6 pairs.
The gate's failure text asks for
pathsrules instead and calls registrywidening "not the fix", so here is why I did not do that — measured both ways
on the same checkout, not argued:
pathsredirecting the six to source takes the test layer from 37 errors to42, and the +5 are TS6133 in
../plugins/plugin-hono-server/src/{hono-plugin,current-user-endpoints}.tsand../drivers/driver-sql/src/sql-driver.ts— other packages' source billed topackages/rest/test-typecheck-debt.json, where they would then go red onthose packages' PRs. Worse, those five are not even real: both packages run
pnpm --filter ... typecheckgreen on this same tree (exit 0, measured), sothe borrowed program MANUFACTURES diagnostics that belong to nobody. A ledger
seeded with those cannot be paid down by the package that owns the file.
packages/rest/vitest.config.tsaliases exactly two of the six(
plugin-hono-server,service-datasource) to source and resolves the otherfour through
dist/. Blanketpathsis not fidelity to vitest either.@objectstack/clientand@objectstack/trigger-record-changeboth carrytest-program deps there, the latter being the package that took this very
#5286sibling route.pathsblock is not that.registry's doc-block permits a widening when the set of programs changed —
which is literally true here, 93 to 94 — but adds "that is a change to this file,
not to a package", written when the gate read only
tsconfig.jsonand a packagecould not move the population. This PR is the first case where a package does.
Happy to switch to
pathsif a maintainer reads that sentence as binding.Verification
All runs below are at
2dfb401c1, the pushed head, taken after the finalcommit. Every exit code captured before any pipe (redirect, then
$?).pnpm --filter @objectstack/rest typecheck— exit 0. Gate's own verdict line:check:test-typecheck: OK — @objectstack/rest's test layer compiles under packages/rest/tsconfig.test.json; 13 file(s) / 37 error(s) held in test-typecheck-debt.json (shrink-only).node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackagainst the real change set, all exit 0 —including
check:type-check-coverage,check:type-source-resolution,check:published-files,check:test-source-alias,check:nul-bytes,check:pm-dispatch-gatesandscripts/pm/bare-root-worklist.mjs --self-test(the last two are the convention-triggered pair this diff incurs by editing
gate scripts).
pnpm lint(eslint . --no-inline-config, the whole repo, not narrowed) —exit 0,
VERDICT command-exit 0.scripts/pm/os-verify-lock.sh; every one endedVERDICT command-exit 0.One declared narrowing:
pnpm check:type-check-debtwas NOT run. It ischeck-type-check-coverage.mjs --re-measure, and it refuses on this worktree —--re-measure cannot run: 32 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk. That is a PREREQUISITE NOT MET, not ared gate: it declines to measure rather than measuring a different world. Its
prerequisite is the full workspace build that
lint.ymlruns before the step,which CI does on this PR regardless. Three things make the omission a
measurement rather than a gap:
--re-measurescores each ledgered package underits own tsconfig, and
@objectstack/restis in neither ledger after thisdiff, so it is not among the projects measured; outside
packages/restthis diffchanges only two gate scripts' data and prose — no package's tsconfig and no
package's source; and the lockfile delta is exactly three lines in
packages/rest's own importer, adding an already-resolvedtsx@4.23.12, so noother package's closure moved. The structural half of the same gate —
check:type-check-coverage, which owns TESTS_COVERED, PINS_CHECKED, RECONCILEDand the composition invariants, and is what this diff actually moves — ran green
above.
Changeset: deliberately none, and the rule applied
packages/rest'sfilesis["dist", "README.md", "CHANGELOG.md"]. This PRchanges no
src/file, sodist/is byte-unaffected;tsconfig.test.jsonandtest-typecheck-debt.jsonare not published; andtsxis a devDependency noconsumer installs. Nothing is released, which is the
skip-changesetlabel'sown definition in
pr-automation.yml. The in-tree precedent for this exactchange shape is
951b025e4—build(trigger-record-change): graduate the package out of the TEST_DEBT ledger(PR #11489),
package.json+tsconfig.test.json+ the coverage gate, nochangeset.
Generated by Claude Code