hachure 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,8 +73,16 @@ Resource Shape envelope. Product-specific records use product-scoped namespaces
73
73
  Pre-1.0: the format uses hard breaking changes rather than compatibility aliases.
74
74
  No forward or backward compatibility guarantees are made across versions. Version
75
75
  bumps are reflected in `schemaVersion` (an integer field in TrustBundle, currently
76
- `3`) and in the status function version (a string exported by the reference
77
- implementation as `statusFunctionVersion`, currently `"1"`).
76
+ `4`) and in the status function version (a string exported by the reference
77
+ implementation as `statusFunctionVersion`, currently `"2"`).
78
+
79
+ Schema version `4` adds optional claim freshness fields (`expiresAt` /
80
+ `ttlSeconds`) and an optional invalidation event vocabulary (event `status:
81
+ "revoked"` and event `type: "invalidation"`). All additions are optional, so
82
+ every bundle valid at `schemaVersion` `3` remains valid; only the deriver
83
+ (`statusFunctionVersion` `2`) folds the new fields into a status. See
84
+ `status-function.md` and the `sf-expired-window` / `sf-revoked-event` /
85
+ `sf-no-freshness-fields` conformance vectors.
78
86
 
79
87
  ---
80
88
 
package/assurance.md CHANGED
@@ -170,10 +170,48 @@ the authenticated identity, and that certificate signs the record. The entire
170
170
  flow completes in the same session as the authorizing act; signing a record
171
171
  retroactively from a different session is not a conforming ceremony.
172
172
 
173
+ **Recommended mechanism for highest-assurance human ceremonies:** WebAuthn /
174
+ passkey with user-verification (UV=1). A device-local authenticator satisfies
175
+ three independent assurance legs simultaneously:
176
+
177
+ - **Presence** — the passkey gesture (biometric or PIN) proves the enrolled
178
+ device is physically present; biometric data never leaves the device.
179
+ - **Identity** — the hardware-backed assertion composes with the OIDC
180
+ certificate: the certificate binds the ephemeral key to the OIDC subject;
181
+ the passkey attests the device-local identity enrolled at registration time.
182
+ - **Engagement** (non-normative) — oversight metrics such as session duration
183
+ or interaction events can accompany the record as non-normative evidence;
184
+ they are not part of the conformance criteria.
185
+
186
+ Because the three legs are independent, losing one (e.g., no biometric sensor
187
+ on the device) does not forfeit the others. An OIDC-only ceremony without a
188
+ passkey assertion is still a conforming L1 ceremony; the passkey upgrade is
189
+ RECOMMENDED where the platform supports it.
190
+
173
191
  Design intent: friction lands only where authority is exercised. Routine
174
192
  read-only operations, status queries, and bundle consumption carry no signing
175
193
  requirement. The ceremony surfaces once, at the moment of consequence.
176
194
 
195
+ ## Collection channels and media evidence
196
+
197
+ Any collection channel defined in the reference implementation (see ADR 0004 in
198
+ the reference implementation) MAY attach media evidence — for example,
199
+ capture-provenance imagery conforming to C2PA — as ordinary evidence entries
200
+ with integrity anchors. Three rules govern such attachments:
201
+
202
+ 1. **Channels do not replace admissible testimony kinds.** A photo or video of
203
+ a signing gesture is attached evidence, not a substitute for an `exchange` or
204
+ `authorized-action` block. The admissible testimony kinds in ADR 0004 remain
205
+ the authoritative record; media is corroborating context.
206
+ 2. **Capture provenance determines evidence grade.** Media accompanied by a
207
+ valid C2PA manifest (or equivalent capture-provenance credential) is treated
208
+ as integrity-anchored evidence at the level its anchor supports. Media
209
+ without capture provenance is ordinary L0 evidence: present, but carrying no
210
+ additional assurance weight beyond the transport channel.
211
+ 3. **Integrity anchors are required for non-L0 media claims.** If a consumer
212
+ policy requires L1 or higher evidence grade for media, the media item MUST
213
+ carry an `IntegrityAnchor` referencing the capture-provenance credential.
214
+
177
215
  ---
