Aggregation-Only Contract (AOC) Invariants
Scope. The Aggregation-Only Contract is the rule that Stella Ops ingestion and verification services aggregate, validate, and verify evidence without ever making severity or policy decisions. This page is the authoritative checklist of the invariants every AOC-bound component must uphold, plus the Attestor-specific verification contract. It is written for implementers of ingestion/verification surfaces and for auditors verifying that those guarantees hold.
Audience & related reading. Start from the security index. The raw-ingestion guard (
aoc:verify) lives on Concelier/Excititor; the verification report contract lives in the Attestor module dossier (../modules/attestor/architecture.md).
Last updated: 2026-05-30 (reconciled against src/Attestor).
Core invariants (all components)
- Tenant isolation: API calls carry the platform tenant header
X-StellaOps-TenantId; storage and caches are keyed by tenant. In the Attestor WebService the value is resolved viaStellaOpsTenantResolver.ResolveTenantIdOrDefault(httpContext)(it falls back to thedefaulttenant when the header is absent rather than hard-rejecting). The verdict-ledger endpoints (Endpoints/VerdictEndpoints.cs) currently read a placeholderX-Tenant-Idheader — see the audit-checklist note below. - Append-only inputs: Evidence, advisories, and attestations are stored immutably; no in-place edits.
- Determinism: Sorting and pagination are stable; timestamps are UTC ISO-8601; hashes are lowercase hex.
- No consensus: Components enforce validation/verification only; no severity or policy decisions inside AOC services.
- Offline-first: External data (feeds, keys, checkpoints) is supplied via bundled inputs. Attestor verification can be forced fully offline per request via the
Offlineflag onAttestorVerificationRequest(StellaOps.Attestor.Core.Verification.AttestorVerificationRequest), which suppresses external transparency-log calls and surfaces issues for missing proofs instead. There is noAttestor__Offline__Enabledglobal config switch in the current code (see “Pending / not-yet-implemented” below). - AOC ingestion scope: The
aoc:verifyscope (StellaOpsScopes.AocVerify) guards the ingestion-guard verification path on the raw-ingestion services (Concelier/aoc/verify, Excititor) and the CLIstella aoc verifycommand — not the Attestor service. The Attestor WebService authorizes its own endpoints with theattestor.verify/attestor.read/attestor.writescope strings (see Attestor-specific invariants).
Attestor-specific invariants
- Trust roots: Verification keys/anchors come from configured local material —
Security.SignerIdentity.FulcioRoots,AllowedSans, andKmsKeysinAttestorOptions(StellaOps.Attestor.Core.Options.AttestorOptions). Keyless chains are built withX509ChainTrustMode.CustomRootTrustandX509RevocationMode.NoCheckagainst the configured Fulcio roots; no network key discovery is performed byAttestorVerificationEngine. - DSSE only: Bundles are DSSE envelopes (
bundle.DssewithPayloadType,PayloadBase64, and inlineSignatures). The engine reconstructs the DSSE PAE (DSSEv1 ...) and verifies each signature; payloads that fail base64 decoding raise thebundle_payload_invalid_base64issue and the signature section fails. (Detached-payload references exist as a distinct envelope feature —StellaOps.Attestor.Envelope.DsseDetachedPayloadReference— and are not part of the inline verification path.) - Signature modes: Verification dispatches on signer
mode.kmsverifies HMAC (HS256/HS384/HS512viaCryptoInterop) againstSecurity.SignerIdentity.KmsKeys; unsupported algs fail closed withsignature_alg_unsupported:<alg>(the regionalHSGost3411/HSSm3ids are explicitly rejected here).keylessverifies the leaf certificate (ECDSA/RSA over the PAE) and validates the chain/SAN. Unknown modes emitsigner_mode_unknown/signer_mode_unsupported:<mode>. - Transparency, gated by config: Inclusion proof, checkpoint, and witness endorsement are evaluated by
EvaluateTransparency. Whether a missing proof/checkpoint/witness fails the request depends onVerification.RequireTransparencyInclusion(defaulttrue),Verification.RequireCheckpoint(defaulttrue), andVerification.RequireWitnessEndorsement(defaultfalse); when not required, the gap downgrades the section toWarninstead ofFail. Issue codes includeproof_missing,proof_inclusion_missing,checkpoint_missing,proof_root_mismatch,witness_missing, andwitness_root_mismatch. There is notransparency=skippedscalar output field; the transparency section carries aVerificationSectionStatus(Pass/Warn/Fail/Skipped) plus aWitnessStatusstring. - Structured report (not a free-form rationale): Each verification produces a
VerificationReport(StellaOps.Attestor.Core.Verification.VerificationReport) with five sections —Policy,Issuer,Freshness,Signatures,Transparency— each exposing aVerificationSectionStatusand a typedIssueslist of stable diagnostic codes (e.g.signature_invalid,bundle_hash_mismatch,freshness_stale,policy_blocked:<component>).OverallStatusis the max-severity roll-up (anyFail⇒Fail; anyWarn⇒Warn; allSkipped⇒Skipped). There is no field literally namedrationale[], and the tokenskey.match/transparency.included/transparency.skippeddo not exist in code — replays should key off the section statuses and issue codes above. - Immutability: Verified entries and audit rows are recorded append-only; retries must not overwrite prior entries.
Guardrails for implementers
- Never permit unsigned or partially signed payloads to proceed past parsing.
MinimumSignatures(default1,Verification.MinimumSignatures) gates the signature section;total == 0fails closed. - When a request sets
Offline, do not attempt outbound transparency-log calls — surface a missing-proof issue instead. - Keep secret material out of logs; log statement digests and key ids only. KMS key comparisons use
CryptographicOperations.FixedTimeEquals. - Round numeric scores/weights only at the presentation boundary; internal math stays high-precision.
Pending / not-yet-implemented (forward-looking — verify before relying on these)
Attestor__Offline__Enabled/Attestor__TrustBundlePath/Attestor__Verification__DisableHttpFetch/Attestor__Transparency__CheckpointPathare NOT implemented. These env keys appear only indocs/modules/attestor/airgap.md, not inAttestorOptions. The implemented offline control is the per-requestAttestorVerificationRequest.Offlineboolean; trust material is configured underSecurity.SignerIdentity.*and Rekor endpoints underRekor.*/TrustRepo.*.- Risk-scoring provenance guarantees (DOCS-RISK-68-002) are still pending once Export/Risk inputs land. Include a deterministic hash list for any new examples or schemas. (The original 2025-12-11 sprint due date is stale; treat as roadmap.)
Audit checklist
- [ ] Tenant resolution is correct per request. NOTE: Attestor main endpoints use
ResolveTenantIdOrDefault(default-tenant fallback, not a hard reject) andVerdictEndpointsreads a placeholderX-Tenant-Id; a missingX-StellaOps-TenantIddoes not currently 4xx in the Attestor service. - [ ] Configured trust material (
FulcioRoots,AllowedSans,KmsKeys) is present and current on disk. - [ ] Transparency section status + issue codes are captured per request (inclusion, checkpoint, witness).
- [ ] Verification entries and audit rows are append-only and carry section statuses + trace ids.
