P0.4.1 — Step 1 Audit

Inventory of the worktree against the task spec for P0.4.1 Runtime Mode Enum (γ Server Lifecycle task group, first task in that group). Scope: what already exists that the new module must integrate with, and what is absent.

Baseline: worktree E:/AMS/.worktrees/claude/p0-4-1-runtime-modes/ at commit 462b0feb (P0.1.3 CI pipeline landed).


§1. Surface being added

Target: src/modes.ts — a new module that does not yet exist.

A worktree scan confirms absence:

  • ls src/modes.ts → “No such file or directory”
  • grep -rn "RuntimeMode\|COLIBRI_MODE\|detectMode\|capabilitiesFor" src/ → zero matches
  • grep -rn "RuntimeMode\|COLIBRI_MODE\|detectMode" docs/ → only planning references (task-breakdown.md §P0.4.1, spec discussions), no implementation

No companion module (src/modes.test.ts, src/__tests__/modes.test.ts) exists either.

This is a greenfield module; the task authors the full surface in a single PR.


§2. Adjacent code that the new module must integrate with

2a. src/config.ts (120 lines — P0.1.4)

Authoritative Phase-0 environment wrapper. Relevant idioms the new module MUST mirror:

  • Eager frozen snapshot + pure factory. Exports both config (eagerly loaded at module import) and loadConfig(env) (pure; takes NodeJS.ProcessEnv). The eager path calls the factory with process.env. Rationale: the factory is the tested unit; the eager export is convenience for runtime.
  • Donor-namespace guard. assertNoDonorNamespace(env) throws synchronously on any AMS_* key. Error message shape: "Colibri does not support the legacy AMS_* namespace. Found: <keys>. Rename to COLIBRI_* or unset." Runs before Zod parse. The new module must mirror this posture specifically for AMS_MODE.
  • Frozen return values. Object.freeze(parsed.data) returns a Readonly<...>. The new module’s capabilitiesFor return value and any exported objects should likewise be immutable.
  • Typing pattern. export type Config = Readonly<z.infer<typeof schema>> — Zod-inferred plus Readonly. For a mode enum, a Zod schema is overkill; a literal union (or as const array → union) is idiomatic for small closed sets.

2b. src/__tests__/config.test.ts (207 lines — P0.1.4)

Establishes the per-test env-isolation pattern the new test file MUST follow:

  • beforeEach replaces process.env with { ...ORIGINAL_ENV } stripped of the Colibri-owned keys and every AMS_* key.
  • afterEach restores ORIGINAL_ENV.
  • The pure loadConfig(env) entry point takes an explicit env object per test — no reliance on jest.isolateModulesAsync (which is broken under ts-jest ESM due to a zod v3 locale-cache bug; comment in the file documents this at lines 10-14 + 133-142).
  • For the one case where module-load-time behavior matters (the eager config export), tests spawn a tsx subprocess with spawnSync + a clean env. This is only needed for the eager path; detectMode(env) has no eager equivalent, so subprocess tests are not required for P0.4.1.

2c. .env.example

Lists the Phase 0 env floor. Does not currently declare COLIBRI_MODE. The P0.4.1 task-breakdown checklist does not require updating .env.example (the mode has a sensible default FULL and is read from env on-the-fly, not snapshotted into config). The audit notes this as a deliberate omission — the env floor is owned by P0.1.1/P0.1.4; a mode var belongs with P0.4.x and can either be added here later (P0.4.2) or documented in docs/2-plugin/modes.md. Out of scope for this audit.

2d. src/index.ts

Placeholder (export {}). No coupling — the new module does not import from it and is not imported from it.

