Skip to content

Commit df22adc

Browse files
test(plugin-auth): keep the absolute-limit switch fixture off the one-hour boundary (#31213)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1X5P75jPsrvBic2pzcJ3eDvb)_ Fixes #31175 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:只改一个测试文件:plugin-auth 里「切换组织时按绝对时长上限判定」那条用例的会话夹具,从「正好 60 分钟前创建」改为「59 分钟前创建」,并补一句注释说明为什么不能贴着上限。 - **为什么改**:上限是 1 小时,判定时会重新读时钟;夹具正好 60 分钟,只要过去 1 毫秒会话就超限被结束,「还没到上限」的断言就红。本地实测:旧夹具在 main 上每次都红,不是偶发。产品判定本身是对的,不动产品代码。 - **风险与回滚**:极低。不改任何发布内容(测试文件不进包),「超限」那条 61 分钟断言原样保留;回滚即还原这一行。 - **先看**:`packages/plugins/plugin-auth/src/organization-access-policy.test.ts`(L398 夹具与 L421 注释)。 - **两分钟验证**:`pnpm --filter @objectstack/plugin-auth exec vitest run src/organization-access-policy.test.ts`,预期 19/19 通过。 - **需要你做的**:无,席位落地。 ## What changes - `packages/plugins/plugin-auth/src/organization-access-policy.test.ts`, in the `deployment()` fixture of the "a switch of the active organization is judged against the target's limits" block: the `s1` session row is created 59 minutes ago instead of exactly 60. The "not over yet" comment now says why the fixture must sit clearly inside the limit. The "over" leg (`created_at` 61 minutes ago, expect `ended` with `revoke_reason: absolute_max`) is unchanged, so the test still proves the absolute limit is judged at the switch, on both sides of it. - The same fixture feeds the idle-timeout case in that block. It judges `last_activity_at` (30 minutes ago, against a 10-minute idle limit) and sets no absolute limit, so 59 vs 60 does not change what it reads. No product file is touched; `organization-access-policy.ts` and `auth-manager.ts` are as on `main`. ## Why `AuthManager.judgeSessionSwitch` (`auth-manager.ts` L6352–6355) takes its own `Date.now()` after the fixture is written and ends the session when `now - created_at > absoluteHours * 3_600_000`. A row made `Date.now() - 60 * MIN` against a one-hour limit is over as soon as one millisecond passes between the fixture and the judgment. The product comparison is right; the fixture sat on the boundary. Main went red on it in `Test Core (7/8)` (run 42384924042); locally the base fixture is red on every run (the memory engine's insert and read take well over a millisecond). ## Tests <details><summary>Acceptance: a 5-second fake-clock advance between fixture and judgment; base fixture red, new fixture green</summary> Each leg runs `pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 src/organization-access-policy.test.ts -t 'the absolute limit is judged too'` under `os-verify-lock`, with the mutation written by `scripts/ablation-replace.mjs` (anchor must hit, blob verified, restored to the HEAD blob). The test imports `./auth-manager` from `src/`, so there is no `dist/` hop and no rebuild per leg. The delay is `vi.useFakeTimers({ toFake: ['Date'] }); vi.setSystemTime(Date.now() + 5_000);` inserted on the line of the "not over yet" assertion; it was removed afterwards and is not in the diff. | leg | fixture | delay | on-disk check | result | |---|---|---|---|---| | 0 | 59 min (head) | none | fixture59=1 fixture60=0 delay=0 | `Tests 1 passed`, exit 0 | | 0b | 60 min (base) | none | fixture59=0 fixture60=1 delay=0 | `AssertionError: expected 'ended' to be 'admitted'`, exit 1 | | 1 | 59 min (head) | +5 s | fixture59=1 fixture60=0 delay=1 | `Tests 1 passed`, exit 0 | | 2 | 60 min (base) | +5 s | fixture59=0 fixture60=1 delay=1 | `AssertionError: expected 'ended' to be 'admitted'`, exit 1 | Restore proof after the last leg: `git diff HEAD` empty, blob on disk `086c2c08` = HEAD blob `086c2c08`. A first attempt at legs 1 and 2 was a no-op: the delay replacement still contained the anchor line, so `ablation-replace` refused it ("anchor count moved 1 -> 1") before running anything. It was redone with a replacement that rewrites the anchor line itself; the table above is the second run. </details> <details><summary>plugin-auth suite and typecheck at head 228d061</summary> - `pnpm --filter @objectstack/plugin-auth test`: `Test Files 234 passed (234)`, `Tests 4168 passed | 10 skipped (4178)`, `VERDICT command-exit 0`. - `pnpm --filter @objectstack/plugin-auth typecheck` (tsc, examples, `check:test-typecheck`): `check:test-typecheck: OK`, `VERDICT command-exit 0`. The first run answered exit 2 on `examples/basic-usage.ts` because plugin-auth's own `dist/` was not built yet in the fresh worktree; after `pnpm --filter @objectstack/plugin-auth build` it is green. - `eslint --no-inline-config packages/plugins/plugin-auth`: exit 0 (plugin-auth has no `lint` script, so a filtered `pnpm lint` would match nothing). </details> ## Acceptance notes - Same-file boundary sweep (the card's L336–346 `minutesAgo` helper): the four concurrent-cap cases use ages 50/40/1, 5/30, 5/30 and 30/20/1 minutes. They only order sessions by age against count caps (`max_concurrent_sessions_per_user`, `maxConcurrentSessions`); no time limit is judged, so nothing sits on a boundary. The other same-file clock fixture, L291 `hourAgo` (60 minutes) against Alpha's 5-minute idle limit, is far over. No other same-file fix. - Rest of `plugin-auth/src/**/*.test.ts`, every `Date.now() - N` fixture read against the limit it meets: none equals its limit. Closest reads: `auth-manager.test.ts` L4997 (created 60 minutes ago, but the case judges a 15-minute idle limit with no absolute limit), `session-of-record.test.ts` L313/L332 (12 h and 2 h against an 8 h cap), `auth-manager.mcp-oauth.test.ts` L359/L546 (consent timestamps, no limit judged). Nothing out of scope to file. ## Verification - Derived gate families (`node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, 52 commands) run at head, reconciled with `--ran`: 51 exit 0, 1 NOT MEASURED (`pnpm check:dual-build-cjs-loads`, exit 3 PREREQUISITE NOT MET: it needs every package's `dist/` and the worktree built only plugin-auth's closure). `node scripts/check-plugin-teardown-shape.mjs --self-test` first answered exit 3 on the shallow clone and is exit 0 after `git fetch --depth=1 origin 621a487`. `--ran`: "52 derived famil(ies) accounted for — 51 run, 1 NOT-MEASURED", exit 0. The 16 `--self-test` families are checker health only, not a verdict on this diff. - `skip-changeset`: plugin-auth `files[]` is `dist`, `README.md`, `CHANGELOG.md`. After a build, the new comment text and the test file name have 0 hits there; positive control `judgeSessionSwitch` has 4 hits in `dist`. --- _Generated by [Claude Code](https://claude.ai/code/session_01FEkQ5mS6HNaJTYsygvmxuM)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 714dda8 commit df22adc

1 file changed

Lines changed: 4 additions & 2 deletions

File tree

‎packages/plugins/plugin-auth/src/organization-access-policy.test.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ describe('a switch of the active organization is judged against the target\'s li
395395
manager.setOrganizationAccessPolicySource({ read: async (id) => policies[id] ?? {}, anyOrganizationRequiresMfa: async () => false });
396396
await engine.insert('sys_session', {
397397
id: 's1', user_id: 'u1', active_organization_id: ORG_B,
398-
created_at: new Date(Date.now() - 60 * MIN), last_activity_at: new Date(Date.now() - 30 * MIN),
398+
created_at: new Date(Date.now() - 59 * MIN), last_activity_at: new Date(Date.now() - 30 * MIN),
399399
expires_at: new Date(Date.now() + 60 * MIN),
400400
});
401401
return { engine, manager };
@@ -418,7 +418,9 @@ describe('a switch of the active organization is judged against the target\'s li
418418
expect(await manager.judgeSessionSwitch('s1', ORG_A)).toBe('admitted');
419419
expect(row(engine).revoked_at).toBeUndefined();
420420
const strict = await deployment({ [ORG_A]: { session_absolute_max_hours: 1 } });
421-
// 60 minutes old against a one-hour limit: not over yet.
421+
// 59 minutes old against a one-hour limit: clearly not over yet. Never on the
422+
// limit itself: the judgment reads its own clock after the fixture, so a session
423+
// made exactly 60 minutes old is over the moment one millisecond passes.
422424
expect(await strict.manager.judgeSessionSwitch('s1', ORG_A)).toBe('admitted');
423425
row(strict.engine).created_at = new Date(Date.now() - 61 * MIN);
424426
expect(await strict.manager.judgeSessionSwitch('s1', ORG_A)).toBe('ended');

0 commit comments

Comments
 (0)