Skip to content

Proof statement v1 - #236

Merged
docushell-dev merged 22 commits into
mainfrom
proof-statement-v1
Aug 9, 2026
Merged

Proof statement v1#236
docushell-dev merged 22 commits into
mainfrom
proof-statement-v1

Conversation

@docushell-dev

Copy link
Copy Markdown
Collaborator

No description provided.

docushell-dev and others added 21 commits August 9, 2026 15:00
The spec is a draft for ruling. Three decisions in section 1 block everything
below them: the artifact shape, the predicateType URI namespace, and the
representation_sha256 conflict. Each carries a recommendation so the tradeoffs
are concrete rather than abstract. Nothing is implemented.

Scope of the eventual change is deliberately narrow: it changes artifact shape
and adds corroboration, and it changes no verification semantics.

Worth recording why the shape work is not a foreign idea. v0.6.0 already
implements artifact-type dispatch in crates/ethos-cli/src/grounding.rs, and the
grounding validation report already requires a self-describing artifact_type
field. Five other output artifacts have neither. This finishes what WP-0..WP-3
started.

Also adds scripts/hooks/commit-msg. The v0.6.0 branch hit two distinct DCO
failures in one afternoon: a commit with no sign-off, and commits whose sign-off
sat outside the trailer block because a blank line separated it from
Co-Authored-By. Git only parses trailers in the final paragraph, so the second
form looks correct and fails. The hook uses git interpret-trailers --parse, the
same mechanism as .github/scripts/check_dco.py, so local and CI agree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
1.1 Artifact shape: in-toto Statement. An established shape with existing
tooling and growing auditor familiarity, for no invented format. Not DSSE as
the on-disk artifact, because it base64-encodes the payload and readable
evidence is half of what Ethos offers.

1.2 Namespace: an Ethos-owned domain, not DocuShell. The product argument is
independence, and a predicateType reading docushell.com erodes it every time
someone opens an artifact. The specific domain is not yet registered; that now
blocks emitting any artifact, since the string is permanent.

1.3 Source identity: representation_sha256 stays authoritative, matching the
standing ruling in docs/v0-6-0-release.md section 8. On the Grounding JSON path
a foreign parser produced the representation and Ethos never touched the source
PDF, so naming the PDF as what was read would assert something Ethos cannot
know.

1.4 Subject shape: both, representation first. This revises the earlier draft,
which said subject holds source documents only. subject is an array, so the
honest answer and the useful one are not in conflict: subject[0] is always the
representation Ethos read, subject[1] is the source document only when the
binding is real. Consumers must not assume subject[0] is the PDF, which is a
documentation obligation recorded in section 1.4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
Base URI is https://docushell.com/ethos/, shape <base>/<predicate>/v<n> for
all seven types. Chosen over a dedicated Ethos domain because a purchase plus a
perpetual renewal obligation is a poor trade against a weak branding signal.
Independence is carried by the Apache-2.0 licence, offline key-free operation,
and byte-reproducible results; a hostname affects none of them. The /ethos/ path
segment scopes the namespace so a later move stays a rename with the old string
kept as an alias.

Records that v<n> versions the predicate schema and never the product, so
grounding/v1 stays v1 across Ethos 0.6, 0.7, and 1.0.

Adds docs/proof-statement-v1-implementation-plan.md: seven work packages in
dependency order, each with a file-by-file touch list and its own acceptance
evidence. Touch points were verified against main rather than assumed. Two
findings shaped the sequencing:

- verification_report_json_bytes in cmd/verify.rs is the only *_json_bytes
  serialiser in the CLI. The other five artifacts serialise inline at their
  write_output call sites, so WP-6 needs a shared serialiser introduced first
  rather than a single edit.

- The config hash mechanism the attestation block needs already exists as
  sha256_hex(c14n(config)) at cmd/verify.rs:128, so WP-3 adds no new machinery.

The load-bearing rule is one statement builder in ethos-core with no command
hand-rolling a statement. That is the same constraint invariant 3 already places
on c14n, and it is the specific failure mode observed in Signet, which builds
four different signable shapes across two files with no schema anywhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
Corroboration is dropped. It was designed from a threat model rather than from
a user, and the case for it does not survive contact with the questions it
raises: no external user has asked for it, two parsers sharing an upstream share
failure modes so their agreement proves little, it doubles parse cost against a
20k docs/day design target, and nobody has measured the divergence rate on real
documents. A near-zero rate makes it not worth building; a high rate makes it
noise reviewers learn to ignore. Those need opposite decisions and the number
does not exist.

