diff --git a/CHANGELOG.md b/CHANGELOG.md index 886a9ca0..2ddb9985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,20 @@ Format: [Semantic Versioning](https://semver.org/). Spec versions follow `MAJOR. ## [Unreleased] +- Propose explicit verifier profile declarations and verifier-result field requirements for obligations 2 and 3 of #116, carried by Imran Siddique following the discussion with @lywinged. Obligations 1 and 4 remain deferred; the v0.1 cutover is unchanged. +- **Breaking, proposed: `silent` mode allows a policy-denied action (#143, following #28).** Section 4.3 now says a deny MUST NOT block in `silent` mode and the audit chain MUST record every would-have-denied decision. v0.9.0 and v0.10.0 described `silent` as evaluated and enforced under the same `eat_profile`, so a producer that followed that text and blocks on deny no longer conforms, and a `silent` record does not say which meaning it was written under. Classified breaking under GOVERNANCE.md, so adoption needs the 30-day comment period and Project Lead sign-off. Section 4.3 also orders the modes: `advisory` is not weaker than `silent`. Enforce remains the default; schema constraints and runtime behavior are unchanged. Classification raised by @lywinged. + +- **Specification precedence and reproducible conformance claims (#247).** The + v0.2 draft now states that normative text governs meaning and conformance; + disagreements in the schema, reference model or explanatory documentation are + defects in those supporting artifacts. Schema validation alone is not complete + conformance, and a normative gap needs a specification decision. Conformance + reports identify the specification revision and schema artifact, including its + digest, plus the verifier or suite version and assessed level where used. This + adds reporting requirements without changing Trust Record fields or verifier + acceptance behavior. Based on the maintainer ruling and producer feedback from + @chernistry and @lywinged in #247. + ## [0.11.0] - 2026-09-25 - **Breaking:** `TraceSandboxAdapter` and `TraceAGTAdapter` require `enforcement_mode`; diff --git a/docs/integration/openshell.md b/docs/integration/openshell.md index 664095d4..bd8a65bd 100644 --- a/docs/integration/openshell.md +++ b/docs/integration/openshell.md @@ -63,8 +63,10 @@ bundle: single `policy.enforcement_mode` is the weakest mode among the layers: - `enforce` only when both layers enforce; -- `advisory` when either layer evaluates without blocking; -- `silent` when a layer enforces while suppressing operational logs. +- `advisory` when either layer evaluates without blocking, or enforces while + suppressing operational logs, since no mode states that combination and + `advisory` understates it rather than overstating it; +- `silent` when either layer runs in `silent` mode. ## Transcript binding diff --git a/docs/schema.md b/docs/schema.md index da6ecb81..3d817216 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -71,7 +71,7 @@ Binds the governance policy in force during this session. | Field | Type | Required | Description | |---|---|---|---| | `bundle_hash` | string | **yes** | `sha256:` digest of the Cedar policy bundle bytes | -| `enforcement_mode` | string | **yes** | One of: `enforce` (evaluated, blocked on deny), `advisory` (evaluated, logged, allowed), `silent` (evaluated and enforced with operational logs suppressed; the audit chain still records every would-have-denied decision), `declared` (the policy is named and bound into the signed record and nothing evaluated it: the honest value for a producer with no policy engine, never a default, and not evidence that any rule was checked). Defaults to `enforce`. Section 4.3 of the spec defines `enforce`, `silent` and `declared`; `advisory` is in the schema's closed set and its one-line meaning is the schema's own description, not spec text | +| `enforcement_mode` | string | **yes** | One of: `enforce` (evaluated, blocked on deny), `advisory` (evaluated, logged, allowed), `silent` (evaluated and allowed, including on deny, with operational logs suppressed; the audit chain still records every would-have-denied decision), `declared` (the policy is named and bound into the signed record and nothing evaluated it: the honest value for a producer with no policy engine, never a default, and not evidence that any rule was checked). Defaults to `enforce`. Section 4.3 of the spec defines `enforce`, `silent` and `declared`; `advisory` is in the schema's closed set and its one-line meaning is the schema's own description, not spec text | | `version` | string | no | Policy bundle version string | | `policy_uri` | string | no | URI to the policy bundle for inspection | diff --git a/docs/verification.md b/docs/verification.md index b2938a5d..e7d1a3c6 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -22,7 +22,11 @@ A signature proves a statement came from the trusted key. It does not establish ### Step 4: Check the EAT profile -The current SDK requires `tag:agentrust-io.com,2026:trace-v0.2`. The superseded v0.1 identifier is rejected. This check is already included in `verify_record`; a custom verifier must also enforce its supported profile. +[Section 3.3](../spec/trace-v0.2.md#33-verification) requires a nonempty `accepted_profiles` set containing only profiles whose schemas and verification semantics the verifier implements. Reject an unsupported member anywhere in that declaration, even if the record names a supported profile. Also reject a record whose `eat_profile` is outside the declared set. + +A successful verification result records both the verified `profile` and the complete `accepted_profiles` set configured at verification time. Retaining only the record's profile loses which other profiles the verifier claimed to support. These fields belong to the verifier's result; they do not alter the signed record. + +The current SDK accepts only `tag:agentrust-io.com,2026:trace-v0.2` and rejects the superseded v0.1 identifier. `verify_record` enforces these checks and returns `result.profile` and `result.accepted_profiles`. Obligations 1 and 4 of [#116](https://github.com/agentrust-io/trace-spec/issues/116), emitter version declarations and downgrade disclosure, remain deferred. ### Step 5: Appraise the claims diff --git a/schema/trace-claim-v0.3-draft.json b/schema/trace-claim-v0.3-draft.json index 93f48d12..5706137b 100644 --- a/schema/trace-claim-v0.3-draft.json +++ b/schema/trace-claim-v0.3-draft.json @@ -235,7 +235,7 @@ "declared" ], "default": "enforce", - "description": "How the policy named by bundle_hash related to this execution. enforce: evaluated and blocked on deny. advisory: evaluated, logged, allowed. silent: evaluated and enforced with operational logs suppressed; the audit chain still records every would-have-denied decision. declared: the policy is named and bound into the signed record and NOTHING evaluated it, which is the honest value for a producer with no policy engine, such as an agent framework. Gateways MUST default to enforce. A deployment MUST explicitly configure silent. declared is never a default, a producer that evaluates policy MUST NOT use it, and a consumer MUST NOT read it as evidence that any rule was checked." + "description": "How the policy named by bundle_hash related to this execution. enforce: evaluated and blocked on deny. advisory: evaluated, logged, allowed. silent: evaluated and allowed, including on deny, with operational logs suppressed; the audit chain still records every would-have-denied decision. declared: the policy is named and bound into the signed record and NOTHING evaluated it, which is the honest value for a producer with no policy engine, such as an agent framework. Gateways MUST default to enforce. A deployment MUST explicitly configure silent. declared is never a default, a producer that evaluates policy MUST NOT use it, and a consumer MUST NOT read it as evidence that any rule was checked." }, "version": { "type": "string", diff --git a/schema/trace-claim.json b/schema/trace-claim.json index da180f98..b73f7f0b 100644 --- a/schema/trace-claim.json +++ b/schema/trace-claim.json @@ -164,7 +164,7 @@ "declared" ], "default": "enforce", - "description": "How the policy named by bundle_hash related to this execution. enforce: evaluated and blocked on deny. advisory: evaluated, logged, allowed. silent: evaluated and enforced with operational logs suppressed; the audit chain still records every would-have-denied decision. declared: the policy is named and bound into the signed record and NOTHING evaluated it, which is the honest value for a producer with no policy engine, such as an agent framework. Gateways MUST default to enforce. A deployment MUST explicitly configure silent. declared is never a default, a producer that evaluates policy MUST NOT use it, and a consumer MUST NOT read it as evidence that any rule was checked." + "description": "How the policy named by bundle_hash related to this execution. enforce: evaluated and blocked on deny. advisory: evaluated, logged, allowed. silent: evaluated and allowed, including on deny, with operational logs suppressed; the audit chain still records every would-have-denied decision. declared: the policy is named and bound into the signed record and NOTHING evaluated it, which is the honest value for a producer with no policy engine, such as an agent framework. Gateways MUST default to enforce. A deployment MUST explicitly configure silent. declared is never a default, a producer that evaluates policy MUST NOT use it, and a consumer MUST NOT read it as evidence that any rule was checked." }, "version": { "type": "string", diff --git a/spec/trace-v0.2.md b/spec/trace-v0.2.md index f102923c..eff86041 100644 --- a/spec/trace-v0.2.md +++ b/spec/trace-v0.2.md @@ -11,6 +11,49 @@ > **Note:** This is a pre-ratification draft. Fields, wire formats, and conformance requirements are subject to change before v1.0. Send feedback to: open an issue on this repository. +## Authority and conformance claims + + + +This specification defines the meaning of TRACE claims and the requirements for +conformance. Normative companion specifications apply within the scope this +specification assigns them. The following rules govern the relationship between +the specification and its supporting artifacts: + +- `schema/trace-claim.json` defines the machine-readable validation constraints. + Passing schema validation alone MUST NOT be represented as establishing TRACE + conformance: semantic requirements and verification checks also apply. Schema + descriptions do not add or override normative requirements. +- The reference model in `src/agentrust_trace/models.py` is an implementation of + those requirements, not an independent source of requirements. +- `docs/*.md`, including proposals under `docs/rfcs/`, explains the specification + and MUST NOT override its normative requirements. + +Where the schema, reference model or explanatory documentation disagrees with +the normative specification, the disagreement MUST be treated as a defect in +the supporting artifact. Neither accepting the union nor requiring the +intersection of conflicting implementations resolves the normative rule. A +schema is too narrow or too broad when it rejects or admits records contrary to +the specification's structural validation constraints. Requirements enforced by +verification, including signature and freshness checks, remain separate. Where +normative text is silent, a rule found only in an implementation, schema description or proposal +MUST NOT be promoted to a normative requirement without the specification +change process. Such a coverage gap requires a specification decision. + +A conformance claim MUST identify the specification version and exact revision +being claimed, together with the schema artifact used for validation. For a +published release, record the release tag or package version and a digest of +the schema bytes; for an unreleased checkout, record the full commit identifier +and schema digest. Identify the verifier or conformance-suite version and the +level assessed when either is used. These are accompanying reporting details, +not new Trust Record fields. A schema-only result MUST be described as schema +validation, not as complete conformance. + +TRACE v0.2 is a draft: `main` can differ from a published package. A finding +against one revision MUST NOT be presented as a finding against another without +checking the applicable requirements and artifacts. Reporting a schema version +does not make that schema authoritative over the normative specification. + ## Changes from v0.1 One normative change, and it is breaking. @@ -462,6 +505,12 @@ Any party, browser, CLI, in-cluster verifier, third-party auditor, verifies: 7. SLSA provenance resolves to a trusted builder. 8. The record-signing key is not revoked as of the entry the record was logged at, per section 3.2.3. A verifier holding no revocation bundle, or only an expired one, reports that rather than treating it as a pass. + + +**Verifier profile compatibility.** A verifier MUST declare a nonempty `accepted_profiles` set of profile identifiers whose schemas and verification semantics it implements. A declaration containing an unimplemented profile MUST be refused, even when the incoming record names another implemented profile. A verifier MUST refuse a record whose `eat_profile` is outside its declared set. These requirements do not override the v0.1 cutover in "Changes from v0.1". + +For each successful verification, the verifier MUST report the verified `profile` and the complete `accepted_profiles` set as verifier-result fields, recording the set configured at verification time. `profile` MUST identify the record's signed `eat_profile`. These fields belong to the verifier's result; they are not new claims added to the signed input record. + No callback to the issuer. No vendor in the trust path beyond silicon root and transparency log operators. #### 3.3.1 Build provenance verification depth @@ -717,7 +766,9 @@ TRACE is a **profile**, not a parallel stack. It binds existing primitives into These components exist in their respective ecosystems. TRACE adds the binding rule that places each into a hardware-attested envelope: -- **`policy` claim.** Policy artifacts (OPA bundles, Cedar policies, custom DSLs) and policy hashing are established. TRACE adds the binding: the policy bundle hash is sealed to the TEE measurement, the enforcement mode is recorded, and substituting the policy invalidates the runtime claim. Gateways MUST default `enforcement_mode` to `enforce`. A deployment MUST explicitly configure `silent` mode; `silent` MUST NOT be the default. In `silent` mode, the audit chain still records every would-have-denied decision; only operational log lines are suppressed. + + +- **`policy` claim.** Policy artifacts (OPA bundles, Cedar policies, custom DSLs) and policy hashing are established. TRACE adds the binding: the policy bundle hash is sealed to the TEE measurement, the enforcement mode is recorded, and substituting the policy invalidates the runtime claim. Gateways MUST default `enforcement_mode` to `enforce`. A deployment MUST explicitly configure `silent` mode; `silent` MUST NOT be the default. In `silent` mode, a policy deny MUST NOT block the action. The audit chain MUST still record every would-have-denied decision; only operational log lines are suppressed. Where modes are compared, `enforce` is the strongest and `silent` the weakest: `advisory` is not weaker than `silent`, because both allow a denied action and `silent` also suppresses operational logs. **`enforcement_mode: "declared"`.** The three modes above all assert that *something evaluated the policy*. `declared` asserts less: the policy is named and bound into the signed record, and nothing evaluated it. That is not a corner case, it is the common one for a producer with no policy engine: an agent framework observed by an adapter has a policy the operator declares and no evaluation of it anywhere, and with only three values such a record had to claim an evaluation that never happened. diff --git a/src/agentrust_trace/models.py b/src/agentrust_trace/models.py index eafba8da..78440b06 100644 --- a/src/agentrust_trace/models.py +++ b/src/agentrust_trace/models.py @@ -160,9 +160,11 @@ class PolicyInfo(_TraceModel): """How the policy named by ``bundle_hash`` related to this execution. The first three all assert that **something evaluated the policy**: - ``enforce`` acted on the result, ``advisory`` did not, ``silent`` acted and - suppressed the log lines. ``declared`` asserts less than any of them: the - policy is named and bound into the signed record, and nothing evaluated it. + ``enforce`` blocked on deny, ``advisory`` logged and allowed, and ``silent`` + allowed even on deny while suppressing operational log lines. The audit + chain still records every would-have-denied decision in ``silent`` mode. + ``declared`` asserts less than any of them: the policy is named and bound + into the signed record, and nothing evaluated it. That case is not hypothetical, it is the common one. An agent framework has no policy engine, so a record produced by observing a LangChain or LlamaIndex diff --git a/src/agentrust_trace/schema/trace-v0.2.json b/src/agentrust_trace/schema/trace-v0.2.json index da180f98..b73f7f0b 100644 --- a/src/agentrust_trace/schema/trace-v0.2.json +++ b/src/agentrust_trace/schema/trace-v0.2.json @@ -164,7 +164,7 @@ "declared" ], "default": "enforce", - "description": "How the policy named by bundle_hash related to this execution. enforce: evaluated and blocked on deny. advisory: evaluated, logged, allowed. silent: evaluated and enforced with operational logs suppressed; the audit chain still records every would-have-denied decision. declared: the policy is named and bound into the signed record and NOTHING evaluated it, which is the honest value for a producer with no policy engine, such as an agent framework. Gateways MUST default to enforce. A deployment MUST explicitly configure silent. declared is never a default, a producer that evaluates policy MUST NOT use it, and a consumer MUST NOT read it as evidence that any rule was checked." + "description": "How the policy named by bundle_hash related to this execution. enforce: evaluated and blocked on deny. advisory: evaluated, logged, allowed. silent: evaluated and allowed, including on deny, with operational logs suppressed; the audit chain still records every would-have-denied decision. declared: the policy is named and bound into the signed record and NOTHING evaluated it, which is the honest value for a producer with no policy engine, such as an agent framework. Gateways MUST default to enforce. A deployment MUST explicitly configure silent. declared is never a default, a producer that evaluates policy MUST NOT use it, and a consumer MUST NOT read it as evidence that any rule was checked." }, "version": { "type": "string",