Skip to content

Code conventions

The rules that keep the codebase clean with several people working in parallel. They originate in the skeleton proposal (2026-09-01, frozen); this page is their home as current truth (D28: a frozen proposal is cited for history only). Enforcement: tach.toml for import boundaries, pre-commit for lint, the test suite for the rest.

Created 2026-09-03. Updated 2026-09-03.

  1. New stage = new package under stages/, one PR with its service, its contract types, and mirrored tests. Never a new function bolted into an existing stage.
  2. Stages never import each other. Only the shared contracts and runs/. Enforced by tach in CI.
  3. Deterministic stages cannot import an LLM client (compose, chunk, normalize). Also tach-enforced; this is what keeps them testable with zero mocks.
  4. Stage functions are pure; idempotency is decided in one place per category. Derivations (pure functions of stored input plus versions) go through run_stage(), which computes the run key and appends a run. Whether the key is stage-supplied over an open set of version axes, and whether mutations (dedup, compose) are a named second category idempotent by unique index plus row lock, is open.
  5. Every table inherits OrgScopedBase. organization_id is part of identity, never a filter convention (invariant 8).
  6. No native Postgres enums on evolving status columns: text plus a check constraint. Registry-validated vocabularies (entity_type, claim_type) get no check constraint at all; the functions layer validates them, so adding a type is not DDL.
  7. Version bumps add modules, never edit them: prompts/v2.py next to v1.py; ontology/v0.py is never edited once v1.py exists. Whether prompts live in the repo or in Langfuse is open; the never-edit rule holds either way.
  8. One PR = one stage or one cross-cutting concern. A PR touching two stages' services means the boundary is leaking: split it or fix the boundary.
  9. No job designed to exceed about 10 minutes. Work that grows with input size shards (per chunk batch, per source), never stretches. Keeps the Lambda path open and bounds what a retry loses.

Naming: plural snake_case tables, _id for references, _at for timestamps; kind for a closed set the code switches on, *_type for a registry-validated vocabulary. The storage-level conventions (supersession columns, caller-derived ids, composite org keys) are owned by the data model doc (design/data-model.md, arriving with feat/graph-data-model).