hachure 0.4.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
 
@@ -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.4.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" },
@@ -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