Skip to content

fix(tests): make plugin-auth's rate-limit isolation radius visible to the gate and hashed by turbo - #10161

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-10029-rate-limit-test-input-radius
Aug 20, 2026
Merged

fix(tests): make plugin-auth's rate-limit isolation radius visible to the gate and hashed by turbo#10161
os-warren merged 1 commit into
mainfrom
claude/issue-10029-rate-limit-test-input-radius

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Part of #10029 — the finding also lists directions B (teach the detector the findUp shape, repo-wide) and C (leave it recorded). This PR implements the graded direction A only, so the card should stay open for a decision on B.

The defect, measured on the parent commit (2d3860df9)

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 for service-sms. It derived its roots with a findUp walk from process.cwd().

check:cross-package-test-inputs resolves seed expressions statically. process.cwd appears nowhere in that detector (grep -c 'process\.cwd'0; counter-checked against __dirname8 and import.meta.dirname7, so the zero is a measurement, not a broken search). The read therefore produced no flag, no declaration, and no turbo input:

$ node scripts/check-cross-package-test-inputs.mjs --list-escapes
@objectstack/plugin-auth (packages/plugins/plugin-auth)
packages/plugins/plugin-auth/src/managed-extension-fields.test.ts <-- only this one

Turbo's @objectstack/plugin-auth#test input set (224 entries, package-relative) contained 0 paths under ../../runtime/src and 0 under ../../services/service-sms/src, against counter-checks of 35 for ../../core/src/security and 11 for ../../services/service* — the declared globs, present as expected.

The cache replay, demonstrated rather than asserted

Reinstating the root import in packages/runtime/src/security/inbound-rate-limit.ts — the exact regression this test guards — and re-running the task:

@objectstack/plugin-auth:test: cache hit, replaying logs 1bf3935543ab055b
Tasks: 26 successful, 26 total
Time: 135ms >>> FULL TURBO # exit 0

The same tree, run directly, is red:

FAIL src/rate-limit-storage-isolation.test.ts > no cross-package consumer reaches the counter through the package ROOT
+ "packages/runtime/src/security/inbound-rate-limit.ts -> { createLazyCounterStore, type CounterStore } from the package root"

A green replayed over a live-broken invariant. That is #7802's shape, on the gate that exists to prevent it.

What changed

  1. Reseed from __dirname. TS1470-free under this CJS-typed package's module: NodeNext, and a spelling the detector resolves — the rationale managed-extension-fields.test.ts and platform-objects/src/managed-api-method-affordance-sweep.test.ts already state for their sibling walks.

  2. Bind each consumer root by 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 (its own header lists "a directory read whose path is only a loop variable" among the shapes that yield nothing). Written as a loop, the globs would have been declared but unheld — nothing would fail if a later edit deleted them, which is check-cross-package-test-inputs' literal collector cannot see split-segment or ascent-relative paths, so four declared radii are held by prose alone #9763's failure mode one level up. Now readdirSync(RUNTIME_SRC, …) / readdirSync(SERVICE_SMS_SRC, …) take named bindings.

  3. Declare the radius in CROSS_PACKAGE_TEST_INPUTS and mirror it into turbo.json's @objectstack/plugin-auth#test inputs.

Three of the declared entries (scripts/check-published-files.mjs, scripts/check-cross-package-test-inputs.mjs, packages/types/src/node-isolation.test.ts) are named in the test's prose rather than read by it. Declared rather than reworded, following the settled treatment of serve.ts on the @objectstack/spec entry and realtime-protocol.mdx on @objectstack/dogfood: the literal collector takes quoted paths without parsing, so a mention forces a declaration, and declaring is cheaper than rewording prose to dodge the scanner. All three are low-churn.

Proof that the gate now SEES the radius (not merely exits 0)

The gate names both directories in its roster — the failure text before the globs were declared:

 - @objectstack/plugin-auth names path(s) no declared glob covers …
packages/runtime/src/ (listed in …/rate-limit-storage-isolation.test.ts)
packages/services/service-sms/src/ (listed in …/rate-limit-storage-isolation.test.ts)

