cursedbelt-server 4.39.0 → 4.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,4 +27,14 @@ export const ACTIVITY_MIGRATIONS = [
27
27
  db.run(statement);
28
28
  },
29
29
  },
30
+ {
31
+ // A store that ran 0001 before the case-insensitive actor index existed never gets it from
32
+ // 0001 again. Idempotent, so on a fresh store (where 0001 already built it) it is a no-op.
33
+ name: "0002_activity_actor_ci",
34
+ up(db) {
35
+ for (const statement of ACTIVITY_DDL)
36
+ if (statement.includes("idx_activity_events_actor_ci"))
37
+ db.run(statement);
38
+ },
39
+ },
30
40
  ];
@@ -33,6 +33,14 @@ export const ACTIVITY_DDL = [
33
33
  // question, and the one a fleet-wide search runs first.
34
34
  `CREATE INDEX IF NOT EXISTS idx_activity_events_actor
35
35
  ON ${ACTIVITY_EVENTS_TABLE} (actor, event_id DESC)`,
36
+ // 🔴 The index the actor filter can actually USE. `buildStream` matches an actor
37
+ // case-insensitively — `LOWER(actor) = LOWER(?)` — and SQLite cannot use the plain
38
+ // `actor` index above for that, so every actor query scanned the stream: measured
39
+ // 2026-09-28 (task 2254) on auth-stage, auth's sign-in familiarity read (actor + action +
40
+ // outcome, on every sign-in and app-open) cost 760 rows a call and 2.2M rows in four
41
+ // days, growing with the log for ever. With this index: 41 rows, flat.
42
+ `CREATE INDEX IF NOT EXISTS idx_activity_events_actor_ci
43
+ ON ${ACTIVITY_EVENTS_TABLE} (LOWER(actor), action, at DESC)`,
36
44
  // "Everything that ever happened to this record."
37
45
  `CREATE INDEX IF NOT EXISTS idx_activity_events_resource
38
46
  ON ${ACTIVITY_EVENTS_TABLE} (kind, resource_id, event_id DESC)`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedbelt-server",
3
- "version": "4.39.0",
3
+ "version": "4.40.1",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "description": "The app-facing Bun/Hono server tier of the cursedbelt split — storage, sharing, activity, guard, sync. React-free; cursedbelt-core below it.",
@@ -352,7 +352,7 @@
352
352
  "dependencies": {
353
353
  "argon2id": "1.0.1",
354
354
  "cursedbelt-core": "^2.1.1",
355
- "cursedops": ">=0.8.1 <0.11.0",
355
+ "cursedops": ">=0.8.1 <0.12.0",
356
356
  "cwip": "^4.6.0",
357
357
  "jose": "^6.2.3"
358
358
  },
@@ -132,19 +132,23 @@ const packageOf = (specifier: string): string | undefined => {
132
132
  * non-zero exit or an empty bundle THROWS rather than returning an empty set. That is the
133
133
  * one way a check like this dies quietly.
134
134
  */
135
- const scan = (entry: string, subpath: string): { packages: Set<string>; bun: Set<string> } => {
135
+ const scan = async (entry: string, subpath: string): Promise<{ packages: Set<string>; bun: Set<string> }> => {
136
136
  const outdir = `${process.env.TMPDIR ?? '/tmp'}/cursedbelt-server-barrel-scan/${subpath.replace(/[^a-z0-9]+/gi, '-')}`;
137
- const build = Bun.spawnSync(['bun', 'build', entry, '--target=bun', '--packages=external', '--outdir', outdir], {
137
+ const child = Bun.spawn(['bun', 'build', entry, '--target=bun', '--packages=external', '--outdir', outdir], {
138
138
  cwd: REPO,
139
+ stdin: 'ignore',
139
140
  stdout: 'pipe',
140
141
  stderr: 'pipe',
141
142
  });
143
+ const [, stderr, exitCode] = await Promise.all([
144
+ new Response(child.stdout).text(),
145
+ new Response(child.stderr).text(),
146
+ child.exited,
147
+ ]);
142
148
  const emitted = [...new Bun.Glob('**/*.js').scanSync({ cwd: outdir, onlyFiles: true })];
143
149
  const bundle = emitted.map((f) => readFileSync(`${outdir}/${f}`, 'utf8')).join('\n');
144
- if (build.exitCode !== 0 || bundle.trim() === '') {
145
- throw new Error(
146
- `could not bundle ${subpath} (${entry}, exit ${build.exitCode}) — a check that cannot measure is not a passing check:\n${build.stderr.toString()}`,
147
- );
150
+ if (exitCode !== 0 || bundle.trim() === '') {
151
+ throw new Error(`could not bundle ${subpath} (${entry}, exit ${exitCode}) — a check that cannot measure is not a passing check:\n${stderr}`);
148
152
  }
149
153
  const packages = new Set<string>();
150
154
  const bun = new Set<string>();
@@ -168,8 +172,8 @@ const scan = (entry: string, subpath: string): { packages: Set<string>; bun: Set
168
172
  * One bundle per subpath, shared by both assertions. Bundling is the expensive part of this
169
173
  * file and the answer cannot change between two `it`s in the same run.
170
174
  */
171
- const scans = new Map<string, { packages: Set<string>; bun: Set<string> }>();
172
- const scanOnce = (entry: string, subpath: string): { packages: Set<string>; bun: Set<string> } => {
175
+ const scans = new Map<string, Promise<{ packages: Set<string>; bun: Set<string> }>>();
176
+ const scanOnce = (entry: string, subpath: string): Promise<{ packages: Set<string>; bun: Set<string> }> => {
173
177
  const cached = scans.get(subpath);
174
178
  if (cached !== undefined) return cached;
175
179
  const fresh = scan(entry, subpath);
@@ -190,8 +194,8 @@ describe('no public subpath statically reaches what its consumers may not have',
190
194
 
191
195
  for (const { subpath, entry } of SUBPATHS) {
192
196
  const allowed = MAY_DRAG[subpath] ?? [];
193
- it(`${subpath} drags ${allowed.length === 0 ? 'no optional peer' : allowed.join(' + ') + ' and nothing more'}`, () => {
194
- const dragged = [...scanOnce(entry, subpath).packages].filter((n) => OPTIONAL_PEERS.has(n)).sort();
197
+ it(`${subpath} drags ${allowed.length === 0 ? 'no optional peer' : allowed.join(' + ') + ' and nothing more'}`, async () => {
198
+ const dragged = [...(await scanOnce(entry, subpath)).packages].filter((n) => OPTIONAL_PEERS.has(n)).sort();
195
199
  const unexpected = dragged.filter((n) => !allowed.includes(n));
196
200
  expect(
197
201
  unexpected,
@@ -213,8 +217,8 @@ describe('no public subpath statically reaches what its consumers may not have',
213
217
 
214
218
  for (const { subpath, entry } of SUBPATHS) {
215
219
  const allowed = MAY_NEED_BUN[subpath] ?? [];
216
- it(`${subpath} ${allowed.length === 0 ? 'loads on a Worker — no bun builtin' : `needs ${allowed.join(' + ')} and nothing more`}`, () => {
217
- const reached = [...scanOnce(entry, subpath).bun].sort();
220
+ it(`${subpath} ${allowed.length === 0 ? 'loads on a Worker — no bun builtin' : `needs ${allowed.join(' + ')} and nothing more`}`, async () => {
221
+ const reached = [...(await scanOnce(entry, subpath)).bun].sort();
218
222
  const unexpected = reached.filter((n) => !allowed.includes(n));
219
223
  expect(
220
224
  unexpected,
@@ -361,16 +361,20 @@ console.log(JSON.stringify(out));
361
361
  `,
362
362
  );
363
363
 
364
- const child = Bun.spawnSync(['bun', 'run', 'entry.ts'], {
364
+ const child = Bun.spawn(['bun', 'run', 'entry.ts'], {
365
365
  cwd: root,
366
+ stdin: 'ignore',
366
367
  stdout: 'pipe',
367
368
  stderr: 'pipe',
368
369
  });
369
- const stdout = child.stdout.toString().trim();
370
- if (child.exitCode !== 0 || stdout === '') {
371
- throw new Error(
372
- `the fixture could not run at all (exit ${child.exitCode}) — that is a broken check, not a passing one:\n${child.stderr.toString()}`,
373
- );
370
+ const [stdoutRaw, stderr, exitCode] = await Promise.all([
371
+ new Response(child.stdout).text(),
372
+ new Response(child.stderr).text(),
373
+ child.exited,
374
+ ]);
375
+ const stdout = stdoutRaw.trim();
376
+ if (exitCode !== 0 || stdout === '') {
377
+ throw new Error(`the fixture could not run at all (exit ${exitCode}) — that is a broken check, not a passing one:\n${stderr}`);
374
378
  }
375
379
  return JSON.parse(stdout) as Record<string, Outcome>;
376
380
  };
@@ -412,16 +416,18 @@ const OPTIONAL_PEERS = new Set(
412
416
  /** `import x from "pkg"`, `import "pkg"`, `export … from "pkg"` — one line each, as bun emits. */
413
417
  const STATIC_SPECIFIER = /^(?:import|export)\s[^\n]*?["']([^"'\n]+)["'];?$|^import\s*["']([^"'\n]+)["'];?$/gm;
414
418
 
415
- const staticOptionalPeersOf = (entry: string): string[] => {
416
- const build = Bun.spawnSync(
419
+ const staticOptionalPeersOf = async (entry: string): Promise<string[]> => {
420
+ const child = Bun.spawn(
417
421
  ['bun', 'build', entry, '--target=bun', '--packages=external'],
418
- { cwd: REPO, stdout: 'pipe', stderr: 'pipe' },
422
+ { cwd: REPO, stdin: 'ignore', stdout: 'pipe', stderr: 'pipe' },
419
423
  );
420
- const bundle = build.stdout.toString();
421
- if (build.exitCode !== 0 || bundle.trim() === '') {
422
- throw new Error(
423
- `could not bundle ${entry} (exit ${build.exitCode}) — a check that cannot measure is not a passing check:\n${build.stderr.toString()}`,
424
- );
424
+ const [bundle, stderr, exitCode] = await Promise.all([
425
+ new Response(child.stdout).text(),
426
+ new Response(child.stderr).text(),
427
+ child.exited,
428
+ ]);
429
+ if (exitCode !== 0 || bundle.trim() === '') {
430
+ throw new Error(`could not bundle ${entry} (exit ${exitCode}) — a check that cannot measure is not a passing check:\n${stderr}`);
425
431
  }
426
432
  const found = new Set<string>();
427
433
  for (const match of bundle.matchAll(STATIC_SPECIFIER)) {
@@ -478,9 +484,9 @@ describe('barrels reach no optional peer statically', () => {
478
484
  });
479
485
 
480
486
  for (const target of NO_STATIC_OPTIONAL_PEERS) {
481
- it(`${pkg.name}/${target.subpath.slice(2)} costs a consumer no optional peer to import`, () => {
487
+ it(`${pkg.name}/${target.subpath.slice(2)} costs a consumer no optional peer to import`, async () => {
482
488
  expect(
483
- staticOptionalPeersOf(target.entry),
489
+ await staticOptionalPeersOf(target.entry),
484
490
  `importing ${pkg.name}/${target.subpath.slice(2)} statically pulls these OPTIONAL peers, so every consumer must install them for code it may never call. Either import the leaf module that has what you need instead of a sibling INDEX (that is how 'otplib' got in here twice), or make the dependency lazy: \`const { x } = await import('pkg')\` inside the function that needs it.`,
485
491
  ).toEqual([]);
486
492
  });
@@ -23,7 +23,6 @@
23
23
  * to skip it. Ignored-vs-not is the property that actually regresses.
24
24
  */
25
25
  import { describe, expect, test } from 'bun:test'
26
- import { spawnSync } from 'node:child_process'
27
26
  import { existsSync } from 'node:fs'
28
27
  import { fileURLToPath } from 'node:url'
29
28
 
@@ -37,13 +36,18 @@ describe('bun.lock is part of the checkout', () => {
37
36
  ).toBe(true)
38
37
  })
39
38
 
40
- test('git is not told to ignore it', () => {
41
- const git = spawnSync('git', ['check-ignore', '-q', 'bun.lock'], { cwd: repoRoot })
42
- if (git.error) return // no git on this host: nothing to assert about ignore rules
39
+ test('git is not told to ignore it', async () => {
40
+ let status: number
41
+ try {
42
+ const child = Bun.spawn(['git', 'check-ignore', '-q', 'bun.lock'], { cwd: repoRoot, stdin: 'ignore', stdout: 'pipe', stderr: 'pipe' })
43
+ status = await child.exited
44
+ } catch {
45
+ return // no git on this host: nothing to assert about ignore rules
46
+ }
43
47
 
44
48
  // exit 0 = the path IS ignored; 1 = it is not; anything else = git could not tell us.
45
49
  expect(
46
- git.status,
50
+ status,
47
51
  'bun.lock is gitignored, so a fresh clone has no lockfile and `bun install --frozen-lockfile` cannot pass there. Delete the stanza from .gitignore rather than the assertion.',
48
52
  ).not.toBe(0)
49
53
  })
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 🔴 The actor filter must reach an index (task 2254).
3
+ *
4
+ * `buildStream` matches an actor with `LOWER(actor) = LOWER(?)`, which the plain `actor` index
5
+ * cannot serve — so until `idx_activity_events_actor_ci` every actor query scanned the stream.
6
+ * Measured on auth-stage, 2026-09-28: 760 rows a call on auth's sign-in familiarity read, growing
7
+ * with the log; 41 with the index. This pins the PLAN, so dropping the index or changing the
8
+ * filter's shape fails here rather than on the next bill.
9
+ */
10
+ import { Database } from "bun:sqlite";
11
+ import { expect, test } from "bun:test";
12
+ import { buildStream } from "./query.js";
13
+ import { ACTIVITY_DDL } from "./schema.js";
14
+
15
+ test("a case-insensitive actor + action filter searches idx_activity_events_actor_ci, never a scan", () => {
16
+ const db = new Database(":memory:");
17
+ for (const ddl of ACTIVITY_DDL) db.run(ddl);
18
+ const insert = db.prepare("INSERT INTO activity_events (at, app, actor, action, outcome) VALUES (?, 'auth', ?, ?, 'ok')");
19
+ for (let i = 0; i < 500; i++) insert.run(new Date(1_790_000_000_000 + i).toISOString(), `user${i % 50}@example.com`, i % 2 ? "auth.sign-in" : "auth.app-open");
20
+ db.run("ANALYZE");
21
+ const { sql, params } = buildStream({ actor: "User7@Example.com", action: "auth.sign-in", outcome: "ok", limit: 20 }, []);
22
+ const plan = (db.query(`EXPLAIN QUERY PLAN ${sql}`).all(...(params as never[])) as { detail: string }[]).map((r) => r.detail).join(" | ");
23
+ expect(plan).toContain("idx_activity_events_actor_ci");
24
+ expect(plan).not.toMatch(/SCAN activity_events/);
25
+ });
@@ -129,7 +129,7 @@ describe("cursedbelt-server/activity/d1", () => {
129
129
  const migrated = new Database(":memory:");
130
130
  runMigrations(migrated, ACTIVITY_MIGRATIONS);
131
131
  expect(shape(withDdl(ACTIVITY_DDL))).toEqual(shape(migrated));
132
- expect(shape(migrated).length).toBe(5);
132
+ expect(shape(migrated).length).toBe(6);
133
133
  });
134
134
 
135
135
  test("🔴 refuses an event that cannot be filtered or attributed, and writes nothing", async () => {
@@ -30,4 +30,12 @@ export const ACTIVITY_MIGRATIONS: readonly Migration[] = [
30
30
  for (const statement of ACTIVITY_DDL) db.run(statement);
31
31
  },
32
32
  },
33
+ {
34
+ // A store that ran 0001 before the case-insensitive actor index existed never gets it from
35
+ // 0001 again. Idempotent, so on a fresh store (where 0001 already built it) it is a no-op.
36
+ name: "0002_activity_actor_ci",
37
+ up(db: Database): void {
38
+ for (const statement of ACTIVITY_DDL) if (statement.includes("idx_activity_events_actor_ci")) db.run(statement);
39
+ },
40
+ },
33
41
  ];
@@ -35,6 +35,14 @@ export const ACTIVITY_DDL: readonly string[] = [
35
35
  // question, and the one a fleet-wide search runs first.
36
36
  `CREATE INDEX IF NOT EXISTS idx_activity_events_actor
37
37
  ON ${ACTIVITY_EVENTS_TABLE} (actor, event_id DESC)`,
38
+ // 🔴 The index the actor filter can actually USE. `buildStream` matches an actor
39
+ // case-insensitively — `LOWER(actor) = LOWER(?)` — and SQLite cannot use the plain
40
+ // `actor` index above for that, so every actor query scanned the stream: measured
41
+ // 2026-09-28 (task 2254) on auth-stage, auth's sign-in familiarity read (actor + action +
42
+ // outcome, on every sign-in and app-open) cost 760 rows a call and 2.2M rows in four
43
+ // days, growing with the log for ever. With this index: 41 rows, flat.
44
+ `CREATE INDEX IF NOT EXISTS idx_activity_events_actor_ci
45
+ ON ${ACTIVITY_EVENTS_TABLE} (LOWER(actor), action, at DESC)`,
38
46
  // "Everything that ever happened to this record."
39
47
  `CREATE INDEX IF NOT EXISTS idx_activity_events_resource
40
48
  ON ${ACTIVITY_EVENTS_TABLE} (kind, resource_id, event_id DESC)`,
@@ -20,26 +20,31 @@ import { fileURLToPath } from 'node:url';
20
20
  const DIR = fileURLToPath(new URL('./typetests/', import.meta.url));
21
21
  const TSC = fileURLToPath(new URL('../../node_modules/typescript/bin/tsc', import.meta.url));
22
22
 
23
- const tsc = (project: string) => {
24
- const run = Bun.spawnSync(['bun', TSC, '-p', project], { stdout: 'pipe', stderr: 'pipe' });
25
- return { code: run.exitCode, out: `${run.stdout.toString()}${run.stderr.toString()}` };
23
+ const tsc = async (project: string) => {
24
+ const child = Bun.spawn(['bun', TSC, '-p', project], { stdin: 'ignore', stdout: 'pipe', stderr: 'pipe' });
25
+ const [stdout, stderr, code] = await Promise.all([
26
+ new Response(child.stdout).text(),
27
+ new Response(child.stderr).text(),
28
+ child.exited,
29
+ ]);
30
+ return { code, out: `${stdout}${stderr}` };
26
31
  };
27
32
 
28
33
  describe("Bun.serve's websocket overload", () => {
29
- it('the trap is still live in bun’s types, and serveBun is the seam that never meets it', () => {
30
- const run = tsc(`${DIR}tsconfig.json`);
34
+ it('the trap is still live in bun’s types, and serveBun is the seam that never meets it', async () => {
35
+ const run = await tsc(`${DIR}tsconfig.json`);
31
36
  expect(run.out).toBe('');
32
37
  expect(run.code).toBe(0);
33
38
  }, 60_000);
34
39
 
35
- it('🔴 without the directives the SAME lines fail — so the check above can go red', () => {
40
+ it('🔴 without the directives the SAME lines fail — so the check above can go red', async () => {
36
41
  const source = readFileSync(`${DIR}serveBunOverload.ts`, 'utf8').replace(/^\s*\/\/ @ts-expect-error.*$/gm, '');
37
42
  const file = `${DIR}serveBunOverload.stripped.ts`;
38
43
  const project = `${DIR}tsconfig.stripped.json`;
39
44
  writeFileSync(file, source);
40
45
  writeFileSync(project, JSON.stringify({ extends: './tsconfig.json', include: ['serveBunOverload.stripped.ts'] }));
41
46
  try {
42
- const run = tsc(project);
47
+ const run = await tsc(project);
43
48
  expect(run.code).not.toBe(0);
44
49
  const errors = run.out.split('\n').filter((line) => line.includes('error TS'));
45
50
  expect(errors).toHaveLength(2);
@@ -25,7 +25,6 @@
25
25
  * when `git` cannot answer — a check that silently degrades to green is worse than none.
26
26
  */
27
27
  import { describe, expect, test } from 'bun:test';
28
- import { spawnSync } from 'node:child_process';
29
28
  import { existsSync } from 'node:fs';
30
29
  import { fileURLToPath } from 'node:url';
31
30
  import pkg from '../package.json';
@@ -36,14 +35,15 @@ const repoRoot = fileURLToPath(new URL('..', import.meta.url));
36
35
  const shippedDirs = (): string[] =>
37
36
  ((pkg as { files?: string[] }).files ?? []).filter((f) => f !== 'dist' && existsSync(`${repoRoot}/${f}`));
38
37
 
39
- const git = (...args: string[]): { ok: boolean; out: string } => {
40
- const r = spawnSync('git', ['-C', repoRoot, ...args], { encoding: 'utf8' });
41
- return { ok: r.status === 0, out: (r.stdout ?? '').trim() };
38
+ const git = async (...args: string[]): Promise<{ ok: boolean; out: string }> => {
39
+ const child = Bun.spawn(['git', '-C', repoRoot, ...args], { stdin: 'ignore', stdout: 'pipe', stderr: 'pipe' });
40
+ const [out, , code] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
41
+ return { ok: code === 0, out: out.trim() };
42
42
  };
43
43
 
44
44
  describe('every file inside the published surface is tracked', () => {
45
- test('git can answer at all — a degraded check must refuse, not pass', () => {
46
- expect(git('rev-parse', '--is-inside-work-tree').ok, 'not a git checkout, so this check proves nothing').toBe(true);
45
+ test('git can answer at all — a degraded check must refuse, not pass', async () => {
46
+ expect((await git('rev-parse', '--is-inside-work-tree')).ok, 'not a git checkout, so this check proves nothing').toBe(true);
47
47
  });
48
48
 
49
49
  test('package.json names a shippable directory to scan', () => {
@@ -52,10 +52,10 @@ describe('every file inside the published surface is tracked', () => {
52
52
  expect(shippedDirs().length, '`files` names no committed directory — nothing would be scanned').toBeGreaterThan(0);
53
53
  });
54
54
 
55
- test('no untracked file sits inside a directory `files` ships', () => {
55
+ test('no untracked file sits inside a directory `files` ships', async () => {
56
56
  // --others = untracked; --exclude-standard honours .gitignore, so deliberately
57
57
  // ignored paths (.agent.noindex, node_modules) are not offences.
58
- const { ok, out } = git('ls-files', '--others', '--exclude-standard', '--', ...shippedDirs());
58
+ const { ok, out } = await git('ls-files', '--others', '--exclude-standard', '--', ...shippedDirs());
59
59
  expect(ok, 'git ls-files failed').toBe(true);
60
60
  const untracked = out === '' ? [] : out.split('\n');
61
61
  expect(