A compensating control already ships: DocuShell shows the reviewer a rendered
crop of the actual page region, and a human reading real pixels catches parser
errors directly. Section 7 records that revisiting requires a measured
divergence number, not an argument.

sources[] goes with it. It existed to carry multiple grounding sources for
comparison. Keeping it would freeze an array at length one, never exercised,
which is precisely the unvalidated shape that forces a v2 later. The singular
grounding field is unchanged.

evidence_tier survives. It is independent of corroboration and useful alone:
one deterministic enum for how strong a match was, so a consumer reads one field
instead of interpreting a capability matrix.

Six work packages remain, six predicate types, no new capability. The release is
now plumbing with a clear payoff: self-describing artifacts that name what
produced them, which is what the 7-year retention and replay commitments in the
DocuShell workbench architecture need in order to mean anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
The gate was left orphaned by the CI cleanup: not in ci.yml, not in
release-gates, failing when run by hand. Two failures, and one of them was
self-inflicted.

test_make_and_ci_run_the_offline_guard asserted that ci.yml contains the literal
string "pip install -r examples/citation-emission/requirements-frameworks.txt".
Removing that CI step broke the guard that asserts the step exists. This is the
same prose-assertion class as the nine milestone-D guards deleted in the cleanup
for exactly this reason, and it was reintroduced by the same commit that applied
the rule elsewhere.

That test is replaced by test_make_target_never_leaks_secrets_or_publishes,
which keeps the half that guards content — no API keys, no publish commands in
the target — and drops the half that asserts CI wiring.

The second failure was environmental: the byte-identical-twice test needs
langchain and llama_index, installed by the pip step that was removed. That test
is real and worth keeping. It runs each framework example twice and asserts
identical output with no API keys, which demonstrates the determinism claim in
the exact place a newcomer looks.

The job is restored to CI with its pip install. These examples are the adoption
path, and broken examples fail for strangers evaluating Ethos rather than for
us, so rotting between publishes is the wrong failure mode to accept.

All four tests pass locally with the frameworks installed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
Promotes section 1.5 from a provisional call to a ruling.

document.ethos.json and chunks.jsonl are not wrapped in statements. A statement
means "X asserts P about Y", and a document graph is not an assertion about the
document; it is the document re-expressed. Wrapping it would read as "here is a
claim about invoice.pdf, and the claim is invoice.pdf". Wrap everything and the
word statement stops distinguishing anything.

Three supporting reasons recorded: chunks.jsonl is streaming NDJSON that
wrapping would bloat or break, both files are consumed by other tools including
DocuShell's retrieval path, and neither needs the wrapper for provenance since
both already carry a document fingerprint, profile hash, and config hash per
SPEC.md.

Same instinct as the II.1 rule in DocuShell's workbench architecture applied one
level down: the thing being judged must not look like the judgment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
A developer landing in this repo would have been pointed the wrong way.
docs/v0-6-0-release.md section 9 listed attestation as a non-goal for v0.6.0,
which is now WP-3, a core deliverable. Both v0.6.0 documents described only the
Grounding JSON scope. Neither said where to start.

Adds to the implementation plan a "Start here" section (reading order,
prerequisites including the commit-msg hook, the build and test loop, rules for
claiming work, and what counts as a finished task) and a task board of 32
numbered tasks with dependencies and acceptance criteria.

Adds WP-0, which keeps the multi-format path open without adding multi-format
support. Three tasks: a test locking the geometry-free text path, bbox becoming
Option in the trait while the schema stays strict, and recording the five gates
from section 10.1. Multi-format itself is out of scope with a written trigger,
because no DocuShell workflow needs it — the workbench scope contract puts
non-PDF formats out of Part I and names no Part II trigger. Docling supporting
every format is a fact about Docling, not a requirement on Ethos.

One finding shaped WP-0's scope: GroundingCell already carries row, col,
row_span, and col_span. Row and column addressing is R1C1, so an XLSX sheet maps
onto the existing table model with no new locator concept. The only blocker is
bbox on cells.

