FROZEN — immutable history. Superseding/current decisions live in
docs/decisions/. Do not edit.
ADR 0002: Trust Bundle#
Status: accepted
Date: 2026-06-10
Context#
TrustInput (src/types.ts) is a self-contained package of trust state: schemaVersion, source, claims[], evidence[], policies[], events[], plus optional identityLinks, claimGroups, and authorityTrace. One type currently plays three roles:
- Function parameter. It is the input to
buildTrustReport()andderiveTrustSnapshot()— the origin of the name. - Producer projection. Survey's
buildSurveyTrustInput()walks the source → extraction → candidate → review chain and packs it into one. From Survey's perspective the same type is an output. - Cross-product interchange. Veritas embeds a
surface.inputblock inside its evidence artifacts, and Flow integrates by queryingsurface.input.claims[]rather than parsing Veritas internals.
The name describes only the first role, from the callee's perspective. A concept named by its position in someone else's function call cannot be first-class: the name stops making sense the moment you step outside that function. Veritas's product-language rule already forbids leading with TrustInput in first-contact docs for this reason.
ADR 0001 separately introduced the product term "Claim Package" for "portable package of claims, evidence, policies, traces, and related context" — the same concept under a second name, with the code name as a third. The contents are also broader than claims: a package carries evidence, policies, and verification events.
Finally, nothing specifies what happens when packages from multiple producers describe the same subject. One package is a wire format; a primitive needs merge semantics.
The product line is pre-stability. Hard renames with migration documentation are preferred over compatibility aliases.
Decision#
One name: Trust Bundle#
Adopt Trust Bundle as both the product term and the code name (TrustBundle). This supersedes the "Claim Package" entry in ADR 0001's vocabulary. The name works from every perspective: Survey emits a bundle, Surface evaluates one, Veritas embeds one, Flow queries one. It also joins the established Trust* family (Trust Snapshot, Trust Report, Trust Panel).
Plain-language definition:
A Trust Bundle is a portable, point-in-time package of trust state from a single producer — claims, the evidence and verification events behind them, and the policies the producer played by — packed so it can cross a product boundary without the receiver needing access to the producer's internals.
The source field identifies the producer. A bundle is the supply side of the ledger; an Inquiry (ADR 0003) is the demand side.
Resource envelope#
Trust Bundles adopt the Kontour Resource Shape (apiVersion, kind, metadata, spec, optional proof) with kind: TrustBundle and apiVersion: hachure.org/v1. The current interface contents become spec; integrity anchors belong in proof. This makes the bundle a peer of other trust-bearing Kontour records instead of a loose TypeScript interface.
Merge semantics#
Bundles from multiple producers fold into one ledger:
identityLinksdeclare co-referent subjects across bundles (existingbuildIdentityIndexmachinery).When bundles assert conflicting values for the same subject and field (under a policy with
incompatibleValues), the conflict is surfaced as acontradictiontransparency gap; both claims are retained — merging is never last-write-wins and never silently smooths a disagreement. Conflicting statuses (policyincompatibleStatuses) or an authority-weighted resolution event derive adisputedstatus. (Implementation: value-conflicts emit the contradiction gap viaderiveIncompatibilityTransparencyGaps;disputedis reserved for status/event conflicts instatus.ts. The gap is the value-conflict signal — a stronger property than overwriting status.)Correction (2026-07-01): the sentence above — "Conflicting statuses (policy
incompatibleStatuses) ... derive adisputedstatus" — does not match this codebase's actual behavior and is corrected here rather than silently left to mislead readers. TracingderiveConflictTransparencyGaps(src/conflict-derivation.ts, lines 34-68) showsincompatibleStatusesis handled identically toincompatibleValues: both produce acontradictiontransparency gap, never adisputedstatus by themselves.TrustReport.summary.disputedClaims(src/report.ts, line 178) is populated purely by scanningclaim.status === "disputed"from each claim's own single-claim status fold — nothing in the cross-producer conflict path sets a claim's status todisputed. A claim'sstatusbecomesdisputedonly through the existing single-claim mechanisms instatus-function.md: blocking non-passing evidence, a terminal event withstatus: "disputed", or an authority-gated resolution overridden by newer blocking evidence. Seehachure-org/spec'smerge.md§7c for the corrected, normative text and the full reasoning trail.Losing evidence is never deleted. Dispute resolution is an authority-weighted, append-only decision event, per ADR 0003.
API.
mergeBundles(bundles: TrustBundle[]): TrustBundlefolds bundles into one ledger: union byid(first-wins [updated 2026-07-01: content-identity dedup with canonical-serialization lexicographic tie-break; see ADR 0005]; throws on a claim-id collision with differing content — never corrupts a claim),sourcebecomesmerged:<a>+<b>, identity union-find dedupes links.mergeBundlesDetailedreturns the same plus acollisions[]report. The merged bundle is fed tobuildTrustReport;surface report --inputis repeatable and merges before reporting (single-input path is byte-identical).
Hard migration#
Rename TrustInput → TrustBundle everywhere, with no deprecated alias. Companion renames follow the same rule (for example buildSurveyTrustInput → buildSurveyTrustBundle, Veritas's surface.input block → trust.bundle). Ship a migration document instead of a compatibility shim.
Migration Map#
| Earlier name | New name | Note |
|---|---|---|
TrustInput |
TrustBundle |
Hard rename, no alias. |
| Claim Package (ADR 0001 vocabulary) | Trust Bundle | Superseded; "Claim Package" should not appear in new docs. |
buildSurveyTrustInput |
buildSurveyTrustBundle |
Survey projection entry point. |
surface.input (Veritas artifact block) |
trust.bundle |
Downstream consumers query trust.bundle.claims[]. |
validateTrustInput |
validateTrustBundle |
Validation entry point. |
Adapter.adapt(record): TrustInput |
Adapter.adapt(record): TrustBundle |
Adapter contract. |
Consequences#
- One concept, one name, in product language and code. ADR 0001's vocabulary table is amended by this ADR.
- Veritas may drop its dual-emission fallback (
metadata.authorityTracefor older Surface runtimes) in the same migration; pre-stability, the fallback is maintenance without benefit. - Bundles already carry
events[], so they are forward-compatible with the event-stream-plus-projection model (Console's contract and ADR 0003's status function): a bundle is exactly the input to the status fold, handed across a boundary when no shared stream exists. - Cross-producer conflict handling now has a specified behavior — value-conflicts surface a
contradictiongap (both claims retained), status/event conflicts derivedisputed[corrected 2026-07-01 — see the correction note above: conflicts surface as contradiction transparency gaps; disputed arises only from the single-claim fold]; never last-write-wins — that implementations and tests can be held to (seesrc/merge.ts,tests/merge.test.ts).