From 0a0019c93a5e6f9c931cbc3e30503fd1587ea27d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 12:09:26 +0000 Subject: [PATCH] fix(tests): make plugin-auth's rate-limit isolation radius visible to the gate and hashed by turbo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `rate-limit-storage-isolation.test.ts` reads `packages/runtime/src` and `packages/services/service-sms/src` to check that neither consumer reaches the fixed-window counter through the package ROOT — the #6040 invariant whose breach silently reinstates the whole better-auth load. It derived its roots with a `findUp` walk from `process.cwd()`, which `check:cross-package-test-inputs` does not resolve (`process.cwd` appears nowhere in that detector), so the read produced no flag, no declaration, and no turbo input. Measured on the parent commit: `--list-escapes` named only `managed-extension-fields.test.ts` for plugin-auth, and `@objectstack/plugin-auth#test` hashed to `1bf3935543ab055b` both before and after a change under `packages/runtime/src`. Reinstating the root import in `packages/runtime/src/security/inbound-rate-limit.ts` — the exact regression this test guards — replayed `cache hit, replaying logs` / `>>> FULL TURBO` in 135ms, exit 0, while a direct vitest run on the same tree was RED. That is #7802's shape, on the gate that exists to prevent it. Three changes: - Reseed the test from `__dirname` (TS1470-free under this CJS-typed package's `module: NodeNext`, and a spelling the detector resolves), following the rationale `managed-extension-fields.test.ts` and `platform-objects/src/managed-api-method-affordance-sweep.test.ts` already state for their sibling walks. - Bind each consumer root by NAME and hand it to `readdirSync` by that name. The gate rosters a DIRECTORY only when a directory-read consumes an expression it can resolve; `join(REPO, root)` over an array element yields no name, so the globs would have been declared but UNHELD. - Declare the radius in CROSS_PACKAGE_TEST_INPUTS and mirror it into `turbo.json`'s `@objectstack/plugin-auth#test` inputs. `managed-extension-fields.test.ts` is deliberately NOT touched: it is the only other escaping read the gate sees in this package and it alone holds the `packages/**/*.object.ts` radius. Part of #10029 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PnJHU45vPJj5UQrxe946Bx --- .../src/rate-limit-storage-isolation.test.ts | 96 +++++++++++++------ scripts/check-cross-package-test-inputs.mjs | 28 +++++- turbo.json | 7 +- 3 files changed, 98 insertions(+), 33 deletions(-) diff --git a/packages/plugins/plugin-auth/src/rate-limit-storage-isolation.test.ts b/packages/plugins/plugin-auth/src/rate-limit-storage-isolation.test.ts index 9e7d85e361..58bc36285d 100644 --- a/packages/plugins/plugin-auth/src/rate-limit-storage-isolation.test.ts +++ b/packages/plugins/plugin-auth/src/rate-limit-storage-isolation.test.ts @@ -41,37 +41,52 @@ import { describe, it, expect } from 'vitest'; import { readFileSync, existsSync, readdirSync } from 'node:fs'; -import { dirname, join, relative } from 'node:path'; +import { dirname, join, relative, resolve } from 'node:path'; /** - * This package is CJS-typed (no `"type": "module"` — it publishes - * `dist/index.js` as CommonJS), so `module: NodeNext` forbids `import.meta` - * here. Walk up from the CWD instead, which works wherever vitest is invoked - * from. + * Seeded from `__dirname`, not from a `findUp` walk of `process.cwd()`, and not + * from `dirname(fileURLToPath(import.meta.url))`. Both halves of that choice are + * load-bearing — the same pair `managed-extension-fields.test.ts` and + * `platform-objects/src/managed-api-method-affordance-sweep.test.ts` state for + * their sibling repo-wide walks: + * + * - `import.meta` is a TS1470 here. This package is CJS-typed (no + * `"type": "module"` — it publishes `dist/index.js` as CommonJS), so under + * `module: NodeNext` the meta-property is an error however well it runs under + * vitest, and this package's test layer IS in front of tsc through the + * `@objectstack/plugin-auth` TEST_DEBT entry in `check-type-check-coverage.mjs`. + * `__dirname` type-checks under the package's own config and is defined at + * runtime by vitest's transform. + * - `check:cross-package-test-inputs` detects an escaping read STATICALLY, by + * resolving the seed expression. A `findUp` walk from `process.cwd()` is not a + * spelling it resolves — `process.cwd()` appears nowhere in that detector — so + * the two cross-package directory reads at the bottom of this file yielded no + * flag and therefore no declaration, SILENTLY. Measured before this change: + * `--list-escapes` named only `managed-extension-fields.test.ts` for + * plugin-auth, and `@objectstack/plugin-auth#test`'s turbo input hash + * (`1bf3935543ab055b`) did not move when `packages/runtime/src` changed — so a + * runtime-only diff that reinstated the package-root import replayed a cached + * green over the very scan that catches it (#7802's shape, #10029). + * + * Deriving the roots any other way puts this file back in that blind spot. */ -function findUp(predicate: (dir: string) => boolean, what: string): string { - let dir = process.cwd(); - for (;;) { - if (predicate(dir)) return dir; - const parent = dirname(dir); - if (parent === dir) throw new Error(`could not locate ${what}`); - dir = parent; - } -} - -const PKG = findUp((dir) => { - const manifest = join(dir, 'package.json'); - if (!existsSync(manifest)) return false; - const { name } = JSON.parse(readFileSync(manifest, 'utf8')) as { name?: string }; - return name === '@objectstack/plugin-auth'; -}, 'the @objectstack/plugin-auth package root'); +const HERE = __dirname; +/** …/packages/plugins/plugin-auth/src → the package root, then the repo root. */ +const PKG = resolve(HERE, '..'); +const REPO = resolve(HERE, '../../../..'); -const REPO = findUp( - (dir) => existsSync(join(dir, 'pnpm-workspace.yaml')), - 'the workspace root (pnpm-workspace.yaml)', -); +const SRC = HERE; -const SRC = join(PKG, 'src'); +/** + * The two consumer packages the root-import scan at the bottom of this file + * walks. Bound here, by name, so the gate can ROSTER them: these two paths are + * `@objectstack/plugin-auth`'s declared cross-package radius in + * `scripts/check-cross-package-test-inputs.mjs`, and the matching + * `$TURBO_ROOT$` entries under `@objectstack/plugin-auth#test` in `turbo.json` + * are what make this task's cache hash move when either directory changes. + */ +const RUNTIME_SRC = resolve(REPO, 'packages/runtime/src'); +const SERVICE_SMS_SRC = resolve(REPO, 'packages/services/service-sms/src'); /** * Strip comments before scanning. The distinction this file turns on — a @@ -280,12 +295,31 @@ describe('@objectstack/plugin-auth — ./rate-limit-storage stays free of better // that package. Scanned by directory rather than by filename so moving a // consumer file does not quietly retire the check. const COUNTER_SYMBOLS = /\b(incrementFixedWindow|createLazyCounterStore|InProcessCounterStore|CounterStore|FixedWindowCount|LazyCounterStoreOptions)\b/; - const roots = ['packages/runtime/src', 'packages/services/service-sms/src']; + // Each root is bound by NAME above and handed to `readdirSync` by that name, + // rather than looped over as `join(REPO, root)` with `root` an array element. + // That is not a style preference: `check:cross-package-test-inputs` learns a + // DIRECTORY input only when a directory-read consumes an expression it can + // resolve, and its own header lists "a directory read whose path is only a + // loop variable" among the shapes that yield no name. Written as a loop the + // two globs below would be declared but UNHELD — nothing would fail if a + // later edit deleted them, which is the #9763 failure mode (prose holding a + // radius) one level up. Written this way the gate rosters both directories + // and fails if the declaration stops covering them. + expect( + existsSync(RUNTIME_SRC), + 'packages/runtime/src — consumer directory moved; re-point this check', + ).toBe(true); + expect( + existsSync(SERVICE_SMS_SRC), + 'packages/services/service-sms/src — consumer directory moved; re-point this check', + ).toBe(true); const offenders: string[] = []; - for (const root of roots) { - const abs = join(REPO, root); - expect(existsSync(abs), `${root} — consumer directory moved; re-point this check`).toBe(true); - for (const entry of readdirSync(abs, { recursive: true, withFileTypes: true })) { + const trees = [ + readdirSync(RUNTIME_SRC, { recursive: true, withFileTypes: true }), + readdirSync(SERVICE_SMS_SRC, { recursive: true, withFileTypes: true }), + ]; + for (const entries of trees) { + for (const entry of entries) { if (!entry.isFile() || !entry.name.endsWith('.ts')) continue; const file = join(entry.parentPath, entry.name); for (const m of stripComments(readFileSync(file, 'utf8')).matchAll(FROM)) { diff --git a/scripts/check-cross-package-test-inputs.mjs b/scripts/check-cross-package-test-inputs.mjs index bdd2f265c5..365ac25fd4 100644 --- a/scripts/check-cross-package-test-inputs.mjs +++ b/scripts/check-cross-package-test-inputs.mjs @@ -295,7 +295,33 @@ const CROSS_PACKAGE_TEST_INPUTS = { '@objectstack/plugin-auth': { // src/managed-extension-fields.test.ts walks every `*.object.ts`, and pins // core's api-key source alongside it. - globs: ['packages/**/*.object.ts', 'packages/core/src/security/**'], + globs: [ + 'packages/**/*.object.ts', + 'packages/core/src/security/**', + // src/rate-limit-storage-isolation.test.ts (#6040) walks BOTH consumer + // packages of the `./rate-limit-storage` subpath by directory, checking + // that neither reaches the counter through the package ROOT — which would + // silently reinstate the whole better-auth load for them. The diff that + // breaks that invariant is a diff in one of these two directories, so + // without them declared the affected-subset filter never adds plugin-auth + // and turbo replays a cached green over the scan (#10029, the #7802 + // shape). Measured: before this entry, `@objectstack/plugin-auth#test` + // hashed to `1bf3935543ab055b` both before and after a change under + // `packages/runtime/src`, and the re-run was `>>> FULL TURBO` in 135ms + // while the invariant was live-broken in the tree. + 'packages/runtime/src/**', + 'packages/services/service-sms/src/**', + // The three below are NAMED in that test's prose rather than read by it — + // the same shape as `serve.ts` on the @objectstack/spec entry above and + // `realtime-protocol.mdx` on @objectstack/dogfood, and settled the same + // way: the literal collector takes quoted paths without parsing, so a + // mention forces a declaration, and declaring the file is cheaper than + // rewording prose to dodge the scanner. All three are low-churn, so the + // added cache invalidation is nominal next to the two directories above. + 'scripts/check-published-files.mjs', + 'scripts/check-cross-package-test-inputs.mjs', + 'packages/types/src/node-isolation.test.ts', + ], }, '@objectstack/plugin-security': { // src/audience-anchor-set-claims.pin.test.ts pins against spec's diff --git a/turbo.json b/turbo.json index 41cfc340eb..ad27efb008 100644 --- a/turbo.json +++ b/turbo.json @@ -112,7 +112,12 @@ "!coverage/**", "!.turbo/**", "$TURBO_ROOT$/packages/**/*.object.ts", - "$TURBO_ROOT$/packages/core/src/security/**" + "$TURBO_ROOT$/packages/core/src/security/**", + "$TURBO_ROOT$/packages/runtime/src/**", + "$TURBO_ROOT$/packages/services/service-sms/src/**", + "$TURBO_ROOT$/scripts/check-published-files.mjs", + "$TURBO_ROOT$/scripts/check-cross-package-test-inputs.mjs", + "$TURBO_ROOT$/packages/types/src/node-isolation.test.ts" ] }, "@objectstack/plugin-security#test": {