Supersession notes added to both v0.6.0 documents, with the superseded non-goals
struck through rather than deleted so the scope change stays visible. Execution
status gains a dated entry. CONTRIBUTING gains a section naming the two rules
easiest to break by accident: goldens move alone, and format questions are not
decided in a PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
Removes the status badge, the beta status block, the "Current evaluation
support" framing, and every "evaluation" qualifier. Users should be able to try
Ethos without wording that asks them to wait.

The change worth explaining is the "Blocked" column. Five rows read Blocked —
Windows artifacts, hosted API, bundled PDFium, benchmark claims, production
positioning. That is internal release vocabulary meaning "not yet approved for
publication". To someone evaluating Ethos it reads as broken or gatekept. The
replacement states what is not supported as a fact about capability: "No OCR.
Fails with ocr_required rather than guessing."

Nothing here claims production readiness. claims_gate.py bans exactly that
family of overclaim — production-ready, release-ready, fastest, world-class,
state-of-the-art — and it passes, so removing hesitation did not buy an
overclaim in exchange.

Adds a statement rather than only cutting: no speed, footprint, or
parser-quality comparison is published because no benchmark has been run whose
numbers we would defend. Saying why there are no numbers is stronger than
staying quiet about it.

public-boundary-claims.json is updated to match. Five pinned README strings
carried the retired beta wording; six replacements carry the same boundaries in
the new voice, including the honest limits. Eleven install and PDFium claims are
unchanged. CHANGELOG records three boundary-exception entries, which is what
that gate exists to force.

The README also now points at the v0.6.0 format plan and task board instead of
naming the Grounding JSON prep document as "the v0.6.0 plan".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
docs/v0-6-0-release.md section 10.1 established by source audit that an evidence
ref of {element_id, expected_text} with no page locator and no bbox reaches
AnchorStatus::Bound at AnchorLevel::Text with no capability limit. Nothing
tested it. A refactor could have closed that path and no one would have found
out until someone tried a flow document years later.

Four tests, no production code touched. They assert the four steps rather than
only the outcome: page_locator_required is false via the element_id disjunct,
requires_bbox excludes AnchorLevel::Text, resolve_page returns
PageCheck::NotChecked rather than NotFound, and the anchor is Bound with an
empty capability-limit list.

The end-to-end test asserts checks.bbox is NotChecked and not
CapabilityLimited. The ref never asked for geometry, so nothing was downgraded
and the caller is owed no warning. Emitting CapabilityLimited there would put a
spurious limitation on every geometry-free verification, which is the kind of
noise that teaches consumers to ignore capability limits.

Verified by mutation rather than assumed. Changing the resolve_page fallthrough
to NotFound fails two of the four; adding AnchorLevel::Text to requires_bbox
fails two. Both mutations were reverted and the diff is additions only.

This is WP-0 task 0.1. It keeps the multi-format door open; it does not open it.
Multi-format support remains out of scope per docs/proof-statement-v1.md
section 7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
…P-0 task 0.2)

GroundingElement, GroundingSpan, GroundingTable, and GroundingCell now carry
Option<[i64; 4]> instead of [i64; 4].

docs/v0-6-0-release.md section 10.1 framed the choice as an unread in-memory
sentinel versus Option and a breaking change to the published 0.5.0 baseline.
v0.6.0 is already a breaking change to that baseline — WP-3 adds a required
attestation field to VerificationReport — so the honest option costs nothing
extra now and would cost a second breaking release later.

The schema does not move, which is the WP-0 constraint. ethos.grounding.v1 still
requires bbox on element, span, table, and cell; media_type is still
const application/pdf; the positive-area check still stands. The Rust type can
now express absence, and the wire contract still refuses it. Verified: no schema
or golden file changed.

Read sites fail closed rather than treating absence as a wildcard. An element
with no declared box contains nothing, so containment filters exclude it and a
bbox query over a geometry-free source resolves to NotFound. The adjacency join
declines when either element lacks geometry, matching the posture it already
takes for CoordinateOrigin::Unknown. The arithmetic helpers — contains_bbox,
bbox_area, union_bbox, element_bboxes_are_adjacent — keep taking [i64; 4]; the
Option handling belongs at the call sites, not in the geometry.

Two tests cover the new semantics, because behaviour that only a comment
describes is behaviour that decays. Both were verified by mutation: turning
is_some_and into map_or(true) inside resolve_bbox, so absence reads as a
wildcard, fails absent_element_geometry_never_satisfies_a_bbox_query. The first
attempt at that mutation hit the wrong call site and the test passed, which is
the reason to run the mutation rather than assume the assertion is wired to
anything.

