Skip to content
GitHub
Decisions

ADR-080: Decision-module integrity — content hash, operator approval, audit-chain attestation

Context

Decision modules under the in-band shift are deployable code artifacts produced by the world agent and served by the multi-tenant decision-server fleet (per ADR-076 D1). A caller invokes POST /api/decide (per ADR-077); the decision server resolves (org, domain, action) at the active or pinned world-model version, loads the module from the module store, and executes the composition root over the supplied context. The decision is binding — the work-frame’s forbidden_actions and allowed_actions are designed so a downstream LLM cannot talk past the decision (per ADR-077).

Binding decisions require integrity guarantees. This ADR specifies the integrity model for v0 — what the customer can verify about a decision module before trusting its decisions, and what mechanisms enforce that the module the customer relies on is the module that gets executed.

Threats addressed

ThreatConcrete attack
T1 — Module substitutionDecision-server serves module X for (org, domain, action, version) when the operator approved module Y
T2 — Module tamperingModule bytes are altered between authoring and execution (in transit, at rest in the module store, or in the decision-server cache)
T3 — Unauthorized deploymentAn unapproved module ends up in production for an (org, domain, action, version)
T4 — Rollback to vulnerable versionA retired module is served against the active-version pointer
T5 — Pipeline compromiseWorld-agent codegen produces malicious or incorrect code (LLM induced into bad output)

Existing structural defenses (carry forward, not introduced here)

The world-agent pipeline already defends T5 structurally: AST-level static analysis at code-gen rejects dangerous constructs (eval, exec, network calls, file I/O, non-deterministic primitives per ADR-083); the multi-axis eval framework validates generated code across predicate correctness, test fidelity, determinism, runtime safety, trace integrity, and readability; the conformity gate enforces structural and authoritative invariants; the implementation-readiness gate runs a five-check pass/fail. T5 is mitigated by these structural mechanisms; this ADR does not re-specify them.

The customer’s trust model under the shift is grounded in product supremacy (per project memory project_world_model_product_supremacy): defense is structural, not procedural. Customers trust Spectral as the operator of the decision-module pipeline because the structural defenses + the audit chain + the methodology disclosure on the System Card are the verification surface. They do not need to be supply-chain auditors of Spectral’s pipeline at v0.

Decision

D1 — Identity by content hash (sha256)

Every module bundle is named by the sha256 hash of its serialized bytes. The hash is the canonical module identity at the byte level; module-version metadata (world-model version, action, codegen template version) is carried in the manifest alongside the hash.

Bundle composition for hashing: composition root, all rule files under rules/, context schema, manifest, and any other files in the bundle. Hashing is deterministic over the canonical serialization of the bundle (sorted file list, normalized line endings, no timestamps in content).

The content hash is the module’s user-facing identity (e.g., sha256:c7f1…9b2a); this decision establishes that convention. (The portal redesign does not surface the content hash on customer surfaces — see docs/design/; it remains the module’s identity at the platform / operator layer.)

D2 — Runtime integrity check at module load

When a decision-server pod fetches a module from the module store for the first time (cache miss for (org, domain, action, version)), the pod:

  1. Reads the module store’s advertised hash for that key.
  2. Downloads the bundle bytes.
  3. Computes the sha256 of the downloaded bytes.
  4. Verifies the computed hash matches the advertised hash. On mismatch, the load fails — the pod logs the integrity failure, alerts operators, and the calling request fails with an appropriate error (502 or equivalent; the work-frame is not constructed because the module did not load). The mismatch is treated as a system unavailability for that (org, domain, action, version).
  5. On match, the bundle is loaded into the pod’s in-process cache and serves subsequent requests from cache.

The cache trusts itself once loaded — repeated requests for the same (org, domain, action, version) after a successful initial load do not re-verify. Pod restarts trigger fresh loads (and fresh verification). Defends T1 (substitution becomes visible at hash check) and T2 (tampering becomes visible at hash check).

D3 — Operator approval as authorization

A module is loadable in production only if an operator approval record exists for its (org, domain, action, version, content_hash) tuple. The approval record is database-recorded, not cryptographically signed at v0.

