Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ Format: [Semantic Versioning](https://semver.org/). Spec versions follow `MAJOR.

## [Unreleased]

### Added

- **`enforcement_mode: "declared"`.** The three existing modes all assert that *something evaluated the policy*: `enforce` acted on the result, `advisory` did not, `silent` acted with the log lines suppressed. `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. An agent framework has no policy engine, so a record built by observing a LangChain or LlamaIndex run has a policy the operator declares and no evaluation of it anywhere. With three values, such a record had to claim an evaluation that never happened; both framework adapters refused to default the field and documented the overstatement instead, which is honest and still leaves every framework record marginally untrue.

`declared` is the weakest value and 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. A verifier appraising for enforcement SHOULD treat it as it treats an absent enforcement claim.

Additive to a closed enum in both the model and the JSON schema, so unknown values are still rejected. Same one-directional consequence as `origin`: a verifier older than this release rejects a record carrying `declared`.

## [0.8.0] — 2026-08-09

### Added
Expand Down
5 changes: 3 additions & 2 deletions schema/trace-claim.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,11 @@
"enum": [
"enforce",
"advisory",
"silent"
"silent",
"declared"
],
"default": "enforce",
"description": "How policy decisions were applied: enforce (block on deny), advisory (log and allow), silent (allow and suppress operational logs; the audit chain still records every would-have-denied decision). Gateways MUST default to enforce. A deployment MUST explicitly configure silent mode; it MUST NOT be the default."
"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."
},
"version": {
"type": "string",
Expand Down
4 changes: 4 additions & 0 deletions spec/trace-v0.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ 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.

**`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.

`declared` is the weakest value and MUST NOT be a default. A producer that evaluates policy MUST NOT use it. A consumer MUST NOT read it as evidence that any rule was checked; it says only that this is the policy the deployment states it was operating under. A verifier appraising for enforcement SHOULD treat `declared` as it treats an absent enforcement claim.
- **`data_class` claim.** Data classification schemes are established (DLP labels, NIST SP 800-60, sensitivity tags). TRACE adds: a classification label is attached to inputs and outputs at the per-call layer and recorded in the Trust Record alongside the runtime evidence.
- **`tool_transcript` claim.** MCP and A2A transcripts exist at the protocol layer. TRACE adds cryptographic binding of the transcript hash into the EAT envelope and per-call parameter classification.
- **AI-agent execution profile.** A profile registry that pins the claim set, evidence requirements, and verification rules for AI-agent workloads specifically.
Expand Down
22 changes: 21 additions & 1 deletion src/agentrust_trace/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,27 @@ class PolicyInfo(BaseModel):
model_config = ConfigDict(extra="forbid")

bundle_hash: DigestStr
enforcement_mode: Literal["enforce", "advisory", "silent"]
enforcement_mode: Literal["enforce", "advisory", "silent", "declared"]
"""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.

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
run has a policy the operator declares and no evaluation of it anywhere. With
only three values, such a record had to overstate: ``advisory`` claims an
evaluation that did not happen. The adapters refused to default the field and
documented the overstatement, which is honest and leaves every framework
record marginally untrue.

``declared`` is deliberately the weakest value and 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. It says only: this is the policy
this deployment says it was operating under.
"""
version: str | None = None
policy_uri: str | None = None

Expand Down
5 changes: 3 additions & 2 deletions src/agentrust_trace/schema/trace-v0.2.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,11 @@
"enum": [
"enforce",
"advisory",
"silent"
"silent",
"declared"
],
"default": "enforce",
"description": "How policy decisions were applied: enforce (block on deny), advisory (log and allow), silent (allow and suppress operational logs; the audit chain still records every would-have-denied decision). Gateways MUST default to enforce. A deployment MUST explicitly configure silent mode; it MUST NOT be the default."
"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."
},
"version": {
"type": "string",
Expand Down
26 changes: 26 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,29 @@ def test_origin_rejects_unknown_fields() -> None:
data["origin"] = {"kind": "log-import", "producer": "x/1.0", "assurance": "operator-asserted"}
with pytest.raises(ValidationError):
TrustRecord.model_validate(data)


# --- enforcement_mode: declared -------------------------------------------


def test_declared_enforcement_mode_parses() -> None:
"""The honest value for a producer with no policy engine."""
data = _load("intel-tdx.json")
data["policy"]["enforcement_mode"] = "declared"
record = TrustRecord.model_validate(data)
assert record.policy.enforcement_mode == "declared"


def test_enforcement_mode_stays_closed() -> None:
"""Adding a value must not turn the field into free text."""
data = _load("intel-tdx.json")
data["policy"]["enforcement_mode"] = "monitor"
with pytest.raises(ValidationError):
TrustRecord.model_validate(data)


def test_the_three_evaluating_modes_still_parse() -> None:
for mode in ("enforce", "advisory", "silent"):
data = _load("intel-tdx.json")
data["policy"]["enforcement_mode"] = mode
assert TrustRecord.model_validate(data).policy.enforcement_mode == mode
12 changes: 12 additions & 0 deletions tests/test_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,15 @@ def test_both_subject_forms_pass_validate_json(subject: str) -> None:
data = _load("intel-tdx.json")
data["subject"] = subject
assert iter_errors(data) == []


def test_declared_enforcement_mode_passes_json_schema() -> None:
data = _load("intel-tdx.json")
data["policy"]["enforcement_mode"] = "declared"
assert iter_errors(data) == []


def test_unknown_enforcement_mode_fails_json_schema() -> None:
data = _load("intel-tdx.json")
data["policy"]["enforcement_mode"] = "monitor"
assert iter_errors(data)