406 tests pass, up from 404. fmt, clippy, verify dependency boundary, and the
minimal grounding feature build are all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
…sk 0.3)

The analysis lives in docs/v0-6-0-release.md section 10.1, which is a release
record and will eventually be superseded. The durable engineering content moves
into docs/bring-your-own-parser.md, where someone implementing a GroundingSource
already reads to find out what is required of them.

Two audiences, one section. Adapter authors learn that geometry is required,
that a zero-area sentinel is rejected outright, and that Option<[i64; 4]> on the
trait is not an invitation to send None — the wire schema still requires the
field. Maintainers get the five gates with their locations, and the reason the
constraint is narrower than it looks: the verifier already binds text with no
geometry, and the tests that keep that true are named so nobody deletes them
during a cleanup.

Also records why the sequencing is DOCX, XLSX, PPTX rather than the intuitive
reverse, including the XLSX measurement that rules out computing column
geometry — a 12.5% swing between Calibri 11 and Verdana 11 on the same nominal
column, driven by system font metrics that ADR-0003 does not cover.

Completes WP-0. Multi-format support is still out of scope; the trigger to
revisit is in docs/proof-statement-v1.md section 7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
New crates/ethos-core/src/statement.rs. The single place a Statement is
constructed, for the same reason ethos-core owns the one c14n implementation:
a wire format assembled independently in several places drifts, and the drift
stays invisible until two producers disagree. Signet is the cautionary case,
building four different signable shapes across two files with no schema
anywhere.

The _type string was verified against the in-toto v1 specification before being
frozen: https://in-toto.io/Statement/v1. Subject entries are ResourceDescriptors
with a required digest map, which is why digest is a BTreeMap rather than a
struct — key order is canonical without relying on serde field order, and the
algorithm lives in the key, so the value is bare hex with no sha256: prefix.

Statement::new encodes the section 1.4 subject ruling in the type signature
rather than leaving it to convention. It takes the representation by value and
the source as an Option, so subject[0] is always what Ethos actually read and an
empty subject array — which in-toto forbids — is unrepresentable. On the
Grounding JSON path a foreign parser produced the representation and Ethos never
touched the PDF, so passing None is the honest answer and inventing a source
subject is the failure this shape prevents.

Gated behind the `full` feature rather than `verify-types` as the plan drafted,
because statement_bytes routes through c14n, which needs serde_json. That makes
invariant 4 safer rather than weaker: ethos-verify building with only the
grounding feature cannot see this module at all. Verified — grounding-only,
verify-types, and ethos-verify all still compile, and the dependency boundary
check passes.

Seven unit tests and one doctest. The load-bearing one is
predicate_survives_wrapping_byte_for_byte, which asserts c14n of the unwrapped
predicate equals c14n of the input. WP-2's payload-equivalence test depends on
that property holding, so it is proven here before anything relies on it.
wire_field_names_match_the_in_toto_spec guards the _type and predicateType
spellings, since renaming either silently produces an artifact no in-toto tool
recognizes.

414 tests pass, up from 406. No schema or golden file changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
verification_report_json_bytes now wraps the report in a Statement before
canonicalizing. Both emit paths — single report and batch NDJSON — flow through
that one function, so this is a single behavioural edit.

Two decisions WP-2 forced that the plan had not anticipated.

subject[0] digests the input file's bytes, not the report's document_fingerprint.
The first attempt used the fingerprint and it broke
foreign_source_without_fingerprint_blocks_fingerprint_pinned_citations: a
foreign source that declares no fingerprint is a supported, tested case, and
failing closed there turned a working verification into internal_error. That is
a semantics change, which ground rule 1 forbids. Hashing the input bytes is also
simply more correct — in-toto matches subjects by digest, so the value has to be
something a consumer holding the same file can compute, and the document
fingerprint is the canonical-graph identity, not derivable from the file. For
the Grounding JSON path the two agree anyway, since representation_sha256 hashes
exactly those bytes.

subject[1] is deliberately absent. The only source binding available is
GroundingJsonSource::source_sha256, documented as the producer-declared PDF
hash, and section 5.1 already says a source-hash match proves only that the
mapper declared the hash of the PDF you supplied. Recording a declaration in a
field that in-toto matches by digest would invite a consumer to resolve it and
conclude Ethos verified against bytes it never saw. Section 1.4's second subject
stays unimplemented pending a ruling on whether an unauthenticated declaration
belongs there at all.

