Skip to content

feat(spec): add the optional origin block, and stop software-only meaning two things - #135

Merged
imran-siddique merged 1 commit into
mainfrom
feat/origin-block
Aug 9, 2026
Merged

feat(spec): add the optional origin block, and stop software-only meaning two things#135
imran-siddique merged 1 commit into
mainfrom
feat/origin-block

Conversation

@imran-siddique

Copy link
Copy Markdown
Member

Prerequisite for the third-party control-plane adapters (response plan list 2, item 3). Ships as 0.7.0.

The problem

runtime.platform: "software-only" is the honest value in two situations that are not the same claim:

  • a dev-mode record, unattested because nothing attested it;
  • a record transcribed from another vendor's control plane, unattested because the party asserting the evidence also wrote the log.

A consumer weighing a record cannot tell those apart from platform alone. That gap is about to become load-bearing: an adapter that emits a record indistinguishable from a dev-mode one destroys the argument the adapters exist to make.

The block

"origin": {
  "kind": "third-party-control-plane",
  "producer": "vendor-gateway/2.1",
  "source_event_id": "evt-7f3a",
  "ingested_at": 1760000000
}

Optional and additive, under the same v0.2 profile URI. Same shape of change as delegation in 0.4.0: existing records stay valid, and absence means self, so every hardware profile in the spec is unaffected.

kind is a closed set (self, third-party-control-plane, log-import) rather than free text, because the entire value of the field is that a verifier can key on it.

The rule that makes it worth having

A record whose kind is not self MUST carry runtime.platform: "software-only".

An importer holding someone else's log has no quote to present, so a hardware platform value there is untrue rather than stronger. It is also precisely what an adapter produces by starting from a hardware example and editing the fields it understood, which is why it is a MUST.

Enforced in two places so they cannot drift, and tested from both directions in each:

  • models.py, a cross-field model_validator;
  • schema/trace-claim.json and the packaged schema, via if/then, so a validator that never loads the Python rejects it too.

What it does not do

It launders assurance in neither direction. Naming your producer does not make unattested evidence attested. A record with a verified hardware quote is what it is whether or not it says origin: self.

Also here

docs/schema.md still described transparency as required, with an empty string for unanchored records. 0.5.1 made it optional below Level 2 and rejects "".

Checks

202 passed, 1 skipped. ruff clean, mypy clean. 11 new tests across the model and the schema.

Note for downstreams: TrustRecord is extra="forbid", so a verifier older than 0.7.0 rejects a record carrying origin, the same one-directional break cmcp took with gateway.attestation_evidence. Producers should not emit origin until their consumers are on 0.7.0.

🤖 Generated with Claude Code

…ning two things

runtime.platform: 'software-only' is the honest value in two situations that are
not the same claim. A dev-mode record is unattested because nothing attested it.
A record transcribed from a third-party control plane is unattested because the
party asserting the evidence also wrote the log. A consumer weighing a record
could not tell those apart from platform alone, and adapters over other vendors'
governance output are about to make that gap load-bearing.

origin is {kind, producer, source_event_id, ingested_at}, optional and additive,
under the same v0.2 profile URI. Same shape of change as delegation in 0.4.0:
existing records stay valid and absence means self. kind is a closed set rather
than free text, because the whole value of the field is that a verifier can key
on it.

The rule that makes it worth having: a record whose kind is not self MUST carry
runtime.platform 'software-only'. An importer holding someone else's log has no
quote to present, so a hardware platform there is untrue rather than stronger,
and it is exactly what an adapter produces by starting from a hardware example
and editing the fields it understood. Enforced in the model and in the JSON
schema with if/then, so a validator that never loads the Python rejects it too,
and tested from both directions in both places.

The block launders assurance in neither direction. Naming a producer does not
make unattested evidence attested, and a verified hardware record is what it is
whether or not it says origin: self.

Also fixed: docs/schema.md still described transparency as required with an
empty string for unanchored records. 0.5.1 made it optional below Level 2 and
rejects the empty string.

