Skip to content

Commit 7b3c939

Browse files
committed
feat(runtime): guard root switches and isolate test global state
Codify one-root-per-process: initCodemap/configureResolver throw on mid-process root changes except audit worktree reindex (runtime-swap bracket). Validate user config at loadUserConfig; roll beforeEach/afterEach teardown across initCodemap suites.
1 parent aae172f commit 7b3c939

40 files changed

Lines changed: 261 additions & 34 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@stainless-code/codemap": minor
3+
---
4+
5+
`createCodemap()` and `loadUserConfig()` now fail fast: switching to a different project root in the same process throws (audit `--base` worktree reindex is exempt), and invalid config files throw at load time instead of on first resolve.

docs/architecture.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,13 +234,13 @@ The npm package exports **`createCodemap`**, **`Codemap`** (`query`, `index`), *
234234
2. **`await cm.index({ mode, files?, quiet? })`** — same pipeline as the CLI (incremental / full / targeted).
235235
3. **`cm.query(sql)`** — read-only SQL against `.codemap/index.db` (opens the DB per call).
236236

237-
**Constraint:** `initCodemap` is global to the process; only one active indexed project at a time.
237+
**Constraint:** One project root per process — a second `initCodemap` / `createCodemap` with a different `root` throws. Audit `--base` worktree reindex brackets root swaps via `runtime-swap.ts`. Re-init on the same root is allowed.
238238

239239
### User config
240240

241241
Optional **`<state-dir>/config.{ts,js,json}`** (default `.codemap/config.*`; default export: object or async factory). **`--config <path>`** overrides with an explicit file (absolute or relative to cwd). Example shape: [`codemap.config.example.json`](../codemap.config.example.json). **Self-healing (D11):** `<state-dir>/.gitignore` is reconciled to canonical on every codemap boot via **`ensureStateGitignore`** (`src/application/state-dir.ts`); JSON config is reconciled via **`ensureStateConfig`** (`src/application/state-config.ts` — prunes unknown keys with a warning, sorts alphabetically, write-only-on-drift). TS/JS configs are validate-only at load time. Bumping the canonical `STATE_GITIGNORE_BODY` constant or the Zod schema IS the migration — every consumer's project repairs itself on next boot. Single attachment point: **`src/cli/bootstrap-codemap.ts`** runs the reconcilers before `loadUserConfig`.
242242

243-
**Validation:** **`codemapUserConfigSchema`** ([Zod](https://zod.dev)) — strict object (unknown keys are rejected). **`defineConfig({ ... })`**, **`parseCodemapUserConfig`**, and **`resolveCodemapConfig`** (CLI and merged `createCodemap({ config })`) all go through the same schema. Invalid config throws **`TypeError`** with a short path/message list.
243+
**Validation:** **`codemapUserConfigSchema`** ([Zod](https://zod.dev)) — strict object (unknown keys are rejected). **`defineConfig({ ... })`**, **`parseCodemapUserConfig`**, **`loadUserConfig`**, and **`resolveCodemapConfig`** (CLI and merged `createCodemap({ config })`) all go through the same schema. Invalid config throws **`TypeError`** with a short path/message list.
244244

245245
**Exports:** `codemapUserConfigSchema`, `parseCodemapUserConfig`, `defineConfig`, and **`CodemapUserConfig`** (inferred type) from the package entry — see **`src/config.ts`** / **`dist/index.d.mts`**.
246246

docs/plans/runtime-test-isolation.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# PR 3 — runtime guards & test isolation
22

3-
> **Status:** open (not started) · **PR:** 3 of 3 · **Effort:** S–M
3+
> **Status:** open (in progress) · **PR:** 3 of 3 · **Effort:** S–M
44
>
55
> **Orchestrator:** [`security-hardening-orchestrator.md`](./security-hardening-orchestrator.md)
66
>
@@ -34,12 +34,12 @@
3434

3535
| ID | Task | Status | Verify |
3636
| --- | -------------------------------------------------------- | ------- | ------------------------------ |
37-
| 5.1 | `runtime-swap.ts` + audit worktree bracket | pending | `bun test src/runtime.test.ts` |
38-
| 5.2 | `initCodemap` / `configureResolver` throw on root switch | pending | runtime tests |
39-
| 5.3 | `resetCodemapForTest` + `installCodemapTestTeardown` | pending ||
40-
| 5.4 | Teardown rollout on `initCodemap` test suites | pending | affected `*.test.ts` |
41-
| 5.5 | `loadUserConfig``parseCodemapUserConfig` at load | pending | `bun test src/config.test.ts` |
42-
| 5.6 | `api.ts` + architecture: throws-on-root-switch | pending ||
37+
| 5.1 | `runtime-swap.ts` + audit worktree bracket | done | `bun test src/runtime.test.ts` |
38+
| 5.2 | `initCodemap` / `configureResolver` throw on root switch | done | runtime tests |
39+
| 5.3 | `resetCodemapForTest` + `installCodemapTestTeardown` | done ||
40+
| 5.4 | Teardown rollout on `initCodemap` test suites | done | affected `*.test.ts` |
41+
| 5.5 | `loadUserConfig``parseCodemapUserConfig` at load | done | `bun test src/config.test.ts` |
42+
| 5.6 | `api.ts` + architecture: throws-on-root-switch | done ||
4343
| 5.s | Commit + PR + CI | pending | `bun run check` |
4444

4545
---

docs/plans/security-hardening-orchestrator.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
| PR | Plan | Status | Blocks |
2525
| ----- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------- |
2626
| **1** | lifted → [`architecture.md`](../architecture.md) (plan retired) | **merged** ([#180](https://github.com/stainless-code/codemap/pull/180) · `a5caca8`) ||
27-
| **2** | lifted → [`architecture.md`](../architecture.md) (plan retired) | **PR open** (`fix/impact-inpath-homonyms`) ||
28-
| **3** | [`runtime-test-isolation.md`](./runtime-test-isolation.md) | **pending** | PR **1** merged (PR **2** optional) |
27+
| **2** | lifted → [`architecture.md`](../architecture.md) (plan retired) | **merged** ([#181](https://github.com/stainless-code/codemap/pull/181) · `aae172f`) ||
28+
| **3** | [`runtime-test-isolation.md`](./runtime-test-isolation.md) | **in progress** (`fix/runtime-test-isolation`) | PR **1** merged (PR **2** optional) |
2929

3030
||| **deferred** | golden `schema.test.ts` + path guards |
3131
||| **skip** | atomic `ensureStateConfig` writes |
@@ -64,17 +64,17 @@ Evaluated 2026-06 against [roadmap § Floors](../roadmap.md#floors-v1-product-sh
6464

6565
## Session log
6666

67-
| Date | Event | Notes |
68-
| ---------- | ----------- | ---------------------------------------------------------------------------- |
69-
| 2026-06-10 | Triage | ROI on 7 slices; 3-PR program adopted. |
70-
| 2026-06-10 | PR 1 impl | PR **1** committed on `fix/security-hardening-wave1`; harden pass in flight. |
71-
| 2026-06-05 | PR 1 harden | `/harden-pr full` — plan retired; contracts in architecture/glossary. |
72-
| 2026-06-05 | PR 1 merge | [#180](https://github.com/stainless-code/codemap/pull/180)`a5caca8`. |
73-
| 2026-06-05 | PR 2 start | `fix/impact-inpath-homonyms``inPath` + homonym walks in impact-engine. |
74-
| 2026-06-05 | PR 2 harden | `/harden-pr full` — plan retired; CLI/MCP/docs parity. |
75-
| | PR 2 merge | _PR URL · merge SHA_ |
76-
| | PR 3 start | _from `main`_ |
77-
|| PR 3 merge | _fill · close orchestrator_ |
67+
| Date | Event | Notes |
68+
| ---------- | ----------- | ----------------------------------------------------------------------------- |
69+
| 2026-06-10 | Triage | ROI on 7 slices; 3-PR program adopted. |
70+
| 2026-06-10 | PR 1 impl | PR **1** committed on `fix/security-hardening-wave1`; harden pass in flight. |
71+
| 2026-06-05 | PR 1 harden | `/harden-pr full` — plan retired; contracts in architecture/glossary. |
72+
| 2026-06-05 | PR 1 merge | [#180](https://github.com/stainless-code/codemap/pull/180)`a5caca8`. |
73+
| 2026-06-05 | PR 2 start | `fix/impact-inpath-homonyms``inPath` + homonym walks in impact-engine. |
74+
| 2026-06-05 | PR 2 harden | `/harden-pr full` — plan retired; CLI/MCP/docs parity. |
75+
| 2026-06-05 | PR 2 merge | [#181](https://github.com/stainless-code/codemap/pull/181)`aae172f`. |
76+
| 2026-06-05 | PR 3 start | `fix/runtime-test-isolation` — root guards + test teardown + config validate. |
77+
|| PR 3 merge | _fill · close orchestrator_ |
7878

7979
---
8080

scripts/agent-eval/parse-agent-log.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { join } from "node:path";
1111

1212
import { resolveCodemapConfig } from "../../src/config";
1313
import { initCodemap } from "../../src/runtime";
14+
import { installCodemapTestTeardown } from "../../src/test-helpers/runtime-reset";
1415
import { resolveGoldenQuery } from "../query-golden/resolve-golden-query";
1516
import { compareLogArms, summarizeLogComparison } from "./compare-live-logs";
1617
import { runLiveMcpArm } from "./live-mcp-arm";
@@ -43,6 +44,8 @@ import {
4344
traditionalToolSequence,
4445
} from "./traditional-probe";
4546

47+
installCodemapTestTeardown();
48+
4649
const sampleLog = join(
4750
import.meta.dir,
4851
"../../fixtures/agent-eval/sample-cursor-log.json",

src/api.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { tmpdir } from "node:os";
44
import { join } from "node:path";
55

66
import { createCodemap } from "./api";
7+
import { installCodemapTestTeardown } from "./test-helpers/runtime-reset";
8+
9+
installCodemapTestTeardown();
710

811
describe("createCodemap", () => {
912
test("query runs against the index database", async () => {

src/api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ export interface CodemapInitOptions {
4545
* and returns a {@link Codemap} handle.
4646
*
4747
* @remarks
48-
* Only one Codemap project per process: `initCodemap` is global; the last `createCodemap()` wins.
48+
* One project root per process: a second `createCodemap()` with a different `root` throws.
49+
* Re-initializing the same root is allowed. Audit `--base` worktree reindex is the only
50+
* production path that may temporarily switch roots (`runtime-swap.ts`).
4951
*/
5052
export async function createCodemap(
5153
options: CodemapInitOptions = {},

src/application/affected-engine.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
44
import { tmpdir } from "node:os";
55
import { join } from "node:path";
66

7+
import { installCodemapTestTeardown } from "../test-helpers/runtime-reset";
8+
9+
installCodemapTestTeardown();
10+
711
import { resolveCodemapConfig } from "../config";
812
import { closeDb, createTables, openDb } from "../db";
913
import { initCodemap } from "../runtime";

src/application/audit-engine.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getTsconfigPath,
1010
initCodemap,
1111
} from "../runtime";
12+
import { enterRuntimeSwap, exitRuntimeSwap } from "../runtime-swap";
1213
import { openCodemapDatabase } from "../sqlite-db";
1314
import {
1415
isGitRepo,
@@ -456,6 +457,7 @@ export function makeWorktreeReindex(): ReindexFn {
456457
// to <worktree>/.codemap/index.db via the default state-dir.
457458
const savedConfig = getCodemapConfig();
458459
let wtDb;
460+
enterRuntimeSwap();
459461
try {
460462
const wtUser = await loadUserConfig(worktreePath, undefined);
461463
initCodemap(resolveCodemapConfig(worktreePath, wtUser));
@@ -466,6 +468,7 @@ export function makeWorktreeReindex(): ReindexFn {
466468
wtDb?.close();
467469
initCodemap(savedConfig);
468470
configureResolver(getProjectRoot(), getTsconfigPath());
471+
exitRuntimeSwap();
469472
}
470473
});
471474
// Catch on the chain itself so one failed reindex doesn't poison the

src/application/boundary-rules.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { writeFileSync, mkdirSync } from "node:fs";
44
import { tmpdir } from "node:os";
55
import { join } from "node:path";
66

7+
import { installCodemapTestTeardown } from "../test-helpers/runtime-reset";
8+
9+
installCodemapTestTeardown();
10+
711
import { resolveCodemapConfig } from "../config";
812
import { closeDb, createTables, openDb, reconcileBoundaryRules } from "../db";
913
import { initCodemap } from "../runtime";

0 commit comments

Comments
 (0)