The goldens do not move, which is better than the plan's approach.
verify_alpha_demo_report_predicates_match_goldens asserts the emitted predicate
against the existing pre-0.6 goldens, so payload equivalence is proven
permanently rather than at one moment: every byte the verifier produces is
unchanged, only nested. Regenerating the goldens would have destroyed exactly
the evidence needed to show that. It also means determinism CI never goes blind
during this migration. A semantic change now fails against the goldens; a
wrapper change fails in verify_emits_a_proof_statement, and keeping those
separate is the point.

Test migration is mechanical: a verify_report helper unwraps the predicate for
assertions, and the ethos grounding check tests are deliberately untouched
because that command is not wrapped until WP-5.

415 tests pass. No golden, schema, or example file changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
Records the ruling of 2026-08-09 and makes section 1.4's "only when the binding
is real" concrete: real means Ethos read the bytes itself, not that a producer
declared a hash.

ethos verify emits no subject[1]. On the Grounding JSON path the only available
source binding is GroundingJsonSource::source_sha256, documented as the
producer-declared PDF hash, and section 5.1 already says a source-hash match
proves only that the mapper declared the hash of the PDF you supplied.

An in-toto subject means "this statement is about these artifacts" and tools
match them by digest. A reviewer holding loan-file.pdf who sees its hash in
subject[1] reasonably concludes Ethos checked their file. It did not; it read a
JSON claiming to come from it. Editing that one line leaves the statement
pointing at the right PDF and still reporting everything grounded. The gap is
invisible at exactly the moment it matters.

--crop-source-pdf is the one case where subject[1] would be honest, because
Ethos loads and validates the real bytes there. If it is ever built, that is the
only permitted source.

Also records why subject[0] digests the input file bytes rather than
document_fingerprint: consumers match subjects by digest, so the value must be
computable from the file they hold.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
Every VerificationReport now carries a required attestation block naming what
produced the verdict. This promotes Part B of the citation-emission plan from
proposal to shipped, and makes it non-optional rather than hardening-gated.

The block is smaller than the spec drafted, deliberately. Section 4 listed four
fields; two of them would have been duplicates. The config hash is already
top-level as verification_config_sha256 and the source fingerprint is already
top-level as document_fingerprint, so nesting copies of both is bloat, not
attestation. The `replay` field is also dropped: a constant string repeated in
every artifact is documentation misfiled as data, and the recipe is already
present as data — verifier version, config hash, claims hash, document
fingerprint. Part B's own text invited dropping it.

What ships is what was actually missing: the verifier crate name and version,
and a hash binding the report to its exact claims input.

claims_sha256 is passed into verify_claims rather than computed there, because
ethos-verify builds against ethos-core with only the grounding and verify-types
features under invariant 4 — it has no c14n and no serde_json. That is the same
reason config_sha256 has always been a parameter, so the shape is symmetric
rather than novel. The verifier identity does come from ethos-verify's own env!
macros, so a library caller gets the same attestation as a CLI user.

Fixtures move, and this time legitimately: the report gained a field. All eight
goldens are purely additive. The three schema examples are +9/-1 each, the minus
being one comma; an earlier regeneration pass rewrote them in c14n key order and
expanded compact arrays, which was correct content but an unreviewable diff, so
the block is text-inserted instead and the hand-maintained formatting survives.

Two tests, one mutation-checked. report_attests_the_verifier_config_and_claims
compares the attested version against the crate's own CARGO_PKG_VERSION rather
than a literal, so a bump that fails to flow through is caught; setting the
version to 0.5.0 by hand fails it. claims_hash_covers_the_claims_and_not_their_
packaging proves different claims produce different hashes.

417 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
Adds EvidenceTier and an optional evidence_tier on every Check. Per check rather
than per report, ruled 2026-08-09: a report legitimately mixes tiers — one quote
bound to an element, one presence claim scoped to a page — and a single
top-level value would have to aggregate, baking a judgment into the artifact
that a consumer cannot undo.

Worth recording honestly: this is a convenience projection, not a new fact. A
consumer could derive it from match_method plus the claim's citation, both
already on every check. The AetherProof idea it comes from does not fully
transfer — model_root_type was load-bearing precisely because a receipt could
not otherwise reveal whether the root was a weights hash or a typed name. It
earns its place anyway, because an evidence product should not make every
consumer write the same derivation and get it subtly wrong.