It is demanding these globs, so they are held: delete them and the gate goes red. After declaring:

OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.

Proof that turbo now HASHES the two packages

inputs#test hash
before224 (runtime: 0, service-sms: 0)1bf3935543ab055b
after489 (runtime: 250, service-sms: 12)844fad10458a8a2b

Ablation — prediction stated first, then observed

Predicted: with the fix in place, the same runtime mutation must (a) move the hash off 844fad10458a8a2b, (b) miss the cache and actually execute, and (c) fail with the same offender string.

Observed — all three:

@objectstack/plugin-auth:test: cache miss, executing ee3c9517b568ff5e
AssertionError: Import the counter from "@objectstack/plugin-auth/rate-limit-storage" …
+ "packages/runtime/src/security/inbound-rate-limit.ts -> { createLazyCounterStore, type CounterStore } from the package root"
Test Files 1 failed | 59 passed (60)
Tests 1 failed | 1334 passed (1335) # 52.4s, exit 1 — not 135ms, not green

Restore proven byte-identical, both sides via git hash-object:

pristine 975b538b2744bc9b944ce12d7e529d91705da43f
mutated 6fc0564f417c77dc5f233553d7b5f02526fbdf68
restored 975b538b2744bc9b944ce12d7e529d91705da43f == pristine

No rebuild was needed for the mutation to be observable, and that is a property of the files involved, not an assumption. The assertion reads packages/runtime/src/**/*.ts as raw source text through fs.readFileSync; nothing on its path resolves through @objectstack/runtime's exports or dist/. The test's own header states this deliberately ("a SOURCE-level scan rather than a probe of dist/", because a gate reading build output passes or fails by local build state). Confirmed against the run: all 25 dependency build tasks stayed cached — no rebuild occurred — and the test still turned red. A stale dist/ therefore cannot mask this ablation in either leg.

Verification

Gate union derived with node scripts/pm/dispatch-gates.mjs (no paths passed) after the final commit, on a clean worktree, at 0a0019c93. Exit codes captured before any pipe.

gateexit
check:cross-package-test-inputs (incl. 57 self-test cases)0
check:slot-lookup0
check:test-source-alias0
check:type-source-resolution0
scripts/docs-audit/check-affected-docs.mjs0
check:query-options-erasure0
check:type-check-coverage0
check:type-check-debt (--re-measure, 33 entries, built closure)0
check:engine-double-contract0
check:where-matcher0
check:nul-bytes0
pnpm --filter @objectstack/plugin-auth typecheck0
turbo run test --filter=@objectstack/plugin-auth0 — 60 files, 1335 tests

check:type-check-debt reported "33 ledger entr(ies) re-measured … none above its recorded number". No ledger entry was raised: scripts/check-type-check-coverage.mjs is untouched (empty diff), so @objectstack/plugin-auth stays at 109 and the self-test fixture at :2766 is unmodified.

Merge-queue cost, stated deliberately

This is direction A's known price and it is not small: @objectstack/plugin-auth#test now re-runs on any diff under packages/runtime/src (250 files) or packages/services/service-sms/src (12). That suite is ~50s / 1335 tests, and the queue runs the full suite under heavier load than the PR path. Buying that is the point — the gate's own header argues the radius is what the list buys over "just always run those packages" — but reviewers should expect runtime-touching PRs to newly schedule this suite where main has been serving a cached green.

Not done, deliberately

  • managed-extension-fields.test.ts is untouched. It is the only other escaping read the gate sees in this package and it alone holds the packages/**/*.object.ts radius; its __dirname seed is intact (its findUp occurrence is a prose mention, verified).
  • The detector is not extended (direction B). Nothing here teaches it the findUp shape, so the class remains open repo-wide — that decision is still the card's.

Release notes input

No changeset: this PR touches one test file, one CI gate script and turbo.json, and publishes nothing. Requesting the skip-changeset label per that mechanism rather than shipping an empty changeset. Following the gate, not habit — content/docs/releases/ is untouched.


Generated by Claude Code

