diff --git a/docs/spec/error-codes.md b/docs/spec/error-codes.md index 94a320c5..550f558d 100644 --- a/docs/spec/error-codes.md +++ b/docs/spec/error-codes.md @@ -8,6 +8,7 @@ This is the normative registry for all error codes used across the cMCP Runtime. | error_code | HTTP status | log level | description | spec reference | |---|---|---|---|---| +| `MEASUREMENT_UNAVAILABLE` | 500 / startup | FATAL (production); WARN (dev mode) | Code/configuration/policy measurement or a TPM/NV operation failed; production startup aborts. `CMCP_DEV_MODE` warns and continues without established gateway measurement. Inspect diagnostic detail and recovery guidance; do not infer TPM replacement or rotate a signing key solely from this code | [failure-modes.md](failure-modes.md#measurement-unavailable-at-startup) | | `ATTESTATION_REPORT_UNAVAILABLE` | 503 | FATAL | TEE provider did not return an attestation report within timeout | [failure-modes.md FM-1](failure-modes.md) | | `ATTESTATION_PROVIDER_UNSUPPORTED` | 500 | FATAL | No supported TEE provider detected and `CMCP_DEV_MODE` is not set | [attestation.md §1.1](attestation.md) | | `POLICY_HASH_MISMATCH` | 500 | FATAL | Measured policy bundle hash does not match deployment manifest | [failure-modes.md FM-4](failure-modes.md) | diff --git a/docs/spec/failure-modes.md b/docs/spec/failure-modes.md index d1b90b3a..4b009372 100644 --- a/docs/spec/failure-modes.md +++ b/docs/spec/failure-modes.md @@ -1,12 +1,12 @@ --- -description: "Exact cMCP runtime behavior for each failure: attestation at startup and mid-session, TEE faults, policy hash mismatch, and MCP parse errors." +description: "cMCP failure behavior and recovery: gateway measurement and TPM/NV operations, production/dev startup, attestation, TEE faults, policy mismatch, and MCP parse errors." --- # Failure Mode Specification --- Status: Draft v0.1 -Last updated: 2026-06-04 +Last updated: 2026-11-01 Stability: Unstable , expect breaking changes before v1.0 --- @@ -26,6 +26,7 @@ Closes #22. | Attestation staleness mid-session | Fail-closed. Active sessions terminated. | Yes (see FM-2) | `attestation_stale: true` on claims produced at or after deadline | `WARN attestation_stale` + session_id + expiry_utc | | TEE fault mid-invocation | Fail-closed on in-flight call. Structured error returned to agent. Session continues if TEE recovers. | No | `status: fault` | `ERROR tee_fault` + call_id + timestamp | | Policy bundle hash mismatch at startup | Fail-closed. Runtime does not start. | No | Not produced | `FATAL policy_hash_mismatch` + expected_hash + actual_hash | +| Gateway measurement or TPM/NV operation unavailable at startup | Production: fail-closed; runtime does not start. `CMCP_DEV_MODE`: warn and continue without established gateway measurement. | Dev mode only (see Measurement unavailable at startup) | No claim in production; dev continuation is not evidence of a successfully measured gateway | Production: `FATAL MEASUREMENT_UNAVAILABLE` + detail; dev mode: `WARN` measurement unavailable | | Sealed signing key unavailable at startup | Fail-closed. Runtime does not start. Nothing is minted to recover. | No | Not produced | `FATAL SEALED_KEY_*` + detail (see FM-1b) | | MCP protocol parse failure | Fail-closed on the specific call. Structured error returned. Session continues. | No | `status: parse_error` | `WARN parse_failure` + call_id + payload_hash | @@ -298,3 +299,28 @@ For calls that are rejected or faulted (FM-3, FM-5), the TRACE Claim records the "signature_note": "TEE fault prevented signing. This claim is for audit record only and must not be treated as an attested proof." } ``` + +## Measurement unavailable at startup + +`MEASUREMENT_UNAVAILABLE` covers failures deriving code/configuration/policy +measurements and failures completing the TPM-backed measurement path. + +**Triggers**: unreadable or unsupported measured inputs, TPM open errors, NV index +define/read/validation errors, `TPM2_NV_Extend` failure, chain-value mismatch, +truncated NV values, and certify failures. Inspect the reported diagnostic detail +rather than assuming an input-file problem. + +**Production**: startup aborts with `FATAL MEASUREMENT_UNAVAILABLE`; no traffic is +processed from this failed startup. + +**Development**: with `CMCP_DEV_MODE`, the runtime logs a warning and continues. +This continuation is not evidence of a successfully measured gateway. + +**Recovery**: restore readable, supported measurement inputs when they are the +reported cause. For a TPM/NV failure, investigate device access, library support, +NV index configuration and the reported chain or certify operation against the +intended deployment state before restarting. This code alone is not evidence +that the TPM changed. Do not bypass measurement or rotate a signing key merely +to suppress this error. Sealed-key recovery remains a separate decision (FM-1b). + +This entry describes existing runtime behavior; it adds no new recovery action. diff --git a/tests/unit/test_measurement_error_registry.py b/tests/unit/test_measurement_error_registry.py new file mode 100644 index 00000000..b4c435cd --- /dev/null +++ b/tests/unit/test_measurement_error_registry.py @@ -0,0 +1,73 @@ +"""Keep startup measurement diagnostics and operator recovery discoverable.""" + +from pathlib import Path + +from cmcp_runtime.tee.measurement import MeasurementUnavailable + +ROOT = Path(__file__).resolve().parents[2] + + +def _row(text, first_column): + rows = [line for line in text.splitlines() if line.startswith(f"| {first_column} |")] + assert len(rows) == 1 + columns = [column.strip() for column in rows[0].split("|")[1:-1]] + assert len(columns) == 5 + return columns + + +def test_measurement_failure_registry_columns_match_runtime_and_modes(): + registry = (ROOT / "docs/spec/error-codes.md").read_text(encoding="utf-8") + columns = _row(registry, f"`{MeasurementUnavailable.code}`") + assert columns[1] == f"{MeasurementUnavailable.http_status} / startup" + assert columns[2] == "FATAL (production); WARN (dev mode)" + for phrase in ( + "TPM/NV", + "production startup aborts", + "`CMCP_DEV_MODE`", + "without established gateway measurement", + "do not infer TPM replacement", + "rotate a signing key solely from this code", + ): + assert phrase in columns[3] + assert columns[4] == "[failure-modes.md](failure-modes.md#measurement-unavailable-at-startup)" + + +def test_measurement_failure_decision_table_covers_both_modes(): + failure_modes = (ROOT / "docs/spec/failure-modes.md").read_text(encoding="utf-8") + assert "## Decision Table" in failure_modes + table = failure_modes.split("## Decision Table", 1)[1].split("\n## ", 1)[0] + columns = _row(table, "Gateway measurement or TPM/NV operation unavailable at startup") + assert "runtime does not start" in columns[1] + assert ( + "`CMCP_DEV_MODE`: warn and continue without established gateway measurement" in columns[1] + ) + assert columns[2] == "Dev mode only (see Measurement unavailable at startup)" + assert "not evidence of a successfully measured gateway" in columns[3] + assert "`FATAL MEASUREMENT_UNAVAILABLE`" in columns[4] + assert "dev mode: `WARN`" in columns[4] + + +def test_measurement_failure_recovery_guidance_is_retained(): + failure_modes = (ROOT / "docs/spec/failure-modes.md").read_text(encoding="utf-8") + assert "## Measurement unavailable at startup" in failure_modes + section = failure_modes.split("## Measurement unavailable at startup", 1)[1].split("\n## ", 1)[ + 0 + ] + prose = " ".join(section.split()) + for phrase in ( + "TPM open errors", + "NV index define/read/validation errors", + "`TPM2_NV_Extend` failure", + "chain-value mismatch", + "truncated NV values", + "certify failures", + "**Production**: startup aborts", + "**Development**: with `CMCP_DEV_MODE`, the runtime logs a warning and continues.", + "not evidence of a successfully measured gateway", + "restore readable, supported measurement inputs", + "investigate device access, library support, NV index configuration", + "This code alone is not evidence that the TPM changed.", + "Do not bypass measurement or rotate a signing key merely to suppress this error.", + "Sealed-key recovery remains a separate decision (FM-1b).", + ): + assert phrase in prose