Five variants, not the four the spec drafted. A table cell is a first-class v1
claim kind, and folding it into element_scoped would understate a precisely
bound cell while a four-value enum would have forced that lie.

The tier is set on FoundTarget where the target resolves, not re-derived from
the citation afterwards, so it cannot drift from the locator precedence it
describes. Two consequences follow: a quote grounded across two adjacent
elements is element_scoped, because two elements joined is still element
precision, and a check whose outcome was decided by a missing span or table
capability, or an unknown coordinate origin, reports capability_limited rather
than a precision it did not achieve. A check that resolved nothing carries no
tier at all.

Fixtures are purely additive, +13 -0. Goldens regenerate cleanly since they are
already in c14n order; the hand-formatted schema examples take a line insertion
after each match_method so their ordering and compact arrays survive.

Two tests. each_check_states_how_precisely_it_bound_evidence asserts three tiers
in one report, since a derivation that only ever sees one tier is untested;
mislabelling the table cell as element_scoped fails it. unresolved_checks_state_
no_tier holds the fail-closed edge.

419 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
Introduces statement_json_bytes in main.rs as the single path from an Ethos
verdict to bytes on disk, and moves all five emitters onto it: verify,
grounding check, evidence anchor, security report, and crop_element. verify had
its own copy from WP-2 and now shares this one, so the statement shape cannot
drift between producers — the same reason ethos-core owns one c14n.

WP-5 is four migrations, not five. answer-release is not emitted by the CLI at
all: it is an app-layer envelope consumers build through
derive_app_answer_release_decision. There is no producer to change, so there is
nothing to wrap and no predicate type worth reserving on speculation.

The grounding validation payload keeps its artifact_type field. Retiring it in
favour of predicateType would break payload equivalence for no benefit today,
and ADR-0016 freezes it as an input contract regardless. Redundant, and the
redundancy is cheaper than the change.

Not migrated, deliberately: ethos doc parse and ethos rag chunk. A document
graph and a chunk stream are representations, not assertions about anything, and
statements are for verdicts (section 1.5). A test asserts chunks.jsonl still has
no _type.

Zero fixture changes. Every one of the four migrations was a pure re-wrap, so no
golden, schema, or example file moved — the same property WP-2 established, now
demonstrated across the whole surface.

every_verdict_command_emits_its_own_predicate_type covers all four commands in
one test, because the failure mode is a single command quietly keeping its own
serialisation, and that drift only surfaces when two producers disagree. Verified
by mutation: restoring the old inline c14n path in security_report fails it.

Also drops three now-unused EthosError imports the shared helper made redundant.

420 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
Adds docs/CLAIMS.md in four parts: what a verdict proves with the mechanism and
the code it lives in, what it does not prove with what would close each gap, a
conservative regulatory mapping carrying a residual-gap column, and the
paragraph to paste into a security questionnaire.

The does-not-prove table is the point of the document. Eight rows, including the
ones easiest to leave out: Ethos verifies a claim against the representation and
not the representation against the document, so a parser error that both drafts
and verifies consistently is invisible; the attestation names the crate version
and not the binary, so a hostile operator can lie in it; subject[0] is what
Ethos read, which on the Grounding JSON path is a parser's output and not the
PDF; a paraphrase of a true fact is a mismatch; and no speed, footprint, or
parser-quality property is claimed because no benchmark has been run whose
numbers we would defend.

CLAIMS.md is added to the claims-gate surfaces. A claims document that is not
itself claim-gated is the obvious gap.

The payload-versus-envelope field table goes into proof-statement-v1.md section
2 rather than a separate contract document, because that table was the only
thing the separate document would have held. It names the attack it exists to
prevent: subject[].name is a caller-chosen label, in-toto matches artifacts by
digest, and a consumer keying a release decision on the name can be handed a
file called loan-file.pdf that is not the loan file.

README gains a "What comes out" section showing the real emitted shape, verified
against actual CLI output rather than written from the spec, plus the upgrade
line: the report you already parse is the predicate, and jq .predicate returns
the previous shape byte for byte.

Completes WP-6 and the release. 420 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
The spec still read as a proposal after being fully implemented. Status was
"ruled, not implemented", the predicate table listed answer-release as a pending
migration when it has no producer, "all six" counted a predicate that does not
exist, and a note told the reader to verify the in-toto _type revision before
freezing a string that was frozen five commits ago. A reference document that
describes a plan rather than the thing is worse than no document, because it
reads as authoritative.

Section 3 now records what exists: five predicates, each with the command that
emits it, plus the two decisions a reader will otherwise re-litigate — why
answer-release has no type, and why grounding-validation keeps its artifact_type
field inside the predicate.

Section 6 stops restating the proof tiers. They are defined once, for readers,
in docs/CLAIMS.md section 5; two copies of a claim is how the two copies start
disagreeing. What the format owes the tiers stays here: signatures attach to the
named-but-empty wrapper layer, the predicate stays byte-identical when one is
added, and DSSE is the signing envelope and deliberately not the artifact.

The changelog told the v0.6.0 story across four separate entry blocks written at
different times. Consolidated into one section that reads in the order a reader
needs it — what changed, how to upgrade, what was added inside the report, and
what the CI scoping did — with every boundary-exception marker preserved so the
release-boundary gate still passes.

420 tests pass. No code changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
The gate parses a verification report at the top level, which moved under
predicate when verify started emitting statements. Caught before pushing by
running every CI job locally; the gate is not in the ordinary cargo test loop,
which is exactly why WP-5 did not surface it.

Worth recording what did not break: the example scripts themselves are
unchanged. They key on the verifier's exit code, and the wrapper does not touch
the exit-code contract. An integrator who copied those scripts needs no changes,
which is the compatibility property that actually matters here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
…ields

Three consumer surfaces read the verification report and none were covered by
cargo test, so WP-3 through WP-5 left them broken. All three surfaced only when
running every CI job locally before pushing.

python/ethos_pdf/emit.py validates reports against strict field allowlists.
attestation and evidence_tier are added as allowed rather than required,
deliberately: the wrapper drives a caller-provided ethos binary that may predate
0.6, and requiring the fields would break those callers with a confusing error
for no benefit.

The npm package's generated type declarations were stale against the schema, and
the TypeScript consumer fixture needed attestation now that the type requires it.

The RAG framework examples gate parsed the report at the top level. Worth noting
what did not break: the example scripts themselves are unchanged, because they
key on the verifier's exit code and the wrapper does not touch the exit-code
contract. An integrator who copied those scripts needs no changes.

All seventeen CI checks pass locally: fmt, clippy, 420 tests, validator ceiling,
fixtures, layout evaluator, Python surface, npm, three schema validators, RAG
examples, verify portability, dependency boundary, minimal grounding build, and
both dogfood directions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
@docushell-dev docushell-dev self-assigned this Aug 9, 2026
@docushell-dev docushell-dev added the contract-change Changes a public artifact shape, schema, or determinism contract label Aug 9, 2026
The determinism workflow was skipped on this PR because its cross-platform job
gates on a contract-change label that did not exist in the repository. Creating
the label and applying it fired the job, which failed on all three platforms at
"verification report repeated-byte and golden equality" — not a platform
difference, but a consumer nothing in cargo test reaches.

examples/verify/check_verify_alpha.py compares CLI output against the goldens
directly. It now unwraps the predicate, and a load_predicate helper carries the
reason: goldens stay in the pre-0.6 report shape on purpose, because comparing
the predicate against them is what proves the wrapper is a pure re-wrap rather
than a reshaping.

A sweep for every file reading all_evidence_grounded or checks found three more:
check_rendered_crops.py, test_citation_emission_v1_contract.py, and
test_trust_benchmark_corpus.py. All three now read the predicate.

The citation-emission gate additionally asserts that ci.yml contains
"make citation-emission-v1-contract", which the CI scoping removed. Restored to
the test job rather than weakening the assertion: seven of its eight tests are
real, it runs in under a second, and it guards the documented pointer-first
integration path and its runnable examples.

Verified: make verify-alpha, citation-emission-v1-contract,
trust-benchmark-corpus, rag-framework-examples, layout-evaluator-alpha,
validator-ceiling-check, cargo test, Python surface, npm, schema validation,
fmt, and clippy all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: docushell-dev <doqshell@gmail.com>
@docushell-dev
docushell-dev merged commit 3777cc9 into main Aug 9, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contract-change Changes a public artifact shape, schema, or determinism contract

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant