Invariant Registry — Engineer Guide
Canonical registry:
Lint tool:tools/lint/invariant_refs.py
Introduced: RM-0.6.4.11
AngaraBase Invariant Registry makes key architectural guarantees machine-traceable: each invariant links ID → RFC/spec → tests → metrics → evidence. This prevents drift between documentation, code, and observability.
Current Invariants (seed v0)
| ID | Subsystem | Risk | Status |
|---|---|---|---|
INV-MVCC-SNAPSHOT-VISIBILITY | tx_overlay | critical | active |
INV-WAL-DURABLE-AFTER-FSYNC | wal | critical | active |
INV-RECOVERY-REDO-UNDO-CLR | recovery | critical | active |
INV-RESOURCE-FAIL-CLOSED | storage/backpressure | high | active |
INV-SPILL-RECURSION-OVERFLOW-53400 | query/spill | high | active |
When INV-ID Is Required in Code
When changing files in the following paths, include a // INV-<ID> comment
or add the file to the scoped allowlist:
| Path | Required invariant |
|---|---|
crates/angarabase/src/tx_overlay/ | INV-MVCC-SNAPSHOT-VISIBILITY |
crates/angarabase/src/wal/ | INV-WAL-DURABLE-AFTER-FSYNC |
crates/angarabase/src/recovery.rs | INV-RECOVERY-REDO-UNDO-CLR |
crates/angarabase/src/storage/backpressure/ | INV-RESOURCE-FAIL-CLOSED |
crates/angarabase/src/query/spill/ | INV-SPILL-RECURSION-OVERFLOW-53400 |
Example code comment
#![allow(unused)]
fn main() {
// INV-WAL-DURABLE-AFTER-FSYNC: UndoAppend must be WAL-durable before PageDelta is written.
// See: RFC-2026-073 §4.Y — ordering invariant (UNDO-before-heap).
assert!(
undo_lsn < page_delta_lsn,
"WAL ordering invariant violated: undo_lsn={} >= page_delta_lsn={}",
undo_lsn, page_delta_lsn
);
}
How to Add a New Invariant
Step 1 — Add an entry to invariants.yaml
- id: INV-<CATEGORY>-<NAME>
title: "Human-readable name"
surface: <module_path> # e.g. tx_overlay, wal, query/spill
risk_level: critical # critical | high | medium
owner: <team> # e.g. storage-team, query-team
spec_refs:
- rfcs/RFC-YYYY-NNN-name.md # path relative to repo root docs/rfcs/
test_refs:
- crates/angarabase/src/<path>/tests.rs
metric_refs:
- angarabase_<metric_name> # Prometheus metric name (not a file path)
evidence_refs: [] # or list of pinned evidence paths
status: active # or pending_evidence if refs TBD
Step 2 — If refs are not implemented yet
Use status: pending_evidence and add target_rm:
status: pending_evidence
target_rm: RM-0.6.5.0 # train that will create tests/metrics
Lint does not check file refs for pending_evidence invariants.
Step 3 — Run lint
python3 tools/lint/invariant_refs.py --check invariants.yaml
# → exit 0: OK
Step 4 — Add a comment in code
Find the assertion/guard in source code and add the // INV-<ID> comment nearby.
Step 5 — Commit
tools/dev/git-commit-safe.sh -m "NEW(invariants): add INV-<ID> to registry" \
-- invariants.yaml
Schema Fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | ✓ | Unique ID, starts with INV- |
title | string | ✓ | Short human-readable name |
surface | string | ✓ | Module/subsystem |
risk_level | enum | ✓ | critical / high / medium |
owner | string | ✓ | Owning team |
spec_refs | list[path] | ✓ | Paths to RFC/spec (checked by lint) |
test_refs | list[path] | ✓ | Paths to test files (checked by lint) |
metric_refs | list[string] | ✓ | Prometheus metrics (names) |
evidence_refs | list[path] | ✓ | Pinned evidence (checked by lint; [] = none) |
status | enum | ✓ | active / pending_evidence |
target_rm | string | for pending_evidence | Train that closes the gap |
Running Lint in CI
Lint runs automatically through docs/validate-docs.sh:
bash docs/validate-docs.sh
# → ✅ Invariant registry valid (5 invariant(s) checked, 0 errors)
Standalone:
python3 tools/lint/invariant_refs.py --check invariants.yaml
# exit 0 → OK
# exit 1 → broken ref or schema error (blocks CI)