… the gate and hashed by turbo
`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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnJHU45vPJj5UQrxe946Bx
@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 0 changed package(s)), so this run has no opinion about the docs.

@os-warrenos-warren added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 20, 2026 — with Claude
@os-warren
os-warren marked this pull request as ready for review August 20, 2026 12:26
@os-warren
os-warren enabled auto-merge August 20, 2026 12:26
@os-warren
os-warren added this pull request to the merge queueAug 20, 2026
Merged via the queue into main with commit 19f98faAug 20, 2026
28 checks passed
@os-warren
os-warren deleted the claude/issue-10029-rate-limit-test-input-radius branch August 20, 2026 12:47
os-zhuang pushed a commit that referenced this pull request Aug 21, 2026
…-test-inputs recognises
The gate resolves four directory-naming seed spellings plus the walked-file
form, not "exactly two". `import.meta.dirname` and `dirname(import.meta.filename)`
arrived with #9763 and the walked `import.meta.url` seed with #8995; both
extensions updated RECOGNISED_PATH_SPELLINGS, which the gate prints, and neither
swept for the count restated in prose elsewhere.
Corrected, pointing at RECOGNISED_PATH_SPELLINGS as the single source instead of
restating a number that drifts:
- managed-extension-fields.test.ts: the seed count, plus two more stale claims
measured on this tree — it is no longer the ONLY escaping read the gate sees
in plugin-auth (#10161 made rate-limit-storage-isolation.test.ts visible), and
that file no longer walks up from process.cwd(). The note also promised a
stale-declaration failure that no longer fires: reseeding this file from
process.cwd() now leaves the gate GREEN, with packages/**/*.object.ts declared
and held by nothing.
- managed-api-method-affordance-sweep.test.ts: the same seed count. Its
stale-declaration consequence IS still real and is now cited from the
measurement (that file is platform-objects' only escaping test; the ablation
exits 1 naming the package).
- check-type-check-coverage.mjs: the same claim in a TEST_DEBT note, the
worst-placed instance since an author reads it while choosing a seed.
- serve-multi-node-cap-advisory.pin.test.ts (not named in the card): stale in the
opposite direction — it claimed the gate cannot follow a new URL() seed or a
resolve() nested into the read. It follows both. The instruction to keep the
whole path in one literal stands, for the reason that is actually true: only
the flat literal collector needs that spelling.
Comments and one note string only — no test code, no type surface. The gate's own
repoRelativeLiterals roster for all three test files is unchanged from origin/main,
so no declaration and no turbo.json input moves.
Part of #10163
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
os-zhuang added a commit that referenced this pull request Aug 21, 2026
…eeds
`check:cross-package-test-inputs` finds escaping tests by scanning source
text, so a seed spelling it does not know yields no flag -- which means no
declaration, silently. Every seed it knew answers "where am I?" off the
module itself. A CJS-typed package cannot ask: plugin-auth publishes
`dist/index.js` as CommonJS, so under `module: NodeNext` `import.meta` is a
TS1470 there, and four of its tests walk up from `process.cwd()` to an
anchor instead. That walk resolved to nothing, so every path built on it
resolved to nothing too.
Measured on `19f98fa1f^`: `rate-limit-storage-isolation.test.ts` read
`packages/runtime/src` and `packages/services/service-sms/src` through such
a seed, appeared in no roster, and turbo replayed a cached green over the
scan it never re-ran -- #7802 exactly, by a fourth spelling. #10161 reseeded
that one file; this closes the class it was an instance of.
`findUpSeeds()` resolves the two predicates that are knowable without
executing anything: a manifest `name` matching THIS package (-> the package
root, named) and a WORKSPACE_ROOT_MARKERS file (-> the repo root, which
escapes on its own exactly as `resolve(HERE, '../../..')` already does).
A manifest naming some OTHER package keeps the escape verdict and loses the
name, the trade `walkLiteral` already makes for an unreadable argument.
Today's findUp population is clean, so no gate turns red and none turns
newly green -- the proof is 14 `--self-test` cases, 8 of which fail without
the detector change.
Part of #10029
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-warren@claude