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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,13 @@ Format: [Semantic Versioning](https://semver.org/). Spec versions follow `MAJOR.

### Added

- **`observed-effect` is a registered `references` relation.** The four registered values cover what was asked, who approved, what the agent did and what a check found; none points at what actually changed. `observed-effect` is a signed record of the state change an observer outside the agent saw over one interval: the state root before and after, the paths observed and any part of them unseen, the digest of the authority change ran under, and a subject digest equal to the after-state root. `docs/references-registry.md` defines the referenced object as facts rather than a wire format and bounds what a relying party may establish to the object, as for `condition-appraisal`: an interval in which the observer and the observed party agree is not attested evidence that the agent's report was true, and one in which they disagree is not a finding against the record. `examples/observed-effect/` carries five signed records against an effect store whose two statements are copied byte for byte from the published observed-effect conformance corpus: verified, altered after issue, a disagreeing interval that verifies exactly as the agreeing one does, unresolvable, and an observer whose key the relying party does not hold; `tests/test_observed_effect_fixtures.py` recomputes every finding from the committed bytes and re-runs the generator against them. Each verdict names the state of the referenced observation (verified, digest mismatch, unresolved, unverified) and never the effect it reports, with resolution, digest match and signature verification kept as separate findings. Proposed on #402. Informative: no requirement, schema shape or wire format changes; the schema's `rel` description gains the name.

- **PIC/TRACE successor evidence is temporally separated from pre-execution authorization (#424, superseding the one-stage binding added in #340 for #338).** The signed pre-execution authorization no longer carries a digest of evidence that can exist only after execution. `intent_bridge.sign_successor_artifact()` creates a separate observer-authenticated post-execution artifact binding `authorization_id` to the exact successor-envelope digest, and `verify_successor_artifact()` checks the observer key, authorization link and envelope binding. `schema/pic-trace-successor-v1.json` defines the detached successor artifact while `schema/pic-trace-bridge-v1.json` remains the pre-execution authorization schema. This preserves the invariant that post-execution evidence cannot be a signed input to the artifact relied on as pre-execution authorization. Reported and implemented by @altrudev; #363 requires corresponding corpus regeneration before merge.

- **`provenance.verify_record()` takes an explicit `now` (#411).** A non-negative integer Unix timestamp pins the freshness decision so it can be replayed. Booleans and other types are refused as configuration errors. Omitting it keeps the host clock.

- **`condition-appraisal` is a registered `references` relation, and the registry says what it is.** #191, #209, #220 and later #321 each asked for a way to bind a governance fact held outside the record, and #226 read them as one question about the `rel` registry of section 3.1.2. This settles the part ruled on there: the registry is informative and open, the verifier rules of section 3.1.2 stay normative and section 3.1.2 now says so below them, and `docs/references-registry.md` holds each registered value's referenced object, what a relying party may establish from a resolved one, and the steps by which a name is added. The fourth value, `condition-appraisal`, is an independent check's finding on whether a stated condition is established by a stated subject: a test run, a schema validation, a contract check. Its referenced object binds the condition and the subject by digest, names its issuer and key, carries the outcome in the issuer's own closed vocabulary, and signs the canonical bytes of the rest; the reference's `digest` is over the object as retained. What a relying party may establish is bounded to the object: that the cited bytes are the cited bytes and, where it holds the issuer's key, that the issuer signed them; neither reaches the record, a `pass` is not attested evidence that the condition held, and a `fail` is not a finding against the record. `examples/condition-appraisal/` carries five signed records from a seeded generator against an appraisal store: confirmed, altered after issue, a fail that verifies exactly as the pass does, unresolvable, and an issuer whose key the relying party does not hold; `tests/test_condition_appraisal_fixtures.py` recomputes every verdict from the committed bytes and re-runs the generator against them. Re-running a cited check is a separate proposal and no part of this change. Proposed by @nutstrut on #226; ruled on by @imran-siddique. Informative: no requirement, schema shape or wire format changes; the schema's `rel` description gains the name.
- **`condition-appraisal` is a registered `references` relation, and the registry says what it is.** #191, #209, #220 and later #321 each asked for a way to bind a governance fact held outside the record, and #226 read them as one question about the `rel` registry of section 3.1.2. This settles the part ruled on there: the registry is informative and open, the verifier rules of section 3.1.2 stay normative and section 3.1.2 now says so below them, and `docs/references-registry.md` holds each registered value's referenced object, what a relying party may establish from a resolved one, and the steps by which a name is added. The fourth value, `condition-appraisal`, is an independent check's finding on whether a stated condition is established by a stated subject: a test run, a schema validation, a contract check. Its referenced object binds the condition and the subject by digest, names its issuer and key, carries the outcome in the issuer's own closed vocabulary, and signs the canonical bytes of the rest; the reference's `digest` is over the object as retained. What a relying party may establish is bounded to the object: that the cited bytes are the cited bytes and, where it holds the issuer's key, that the issuer signed them; neither reaches the record, a `pass` is not attested evidence that the condition held, and a `fail` is not a finding against the record. `examples/condition-appraisal/` carries five signed records from a seeded generator against an appraisal store: verified, altered after issue, a fail that verifies exactly as the pass does, unresolvable, and an issuer whose key the relying party does not hold; `tests/test_condition_appraisal_fixtures.py` recomputes every verdict from the committed bytes and re-runs the generator against them. Re-running a cited check is a separate proposal and no part of this change. Proposed by @nutstrut on #226; ruled on by @imran-siddique. Informative: no requirement, schema shape or wire format changes; the schema's `rel` description gains the name.

- **The schema, the reference model and the schema reference carry the reproducibility claim (section 3.1.4).** `schema/trace-claim.json` and its packaged copy gain the optional top-level `reproducibility` object: `function`, `code_identity`, an optional `code_resolver`, `input_closure` as `{id, digest, resolver}` entries with every member required, and `transcript_digest`. `appraisal` gains `method`, the closed set `re-execution`, and `re_execution`: `outcome` of `reproduced`, `diverged` or `not-attempted`, `observed_digest`, `reason`, and an optional `verifier_code_identity`. The three presence rules the section states are schema conditionals and model validators alike: `re_execution` is present exactly when `method` is `re-execution`, `observed_digest` is required on `diverged`, `reason` is required on `not-attempted`. `models.py` adds `Reproducibility`, `ClosureEntry` and `ReExecution`; `docs/schema.md` documents every member in its section. `examples/reproducibility-claim/` holds 21 signed vectors from a seeded key: five accepted records, a claim with no result and one result per outcome, and sixteen rejected ones, two per rule, with the transcript and closure digests recomputable from the JSON they are taken over. Tracks #364; closes #366.

Expand Down
70 changes: 70 additions & 0 deletions docs/references-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ schema description, in `docs/schema.md`, or in this document.
| `approval-outcome` | An attributable human approval attached to a step-up or defer decision | §3.1.2; the CHAP mapping in [`crosswalks/chap-review-decisions.md`](crosswalks/chap-review-decisions.md) | [`examples/chap-approval-outcome/`](../examples/chap-approval-outcome/) |
| `behavior-trace` | A behavioural record of what the agent did, of which this record is the environment evidence | §3.1.2 | none committed |
| `condition-appraisal` | An independent check's finding on whether a stated condition is established by a stated subject | This document, below | [`examples/condition-appraisal/`](../examples/condition-appraisal/) |
| `observed-effect` | A signed record of the state change an observer outside the agent saw over one interval | This document, below | [`examples/observed-effect/`](../examples/observed-effect/) |

The first three were registered by the change that introduced the block (#197) and
their referenced objects are defined to the extent the documents cited define them.
Expand Down Expand Up @@ -129,6 +130,75 @@ separate proposal from this registration. Nor is it compliance evidence: §3.1.2
says of the whole block that a record pointing at a check attests that it points there,
and nothing about whether an obligation was met.

## `observed-effect`

### Meaning

The record points at what an observer the agent does not control saw change while the
agent ran: the state before an interval, the state after it, the paths the observation
covered, and the authority under which change was permitted. The four values registered
before it cover what was asked (`authorized-intent`), who approved (`approval-outcome`),
what the agent did (`behavior-trace`) and what a check found (`condition-appraisal`);
this is the question a relying party asks after all four, what actually changed.

Two neighbours it is not. A `behavior-trace` is a record of the agent's actions, often
written by the agent or its host, and a state change can happen that no action in it
accounts for, or fail to happen where one claims it did; an `observed-effect` is taken
over the state itself, from outside the party it describes. And it is not a
`condition-appraisal`: it states what changed and within what scope, and it holds the
change against no criteria. A check over an observed interval, such as whether a write
fell outside the permitted paths, is a `condition-appraisal` whose subject is the
interval.

### The referenced object

A signed, content-addressed statement. The committed example uses an in-toto Statement
in a DSSE envelope whose `predicateType` is
`https://probityai.github.io/agent-evidence-vectors/predicate/v1/observed-effect`, and
the wire format is the issuer's; what registration fixes is the set of facts the object
carries and how the reference's `digest` is taken over it.

| Fact | Carried as | Why it is required |
|---|---|---|
| The interval | The state root before, the state root after, and the times the interval opened and was sealed | Without both roots there is no change to point at, only a claim that one happened |
| The scope | The paths the observation covered, and any part of them it could not see | A change observed over part of a system says nothing about the rest; an unstated scope reads as total |
| The authority | A digest over the policy or grant under which change was permitted | Binds the change to the permission it ran under, so a later grant cannot be read back onto it |
| The observer | An identifier for the party that observed, and a reference to its verification key | §3.3.2's `issuer` and `issuer_key_id`, with the binding to a call replaced by the bindings above |
| The subject | A digest equal to the after-state root | Makes the object identify a state, not only describe one |
| The signature | The observer's signature over the object | §3.3.2 step 1, applied to this object; the committed example signs the DSSE pre-authentication encoding of the statement |

The reference's `digest` is over the RFC 8785 canonical form of the complete object,
signature included, as the resolver retains it. `id` is the object's identifier within
the resolver's system and `resolver` is the party obliged to keep it resolvable, as for
every entry.

The committed example, [`examples/observed-effect/`](../examples/observed-effect/),
resolves two statements copied byte for byte from a published conformance corpus for
that predicate type, so a second implementation already verifies the objects it cites.
A producer whose observer emits another shape that carries these facts is within this
definition.

### What a verifier may conclude

The same three findings as for `condition-appraisal`, each separable, all three
reported:

1. **Whether the reference resolves.** If not, the observation is unresolved, which is a
different answer from "nothing changed", and under rule 3 of §3.1.2 it is never a
reason to reject the record.
2. **Whether the resolved bytes are the cited bytes.** A digest match establishes
identity; a mismatch is a finding about the store.
3. **Whether the object verifies under its named observer's key**, when the relying
party holds that key. An observer key the verifier does not hold makes the object
unverified, not invalid.

All three are findings about the object and none reaches the record. What the object
reports is carried as the observer stated it and promotes in neither direction: an
interval in which the observer and the observed party agree is not attested evidence
that the agent's report was true, and one in which they disagree is not a finding
against the record. The example set holds an agreeing and a disagreeing interval side
by side, and the two records verify identically.

## Adding a name

A registration is a non-breaking spec change of the informative kind under
Expand Down
4 changes: 2 additions & 2 deletions docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,13 @@ A record whose `kind` is not `self` **must** carry `runtime.platform: "software-

## `references` {#trace-field-references}

An array of pointers to facts held outside this record: an authorization decided before execution, a human approval, a behavioural trace, an independent check's finding. What the signature attests is that this record points there, not the truth of what it points at.
An array of pointers to facts held outside this record: an authorization decided before execution, a human approval, a behavioural trace, an independent check's finding, an observed change of state. What the signature attests is that this record points there, not the truth of what it points at.

`origin` records where evidence *came from* and can lower assurance. `references` records what a record *points at* and cannot. Before the block existed, a record that needed to name something external had to use `origin` and take `runtime.platform: "software-only"` with it, which said something untrue about how the evidence was obtained.

| Field | Type | Required | Description |
|---|---|---|---|
| `rel` | string | **yes** | Registered values: `authorized-intent`, `approval-outcome`, `behavior-trace`, `condition-appraisal`. A registry rather than a closed set, so the schema does not restrict which relation is named: only that one is: the value must be non-empty |
| `rel` | string | **yes** | Registered values: `authorized-intent`, `approval-outcome`, `behavior-trace`, `condition-appraisal`, `observed-effect`. A registry rather than a closed set, so the schema does not restrict which relation is named: only that one is: the value must be non-empty |
| `id` | string | **yes** | Identifier of the referenced fact within the resolver's system |
| `resolver` | string | **yes** | Identifier of the party obliged to resolve `id` |
| `retention` | string | no | ISO 8601 duration the resolver undertakes to keep `id` resolvable. An undertaking only; nothing enforces it |
Expand Down
8 changes: 7 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,16 @@ checking any archived record, including these.
See that directory's README.
- `condition-appraisal/`: five signed Trust Records, each citing an independent check's
finding through a `references` entry with `rel: "condition-appraisal"`, against an
appraisal store committed beside them: confirmed, altered after issue, a fail that
appraisal store committed beside them: verified, altered after issue, a fail that
verifies exactly as the pass does, unresolvable, and an issuer whose key the relying
party does not hold. Every digest recomputes; see that directory's README and
`docs/references-registry.md`.
- `observed-effect/`: five signed Trust Records, each citing an observed mutation
interval through a `references` entry with `rel: "observed-effect"`, against an effect
store committed beside them: verified, altered after issue, an interval where the
observer and the observed party disagree that verifies exactly as the agreeing one
does, unresolvable, and an observer whose key the relying party does not hold. Every
digest recomputes; see that directory's README and `docs/references-registry.md`.
- `signature-encoding/`: three signed Trust Records that separate a canonically-encoded
embedded signature from two non-canonical respellings of the same 64 bytes. Proposed,
not accepted normative text (#247); carries a `scan_published_signatures.py` tool
Expand Down
Loading
Loading