202 passed, ruff and mypy clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@imran-siddique
imran-siddique merged commit 1794b61 into main Aug 9, 2026
8 checks passed
@imran-siddique
imran-siddique deleted the feat/origin-block branch August 9, 2026 00:34
lywinged added a commit to lywinged/trace-spec that referenced this pull request Aug 10, 2026
Upstream merged agentrust-io#148, the last large piece this fork was carrying, which the
maintainer rebased and opened himself under this fork's authorship. With agentrust-io#122,
agentrust-io#125, agentrust-io#126, agentrust-io#136 and agentrust-io#137 already merged, most of the thirty-three commits here
described work that now lives upstream with better provenance than this fork can
give it: a PR number and a maintainer's review.

`git rebase upstream/main` was tried first and abandoned. It stopped on the oldest
commit in the set, a schema-and-version alignment whose content upstream has since
taken, superseded and released three times over. Replaying thirty-three commits
against fifteen of upstream's resolves early commits into shapes that no longer
mean anything.

So: main reset to upstream/main, fork-only material re-applied. The old history is
tagged `archive/pre-576507b` and pushed rather than discarded.

What is held here, and why it is held:

- agentrust-io#117 gap disclosure: design note, two normative drafts, 18 vectors, generator
- agentrust-io#116 verifier compatibility: 8 vectors, generator, normative draft
- the normative crosswalk, mapping every RFC 2119 statement to whom it binds
- `docs/conformance-method.md`, `coverage-report/` (historical), DECISIONS.md
- the independent signature path and the package-consistency test

Each is an unaccepted proposal or a method write-up, not unfinished work.

Four files needed a real merge, and not in the same direction. `models.py` and
`__init__.py` are upstream's plus this fork's two profile constants, because
upstream had moved on with `origin` (agentrust-io#135) and the `declared` enforcement mode
(agentrust-io#143) and a wholesale copy would have dropped both. `sign.py` and `test_sign.py`
are this fork's `accepted_profiles` version, which supersedes the minimal cutover
check upstream took from agentrust-io#125 -- a supersession CLAUDE.md predicted when agentrust-io#125 was
offered. All four merged cleanly three-way against a817621, the last commit the
two histories agree on.

The crosswalk guard earned its place in the same run: upstream added two normative
statements this fork had never seen, and `test_normative_crosswalk.py` failed until
both had rows. A source-derived inventory noticing its subject moved is the
property that document exists to have.

432 passed, 1 skipped. ruff and mypy clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: lywinged <48041247+lywinged@users.noreply.github.com>
lywinged added a commit to lywinged/trace-spec that referenced this pull request Aug 24, 2026
Upstream merged agentrust-io#148, the last large piece this fork was carrying, which the
maintainer rebased and opened himself under this fork's authorship. With agentrust-io#122,
described work that now lives upstream with better provenance than this fork can
give it: a PR number and a maintainer's review.

`git rebase upstream/main` was tried first and abandoned. It stopped on the oldest
commit in the set, a schema-and-version alignment whose content upstream has since
taken, superseded and released three times over. Replaying thirty-three commits
against fifteen of upstream's resolves early commits into shapes that no longer
mean anything.

So: main reset to upstream/main, fork-only material re-applied. The old history is
tagged `archive/pre-576507b` and pushed rather than discarded.

What is held here, and why it is held:

- agentrust-io#117 gap disclosure: design note, two normative drafts, 18 vectors, generator
- agentrust-io#116 verifier compatibility: 8 vectors, generator, normative draft
- the normative crosswalk, mapping every RFC 2119 statement to whom it binds
- `docs/conformance-method.md`, `coverage-report/` (historical), DECISIONS.md
- the independent signature path and the package-consistency test

Each is an unaccepted proposal or a method write-up, not unfinished work.

Four files needed a real merge, and not in the same direction. `models.py` and
`__init__.py` are upstream's plus this fork's two profile constants, because
upstream had moved on with `origin` (agentrust-io#135) and the `declared` enforcement mode
(agentrust-io#143) and a wholesale copy would have dropped both. `sign.py` and `test_sign.py`
are this fork's `accepted_profiles` version, which supersedes the minimal cutover
check upstream took from agentrust-io#125 -- a supersession CLAUDE.md predicted when agentrust-io#125 was
offered. All four merged cleanly three-way against a817621, the last commit the
two histories agree on.

The crosswalk guard earned its place in the same run: upstream added two normative
statements this fork had never seen, and `test_normative_crosswalk.py` failed until
both had rows. A source-derived inventory noticing its subject moved is the
property that document exists to have.

432 passed, 1 skipped. ruff and mypy clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: lywinged <48041247+lywinged@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant