Corpus versioning
When to issue a new regulatory corpus snapshot, how to chain successors, and the legal-admissibility requirements that drive corpus immutability.
Why immutability matters
A Verdict issued in 2026-Q3 is a record of "what the rules said when this transfer was evaluated." Six months later — when a regulator asks the same question or a counterparty disputes the result — that Verdict has to be reproducible against the same rules. If the corpus is mutable, the same Verdict can yield different results before and after a code edit, and the audit trail becomes legally indefensible.
Stripe handles this with API versioning at the call site; ISDA handles it with Master Agreement supplement numbers; clinical trials handle it with protocol amendment numbers. DMTR handles it with regulatory_corpus_id pinned at evaluation time, against immutable snapshots.
Versioning rules
Format. <COUNTRY>@<YYYY-MM-DD>.<N> where <N> increments within the same date if multiple snapshots are issued in one day.
Immutability. Once a snapshot constant is committed and any Verdict references it, the constant must not be modified. Apparent typos in citations are treated as material — fix forward by issuing a new snapshot, not by editing the old one.
Chaining. New snapshots set supersedes: "<previous version>" so the chain is auditable. Older snapshots remain in CORPUS_REGISTRY indefinitely.
Effective dates. effective_from is when the snapshot's contents reflect the operative regulatory state in the jurisdiction (typically the publication date of the most recent material act it captures). effective_until is set on the previous snapshot when superseded.
What triggers a new snapshot
| Trigger | Example (Argentina) |
|---|---|
| New statute / decree / regulation | Decreto 105/2026 expanding RIGI to onshore upstream |
| Derogation of an existing statute | Decreto 1083/2024 derogating federal Ley 26.160 |
| Material threshold change | RIGI sector floor adjustment |
| New BCRA Comunicación material to FX flows | "A" 8226 (cepo lift for personas humanas) |
| New BIS / OFAC / EAR posture material to chip class | Diffusion Rule rescission (May 2025) |
| New open interpretive assumption | New counsel-routed question added to open_assumptions |
| Counsel sign-off on an existing assumption | current_position change from pending_counsel_opinion to counsel_confirmed / counsel_disconfirmed |
| New recognized provincial / regulatory authority | Addition of a new RegulatoryAuthority to the snapshot |
What does not trigger a new snapshot: typo fixes (require a new snapshot), comment improvements (require a new snapshot), test additions (no snapshot effect). The discipline is rigid because the legal-admissibility floor demands it.
How a check pins to a snapshot
// In a check, the corpus is read at evaluation time:
import { lookupCorpus } from "@dmtr/energy/regulatory";
export function checkSomething(slot, opts) {
const corpus = lookupCorpus(slot.jurisdiction.country, slot.regulatory_corpus_id);
// ... read corpus.tax_stability_regimes, .open_assumptions, etc.
}
// In a Verdict, the corpus version is recorded:
{
check: "rigi_eligibility",
status: "pass",
detail: "...",
authority: "AR-RIGI",
corpus_version: "AR@2026-05-06.1"
}The corpus_version field on a CheckResult is the legally-operative anchor. Together with the rights-engine engine_version (a separate concept — see engine snapshots), a Verdict is fully reproducible at any point in the future.
Engine snapshots vs. corpus snapshots
| Corpus snapshot | Engine snapshot | |
|---|---|---|
| What it captures | Regulatory state of a jurisdiction (statutes, authorities, programs, open questions) | Behavior of the rights-engine code (which checks ran, with what thresholds, against what corpus) |
| Granularity | Per country | Repo-wide |
| Format | TypeScript constant | JSON snapshot in engine-snapshots/ directory |
| Trigger for new version | Material regulatory change in the jurisdiction | Material code change in @dmtr/energy/rights-engine or @dmtr/core/rights-engine |
| Pinned via | Position.regulatory_corpus_id, Verdict.corpus_version | Verdict.engine_version (release tag) |
Both are required for full reproducibility — a Verdict needs to know which rules ran (engine_version) AND what the rules referenced (corpus_version).
Adding a new snapshot
// 1. Copy the previous snapshot to a new constant:
export const ARGENTINA_CORPUS_2026_07: RegulatoryCorpusSnapshot = {
...ARGENTINA_CORPUS_2026_05,
corpus_version: "2026-07-15.1",
effective_from: "2026-07-15",
supersedes: "AR@2026-05-06.1",
// ... apply material changes
};
// 2. Register it in `index.ts`:
export const CORPUS_REGISTRY = {
"AR@2026-05-06.1": ARGENTINA_CORPUS_2026_05,
"AR@2026-07-15.1": ARGENTINA_CORPUS_2026_07,
// ...
};
// 3. Add `effective_until: "2026-07-15"` to the previous snapshot.
// 4. Open a PR with the diff visible — counsel reviews the delta.Backfill
When a Position or CapacityContract is created without regulatory_corpus_id, the rights-engine treats it as referencing the latest snapshot for the slot's country. This is permissive for Developer Preview use but should be tightened to require explicit pinning before production-deal reliance.
Counsel-review items (open assumptions)
Interpretive regulatory questions the schema cannot resolve without outside-counsel sign-off. Each item carries a conservative default, an implication-if-disconfirmed, and routing to specific firms. Verdicts that depend on an open assumption return conditional or counsel_review with the assumption ID attached.
Jurisdictions roadmap
What's needed to lift each stub regulatory corpus to production-grade. Per jurisdiction: statutory authorities, tax-stability anchors, indigenous consultation regime, resource-adequacy programs, and the canonical secondary-source citations that should anchor each section.