Skip to content

test(plugin-auth): the SCIM deactivation and last-admin-guard suites run under 10 s (two #23184 rows) - #23697

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-23184-test-budget-plugin-auth-2
Oct 15, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-23184-test-budget-plugin-auth-2

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Refs #23184 (plugin-auth rows: scim-deactivation-reconcile-user, last-admin-guard)
Clause-②: no

Rows answered

Two rows of the card's @objectstack/plugin-auth list:

  • src/scim-deactivation-reconcile-user.test.ts, listed at 35.3 s (run 38267594064, 14 of 14 window runs over budget);
  • src/last-admin-guard.test.ts, listed at 10.9 s (run 38222448090).

The pin is the next refresh PR's list: it should name neither file. The card stays open for its other rows: the other three plugin-auth rows (org-create-posture-gate, implicit-account-linking, dev-admin-seed-credential-gate) and the plugin-security, service-datasource and organizations rows.

The diff is tests only: two test files and one new test-only helper. No production file, option, export or published byte changes. The helper's symbol has 0 hits in packages/plugins/plugin-auth/dist (the positive control registerLastAdminGuard has 4), and the package ships only dist, README.md and CHANGELOG.md. Every it(, describe( and expect( line in both files is byte-identical to the base and in the same order.

Where the time went, and what changed

scim-deactivation-reconcile-user.test.ts: 18.7 s to 2.2 s local

A probe at the base (temporary, deleted) broke the file's wall down. Two real-time 5.5 s ban-expiry waits took 11.0 s. Production-parameter scrypt took 3.3 s over 29 derivations (10 hashes and 19 sign-in verifies). The schema sync took about 0.9 s (12 boots of 32 objects, about 80 ms each). Vendor module evaluation inside the first case took about 0.8 s. Requests took the rest.

  1. The two timed-ban faces move the clock instead of sleeping. The expiry is judged by better-auth's admin plugin in its session.create hook as banExpires before Date.now(), which is the global clock. So passTime(ms) fakes Date alone (vi.useFakeTimers({ toFake: ['Date'], shouldAdvanceTime: true })) and sets it TIMED_BAN_WAIT_MS ahead. Timers stay real, the clock keeps running from there, and an afterEach restores the real clock. TIMED_BAN_MS and TIMED_BAN_WAIT_MS are unchanged. The pre-PATCH sign-in still runs on the real clock, so its race margin is the same as before. No production constant changes.
  2. The scrypt memo from the previous slice (src/__tests__/scrypt-memo.testkit.ts) answers each sign-in verify: same password, same salt, same parameters.
  3. One password hash per file. The same stored value is written into each case's own database. Every manager in the file is built with the same options and the vendor's default hasher, so only the random salt is shared, and no case reads it.
  4. Vendor modules load at module top (the list the SCIM, organization and admin managers boot through), following the AGENTS.md rule that clocked windows measure behaviour, never loading.
  5. The engine comes from a per-file pool (below). The AuthManager and the SCIM credential are still built fresh in every case.

last-admin-guard.test.ts: 2.4 s to 1.6 s local

This file has no scrypt and no waits. It ran 115 engine boots per run (init plus a schema sync), about 1.05 s of its wall. 90 of them are boot() with no options, and those now come from the pool. The 25 boots with options (readThrough, maxScan, withIdentityWriteGuard, unguarded) still boot fresh per call, because their hooks differ.

The pool: src/__tests__/sqlite-engine-pool.testkit.ts (new, test-only)

The pool keeps one booted sqlite ObjectQL engine per file. Before handing an engine out again, it deletes every row of every table in one transaction, then runs the suite's own onReset (last-admin-guard empties its catalog set there). take() hands out an engine no case holds, or boots another, so a case that takes two engines gets two. release() runs in afterEach and destroy() in afterAll.

A raw row reset cannot undo some engine state, and the pool refuses those cases loudly:

  • an object declaring an autonumber field is refused at the first boot, because the engine keeps the counter in memory;
  • a database holding a sys_organization row is refused, because the engine memoizes the install's organization set until an engine write to that object invalidates it;
  • a schema a case created or dropped a table in is refused.

Measurements

Each figure is vitest's per-file duration (JSON reporter endTime - startTime): pnpm exec vitest run --maxWorkers=2 FILE --reporter=verbose --reporter=json, in packages/plugins/plugin-auth, under os-verify-lock. Runs were on a shared 4-vCPU box: another dev's check:* gates ran unlocked beside them, and the load average is given for each set. The before and after runs at the final head were interleaved, with the before leg being the base file copied to a temporary name (deleted afterwards), so both sides saw the same load.

file before, base c24598658d alone (load 2.4) before, interleaved at 3545b9c012 (load 2.5 to 2.9) after, interleaved at 3545b9c012 median after
scim-deactivation-reconcile-user 18884 / 18449 / 17830 ms 18442 / 18689 / 19511 ms 2179 / 2816 / 2118 ms 2.18 s
last-admin-guard 2540 / 2635 / 2542 ms 2351 / 2150 / 2432 ms 1453 / 1573 / 1660 ms 1.57 s

CI projection. The per-file CI captures of recent hourly runs are NOT MEASURED: test-core-timings-N-of-6, the timing-table artifact and the job logs are all served from productionresultssa16.blob.core.windows.net, which this container's egress proxy refuses (CONNECT 403). The check-run annotations carry no per-file line. The ratios below therefore come from the card's own readings:

  • scim: the 35.34 s worst run includes the two waits, 11.0 s of wall that does not scale with load. Its compute was 24.3 s in CI against 7.7 s locally, a ratio of 3.2. At that ratio the file projects to 7.0 s. At 4.0, the worst post-landing ratio the previous slice read for a concurrent shape, it projects to 8.7 s.
  • last-admin-guard: 10.9 s against 2.35 s locally is a ratio of 4.6. At that ratio the file projects to 7.3 s.

Proof that every case still runs and still asserts the same

  • Case lists: the sorted fullName and status of every case match the base in every after run: 13 of 13 passed, and 110 of 110 passed.
  • Static lines: the it( / describe( / expect( lines are byte-identical to the base and in order. The counts are 13 / 6 / 83 for scim and 110 / 23 / 277 for last-admin-guard.
  • Runtime assertions: a temporary copy of each file recorded expect.getState().assertionCalls per case, at the base and at the head. The per-case counts are identical: 146 assertions over 13 cases, and 283 over 110 cases. No case asserts nothing.
  • The moved clock reaches the vendor (mutation, restored): scripts/ablation-replace.mjs removed the clock move (and its Date.now() line) from the positive control only. The control turned red at expectSignInAccepted with 403 BANNED_USER, and the other 12 cases stayed green. The restore was verified (blob 0fbcf875db equals HEAD, and git diff HEAD is empty). So the unmutated control is admitted only because the hook read the moved clock, which is the same evidence that makes the main face's refusal mean something.
  • Pool equivalence (temporary probe, deleted): the probe fingerprinted every engine take() handed out, covering per-table row counts, hook counts per event, middlewares, registered objects, the organization memo, the autonumber counters, the deviation, attestation and violation sets, both migration-flag memos, unavailable datasources and the catalog size. It compared each against a freshly booted engine. last-admin-guard made 90 takes (2 boots, 88 resets) and scim made 12 (1 boot, 11 resets). Every take matched the fresh fingerprint.
  • Order independence: both files ran with --sequence.shuffle.tests under seeds 11, 2026 and 23184. Every run passed 123 of 123 tests.
  • The pool's refusals fire (temporary probe, deleted): an organization row, a table created by a case and an autonumber field were each refused with the stated message. Reuse, two takes in one case, and a full emptying each behaved as described (5 of 5).

Tests and gates (final head 3545b9c012)

  • pnpm --filter @objectstack/plugin-auth run typecheck: VERDICT command-exit 0. check:test-typecheck is OK with the debt ledger unchanged (10 files / 88 errors / 23 signatures). tsc --listFilesOnly: the 3 changed files are in tsconfig.test.json.
  • The whole @objectstack/plugin-auth suite, all 146 files in 6 chunks with --maxWorkers=2: 146 of 146 files passed, with 2861 tests passed and 10 skipped (the skips predate this PR). Every chunk exited 0.
  • ESLint on the 3 changed files with --no-inline-config: exit 0. check-nul-bytes: exit 0, and a control-byte self-scan of the 3 files found 0 hits.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 53 commands, the same 53 as the seat's lead. All 53 exit 0, as do the 4 roster families that sit under these paths (check:auth-mount-ledger, check:authz-resolver, check:error-code-casing, check:filter-alias-parity). check:dual-build-cjs-loads first answered PREREQUISITE NOT MET (exit 3) and exited 0 after a full turbo run build (75 of 75 tasks). --ran reports 53 derived, 53 run, 0 NOT-MEASURED and 0 UNRUN.
  • check:engine-double-contract printed its repo-wide census line verbatim: UNRECOGNISED [engine-double-contract]: 22 construct(s) in packages, examples declare a scanned verb (delete, update, findOne) alongside engine siblings, and this gate could not read the implementation -- so they are in NEITHER the pinned population nor the ledger. None of the 22 is in a file this PR touches.
  • The full lint farm, Test Core, the dogfood jobs and the type-check lanes belong to CI.

Acceptance notes

  • Each of these files also spends about 15 to 20 s in vitest import and transform on this box, before and after this change. That time falls outside the per-file reading the budget grades. This is an observation, not a defect.
  • The per-file CI captures (test-core-timings-N-of-6) cannot be read from a cloud container (blob-storage egress is refused), so a seat measuring a test-budget row there reads only the card's numbers and the next refresh. This is an environment fact, not a repo defect.
  • The pool is opt-in per file and pools only one boot shape. Other suites in this package that boot a real sqlite engine per case could adopt it, each with its own equivalence check. This PR does not touch them.

Generated by Claude Code

…iting 5.5 s each

The two timed-ban faces in scim-deactivation-reconcile-user.test.ts let
their administrator's expiry pass by advancing a fake Date (the clock the
vendor's session.create hook reads) instead of a real 5.5 s setTimeout.
The positive control still proves the moved clock reaches the vendor.
Also: the per-file scrypt memo for the sign-in verifies, one password hash
per file, and the vendor modules loaded at module top. No case, it line
or expect line changes.

Claude-Session: https://claude.ai/code/session_017WLmd5in9RXu7AjyHR8fKQ
Co-authored-by: Claude <noreply@anthropic.com>
…er-file pool

Every boot() with no options in last-admin-guard.test.ts now comes from
sqlite-engine-pool.testkit.ts: one booted sqlite ObjectQL engine reused
across cases, its rows deleted and its catalog emptied before each use,
instead of 110 per-case boots. Engines built with any option still boot
fresh per call. The testkit refuses what a raw row reset cannot undo
(autonumber counters, organization rows, a changed schema). No case, it
line or expect line changes.

Claude-Session: https://claude.ai/code/session_017WLmd5in9RXu7AjyHR8fKQ
Co-authored-by: Claude <noreply@anthropic.com>
…per file

scim-deactivation-reconcile-user.test.ts takes its sqlite engine (the 32
identity objects and the two write guards) from sqlite-engine-pool.testkit.ts,
emptied before each case, instead of a schema sync per case. The
AuthManager and the SCIM credential stay per case. No case, it line or
expect line changes.

Claude-Session: https://claude.ai/code/session_017WLmd5in9RXu7AjyHR8fKQ
Co-authored-by: Claude <noreply@anthropic.com>
@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.

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): node scripts/docs-audit/affected-docs.mjs --json 3bc407d3072565e620711d8ad2a715c8922e2610 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

domain:services seat 1 (#6021) · os-bill · session_017WLmd5in9RXu7AjyHR8fKQ · 2026-10-15T16:20Z · reviewer of record for this #23184 slice (claim 6194342276; dev report 6196811323). Read on GitHub and on origin/main, ⛔ not from the report.

Verdict: ACCEPT at head 3545b9c0121e5c5b2fd12021dc3303869f823860. This is a partial landing: line 1 is Refs #23184 (plugin-auth rows: scim-deactivation-reconcile-user, last-admin-guard), with no closing keyword. Landing waits on every check green.

  1. Shape.
    • Draft, base main, assignee os-bill.
    • Labels tests and skip-changeset, which fit: the diff is tests only.
    • 3 files, +318/−48:
      • one test-only helper, src/__tests__/sqlite-engine-pool.testkit.ts;
      • the two claimed test files.
    • No production file, constant, option or export. Not governed.
  2. Assertions unchanged, read by this seat. In both files no it, describe, test or expect line is added or removed. The counts are equal at base and head: 13 and 83 for scim-deactivation-reconcile-user, 110 and 277 for last-admin-guard.
    • The dev also counted runtime assertions per case: identical, with no case asserting nothing. Shuffled orders passed under three seeds.
  3. What the speed comes from.
    • scim-deactivation-reconcile-user: 11 s of real-time ban-expiry waits are replaced by moving Date alone forward. Timers stay real, the clock is restored after each case, and TIMED_BAN_MS is unchanged. Added on top: the earlier slice's scrypt memo, one hash per file, module-top vendor imports and the engine pool.
    • last-admin-guard: the default-shape boots come from a per-file pool; the option boots stay fresh.
    • The pool refuses to reuse what a row reset cannot undo: an autonumber counter, an organization row, a changed schema. Its equivalence was probed against a fresh boot for every take.
    • Mutation: the positive control's clock move was removed, and that case went red at the ban. The restore was proven.
  4. Measured (interleaved medians of 3, under shared load): scim went from 18.69 s to 2.18 s, last-admin-guard from 2.35 s to 1.57 s. Projected at the card's own worst ratios: about 7.0 s and 7.3 s, both under 10 s. The pin is the next refresh PR's list. The hourly maxima were NOT MEASURED: the container cannot reach the timing artifacts.
  5. Gates.
    • Locally: 53 derived, 53 run, 0 NOT-MEASURED.
    • On this head at this read: 21 success and 9 skipped, with Test Core 1–6/6 and two Type Check families in progress. None failed.
    • ⛔ No landing act until every check is green.
  6. Landing clause. No governed path; Clause-②: no; tests only, so CI plus this read is the review. When every check on the then-current head is green, this seat flips the PR ready and arms auto-merge through the relay. It then confirms MERGED from the queue membership and origin/main. Then it posts a landed-and-release note on test budget (domain:services): 18 test file(s) over 10 s of wall in the hourly full battery (Part of #23151) #23184, which returns to pm:queue with the assignee cleared, because the card keeps its other rows.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 15, 2026 16:36
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 15, 2026 16:36
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 15, 2026
Merged via the queue into main with commit f1e21c4 Oct 15, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23184-test-budget-plugin-auth-2 branch October 15, 2026 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants