/** * CAS attestation binder (ID781 option A follow-on) — separate record linking a frozen * GuardExecutionProof (v1) with an ExecuteIfUnchangedOutcome. * * Does NOT mutate execution-proof assembly or conditional-write outcome types. * Additive `cas_evidence` is observation-only (optional on the proof / outcome). * Does NOT claim that the host write is unique, that version tokens equal change_fp, or that * committed_stale_detected is "safe". Those non-claims are always set on `limits`. * * The proof's own limit `conditional_write_is_host_asserted_not_cas_verified` names this gap; * this module is the separate attestation surface that pairs proof + CAS outcome without * widening the proof spec. */ import type { ExecutorKeyRegistry } from '@coderifts/sdk'; import type { GuardExecutionProof, ExecutionResultHash } from './execution-proof.js'; import type { ExecuteIfUnchangedOutcome, IndeterminateReason, VersionToken } from './conditional-write.js'; /** Machine-readable schema id — mirrors EXECUTION_PROOF_SPEC style. */ export declare const CAS_ATTESTATION_SPEC: "cas-attestation.v1"; /** * Explicit non-claims. Always set (same discipline as GuardExecutionProof.limits). * Readers that treat missing fields as "ok" are wrong. */ export type CasAttestationLimits = { /** Host may have performed other writes outside executeIfUnchanged. */ does_not_claim_only_write_on_host: true; /** version_token / post_commit_token are opaque CAS resource versions — not change_fp identity. */ does_not_claim_version_token_proves_change_fp_match: true; /** committed_stale_detected means write ran and post-check disagreed — not "safe" or rolled back. */ does_not_claim_committed_stale_is_safe: true; /** execution_result_hash hashes the factory return, not CAS file bytes / token equality. */ does_not_claim_execution_result_hash_equals_cas_bytes: true; /** Same host-bypass residual as the proof family — package cannot force host CAS use. */ does_not_claim_host_cannot_bypass: true; /** Attestation is linkage of two inputs, not a re-decision of governance ALLOW/BLOCK. */ does_not_claim_governance_redecision: true; }; /** CAS branch projection — only fields present on the measured outcome branch. */ export type CasAttestationCas = { status: 'committed'; write_ran: true; version_token: VersionToken; } | { status: 'refused'; write_ran: false; reason: 'stale_version_token'; expected_token: VersionToken; current_token: VersionToken | null; } | { status: 'committed_stale_detected'; write_ran: true; reason: 'stale_during_commit'; expected_token: VersionToken; post_commit_token: VersionToken | null; } | { status: 'indeterminate'; /** * 'unknown' is a THIRD value, never a default to false. Collapsing it to false would say the * write did not run, which is a claim we do not have; collapsing it to true would say it did. */ write_ran: 'unknown'; reason: IndeterminateReason; expected_token: VersionToken; observed_token: VersionToken | null; }; /** * Frozen cas-attestation.v1 record. * References are lifted from the proof; cas is projected from the outcome; derived flags * are only what is honestly computable from those two inputs. */ export type CasAttestation = { attestation_spec: typeof CAS_ATTESTATION_SPEC; references: { decision_id: string | null; change_fp: string | null; operation: string | null; execution_result_hash: ExecutionResultHash; receipt_verified: boolean; }; cas: CasAttestationCas; derived: { /** * receipt.verified === true AND outcome.status === 'committed' * (clean commit only — committed_stale_detected is NOT this flag). */ authorized_and_committed: boolean; /** * ATOMIC / v2 only: host-reported commit. Never co-named with authorized_and_committed. * Absent on non-ATOMIC records (byte-identical 9.0.0 derived). */ authorized_and_host_reported_committed?: boolean; /** Write mutation ran: committed or committed_stale_detected. */ write_ran: boolean; /** outcome.status === 'committed_stale_detected'. */ stale_during_commit: boolean; /** outcome.status === 'refused'. */ refused: boolean; /** * outcome.status === 'indeterminate'. Downstream MUST block on this: it is not a pass, and it * is not a failure either. Reconciliation is required before anything may proceed. */ indeterminate: boolean; }; /** * Observation-side CAS evidence class (S2-F2a R3). Never a verdict/preimage field. * executor_attested only after a customer-pinned registry verifies the token. * A lying/invalid attestation stays host_claimed with attest_status visible * (same principle as N-4's lying sink). No registry → host_claimed, no penalty. */ cas_evidence: CasEvidence; limits: CasAttestationLimits; }; /** Tri-state CAS evidence (N-4 monitoring_delivery is the pattern). */ export type CasEvidenceClass = 'executor_attested' | 'host_claimed' | 'absent'; export type CasEvidence = { class: CasEvidenceClass; attest_status: string | null; executor_kid: string | null; grant_jti: string | null; }; export type ExecutorAttestationConfig = { /** Customer-pinned executor key registry. Required to attempt verification. */ registry: ExecutorKeyRegistry; /** * The PINNED CodeRifts ISSUER keyring, used to AUTHENTICATE an execution grant before it can * count as a kernel binding under an enforcing profile (1433). * * IT LIVES HERE, not on `executionGrant`, and the difference matters: `executionGrant` gates * whether the guard REQUESTS a grant, and setting `enabled: true` merely to supply a key would * turn on a request path the caller never asked for. This section is the verification side, and * a key for checking evidence belongs with the other key for checking evidence. * * Absent under an enforcing profile is FAIL-CLOSED: a grant that cannot be authenticated does * not become a binding. */ issuerKeyring?: { keys?: Array<{ kid?: string; public_key_pem?: string; status?: string; }>; } | null; }; export type EvaluateCasEvidenceOpts = { registry?: ExecutorKeyRegistry | null; grant?: string | null; receipt_digest?: string | null; grant_fields?: { jti?: string; scope_hash?: string; state_nonce?: string; receipt_digest?: string; } | null; /** * ISSUER keyring for the execution grant — the pinned CodeRifts public keys, NOT the executor * registry above. Supplied, a grant is AUTHENTICATED before it counts as a kernel binding. * * ── WHY THIS EXISTS (1431) ─────────────────────────────────────────────────────────────── * * MEASURED, then closed. The guard authenticated the receipt (vendored verify.js) and the * executor attestation (SDK verifyExecutionAttestation) and never the grant. The SDK's * cross-check DECODES the grant (`parseGrantFields`) to compare jti / scope_hash / state_nonce * against the attestation's payload, so a token whose payload simply copied those three values * — with the literal word NEM-ALAIRAS in the signature slot — passed, and ENFORCING_STRICT * went from `authorized_not_committed` to `authorized_and_committed` on the strength of it. * * A binding checked against an unauthenticated document is not a binding. * * OMITTED IS FAIL-CLOSED, and this is a behaviour change worth stating: a caller who supplies a * grant but no issuer keyring can no longer have it counted as a kernel binding, because * nothing here can tell a real grant from a copied one. The observation degrades to * `authorized_not_committed` / `commit_evidence_missing` — the honest name for "we could not * check" — rather than keeping the old, unearned upgrade. */ grant_keyring?: { keys?: Array<{ kid?: string; public_key_pem?: string; status?: string; }>; } | null; /** * The decision receipt's own bytes, and the keyring that signs them. * * Optional, and their ABSENCE is what it looks like: the core marks the result caller-asserted * and says so in its shortfalls. Supplying them moves the receipt from "this guard's own bind * step said so" to "the shared predicate verified a signature". */ receipt_token?: string | null; receipt_keyring?: { keys?: Array<{ kid?: string; public_key_pem?: string; status?: string; }>; } | null; /** Clock injection for grant expiry, tests only. */ now?: number; /** Strict-only tightening of derived.authorized_and_committed. Absent = 9.0.0 formula. */ profile?: 'ENFORCING_STRICT' | 'ENFORCING_ATOMIC'; }; /** Result of authenticating a supplied grant against the pinned issuer keyring. */ export type GrantAuthentication = { /** true only when a signature verified against a pinned issuer key. */ authenticated: boolean; /** The verifier's status, or why authentication was not even attempted. */ status: string; reason: string | null; }; /** * Authenticate an execution grant against the pinned ISSUER keyring, through the vendored * canonical core (receipt-verifier verify-grant.js — cr.exec.v1 AND cr.exec.v2). * * Loaded lazily and behind try/catch so a host that never supplies a keyring never pays for it, * and so a packaging fault surfaces as NOT authenticated rather than as a crash inside the guard. */ export declare function authenticateGrant(token: string | null | undefined, keyring: EvaluateCasEvidenceOpts['grant_keyring'], now?: number): GrantAuthentication; /** Existing CasAttestation.derived name and its honest sibling — not a parallel taxonomy. */ export type CommitLabel = 'authorized_and_committed' | 'authorized_not_committed' | 'authorized_and_host_reported_committed'; export declare const COMMIT_EVIDENCE_MISSING: "commit_evidence_missing"; export type StrictCommitObservation = { commit_label: CommitLabel; commit_evidence_reason?: typeof COMMIT_EVIDENCE_MISSING; }; /** * ENFORCING_STRICT success name. authorized_and_committed only when the evidence class is * executor_attested AND a kernel binding (grant jti / scope_hash / receipt_digest) was supplied * so the attestation actually cross-checked this outcome. Otherwise authorized_not_committed * with reason commit_evidence_missing. Observation-side — does not change enforced. */ export declare function strictCommitObservation(outcome: unknown, evidence: CasEvidence | undefined, opts?: EvaluateCasEvidenceOpts): StrictCommitObservation; /** Pull the attestation token from the CAS outcome or the mutation response body. */ export declare function extractExecutorAttestationToken(outcome: unknown): string | null; /** * Observation-side CAS evidence. Does not change authorized_and_committed. * Invalid attestation never upgrades the class (lying token stays host_claimed). */ export declare function evaluateCasEvidence(outcome: unknown, opts?: EvaluateCasEvidenceOpts): CasEvidence; /** Type guard: object carries the frozen v1 proof_spec. */ export declare function isGuardExecutionProof(x: unknown): x is GuardExecutionProof; /** Type guard: object is one of the three ExecuteIfUnchangedOutcome branches. */ export declare function isExecuteIfUnchangedOutcome(x: unknown): x is ExecuteIfUnchangedOutcome; /** * Bind a frozen GuardExecutionProof with an ExecuteIfUnchangedOutcome into a cas-attestation.v1. * * Validation-first: rejects non-v1 proofs and non-outcome shapes (throws TypeError — * builders fail closed; the human render layer soft-fails instead). */ export declare function buildCasAttestation(proof: GuardExecutionProof, outcome: ExecuteIfUnchangedOutcome, opts?: EvaluateCasEvidenceOpts): CasAttestation; //# sourceMappingURL=cas-attestation.d.ts.map