2e. Jest + TypeScript configuration (NOT changing)

  • jest.config.tscollectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts', '!src/**/__tests__/**']. src/modes.ts will automatically be picked up for coverage the moment it lands. No config edit required.
  • tsconfig.jsoninclude: ['src/**/*'], exclude: ['**/*.test.ts', '**/*.spec.ts']. src/__tests__/modes.test.ts is excluded from the build but still visible to ts-jest. No config edit required.
  • .eslintrc.jsonoverrides entry for **/*.test.ts sets parserOptions.project: null + relaxes no-explicit-any and no-console. src/__tests__/modes.test.ts inherits that entry. No config edit required.
  • package.jsonscripts.test = node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage; scripts.lint = eslint src. Both pick up the new file without modification.

§3. What “runtime mode” means in the Phase 0 MCP server bootstrap

The γ Server Lifecycle concept (owning task group P0.4) defines four Phase-0 modes. Cited authority:

  • docs/guides/implementation/task-breakdown.md §P0.4.1 (lines 216-230 of the task catalogue) — lists modes FULL | READONLY | TEST | MINIMAL, spec detectMode() reads COLIBRI_MODE, defaults to FULL, and notes “the donor WATCH mode is excluded — file watching is not in Phase 0 α scope”.
  • docs/colibri-system.md — Colibri is MCP orchestration across three axes (execution + intelligence + legitimacy). Modes are a control-plane mechanism that lets the server bootstrap at different privilege levels for different callers.
  • CLAUDE.md §10 — system identity; modes are not named there but the “execution of work” axis is where modes sit operationally.

Semantic sketch (derived from task-breakdown §P0.4.1 + conceptual need for P0.2.3 two-phase startup):

Mode Purpose DB writes MCP accept External I/O Integration tests
FULL Production bootstrap — everything on yes yes yes no
READONLY Safe-mode diagnostics, recovery no yes no no
TEST Jest + CI integration runs yes yes yes yes
MINIMAL Bare α boot without β/ε/ζ/η/ν domain code no yes no no

READONLY and MINIMAL expose the same capability booleans, but they exist for semantically different reasons:

  • READONLY = “the runtime is fully wired but refuses to mutate state” (safe-mode).
  • MINIMAL = “only the α transport + middleware + DB-open is wired; no domain handlers are registered” (boot-floor).

A comment in the module will document this distinction; callers should gate additional behavior on mode identity (not just capability bits) where semantics matter (e.g. domain registration happens in FULL and TEST only, not in MINIMAL or READONLY).


§4. Consumers (upcoming tasks that import from src/modes.ts)

Per task-breakdown and the Sigma dispatch brief, the new module unblocks:

  • P0.2.3 Two-phase startup — capability gating for middleware + DB open. Server bootstrap reads detectMode() once, then uses capabilitiesFor(mode) to decide which stages to run eagerly vs defer.
  • P0.2.4 Health toolserver_health surface must return the current mode string in its payload (task-breakdown L146-149 lists mode in the return shape).

No current code imports from src/modes.ts — it does not exist yet. No outbound dependency migration is required.


§5. Forbidden-phrase check

Per contract pattern from R75 (docs/contracts/r75-plan-red-contract.md §2) and CI guard in .github/workflows/ci.yml (forbidden-term scan), Phase 0 code MUST NOT:

  • Treat AMS_MODE as a live, supported variable (enforcement is the job of this task).
  • Reference data/ams.db as a live write target (this module does not touch the DB at all — not a concern).
  • Cite “78 tables”, “484 tools”, “11 middleware” as Colibri facts (this module does not describe any of those surfaces).

The implementation consciously names COLIBRI_MODE as the one supported mode env var and throws on AMS_MODE.


§6. Acceptance-criteria inventory (from task-breakdown §P0.4.1)

Lifted verbatim from docs/guides/implementation/task-breakdown.md §P0.4.1, with the Sigma-dispatch refinements noted in parentheses:

  • 4 modes in Phase 0: FULL | READONLY | TEST | MINIMAL. (Matches Sigma spec.)
  • detectMode(): reads COLIBRI_MODE env var, defaults to FULL. (Sigma adds: must throw on AMS_MODE.)
  • Each mode has a capability set. Task-breakdown lists { canWrite, canRunTests, heavyInit }; Sigma spec uses richer names { canWriteDatabase, canAcceptMCPConnections, canDispatchExternalIO, canRunIntegrationTests }. The Sigma naming is strictly more descriptive and is the one implemented. Downstream consumers (P0.2.3, P0.2.4) will read the richer names.
  • Test: detectMode() returns correct mode for each env value.
  • Test: all 4 modes have correct capability sets.
  • Test: reject AMS_MODE.
  • Test: reject invalid COLIBRI_MODE value (uppercase-enforced, so lowercase fails).
  • 100% statement + function + line coverage on src/modes.ts.

§7. Summary

  • Surface: new module src/modes.ts + test file src/__tests__/modes.test.ts. Zero existing implementation.
  • Integration point: src/config.ts’s idioms (eager frozen + pure factory + AMS_* guard + Readonly<> return types) are the style the new module mirrors.
  • Test pattern: per-test env isolation via the pure detectMode(env) factory; no jest.isolateModulesAsync (broken under ts-jest ESM); no subprocess harness needed (module has no eager side-effects).
  • Unblocks: P0.2.3 two-phase startup, P0.2.4 health tool.
  • Out of scope: updating .env.example, docs/2-plugin/modes.md, or package.json/tsconfig.json/jest.config.ts/.eslintrc.json.

P0.4.1 Step 1 Audit — 2026-04-16 — branch feature/p0-4-1-runtime-modes @ baseline 462b0feb. Next: Step 2 Contract.


Back to top

Colibri — documentation-first MCP runtime. Apache 2.0 + Commons Clause.

This site uses Just the Docs, a documentation theme for Jekyll.