Skip to main content
System invariants are rules that must hold true at all times. Violating any of them blocks a release. They are enforced through a combination of lint rules, architecture guard scripts, file-scan tests, and CI gates.

Non-negotiables

These nine rules define the platform’s safety and correctness boundaries. Every change to the codebase is evaluated against them.
1

The system prompt is not a database

User-specific knowledge belongs in the knowledge graph, not hardcoded into LLM system prompts. The system prompt provides instructions; the retrieval pipeline provides context.
2

Learning is gated: candidate, eval, approval, apply

No extracted fact becomes active truth without passing through evaluation. Facts enter as candidate, are scored and evaluated, and only transition to active after approval and patch application.
3

Evidence is untrusted text

All evidence retrieved from the knowledge graph is treated as untrusted. An injection warning is appended when evidence is included in LLM context to prevent prompt injection via stored content.
4

Roles are server-derived

The API derives user roles from authenticated sessions. Client role claims are never trusted. The role hierarchy is: viewer < operator < admin < owner.
5

Realtime path is read-only for truth tables

The realtime plane (API + brain-core) may read facts, entities, chunks, and sources but must never write to them. This is enforced by import restrictions on @reflection/db/queries/admin.
6

Facts are append-only

Facts use temporal validity (valid_from, valid_to) and supersession (supersedes_fact_id) instead of destructive updates. Old facts are never deleted — they are superseded.
7

No process.env outside designated files

Environment variable access is restricted to packages/shared/src/env.ts (backend) and apps/web/src/lib/env.ts (frontend). All other code imports from @reflection/shared/env.
8

No deep imports

Code must import from package entrypoints (@reflection/db/queries/read), never from internal paths (@reflection/db/src/queries/facts.ts).
9

Every external input is Zod-validated

All data entering the system — API request bodies, webhook payloads, event data, query parameters — is validated through Zod schemas defined in @reflection/schemas.
The eval gate (rule 2) and read-only realtime (rule 5) are the two invariants most likely to cause production incidents if violated. Both are enforced at multiple layers — ESLint, architecture guard, and dedicated file-scan tests — to prevent accidental bypass.

Ingestion lifecycle authority

The ingestion pipeline has its own set of invariants that govern how source processing state is tracked:
  1. source_ingestions is the only runtime authority for ingestion lifecycle state. No other table drives lifecycle branching.
  2. Lifecycle semantics are defined once in @reflection/schemas (ingestion-lifecycle.ts). Workers, DB query modules, and API status mapping all consume this shared kernel.
  3. ingest_runs is telemetry-only — it exists for observability and compatibility but must not drive lifecycle branching decisions.

Voice tool role policy

Voice tools — the server-tool callbacks that the voice provider calls during a conversation — follow strict role-based access:
  1. Mutation-capable tools require operator role or above (operator, admin, owner).
  2. Viewer and visitor sessions are retrieval-only by design.
  3. Tool-requested learning intents flow through the async ingestion/eval gate. The realtime path does not directly mutate truth tables, even when a tool requests it.

Complexity budget

The project operates under a “minimum viable abstraction” posture. These hard rules prevent unnecessary complexity from entering the codebase:
  1. No new infrastructure component without a measured bottleneck.
  2. No generic abstraction until at least 3 concrete call sites exist.
  3. No second implementation path for the same responsibility.
  4. No LLM entity extraction in the realtime path.
  5. No cross-layer shortcuts around boundaries.
  6. No placeholder comments (TODO, FIXME, HACK) in source files.
  7. Max cyclomatic complexity of 20 per function (enforced by ESLint).
A pull request is rejected if it:
  • Adds indirection without materially reducing duplication or coupling.
  • Introduces speculative extension points for hypothetical future use.
  • Expands scope beyond the current milestone gate.
  • Violates any non-negotiable invariant.

Definition of done

Every change must meet these criteria before merging:
  • Failing tests written first for changed behavior (test-driven development).
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass for the touched scope.
  • No focused or skipped tests (test.only, test.skip) in committed code.
  • No placeholder comments (TODO, FIXME, HACK).

Release blockers

Any of the following conditions blocks a release:
  • A boundary violation against any non-negotiable invariant.
  • A failing safety or invariant regression test.
  • An undocumented complexity increase that violates the complexity budget.
The enforcement matrix — which invariants are checked by ESLint, the architecture guard, and file-scan tests — is documented in the repository’s AGENTS.md. Each invariant is covered by at least two independent enforcement mechanisms to prevent single-point bypass.