Approval record schema (illustrative; specifics belong in the schema migration that lands the table):

platform.module_approvals
- approval_id uuid primary key
- org_id text not null
- domain text not null
- action text not null
- world_model_version int not null
- content_hash text not null
- conformity_outcome jsonb not null -- gate result + invariants checked
- readiness_outcome jsonb not null -- 5-check pass/fail + diagnostics
- approved_by_user_id uuid not null -- operator identity (FK to core.users)
- approved_at timestamptz not null default now()
- notes text null
- deleted_at timestamptz null

Mutation of module_approvals is gated by auth scope. The scope model from ADR-006 sections 4–5 carries forward; a dedicated scope (approve:modules or write:operations-equivalent — exact scope name belongs in the implementation epic) gates the approval endpoint. Only authenticated operators with that scope can create approval records.

The decision server consults module_approvals at module load: if no approval record exists for the bundle’s content hash and (org, domain, action, version) tuple, the load fails. Defends T3 — an unapproved module cannot reach production even if it is uploaded to the module store.

D4 — Build provenance as structured attestation in the audit chain

Each module bundle’s manifest carries a structured attestation describing how the module was generated:

  • world_model_version — the (org, domain, version) triple whose authoring this module compiles.
  • world_agent_version — which world-agent code (release / build identifier) generated the predicate code.
  • codegen_template_version — which deterministic codegen template produced the composition root.
  • eval_framework_version — which internal eval framework version validated the generated code.
  • conformity_gate — structured outcome (pass/fail + invariants verified + any non-fatal observations).
  • readiness_gate — five-check outcome (codegen success, test pass, multi-axis eval pass, deployment readiness, plus the reserved fifth check).
  • rule_provenance — for each rule in the bundle, the dual provenance (assertion provenance per the Authoritative/Curated/Distilled/Researched/Observed taxonomy; code provenance naming generation run identifier).
  • built_at — timestamp of bundle assembly.

The attestation is part of the manifest signed into the module bundle by inclusion (the manifest is part of the hashed bytes). At deployment, the attestation is replicated into the audit chain so each decision’s audit record can name the module that made it. Defends downstream forensics: any decision can be traced back to the generation run, the gate outcomes, the rule corpus, and the operator who approved deployment.

D5 — Trust posture: Spectral-as-trusted-operator

Spectral runs the world-agent pipeline, the module store, the decision-server fleet, and the audit chain. The customer’s trust foundation is:

  • Structural defenses in the world-agent pipeline (AST analysis, conformity gate, readiness gate, multi-axis eval) — verifiable through the methodology disclosure on the System Card.
  • Audit chain transparency — every decision names the module that made it; every module names its build provenance; every approval names its operator. The customer can audit retrospectively.
  • Restatement discipline — when something needs correction (assertion change, code regeneration, behavioral correction per ADR-026), the record is explicit; prior versions remain authoritative for decisions made under them.
  • Methodology disclosure on the System Card — the customer sees how Spectral generated and validated each module without needing to be a supply-chain auditor of the pipeline.

Customers do not perform cryptographic signature verification themselves at v0. Spectral’s role is the trusted operator; the structural + audit-chain mechanisms are the verification surface customers interact with.

D6 — Deferred to v1+

The following are deliberately not specified in v0:

  • Cryptographic signing (Sigstore, in-toto, SLSA-style attestation). The integrity model in D1–D4 uses content hashes for runtime integrity and database records for authorization; cryptographic signatures can layer on later as an additional verification surface without changing the underlying mechanism.
  • Multi-party operator approval (M-of-N approvals before a module reaches production). v0 uses single-operator approval; multi-party can extend later if operational scale or compliance requirements emerge.
  • Customer-side cryptographic verification. v0 customers verify through the audit chain + methodology disclosure, not by checking signatures themselves. If regulated customers later require independent verifiability, the deferred cryptographic layer surfaces it.
  • Key rotation policy. Not relevant until cryptographic signing lands.
  • Module-store consistency model. Settled separately by ADR-085 (commit-then-signal upload, replica honor-or-refuse semantics, staleness protections) — orthogonal to integrity and not specified here.
  • Execution sandbox specifics. Settled separately by ADR-083 (restricted exec context, OS-level sandbox on decision-server pods) — runtime safety is distinct from integrity and not specified here.