178
216
 
179
217
  ## Non-goals
@@ -17,6 +17,9 @@ implementation derives the expected statuses.
17
17
  | `sf-disputed-blocking.json` | Verified event + blocking contradicting evidence → disputed | 2026-06-10T00:00:00.000Z |
18
18
  | `sf-authority-resolved.json` | Disputed claim resolved by authority-gated event | 2026-06-10T00:00:00.000Z |
19
19
  | `sf-reference-bundle-snapshot.json` | Full the reference bundle at fixed now — four claims | 2026-06-10T00:00:00.000Z |
20
+ | `sf-expired-window.json` | Claim-intrinsic validity window — stale past `expiresAt` and past `ttlSeconds` (schema 4) | 2026-06-10T00:00:00.000Z |
21
+ | `sf-revoked-event.json` | Explicit invalidation event (`status: revoked`, `type: invalidation`) → stale (schema 4) | 2026-06-10T00:00:00.000Z |
22
+ | `sf-no-freshness-fields.json` | No freshness fields present → derives unchanged from `statusFunctionVersion` `1` | 2026-06-10T00:00:00.000Z |
20
23
 
21
24
  ## Test vector format
22
25
 
@@ -0,0 +1,102 @@
1
+ {
2
+ "now": "2026-06-10T00:00:00.000Z",
3
+ "input": {
4
+ "schemaVersion": 4,
5
+ "source": "spec-conformance:expired-window",
6
+ "claims": [
7
+ {
8
+ "id": "claim.window.expires-at",
9
+ "subjectType": "data-field",
10
+ "subjectId": "dataset:session-token",
11
+ "surface": "public-data.status",
12
+ "claimType": "public-data-field",
13
+ "fieldOrBehavior": "tokenValid",
14
+ "value": true,
15
+ "createdAt": "2026-05-01T00:00:00.000Z",
16
+ "updatedAt": "2026-05-01T00:00:00.000Z",
17
+ "expiresAt": "2026-06-01T00:00:00.000Z",
18
+ "impactLevel": "high",
19
+ "verificationPolicyId": "policy.public-data-field.manual"
20
+ },
21
+ {
22
+ "id": "claim.window.ttl",
23
+ "subjectType": "data-field",
24
+ "subjectId": "dataset:cache-entry",
25
+ "surface": "public-data.status",
26
+ "claimType": "public-data-field",
27
+ "fieldOrBehavior": "cacheFresh",
28
+ "value": true,
29
+ "createdAt": "2026-06-09T00:00:00.000Z",
30
+ "updatedAt": "2026-06-09T00:00:00.000Z",
31
+ "ttlSeconds": 3600,
32
+ "impactLevel": "medium",
33
+ "verificationPolicyId": "policy.public-data-field.manual"
34
+ }
35
+ ],
36
+ "evidence": [
37
+ {
38
+ "id": "evidence.window.expires-at",
39
+ "claimId": "claim.window.expires-at",
40
+ "evidenceType": "human_attestation",
41
+ "method": "attestation",
42
+ "sourceRef": "operator review",
43
+ "excerptOrSummary": "Token issued and attested valid until 2026-06-01.",
44
+ "observedAt": "2026-05-01T00:00:00.000Z",
45
+ "collectedBy": "operator"
46
+ },
47
+ {
48
+ "id": "evidence.window.ttl",
49
+ "claimId": "claim.window.ttl",
50
+ "evidenceType": "human_attestation",
51
+ "method": "attestation",
52
+ "sourceRef": "operator review",
53
+ "excerptOrSummary": "Cache entry attested fresh at refresh time.",
54
+ "observedAt": "2026-06-09T00:00:00.000Z",
55
+ "collectedBy": "operator"
56
+ }
57
+ ],
58
+ "policies": [
59
+ {
60
+ "id": "policy.public-data-field.manual",
61
+ "claimType": "public-data-field",
62
+ "requiredEvidence": ["human_attestation"],
63
+ "requiredMethods": ["attestation"],
64
+ "requiresCorroboration": false,
65
+ "acceptanceCriteria": ["operator attestation"],
66
+ "reviewAuthority": "operator",
67
+ "validityRule": { "kind": "manual" },
68
+ "stalenessTriggers": ["validity window expires"],
69
+ "conflictRules": [],
70
+ "impactLevel": "high"
71
+ }
72
+ ],
73
+ "events": [
74
+ {
75
+ "id": "event.window.expires-at.verified",
76
+ "claimId": "claim.window.expires-at",
77
+ "status": "verified",
78
+ "actor": "operator",
79
+ "method": "attestation",
80
+ "evidenceIds": ["evidence.window.expires-at"],
81
+ "createdAt": "2026-05-01T00:00:00.000Z",
82
+ "verifiedAt": "2026-05-01T00:00:00.000Z"
83
+ },
84
+ {
85
+ "id": "event.window.ttl.verified",
86
+ "claimId": "claim.window.ttl",
87
+ "status": "verified",
88
+ "actor": "operator",
89
+ "method": "attestation",
90
+ "evidenceIds": ["evidence.window.ttl"],
91
+ "createdAt": "2026-06-09T00:00:00.000Z",
92
+ "verifiedAt": "2026-06-09T00:00:00.000Z"
93
+ }
94
+ ]
95
+ },
96
+ "expect": {
97
+ "statusByClaimId": {
98
+ "claim.window.expires-at": "stale",
99
+ "claim.window.ttl": "stale"
100
+ }
101
+ }
102
+ }
@@ -0,0 +1,68 @@
1
+ {
2
+ "now": "2026-06-10T00:00:00.000Z",
3
+ "input": {
4
+ "schemaVersion": 4,
5
+ "source": "spec-conformance:no-freshness-fields",
6
+ "claims": [
7
+ {
8
+ "id": "claim.api.rate-limit",
9
+ "subjectType": "api",
10
+ "subjectId": "public-api",
11
+ "surface": "api.guarantees",
12
+ "claimType": "software-evidence",
13
+ "fieldOrBehavior": "rate limit is enforced",
14
+ "value": "100 requests/minute",
15
+ "createdAt": "2026-04-25T00:00:00.000Z",
16
+ "updatedAt": "2026-04-25T00:05:00.000Z",
17
+ "impactLevel": "high",
18
+ "currentIntegrityRef": "commit:abc123",
19
+ "verificationPolicyId": "policy.software-evidence.commit"
20
+ }
21
+ ],
22
+ "evidence": [
23
+ {
24
+ "id": "evidence.api.rate-limit.test",
25
+ "claimId": "claim.api.rate-limit",
26
+ "evidenceType": "test_output",
27
+ "method": "validation",
28
+ "sourceRef": "ci:1847",
29
+ "excerptOrSummary": "Rate-limit tests passed.",
30
+ "observedAt": "2026-04-25T00:05:00.000Z",
31
+ "collectedBy": "ci",
32
+ "integrityRef": "commit:abc123"
33
+ }
34
+ ],
35
+ "policies": [
36
+ {
37
+ "id": "policy.software-evidence.commit",
38
+ "claimType": "software-evidence",
39
+ "requiredEvidence": ["test_output"],
40
+ "requiredMethods": ["validation"],
41
+ "requiresCorroboration": false,
42
+ "acceptanceCriteria": ["evidence check passes for the affected commit"],
43
+ "reviewAuthority": "repo policy",
44
+ "validityRule": { "kind": "commit" },
45
+ "stalenessTriggers": ["new commit touches the same surface"],
46
+ "conflictRules": [],
47
+ "impactLevel": "high"
48
+ }
49
+ ],
50
+ "events": [
51
+ {
52
+ "id": "event.api.rate-limit.verified",
53
+ "claimId": "claim.api.rate-limit",
54
+ "status": "verified",
55
+ "actor": "ci",
56
+ "method": "npm test",
57
+ "evidenceIds": ["evidence.api.rate-limit.test"],
58
+ "createdAt": "2026-04-25T00:05:00.000Z",
59
+ "verifiedAt": "2026-04-25T00:05:00.000Z"
60
+ }
61
+ ]
62
+ },
63
+ "expect": {
64
+ "statusByClaimId": {
65
+ "claim.api.rate-limit": "verified"
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,76 @@
1
+ {
2
+ "now": "2026-06-10T00:00:00.000Z",
3
+ "input": {
4
+ "schemaVersion": 4,
5
+ "source": "spec-conformance:revoked-event",
6
+ "claims": [
7
+ {
8
+ "id": "claim.access.grant",
9
+ "subjectType": "credential",
10
+ "subjectId": "access:deploy-key-7",
11
+ "surface": "access-control.grants",
12
+ "claimType": "software-evidence",
13
+ "fieldOrBehavior": "deployKeyValid",
14
+ "value": true,
15
+ "createdAt": "2026-05-01T00:00:00.000Z",
16
+ "updatedAt": "2026-06-05T00:00:00.000Z",
17
+ "impactLevel": "critical",
18
+ "verificationPolicyId": "policy.software-evidence.commit"
19
+ }
20
+ ],
21
+ "evidence": [
22
+ {
23
+ "id": "evidence.access.grant",
24
+ "claimId": "claim.access.grant",
25
+ "evidenceType": "test_output",
26
+ "method": "validation",
27
+ "sourceRef": "grant issuance log",
28
+ "excerptOrSummary": "Deploy key issued and validated.",
29
+ "observedAt": "2026-05-01T00:00:00.000Z",
30
+ "collectedBy": "access-service"
31
+ }
32
+ ],
33
+ "policies": [
34
+ {
35
+ "id": "policy.software-evidence.commit",
36
+ "claimType": "software-evidence",
37
+ "requiredEvidence": ["test_output"],
38
+ "requiredMethods": ["validation"],
39
+ "requiresCorroboration": false,
40
+ "acceptanceCriteria": ["evidence check passes"],
41
+ "reviewAuthority": "repo policy",
42
+ "validityRule": { "kind": "commit" },
43
+ "stalenessTriggers": ["revocation"],
44
+ "conflictRules": [],
45
+ "impactLevel": "critical"
46
+ }
47
+ ],
48
+ "events": [
49
+ {
50
+ "id": "event.access.grant.verified",
51
+ "claimId": "claim.access.grant",
52
+ "status": "verified",
53
+ "actor": "access-service",
54
+ "method": "validation",
55
+ "evidenceIds": ["evidence.access.grant"],
56
+ "createdAt": "2026-05-01T00:00:00.000Z",
57
+ "verifiedAt": "2026-05-01T00:00:00.000Z"
58
+ },
59
+ {
60
+ "id": "event.access.grant.revoked",
61
+ "claimId": "claim.access.grant",
62
+ "status": "revoked",
63
+ "type": "invalidation",
64
+ "actor": "security-officer",
65
+ "method": "manual-revocation",
66
+ "evidenceIds": [],
67
+ "createdAt": "2026-06-05T00:00:00.000Z"
68
+ }
69
+ ]
70
+ },
71
+ "expect": {
72
+ "statusByClaimId": {
73
+ "claim.access.grant": "stale"
74
+ }
75
+ }
76
+ }
package/index.mjs CHANGED
@@ -24,7 +24,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
24
24
  // Any implementation claiming conformance at this version must produce the
25
25
  // same status outputs as the test vectors for all cases in conformance/.
26
26
  // ---------------------------------------------------------------------------
27
- export const statusFunctionVersion = '1';
27
+ export const statusFunctionVersion = '2';
28
28
 
29
29
  // ---------------------------------------------------------------------------
30
30
  // Schemas — Map of record name (filename without .schema.json) → parsed JSON.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hachure",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Hachure \u2014 canonical distribution of the open trust format: normative JSON schemas, conformance test vectors, and spec constants.",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
@@ -12,9 +12,19 @@
12
12
  "claimType": { "type": "string" },
13
13
  "fieldOrBehavior": { "type": "string" },
14
14
  "value": {},
15
- "status": { "enum": ["unknown", "proposed", "assumed", "verified", "stale", "disputed", "superseded", "rejected"] },
15
+ "status": { "enum": ["unknown", "proposed", "assumed", "verified", "stale", "disputed", "superseded", "rejected", "revoked"] },
16
16
  "createdAt": { "type": "string", "format": "date-time" },
17
17
  "updatedAt": { "type": "string", "format": "date-time" },
18
+ "expiresAt": {
19
+ "description": "Optional absolute validity window: ISO-8601 instant after which the claim is no longer fresh. Canonical when both expiresAt and ttlSeconds are present (expiresAt wins). Behaviour is derived by Surface, not by this schema.",
20
+ "type": "string",
21
+ "format": "date-time"
22
+ },
23
+ "ttlSeconds": {
24
+ "description": "Optional relative validity window in seconds, resolved by the deriver against the governing verification event's verifiedAt (falling back to createdAt). If expiresAt is also present, expiresAt takes precedence. Behaviour is derived by Surface, not by this schema.",
25
+ "type": "integer",
26
+ "minimum": 0
27
+ },
18
28
  "impactLevel": { "enum": ["low", "medium", "high", "critical"] },
19
29
  "materiality": { "enum": ["low", "medium", "high"] },
20
30
  "currentIntegrityRef": { "type": "string" },
@@ -24,9 +24,34 @@
24
24
  "collectedBy": { "type": "string" },
25
25
  "integrityRef": { "type": "string" },
26
26
  "integrityAnchor": { "$ref": "#/$defs/integrityAnchor" },
27
+ "passing": {
28
+ "type": "boolean",
29
+ "description": "Whether the check that produced this evidence passed. Omitted when the evidence is not the output of a pass/fail check."
30
+ },
31
+ "blocking": {
32
+ "type": "boolean",
33
+ "description": "Whether a non-passing result should block (e.g. trigger disputed status under a policy conflict rule)."
34
+ },
35
+ "execution": {
36
+ "$ref": "#/$defs/execution",
37
+ "description": "How the evidence was produced: the command or tool invocation, exit code, and duration — making artifacts self-describing without requiring the producer's adapter config."
38
+ },
27
39
  "metadata": { "type": "object" }
28
40
  },
29
41
  "$defs": {
42
+ "execution": {
43
+ "type": "object",
44
+ "required": ["runner", "label"],
45
+ "properties": {
46
+ "runner": { "enum": ["bash", "mcp"] },
47
+ "label": { "type": "string" },
48
+ "exitCode": { "type": "integer" },
49
+ "isError": { "type": "boolean" },
50
+ "durationMs": { "type": "number" },
51
+ "metadata": { "type": "object" }
52
+ },
53
+ "additionalProperties": false
54
+ },
30
55
  "integrityAnchor": {
31
56
  "type": "object",
32
57
  "required": ["id", "kind", "algorithm", "value", "sourceRef"],
@@ -26,7 +26,7 @@
26
26
  "additionalProperties": false,
27
27
  "properties": {
28
28
  "value": {},
29
- "status": { "enum": ["unknown", "proposed", "assumed", "verified", "stale", "disputed", "superseded", "rejected"] }
29
+ "status": { "enum": ["unknown", "proposed", "assumed", "verified", "stale", "disputed", "superseded", "rejected", "revoked"] }
30
30
  }
31
31
  },
32
32
  "inputSnapshot": {
@@ -37,7 +37,7 @@
37
37
  "additionalProperties": false,
38
38
  "properties": {
39
39
  "claimId": { "type": "string" },
40
- "status": { "enum": ["unknown", "proposed", "assumed", "verified", "stale", "disputed", "superseded", "rejected"] }
40
+ "status": { "enum": ["unknown", "proposed", "assumed", "verified", "stale", "disputed", "superseded", "rejected", "revoked"] }
41
41
  }
42
42
  }
43
43
  },
@@ -6,7 +6,7 @@
6
6
  "additionalProperties": false,
7
7
  "required": ["schemaVersion", "source", "claims", "evidence", "policies", "events"],
8
8
  "properties": {
9
- "schemaVersion": { "enum": [2, 3] },
9
+ "schemaVersion": { "enum": [2, 3, 4] },
10
10
  "source": { "type": "string" },
11
11
  "claims": {
12
12
  "type": "array",
@@ -7,7 +7,11 @@
7
7
  "properties": {
8
8
  "id": { "type": "string" },
9
9
  "claimId": { "type": "string" },
10
- "status": { "enum": ["unknown", "proposed", "assumed", "verified", "stale", "disputed", "superseded", "rejected"] },
10
+ "status": { "enum": ["unknown", "proposed", "assumed", "verified", "stale", "disputed", "superseded", "rejected", "revoked"] },
11
+ "type": {
12
+ "description": "Optional event classifier. 'invalidation' marks a ledger line that explicitly revokes/stales a previously-good claim (use with status 'revoked' or 'stale'). 'verification' is the default assertion of a status. Surface treats an invalidation event as terminal for the claim.",
13
+ "enum": ["verification", "invalidation"]
14
+ },
11
15
  "actor": { "type": "string" },
12
16
  "method": { "type": "string" },
13
17
  "evidenceIds": { "type": "array", "items": { "type": "string" } },
@@ -1,7 +1,7 @@
1
1
  # Status Derivation — Specification
2
2
 
3
3
  **Function:** `status = f(claim, evidence, events, policy, authorityTrace, now)`
4
- **Version constant:** `statusFunctionVersion` (currently `"1"`)
4
+ **Version constant:** `statusFunctionVersion` (currently `"2"`)
5
5
  **Source of truth:** `src/status.ts` in `@kontourai/surface`
6
6
 
7
7
  ---
@@ -110,8 +110,17 @@ Filter all events to those matching `claim.id`, sort most-recent-first by `creat
110
110
  Let `latestEvent` be the first (most recent) event.
111
111
 
112
112
  If `latestEvent` exists and its `status` is one of `"rejected"`, `"disputed"`,
113
- `"superseded"`, or `"stale"` — return that status. These are terminal: they are
114
- not overridden by evidence inspection.
113
+ `"superseded"`, `"stale"`, or `"revoked"` — return that status. These are
114
+ terminal: they are not overridden by evidence inspection. An event with
115
+ `type: "invalidation"` is always terminal in this step regardless of its
116
+ `status` (it asserts the claim is no longer good); `"revoked"` derives `stale`
117
+ (treated as an explicit, event-driven staleness) unless a later verification
118
+ event re-asserts the claim.
119
+
120
+ > **Schema-version note (`statusFunctionVersion` `"2"`, `schemaVersion` `4`):**
121
+ > the `"revoked"` event status and `type: "invalidation"` classifier are new.
122
+ > Bundles that never use them derive identically to `statusFunctionVersion`
123
+ > `"1"`.
115
124
 
116
125
  ### Step 3: Assumed from event
117
126
 
@@ -123,7 +132,21 @@ If `latestEvent` exists and `latestEvent.status === "verified"`:
123
132
 
124
133
  #### 4a. Staleness check
125
134
 
126
- If a policy is present, check whether the verification is stale based on
135
+ **Claim-intrinsic validity window (`statusFunctionVersion` `"2"`, schema `4`).**
136
+ Before consulting the policy validity rule, check the claim's own validity
137
+ window, which overrides policy timing when present:
138
+
139
+ - If `claim.expiresAt` is set, the claim is stale when `now > Date.parse(claim.expiresAt)`.
140
+ - Else if `claim.ttlSeconds` is set, compute
141
+ `expiry = Date.parse(latestEvent.verifiedAt ?? latestEvent.createdAt) + claim.ttlSeconds * 1000`;
142
+ the claim is stale when `now > expiry`.
143
+ - **Precedence:** `expiresAt` wins over `ttlSeconds` when both are present.
144
+ When neither is present, fall through to the policy validity rule below
145
+ (identical to `statusFunctionVersion` `"1"`).
146
+
147
+ If the claim-intrinsic window marks the claim stale: return **`stale`**.
148
+
149
+ Otherwise, if a policy is present, check whether the verification is stale based on
127
150
  `policy.validityRule.kind`:
128
151
 
129
152
  - **`"commit"`** — stale if `claim.currentIntegrityRef` is set AND none of the
@@ -30,7 +30,7 @@ Producers supporting this profile expose a verification endpoint at a well-known
30
30
  path on the same host that issued the bundle:
31
31
 
32
32
  ```
33
- GET https://<producer-host>/.well-known/hachure/verify?ref=<integrityRef>[&ref=...]
33
+ GET https://<producer-host>/.well-known/hachure/verify?ref=<ref>[&ref=...]
34
34
  ```
35
35
 
36
36
  Multiple refs may be passed as repeated `ref` query parameters. For large sets,
@@ -40,17 +40,39 @@ a POST variant is also allowed:
40
40
  POST https://<producer-host>/.well-known/hachure/verify
41
41
  Content-Type: application/json
42
42
 
43
- { "refs": ["<integrityRef>", ...] }
43
+ { "refs": ["<ref>", ...] }
44
44
  ```
45
45
 
46
- `integrityRef` is the integrity anchor value carried on a claim or bundle
47
- (`claim.integrityRef` or a bundle-level `integrityAnchor.value`).
48
-
49
46
  Producers MAY require authentication before serving a response. The response
50
47
  semantics defined in this profile are unchanged by the presence or absence of
51
48
  authentication; the receiver simply may not be able to reach the endpoint
52
49
  without valid credentials.
53
50
 
51
+ ### Ref resolution semantics
52
+
53
+ A `ref` value in the request MAY be any of the following:
54
+
55
+ - **(a) A claim `id`** — the stable identifier on a claim object (`claim.id`).
56
+ - **(b) A claim's current integrity ref** — the `currentIntegrityRef` string field
57
+ on a claim (`claim.currentIntegrityRef`, per `schemas/claim.schema.json`).
58
+ Note: the analogous full anchor object is `claim.currentIntegrityAnchor`; when
59
+ resolving by integrity value, match against `currentIntegrityRef` or
60
+ `currentIntegrityAnchor.value`.
61
+ - **(c) A bundle-level integrity anchor value** — the `.value` field of an
62
+ `integrityAnchor` object carried on an `authorityTrace` or `evidence` record
63
+ within a bundle (`schemas/trust-bundle.schema.json` `$defs/integrityAnchor`
64
+ → `value`).
65
+
66
+ Producers MUST support lookup by (a) and (b). Producers MAY support lookup by (c).
67
+ Receivers SHOULD prefer (a) for stability; (b) and (c) are useful when only an
68
+ integrity value is available from a prior bundle snapshot.
69
+
70
+ **Multi-match:** a single ref value MAY match more than one claim (for example,
71
+ two claims that share the same `currentIntegrityRef`). When a ref matches multiple
72
+ claims, the response includes ALL matching claims. A ref is reported as unknown
73
+ (in `unknownRefs`) only when it matches nothing — zero claims, evidence records,
74
+ or authority traces.
75
+
54
76
  ---
55
77
 
56
78
  ## Response shape
@@ -60,8 +82,9 @@ scoped to the records matching the requested refs, plus a `metadata` extension
60
82
  block. The bundle carries:
61
83
 
62
84
  - **`source`** — the producer identifier, matching `source` in the original bundle.
63
- - **`claims`** — all claims whose `integrityRef` was among the requested refs.
64
- Each claim is returned in its current form as the producer knows it.
85
+ - **`claims`** — all claims that matched the requested refs (by id, currentIntegrityRef,
86
+ or anchor value as described above). When a ref matched multiple claims, all are
87
+ included. Each claim is returned in its current form as the producer knows it.
65
88
  - **`evidence`** and **`events`** — the delta since issuance where the producer
66
89
  can supply it: new evidence items, new verification events, revocation events,
67
90
  dispute events. Producers that do not track a delta MAY return the full current
@@ -69,20 +92,35 @@ block. The bundle carries:
69
92
  means it was never present in the original bundle.
70
93
  - **`authorityTrace`** — current authority traces relevant to the matched claims,
71
94
  including any that have been revoked since the original bundle was issued.
72
- - **`metadata`** — a free-form object on the bundle. This profile defines four
95
+ - **`metadata`** — a free-form object on the bundle. This profile defines five
73
96
  reserved keys within `metadata`:
74
97
 
75
98
  | Key | Type | Required | Meaning |
76
99
  |---|---|---|---|
77
100
  | `respondedAt` | ISO 8601 string | yes | The timestamp at which the producer assembled this response. |
78
- | `statusFunctionVersion` | string | yes | The status function version active at the producer at response time. |
101
+ | `statusFunctionVersion` | string | yes | The status function version the producer's current evaluation pipeline uses — i.e. the value the implementation exports (e.g. `statusFunctionVersion` from the reference implementation), independent of any version values stored inside served bundles. |
79
102
  | `requestedRefs` | string[] | yes | The full list of refs from the request, in order. |
80
- | `unknownRefs` | string[] | yes | Refs from the request that the producer does not recognise. Must be present even if empty. |
103
+ | `unknownRefs` | string[] | yes | Refs from the request that matched nothing. Must be present even if empty. |
104
+ | `evaluatedAt` | `"response"` \| `"generation"` | no | When omitted or `"response"`, the producer evaluated claims dynamically at response time. `"generation"` indicates the producer cannot evaluate dynamically (e.g. static file serving) and the `statusFunctionVersion` reflects the version recorded when the bundle was originally generated. |
81
105
 
82
106
  Unknown refs are reported in `unknownRefs` honestly. A producer MUST NOT silently
83
107
  omit a ref it does not recognise; it MUST include it in `unknownRefs` so the
84
108
  receiver can distinguish "no changes" from "not found."
85
109
 
110
+ **`statusFunctionVersion` source:** producers report the version their current
111
+ evaluation pipeline uses — the value their implementation exports at response time.
112
+ This is independent of any `statusFunctionVersion` fields stored inside the claims
113
+ or events within served bundles. Producers that serve static bundles without
114
+ dynamic evaluation MUST report the version that was active at generation time and
115
+ MUST set `evaluatedAt: "generation"` in metadata.
116
+
117
+ **`integrityAnchor` shape:** integrity anchor objects in responses follow the
118
+ `integrityAnchor` definition in `schemas/claim.schema.json` (`$defs/integrityAnchor`)
119
+ and `schemas/trust-bundle.schema.json` (`$defs/integrityAnchor`). Required fields
120
+ are `id`, `kind`, `algorithm`, `value`, and `sourceRef`. On claims the anchor is
121
+ carried as `currentIntegrityAnchor`; on authority-trace and evidence records it
122
+ is carried as `integrityAnchor`.
123
+
86
124
  ---
87
125
 
88
126
  ## Assurance levels
@@ -150,3 +188,19 @@ unsigned responses are valid.
150
188
  specified here.
151
189
  - **Key management.** Public-key distribution, rotation, and revocation are out
152
190
  of scope. See the ADR 0004 backlog note above.
191
+
192
+ ---
193
+
194
+ ## Changelog
195
+
196
+ **Amended after first independent implementation — hachure.org site function.**
197
+ Five ambiguities resolved: (1) corrected claim integrity field names to schema
198
+ truth (`currentIntegrityRef`, `currentIntegrityAnchor`) and noted authority-trace
199
+ and evidence fields (`integrityRef`, `integrityAnchor`); (2) defined ref resolution
200
+ semantics — producers MUST support id and currentIntegrityRef lookups, MAY support
201
+ anchor-value lookups; (3) clarified multi-match: a ref matching multiple claims
202
+ returns all of them, unknown only when nothing matches; (4) clarified
203
+ `statusFunctionVersion` source as the producer's current pipeline export, added
204
+ optional `evaluatedAt` metadata key (`"response"` | `"generation"`) for static
205
+ producers; (5) added normative cross-reference to `integrityAnchor` schema
206
+ definition in `schemas/claim.schema.json` and `schemas/trust-bundle.schema.json`.