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 +10 -2
- package/assurance.md +38 -0
- package/conformance/README.md +3 -0
- package/conformance/sf-expired-window.json +102 -0
- package/conformance/sf-no-freshness-fields.json +68 -0
- package/conformance/sf-revoked-event.json +76 -0
- package/index.mjs +1 -1
- package/package.json +1 -1
- package/schemas/claim.schema.json +11 -1
- package/schemas/evidence.schema.json +25 -0
- package/schemas/inquiry-record.schema.json +2 -2
- package/schemas/trust-bundle.schema.json +1 -1
- package/schemas/verification-event.schema.json +5 -1
- package/status-function.md +27 -4
- package/verification-endpoint.md +64 -10
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
|
-
`
|
|
77
|
-
implementation as `statusFunctionVersion`, currently `"
|
|
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
|
package/conformance/README.md
CHANGED
|
@@ -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 = '
|
|
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
|
@@ -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" } },
|
package/status-function.md
CHANGED
|
@@ -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 `"
|
|
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 `"
|
|
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
|
-
|
|
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
|
package/verification-endpoint.md
CHANGED
|
@@ -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=<
|
|
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": ["<
|
|
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
|
|
64
|
-
|
|
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
|
|
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
|
|
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
|
|
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`.
|