Alternatives considered

Cryptographic signing at v0 (Sigstore, in-toto, GPG, or project-specific PKI). Rejected for v0. Adds key-management complexity (where signing keys live, rotation policy, revocation), CI/CD complexity (the world-agent pipeline becomes a signing authority), and customer-side verification complexity (customers run their own verifiers) — none of which buy a v0-customer-visible benefit beyond what content hashes + audit chain already provide. The deferral is reversible: cryptographic signatures can layer on later as an additional surface without changing D1–D4.

No runtime integrity check (trust the module store + in-process cache). Rejected. Defends nothing against T2 (tampering at rest in the module store or in transit to the pod) and nothing against T1 (substitution at the module store). The hash check at load is cheap (one sha256 over the bundle bytes) and detects both classes. Skipping it for performance reasons would be premature optimization at the cost of integrity.

Single approval record per module, regardless of operator identity. Rejected. Approval records name the approving operator’s user_id; operator identity is part of the audit trail. An anonymous approval record loses the “who approved this” question, which is essential for downstream investigation when something goes wrong.

Multi-party operator approval (M-of-N) at v0. Rejected for v0. Alpha-stage operations have a small operator pool; single-operator approval is appropriate. M-of-N can extend later as the operator pool grows or as compliance requires it; the schema (approval_id as primary key with approved_by_user_id as one column) supports either model with no breaking change.

Customer-side cryptographic verification at v0. Rejected. The customer trust model under the shift is “Spectral runs the decision modules; the audit chain is the verification surface.” Customers are not auditors of Spectral’s supply chain at v0; they consume the binding decision contract and rely on the structural defenses + audit chain. Regulated-industry customers who require independent verifiability are out of scope for v0 (per project_world_model_product_supremacy framing); when they enter scope, the deferred cryptographic layer is the answer.

External attestation service (sigstore.dev, in-toto runner). Rejected. Adds an external dependency on a third-party verifier without v0 benefit beyond what content hashes + audit-chain attestation provide. The deferred cryptographic layer can use external services later if appropriate.

Hash check on every request, not just cache misses. Rejected. The in-process cache is the pod’s own memory; if it has been compromised, the pod is compromised. Per-request hash checking would add CPU cost without buying a meaningful integrity guarantee.

Consequences

  • A new database table (platform.module_approvals or equivalent — exact schema lands in the implementation epic) records operator approvals. Migration is part of the decision-module-hosting build-out.
  • Module bundle format includes a manifest with the build-provenance attestation (D4); the manifest is part of the hashed bundle.
  • The decision server’s module-load path verifies the content hash against the module store’s advertised hash before serving from cache; mismatch fails the load (502 / unavailable).
  • The decision server’s module-load path verifies an approval record exists before serving; missing approval fails the load (the calling request gets an appropriate error).
  • A new auth scope (approve:modules or equivalent) gates the operator approval endpoint; specifics land per ADR-006 scope model + ADR-039 auth.
  • The audit chain for each decision records the module’s content hash and the build-provenance attestation, enabling per-decision forensics.
  • Sigstore / in-toto-style cryptographic signing is reserved as a future layer. The v0 model is forward-compatible with adding cryptographic signing later: signed manifests can carry a signatures block alongside the existing attestation; signature verification at load can layer on top of hash verification.
  • The System Card’s methodology disclosure (per ADR-082; ADR-015 reframe pending in Session 3d.4) names which integrity mechanisms are in place for each customer’s deployed module instance.
  • Module store consistency, execution sandbox specifics, and noisy-neighbor handling at runtime are settled separately by ADR-085, ADR-083, and ADR-084.
  • The threat model documented here is the v0 baseline. Subsequent ADRs can extend coverage (e.g., supply-chain compromise of the world-agent build artifacts, multi-tenant isolation breaks under load) as those concerns surface.