Invariants¶
The AutonomyOps ADK enforces thirteen architectural invariants (INV-01–INV-13). Violating any invariant is a build-breaking failure. CI guardrails and failure injection tests detect violations automatically. No invariant may be relaxed without a signed-off Architecture Decision Record (ADR).
For the machine-readable traceability table (invariant → code module → CI guardrail → FI test), see Traceability → Invariant Map.
Summary Table¶
Inv |
Name |
Enforcement Layer |
FI Coverage |
|---|---|---|---|
INV-01 |
No Shared State |
Startup path check + |
scan-only |
INV-02 |
No Convergence Tracking |
|
scan-only |
INV-03 |
No Leader Election |
|
scan-only |
INV-04 |
Disk Ceiling |
|
FI-C1-01, FI-C2-01, FI-C2-02 |
INV-05 |
Platform Assurance Binding |
|
FI-C1-05 |
INV-06 |
Local-Only Activation |
Scheduler local-only params; code review |
structural |
INV-07 |
Bounded Memory |
cgroup v2 + pool-bounded transport buffers |
via INV-05 FI |
INV-08 |
Idempotent Segment Receive |
Segment ID dedup in local index |
unit tests |
INV-09 |
Authorization Boundary |
mTLS + CRL in transport layer |
FI-C3-02 |
INV-10 |
Mission-Layer Decoupling |
|
FI-C1-03 |
INV-11 |
Deterministic Relay |
Lexicographic comparator, no PRNG in relay path |
structural |
INV-12 |
Bounded Retry |
|
FI-C4-01, FI-C4-02, FI-C4-03 |
INV-13 |
Bounded Per-Peer Ingest Quota |
|
FI-C3-03 |
INV-02 — No Convergence Tracking¶
Statement: No convergence protocol, vector clock, CRDT, gossip, or anti-entropy
mechanism may exist in any edge/ package.
Rationale: Convergence requires shared coordination state, which violates INV-01 and introduces undefined behaviour under network partition. The edge capability is designed for deterministic relay, not eventual consistency.
Enforcement:
edge/ci/scan_prohibited— prohibited symbols:raft,paxos,gossip_convergence,vector_clock,crdt,anti_entropy, and related patterns
INV-03 — No Leader Election¶
Statement: No node may be designated a leader, primary, or coordinator. All nodes are peers with identical role and authority.
Rationale: Leader election is a form of convergence coordination. It also creates a single point of failure incompatible with the autonomous node model.
Enforcement:
edge/ci/scan_prohibited— prohibited election/nomination patternsCode-review checklist sign-off required for any change touching peer selection
INV-04 — Disk Ceiling¶
Statement: Disk usage never exceeds storage.disk_ceiling_bytes after any Write+fsync
returns successfully. The runtime pre-evicts before the ceiling is reached. If pre-eviction
cannot free sufficient space, writes are rejected with ErrCeilingExceeded.
Code modules: edge/storage/localstore.go (ceiling check, WithDiskUsedFunc),
edge/eviction/local.go, edge/storage/recovery.go
Enforcement:
After every
Write+fsync: kernelstatfscheckSynchronous eviction triggered when usage approaches the configured threshold
ErrCeilingExceededreturned if post-eviction space is still insufficient
FI Tests:
FI-C1-01
TestFI_DiskCeiling_ErrCeilingExceededFI-C2-01
TestFI_DiskCeiling_ConcurrentWritesAtBoundaryFI-C2-02
TestFI_DiskCeiling_EvictionFailure_WriteRejectedCleanly
See also: Disk Ceiling
INV-05 — Platform Assurance Binding¶
Statement: High-durability mode requires a validated cgroup v2 memory + CPU controller.
If the platform cannot provide cgroup v2 assurance at startup, the node logs a warning
(edge.assurance.reduced) and refuses to enter high-durability mode.
Code modules: edge/assurance/assurance.go, edge/config/cgroup_linux.go, edge/config/cgroup_other.go
Enforcement:
Startup: cgroup v2 probe; absent →
LogAssuranceReduced()+edge.assurance.reducedlog; high-durability operations refused
FI Tests:
FI-C1-05
TestFI_Assurance_HighDurabilityUnavailableTestFI_Assurance_ReducedEnvelopeLogged
See also: cgroup v2
INV-06 — Local-Only Activation¶
Statement: Activation functions accept only local-state parameters. No remote identifiers, fleet-wide tokens, external coordination primitives, or peer-sourced inputs may appear in activation function signatures.
Code modules: edge/scheduler/local.go, edge/scheduler/scheduler.go
Enforcement: Code review checklist; edge/ci/scan_prohibited (remote-state call patterns)
INV-07 — Bounded Memory¶
Statement: Memory usage is bounded by the configured resource envelope. cgroup v2 enforces this at the OS level when available. Transport layer transfer buffers are pool-bounded and never grow without bound as peer count scales.
Code modules: edge/storage/localstore.go, edge/transport/transport.go (pool-bounded buffers),
edge/quota/localquota.go
Enforcement:
cgroup v2 memory controller (when present, via INV-05)
Load shedding on inbound when approaching memory ceiling
INV-08 — Idempotent Segment Receive¶
Statement: Receiving the same segment ID multiple times is safe and has no net effect after the first receive. Deduplication is handled locally; no peer coordination is required.
Code modules: edge/index/localindex.go, edge/storage/localstore.go
Enforcement:
Segment ID checked against local index before write
Duplicate segment → silently discarded; no error returned to peer
Tests: edge/storage/store_test.go, edge/index/localindex_test.go (unit)
INV-10 — Mission-Layer Decoupling¶
Statement: The edge/ module must not import adk/runtime, adk/policy,
adk/orchestrator, or any mission-layer package. The edge capability operates
independently of whatever agent framework (LangChain, custom, etc.) is running alongside it.
Code modules: edge/ (import isolation enforced at compile time)
Enforcement:
edge/ci/scan_dependencies— import-graph check runs on every PR; fails if prohibited imports foundCompile-time isolation (build will fail if imports are added)
FI Tests:
FI-C1-03
TestFI_MissionDecoupled_NoMissionImportFI-C1-03
TestFI_MissionProcessKill_RelayUnaffectedShell:
FI-C1-03_mission_kill.sh
INV-11 — Deterministic Relay¶
Statement: Relay order and peer selection are fully deterministic for a given local state. No PRNG, wall-clock tie-breaking, or randomized selection is used in the relay path.
Code modules: edge/scheduler/local.go (lexicographic tie-breaking), edge/segment/segment.go
Enforcement:
edge/ci/scan_prohibited— PRNG/random call patterns flagged in relay code pathsStructural: no PRNG import in relay path
INV-12 — Bounded Retry¶
Statement: Each segment-relay attempt increments a monotonic counter associated with that
segment. When MaxRetryCount is reached, the segment enters the EXHAUSTED terminal state
and is never retried again. The counter does not reset on reconnect, timer expiry, or daemon
restart (it is persisted in the local index).
Code modules: edge/retry/retry.go (Tracker, RetryExhausted terminal state)
Enforcement:
RecordAttempt()monotonically increments; no reset pathTerminal
EXHAUSTEDon reachingMaxRetryCount; subsequent calls returnErrRetryExhausted
FI Tests:
FI-C4-01
TestFI_RetryExhaustion_TerminalStateFI-C4-02
TestFI_RetryCount_MonotonicFI-C4-03
TestFI_RetryExhausted_NoBeyondWindowShell:
FI-C4-01_retry_exhaustion.sh
See also: Retries
INV-13 — Bounded Per-Peer Ingest Quota¶
Statement: Ingest from each peer is bounded by a rolling-window token bucket. When the
per-peer quota is exceeded, ErrQuotaExceeded is returned and logged as edge.quota.exceeded.
The peer session remains open; quota exhaustion is not a disconnection event.
Code modules: edge/quota/localquota.go (LocalEnforcer, rolling-window token bucket)
Enforcement:
Per-peer bucket checked on every ingest call
edge.quota.exceededlog entry on breach
FI Tests:
FI-C3-03
TestFI_Quota_EnforcedPerPeerTestFI_Quota_PeerIsolation
Do Not Do¶
❌ Do NOT add a convergence/gossip/CRDT import to any
edge/package —edge/ci/scan_prohibitedcatches this in CI❌ Do NOT designate any node as a leader, primary, or coordinator for any purpose
❌ Do NOT reset a retry counter on reconnect or timeout — INV-12 requires monotonic persistence
❌ Do NOT allow segment exchange before mTLS handshake completes — INV-09
❌ Do NOT import
adk/runtime,adk/policy, oradk/orchestratorfromedge/— INV-10❌ Do NOT use PRNG or wall-clock tie-breaking in the relay scheduler path — INV-11
ContractRefine Invariants (INV-CR-*)¶
The ContractRefine layer (Architecture) introduces
a semantic preflight stage above the existing mechanical enforcement stack.
The invariants below pin the safety + security properties operators rely
on when a pre-execution contract verdict influences runtime behavior. They
are scoped to the runtime/contract/ package and the /v1/preflight
endpoint; they neither alter nor relax INV-01..INV-13.
The numbering range INV-CR-* is deliberately disjoint from INV-* so
that traceability tools can filter either family independently.
INV-CR-1 — Preflight is opt-in by policy bundle¶
Statement: ContractRefine enforcement is advisory by default. A
preflight verdict only blocks /v1/tool execution when (a) the active
policy bundle’s manifest declares requires_preflight: true for the
requested capability AND (b) the runtime config contract_refine.mode
is enforce. Existing policy bundles that do not declare the flag are
loaded unchanged and behave exactly as they did pre-#1020: the absent
field is treated as requires_preflight: false, so no capability owned
by the bundle requires a preflight header.
Rationale: Backward compatibility for existing bundles is
load-bearing — operators must be able to upgrade the runtime without
republishing every bundle. The mode ladder (disabled → advisory
→ enforce) mirrors the existing attestation.mode ladder operators
are already familiar with.
Code modules: runtime/contract/ (verifier wiring),
runtime/server.go (mode ladder), policy/ (bundle manifest field).
Enforcement:
Bundle loader treats the absent
requires_preflightfield asfalse. The loader MUST NOT reject a bundle for the field’s absence under any mode — includingenforce— because doing so would break the upgrade-runtime-without-republishing-bundles backward-compatibility contract aboveMode ladder check at
/v1/preflightentry:disabled⇒ 503Mode ladder check at
/v1/toolentry: deny-on-missing-header is gated onmode == enforceANDbundle.manifest.requires_preflight == truefor the requested capability. Inadvisorymode the header is consumed for WAL correlation but does not gate execution (see INV-CR-3 for the binding-check ladder)Default (post-C12 #1032): the zero-value
ContractRefineModeresolves toadvisoryviaeffectiveContractRefineMode(seeruntime/server_preflight.go). Operators who want the pre-C12 silent posture setruntime.contract_refine.mode: disabledexplicitly — the value is preserved when set, only the unset/zero case changed. The flip preserves backward compatibility for existing bundles + clients (header optional;requires_preflightabsent ⇒false); only the runtime’s “no opinion” behavior changed from 503 to “evaluate + log without enforcing”
INV-CR-2 — Verifier is fail-closed¶
Statement: A verifier that is unavailable, a resolver that returns an
error, an unknown contract kind in the policy bundle, or a malformed
PreflightRequest MUST resolve to outcome: deny with a reason in the
contract: layer. A verdict of pass MUST NEVER be returned on any
error path.
Rationale: Mirrors the existing fail-closed discipline at /v1/tool.
Operators must be able to reason about the worst-case behavior of the
preflight layer: a broken or misconfigured verifier blocks execution;
it never silently approves it.
Code modules: runtime/contract/verifier.go,
runtime/contract/resolvers.go, runtime/server.go
(handlePreflight error path).
Enforcement:
Table-driven test in
runtime/contract/verifier_test.gocovers each error path and assertsoutcome == "deny"runtime/reasons_shape_test.goextended with thecontract:layer including theverifier_unavailable,resolver_error, andintrospector_errorsubreasons
INV-CR-3 — Preflight id is single-purpose and bounded¶
Statement: A preflight_id binds to exactly one (capability, normalized_params_hash) tuple and one expiry timestamp. It is not a
generic capability token, not transferrable across capability kinds, and
not extensible after issuance. The runtime’s response to a binding
mismatch is mode-gated: enforce mode denies; advisory mode records
the mismatch on the WAL and lets execution proceed.
Rationale: Prevents the preflight verdict from becoming an
operator-side bypass token (a pass for one tool call must not be
replayable to authorize a different one), while leaving operators a
safe rollout posture in advisory where the binding signal can be
observed in production traffic before being enforced.
Code modules: runtime/contract/binding.go (preflight-id issuance +
binding check), runtime/server.go (handleTool binding verification +
mode-gated enforcement).
Enforcement:
Binding check runs on every
/v1/toolcall that presents theX-Autonomy-Preflight-Idheader, in every mode (disabledexcepted — see below). The check evaluates capability match + normalized-params-hash match + not-expiredenforcemode: a binding mismatch ⇒ deny withcontract: preflight_binding_mismatch. This is the GA posture.advisorymode: a binding failure emits a fullautonomy.decisionWAL frame withoutcome=denyandreason=contract: preflight_*under the sameaudit_idthe/v1/toolrequest will use, but the request CONTINUES to the policy layer withAction.Preflight=nil(no wire-level 403). The operator opts into this posture to observe the deny signal in production traffic before flipping toenforce.disabledmode: the header is ignored entirely (/v1/preflightreturns 503; nopreflight_idcan have been issued)Expiry-window default ≤ a small constant (workplan §6 specifies the exact bound). Expiry past the window is treated identically to mismatch by the rules above
INV-CR-4 — Reason shape conformance¶
Statement: Every reason string produced by the ContractRefine layer
matches the canonical <layer>: <subreason> regex enforced by
runtime/reasons_shape_test.go. The layer prefix is always contract.
Subreasons are lowercase ASCII; underscores are permitted after the
first character.
Rationale: ADK observability and audit tooling parses reason strings across all layers under one regex. A new layer that violates the contract corrupts every downstream consumer.
Code modules: runtime/contract/reasons.go,
runtime/reasons_shape_test.go.
Enforcement:
Unit test asserts every subreason in the
contract:allow-list matches the regexThe allow-list is the single source of truth; runtime code may not emit a subreason that is absent from it
INV-CR-5 — Determinism¶
Statement: Same PreflightRequest bytes + same resolver outputs +
same policy bundle ⇒ same PreflightDecision bytes, modulo the freshly
generated preflight_id and audit_id (both of which are explicitly
excluded from the determinism check).
Rationale: Operators must be able to reproduce a preflight verdict offline from the WAL record. Non-determinism in the verifier breaks audit reproducibility and lets unrelated state (wall-clock, randomness, external services) leak into the governance verdict.
Code modules: runtime/contract/verifier.go (the exported
contract.DefaultVerifier), runtime/contract/types.go,
runtime/contract/repair.go.
Enforcement:
Determinism test in
runtime/contract/verifier_test.goruns the verifier twice on identical inputs and asserts byte-identical decisions (after id elision)The verifier MUST NOT read wall-clock except via an injected clock the test can pin; MUST NOT call
math/randorcrypto/randoutside id generation
INV-CR-6 — Refinement is bounded¶
Statement: At most one repair attempt per preflight session is
allowed by default. The bound is configurable up to a small constant
≤ 3. Beyond that, the verifier returns outcome: deny with reason
contract: repair_exhausted. No unbounded refinement loop is permitted.
Rationale: ContractRefine is not a planner and not a model-in-loop. Repair hints exist to let a deterministic caller correct a structurally invalid plan once; they do not exist to drive an open-ended search.
Code modules: runtime/contract/session.go,
runtime/contract/verifier.go.
Enforcement:
Session counter incremented on each
repair_requiredoutcomeCounter ≥ bound ⇒ next call resolves to
denyUnit test pins the counter ladder
INV-CR-7 — No model-in-loop¶
Statement: The ContractRefine verifier MUST NOT call an LLM, an external network service, a non-deterministic library, or any resolver whose output is not a pure read-through of existing ADK substrate (attestation snapshot, rollback registry, policy bundle, lock fingerprint).
Rationale: Governance verdicts that depend on a model are not deterministic, not auditable offline, and not stable under model upgrades. ContractRefine sells a deterministic verifier as the differentiator vs prompt-engineering “guardrails”.
Code modules: runtime/contract/ (entire package).
Enforcement:
Static check (CI grep) for
net/http.Client.Do,openai.,langchainimports insideruntime/contract/Import-graph review on every PR touching the package
INV-CR-8 — Correlation discipline¶
Statement: Every autonomy.preflight WAL event carries
lock_fingerprint, policy_ref, preflight_id, and audit_id
attributes. The same audit_id MUST appear on the matching
autonomy.decision WAL event when the preflight is consumed at
/v1/tool.
Rationale: Forward traceability — intent stated → preflight verdict → tool decision — is one of the GTM claims ContractRefine ships. The join is unverifiable if either side drops the correlation key.
Code modules: telemetry/emitter.go (new EventKind constant —
this is where the enum lives), telemetry/events.go (per-kind builder
helper), runtime/server.go (handlePreflight emit, handleTool join).
Enforcement:
Telemetry test asserts attribute presence on both event kinds
Join test: emit a preflight, consume at
/v1/tool, query the WAL for events with the sharedaudit_id, assert both event kinds are returned
INV-CR-9 — No bypass of mechanical layers¶
Statement: A preflight verdict of pass MUST NOT bypass policy
evaluation, attestation, allowlist, or egress-DLP at /v1/tool.
ContractRefine is additive. A pass preflight permits the caller to
attempt the tool call; it does not pre-approve the call.
Rationale: Operators rely on layered defense. A preflight verdict is a semantic statement about the plan; mechanical layers enforce identity, shape, and resource constraints independently. The two must stack, not substitute.
Code modules: runtime/server.go (handleTool ordering),
runtime/contract/ (no enforcement-bypass code path exposed).
Enforcement:
Integration test wires a verifier that always returns
passAND a policy that always denies, asserts the call deniesUnit test pins the ordering of pipeline stages in
handleTool
Multi-Modal Policy invariants (target — epic #747)¶
Note
Status — target invariants for the Phase 1 substrate
INV-MM-1 through INV-MM-7 below are TARGET invariants for the
multi-modal policy authoring substrate. The substrate they constrain
— the policy/ir/ package, policy_frontend fields on the inner +
outer manifests, widened runtime.Decision, the --with-explainability
CLI flag — does not exist in the repository yet. Each Enforcement
section names the planned test or CI gate that will pin the
invariant once the substrate lands; today none of those tests or gates
exist.
Each invariant carries a **Status:** line at the top of its
Enforcement section naming the child PR that lands it. The pattern
matches how INV-CR-1 .. INV-CR-9 were specified during the #1020
epic — written into the architecture doc first so subsequent PRs have
a single design contract to implement against. As the substrate
lands, this banner will narrow to “INV-MM-N is enforced; INV-MM-M is
still planned” until all seven are live.
INV-MM-1 — Ternary outcome preservation¶
Statement: Phase 1 of the multi-modal policy epic ships no new
runtime.Outcome enum values. Allow, Deny, and Defer remain the
closed set. A pure-Rego policy that never references the Phase 2
outcomes (audit_only, require_approval, rate_limited, sandboxed)
continues to produce Allow/Deny/Defer exactly as it does today, with
byte-identical WAL events except for the optional explainability fields
listed in
INV-MM-6.
Rationale: Operators upgrading across the Phase 1 cut must see no behavioral change in their existing Rego bundles. The Phase 2 outcome additions ride a separate epic with its own backward-compat gate (manifest schema v1.4); shipping them mixed in with Phase 1 would muddle the upgrade story.
Code modules: runtime/decision.go (Outcome enum),
policy/evaluator.go (Rego → Outcome mapping).
Enforcement:
Status: existing
policy/evaluator_test.gosuite already covers Allow / Deny / undefined → Deny / non-bool → Deny / eval-error → Deny fail-closed semantics today. The “ternary outcome preservation” half of this invariant is therefore enforced today by definition — no new test required for C01–C07. The “no new Outcome values in Phase 1” half is enforced by the fact that no child PR on this epic touchesruntime/decision.go’sOutcomeenum.Planned (Phase 2): an assertion in
runtime/decision_test.gothat theOutcomeenum has exactly 3 values, so the Phase 2 PR that addsaudit_onlyetc. must update the assertion (and the manifest version bump) in lockstep.
INV-MM-2 — Evaluator is fail-closed¶
Statement: The existing
policy/evaluator.go
fail-closed contract is preserved verbatim: undefined results,
non-bool values, and evaluation errors all resolve to Deny. Only
data.autonomy.allow == true produces Allow. The IR → Rego backend
MUST emit Rego that the unchanged evaluator gates correctly on this
contract.
Rationale: Production governance depends on the operator’s ability to reason about worst-case behavior: a broken IR program, a corrupted Rego module, or an evaluator error MUST NOT silently approve calls.
Code modules: policy/evaluator.go (evaluator unchanged),
policy/ir/compiler (Rego backend MUST emit fail-closed shape; lands
in C03).
Enforcement:
Status: the evaluator-side half is enforced today — existing fail-closed tests in
policy/evaluator_test.gocover undefined / non-bool / eval-error → Deny. Every child PR on this epic MUST run these unchanged.Planned (C03): a new test in
policy/ir/compiler_test.gocompiles a corrupted IR program and asserts the generated Rego still gates fail-closed (i.e.data.autonomy.allowremainsfalseor undefined). Does not exist today.
INV-MM-4 — IR-to-Rego compile is deterministic¶
Statement: Compiling the same IR program produces byte-identical
Rego output across runs, across hosts, and across operator
environments. The compiler MUST NOT read the wall clock, MUST NOT call
math/rand or crypto/rand, and MUST NOT depend on filesystem entry
ordering. Generated identifier names MUST derive from the IR program’s
content (rule IDs or deterministic hashes of rule positions), never
from process state.
Rationale: Two operators on different hosts building the same IR
program MUST produce the same bundle_hash (which the bundle signer
covers). Non-determinism in the compiler breaks the signed-bundle
supply chain.
Code modules: policy/ir/compiler.go (lands in C03).
Enforcement:
Status: not enforced today — the substrate does not exist. C03 will land both the compiler AND the gates below in the same PR.
Planned (C03): property test
TestIRToRego_Deterministicinpolicy/ir/compiler_test.gocompiles a random IR program N times and asserts byte-identical output.Planned (C03): a
go vet-style check (inci/or as a build tag) forbidstime.Now(),math/rand,crypto/rand, andos.ReadDir(which is order-undefined on some filesystems) inside thepolicy/ir/compilerbuild target.
INV-MM-5 — Frontend provenance is recorded on both manifest layers¶
Statement: The policy_frontend field is recorded on both the
inner policy.BundleManifest
(read by the runtime + autonomy policy inspect|load|eval) AND the
outer bundle.Manifest
schema v1.3 (read by OCI consumers + bundle inspect|verify without
unpacking). bundle.Manifest.Validate() cross-checks the two values
at verify time; mismatch fails closed.
Rationale: The runtime / autonomy policy CLI paths never read the
outer bundle.Manifest; OCI / bundle inspect paths never unpack the
inner manifest. Recording on only one layer leaves the other surface
blind to frontend provenance, which Phase 1’s explainability output
depends on. Cross-checking at verify catches a tampered bundle whose
inner and outer manifests disagree.
Code modules: policy/builder.go (inner BundleManifest writer),
bundle/manifest.go (outer schema v1.3 + cross-check),
bundle/verify.go. All gain the policy_frontend field in C04.
Enforcement:
Status: not enforced today — neither manifest carries
policy_frontend, and no tamper-check exists inbundle/verify.go. C04 lands the field on both layers AND the gates below.Planned (C04): test in
bundle/verify_test.goconstructs a tampered bundle with mismatchedpolicy_frontendon inner vs outer manifests and asserts verify fails with a documented sentinel error.Planned (C04): test in
policy/builder_test.goasserts a v1.2 (legacy, no field) and a v1.3 bundle both load cleanly; legacy defaults to"rego".
INV-MM-6 — Explainability metadata survives end-to-end¶
Statement: When a policy emits a decision with MatchedRule,
EvaluationTrace, or RemediationHint populated, those fields survive
from policy.Eval through runtime.Interceptor.Allow into the WAL
decision event and out through autonomy wal inspect --kind decision.
Widening any one of these layers without the others MUST fail CI.
Rationale: The Phase 1 acceptance criterion is “rule origin
visible to operators.” Widening only the runtime Decision struct
without widening the WAL event schema means the IR knows the rule
origin but the operator never sees it. Widening only the WAL without
the CLI means the field is in the event but wal inspect strips it.
The end-to-end pin makes the contract observable as a single
invariant.
Code modules: runtime/decision.go (Decision struct; widens in
C05), policy/evaluator.go (populates; widens in C05),
runtime/server.go (forwards; widens in C06), telemetry/events.go
(event schema; widens in C06), cmd/autonomy/commands/wal.go
(renders; widens in C06), cmd/autonomy/commands/audit_cmd.go
(adds --with-explainability flag in C06).
Enforcement:
Status: not enforced today — none of the explainability fields exist on
runtime.Decision, in the WAL event schema, or in the CLI surface. C05 widens the runtime contract; C06 widens the WAL + CLI surfaces. The end-to-end pin (the load-bearing claim of this invariant) only becomes verifiable after both PRs land.Planned (C06): integration test in
runtime/server_test.goposts a tool call with a YAML-authored bundle, asserts the decision WAL frame carriesmatched_rule.idANDmatched_rule.source_file.Planned (C06): CLI test asserts
autonomy wal inspect --kind decisiondisplays those fields when present.Planned (C06): a drift test pins that every field added to
runtime.Decisionis also present in the WAL event schema (telemetry/events.go) — adding a field to one without the other fails CI.
INV-MM-7 — IR is the only contract between frontends and backends¶
Statement: Frontends (YAML, JSON, NL, Graph) MUST NOT call the Rego
backend directly. Backends (Rego today; CEL, WASM, native in v2) MUST
NOT inspect the originating frontend’s syntax. The IR Go types in
policy/ir/ are the only shared substrate. A new frontend adds a
compiler frontend → ir.Program; a new backend adds a compiler ir.Program → <target>.
Rationale: N frontends × M backends ⇒ N×M compilers without an IR. With the IR, it’s N+M. The IR is also the architectural pin that lets Phase 1 ship YAML without needing to redesign for the future Graph frontend.
Code modules: policy/ir/ (interface definition; lands in C02),
all policy/<frontend>/ compilers (policy/yamldsl/ lands in C03),
policy/ir/compiler.go (Rego backend; lands in C03).
Enforcement:
Status: not enforced today — none of the packages exist. C02 + C03 land the substrate AND the gates below. The discipline is documented now so any reviewer can hold the implementing PRs to it.
Planned (C03): add a
ci/test_arch_invariant.shsub-check forbidding anypolicy/<frontend>/package from importingpolicy/ir/compileror any backend package — they import onlypolicy/ir(the types).Planned (C03): same check forbids any backend from importing any
policy/<frontend>/package.
INV-MM-8 — preflight surfaces are frontend-orthogonal¶
Statement: The ContractRefine surfaces shipped by epic #1020 —
policy.BundleManifest.RequiresPreflight (manifest-layer binding
gate) and the input.preflight shape (Rego rules read the verified
PreflightDecision) — behave identically regardless of which frontend
(rego, yaml-dsl, future json-rules / nl / graph) authored
the bundle. Frontend choice MUST NOT change which /v1/tool calls
the runtime binding gate fires on, MUST NOT change the
input.preflight shape Rego rules consume, and MUST NOT introduce
any code path that branches on policy_frontend to alter preflight
enforcement.
Rationale: Without this pin, operators who migrate a
requires_preflight: true bundle from Rego to YAML could see
behavior change in ways the YAML author never wrote. The two
ContractRefine surfaces are deliberately at orthogonal layers
(manifest field for the runtime’s binding gate; input contract for
the policy evaluator); the IR is supposed to slot into the policy
evaluator’s input contract unchanged, so a YAML rule expressing
input.preflight.outcome == "pass" MUST compile to a Rego predicate
that evaluates identically to the hand-written form. The invariant
makes this load-bearing as a single observable property.
Code modules:
policy/builder.go
(BundleManifest holds both RequiresPreflight and the planned
PolicyFrontend field; the two MUST stay independent),
runtime/server.go
(handleTool’s binding gate reads RequiresPreflight; MUST NOT read
PolicyFrontend),
policy/evaluator.go
(populates input.preflight from runtime.Action.Preflight; MUST
treat every Rego bundle identically regardless of its origin
frontend),
policy/ir/compiler.go (lands C03; lowers field: preflight.<...>
predicates to the equivalent Rego input.preflight.<...> access).
Enforcement:
Status: the
RequiresPreflighthalf is already enforced today by virtue of INV-MM-3 — adapters MUST NOT readBundleManifest.PolicyFrontend, which means they cannot branch on it to changeRequiresPreflightbehavior. The CI sub-check planned in INV-MM-3 covers this leg.Planned (C03): the IR → Rego property test fixture set includes preflight-bound predicates (e.g.
field: preflight.outcome,value: pass) and asserts the generated Rego accessesinput.preflight.outcomeidentically to a hand-written predicate. This pins theinput.preflighthalf.Planned (C04): integration test in
policy/builder_test.goor a sibling test file constructs a YAML-authored bundle withrequires_preflight: trueANDpolicy_frontend: yaml-dsl, signs/verifies it, then drives/v1/toolwith and without theX-Autonomy-Preflight-Idheader. Asserts the binding gate fires identically to a Rego-authored bundle’s gate behavior (the C08 test inruntime/server_preflight_binding_test.gois the reference shape).Planned (C03): a small drift test that constructs a YAML rule expressing
input.preflight.outcome == "pass"and asserts the emitted Rego is byte-identical (modulo deterministic identifier hashing) to a hand-written Rego predicate with the same semantics.
See Also¶
Traceability → Invariant Map — invariant → code → test linkage table
Architecture → Overview — high-level module map
FI Catalog → Matrix — full FI scenario matrix
Contributing → Invariant Rules — how to write invariant-safe code