hachure 0.1.0 → 0.2.2
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 +18 -6
- package/index.mjs +10 -10
- package/interop-in-toto.md +1 -1
- package/package.json +9 -4
- package/status-function.md +5 -5
- package/verification-endpoint.md +152 -0
package/README.md
CHANGED
|
@@ -14,17 +14,17 @@ npm i -D hachure
|
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
The package ships the normative JSON schemas, conformance test vectors, and the
|
|
17
|
-
`
|
|
17
|
+
`statusFunctionVersion` constant that ties implementations to a specific
|
|
18
18
|
algorithm revision.
|
|
19
19
|
|
|
20
20
|
**Claiming conformance:** run the test vectors from `testVectors` against your
|
|
21
21
|
implementation. For each vector, call your status-derivation function with
|
|
22
22
|
`vector.input` and `vector.now`, then assert that the derived status for every
|
|
23
23
|
claim ID matches `vector.expect.statusByClaimId`. Passing all vectors for a given
|
|
24
|
-
|
|
24
|
+
status function version is the bar for a conforming implementation.
|
|
25
25
|
|
|
26
26
|
```js
|
|
27
|
-
import { testVectors,
|
|
27
|
+
import { testVectors, statusFunctionVersion } from 'hachure';
|
|
28
28
|
|
|
29
29
|
for (const { name, vector } of testVectors) {
|
|
30
30
|
const results = deriveStatuses(vector.input, new Date(vector.now));
|
|
@@ -36,7 +36,6 @@ for (const { name, vector } of testVectors) {
|
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
-
|
|
40
39
|
## What this is
|
|
41
40
|
|
|
42
41
|
Hachure is an open format for portable trust state. It defines how claims about
|
|
@@ -74,8 +73,8 @@ Resource Shape envelope. Product-specific records use product-scoped namespaces
|
|
|
74
73
|
Pre-1.0: the format uses hard breaking changes rather than compatibility aliases.
|
|
75
74
|
No forward or backward compatibility guarantees are made across versions. Version
|
|
76
75
|
bumps are reflected in `schemaVersion` (an integer field in TrustBundle, currently
|
|
77
|
-
`3`) and in
|
|
78
|
-
implementation
|
|
76
|
+
`3`) and in the status function version (a string exported by the reference
|
|
77
|
+
implementation as `statusFunctionVersion`, currently `"1"`).
|
|
79
78
|
|
|
80
79
|
---
|
|
81
80
|
|
|
@@ -227,6 +226,19 @@ validates TrustBundle input against these schemas via `validateTrustBundle()`.
|
|
|
227
226
|
|
|
228
227
|
---
|
|
229
228
|
|
|
229
|
+
## Profiles
|
|
230
|
+
|
|
231
|
+
The core specification covers record shapes and status semantics. Profiles are
|
|
232
|
+
optional, independently adoptable conventions for interop and transport. Adopting
|
|
233
|
+
a profile requires no changes to core record shapes or the status function.
|
|
234
|
+
|
|
235
|
+
| Profile | File | What it covers |
|
|
236
|
+
|---|---|---|
|
|
237
|
+
| in-toto interop | [interop-in-toto.md](interop-in-toto.md) | Wrapping a TrustBundle as a signed in-toto Statement v1 / DSSE envelope. |
|
|
238
|
+
| Verification endpoint | [verification-endpoint.md](verification-endpoint.md) | Producer-served HTTP endpoint for receivers to fetch post-export event deltas. |
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
230
242
|
## Out of scope: future extension profiles
|
|
231
243
|
|
|
232
244
|
The following producer domains are explicitly out of scope for this core specification.
|
package/index.mjs
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* hachure — canonical npm distribution of the Hachure trust format spec.
|
|
3
3
|
*
|
|
4
4
|
* Exports:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* schemas
|
|
9
|
-
*
|
|
10
|
-
* testVectors
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
5
|
+
* statusFunctionVersion — spec-side declaration of the current status
|
|
6
|
+
* derivation algorithm version. Reference
|
|
7
|
+
* implementations must export a matching value.
|
|
8
|
+
* schemas — Map<recordName, parsedSchemaObject> for every
|
|
9
|
+
* normative schema shipped with this package.
|
|
10
|
+
* testVectors — Array<{name, vector}> of all conformance test
|
|
11
|
+
* vectors. Each vector has `input`, `expect`, and
|
|
12
|
+
* `now` fields; run them against your implementation
|
|
13
|
+
* to claim conformance.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { readFileSync, readdirSync } from 'node:fs';
|
|
@@ -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
|
|
27
|
+
export const statusFunctionVersion = '1';
|
|
28
28
|
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
// Schemas — Map of record name (filename without .schema.json) → parsed JSON.
|
package/interop-in-toto.md
CHANGED
|
@@ -124,7 +124,7 @@ A Kontour `TrustBundle` adds **living status** on top:
|
|
|
124
124
|
|---|---|
|
|
125
125
|
| Status is sealed at signing time. | Status is recomputed from events at query time: `f(claim, events, policy, now)`. |
|
|
126
126
|
| Tamper-evident; content cannot change. | Append-only; new events and evidence accumulate. |
|
|
127
|
-
| Verifier trusts the signer's identity. | Verifier trusts the derivation algorithm (`
|
|
127
|
+
| Verifier trusts the signer's identity. | Verifier trusts the derivation algorithm (`statusFunctionVersion`). |
|
|
128
128
|
| Useful for supply-chain audits and legal holds. | Useful for operational dashboards, gates, and consumer inquiries. |
|
|
129
129
|
|
|
130
130
|
The two are complementary: embed the bundle in an in-toto envelope to
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hachure",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Hachure
|
|
3
|
+
"version": "0.2.2",
|
|
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",
|
|
7
7
|
"exports": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"index.mjs",
|
|
16
16
|
"README.md",
|
|
17
17
|
"status-function.md",
|
|
18
|
-
"interop-in-toto.md"
|
|
18
|
+
"interop-in-toto.md",
|
|
19
|
+
"verification-endpoint.md"
|
|
19
20
|
],
|
|
20
21
|
"scripts": {
|
|
21
22
|
"test": "node --test test/index.test.mjs"
|
|
@@ -30,5 +31,9 @@
|
|
|
30
31
|
"license": "MIT",
|
|
31
32
|
"publishConfig": {
|
|
32
33
|
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/hachure-org/spec.git"
|
|
33
38
|
}
|
|
34
|
-
}
|
|
39
|
+
}
|
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:** `
|
|
4
|
+
**Version constant:** `statusFunctionVersion` (currently `"1"`)
|
|
5
5
|
**Source of truth:** `src/status.ts` in `@kontourai/surface`
|
|
6
6
|
|
|
7
7
|
---
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
## Principle
|
|
10
10
|
|
|
11
11
|
Claim status is a pure, versioned, deterministic function (ADR 0003 §7). Given the
|
|
12
|
-
same inputs and the same
|
|
12
|
+
same inputs and the same status function version, any conforming implementation
|
|
13
13
|
must derive the same status. There is no stored status field that overrides
|
|
14
14
|
computation; the derived status is always recomputed from the input bundle at
|
|
15
15
|
evaluation time.
|
|
@@ -20,7 +20,7 @@ no clock-tick events and no background expiry.
|
|
|
20
20
|
|
|
21
21
|
Reproducibility guarantee: if two independent implementations receive the same
|
|
22
22
|
`(claim, evidence, events, policies, authorityTrace, now)` and the same
|
|
23
|
-
|
|
23
|
+
status function version, they must return the same `TrustStatus`.
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
@@ -214,12 +214,12 @@ From weakest to strongest: `unknown` < `rejected` < `superseded` < `disputed` <
|
|
|
214
214
|
|
|
215
215
|
## Versioning
|
|
216
216
|
|
|
217
|
-
`
|
|
217
|
+
`statusFunctionVersion` is a string exported by `@kontourai/surface`. It is
|
|
218
218
|
incremented when the algorithm changes in a way that could produce different outputs
|
|
219
219
|
for the same inputs. `InquiryRecord.statusFunctionVersion` captures which version
|
|
220
220
|
was active at resolution time, enabling re-evaluation when the algorithm version
|
|
221
221
|
changes.
|
|
222
222
|
|
|
223
|
-
Conforming implementations must declare which
|
|
223
|
+
Conforming implementations must declare which status function version value they
|
|
224
224
|
implement. Implementations claiming version `"1"` must satisfy all conformance
|
|
225
225
|
cases in `spec/conformance/`.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Verification Endpoint — Extension Profile
|
|
2
|
+
|
|
3
|
+
**Profile type:** OPTIONAL extension
|
|
4
|
+
**Status:** draft
|
|
5
|
+
**Namespace:** `hachure.org/v1`
|
|
6
|
+
**Depends on:** core record shapes, [status-function.md](status-function.md), [interop-in-toto.md](interop-in-toto.md)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
A TrustBundle is a point-in-time snapshot. Once exported, it carries no channel
|
|
13
|
+
back to the producer. A receiver holding a bundle from last week has no way to
|
|
14
|
+
learn that an authority trace was revoked yesterday, that a blocking evidence item
|
|
15
|
+
was added the day after export, or that a claim has since been superseded. The
|
|
16
|
+
receiver can re-run the status function as many times as it likes, but can only
|
|
17
|
+
produce the same answer from the same stale inputs.
|
|
18
|
+
|
|
19
|
+
This profile defines a lightweight channel for receivers to ask a producer: *what
|
|
20
|
+
has changed since this bundle was issued?* The response delivers fresh inputs for
|
|
21
|
+
the receiver's own status recomputation. It is testimony with a timestamp — a
|
|
22
|
+
record of what the producer asserted at the moment of response. It is never a
|
|
23
|
+
verdict the receiver is expected to obey.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Discovery
|
|
28
|
+
|
|
29
|
+
Producers supporting this profile expose a verification endpoint at a well-known
|
|
30
|
+
path on the same host that issued the bundle:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
GET https://<producer-host>/.well-known/hachure/verify?ref=<integrityRef>[&ref=...]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Multiple refs may be passed as repeated `ref` query parameters. For large sets,
|
|
37
|
+
a POST variant is also allowed:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
POST https://<producer-host>/.well-known/hachure/verify
|
|
41
|
+
Content-Type: application/json
|
|
42
|
+
|
|
43
|
+
{ "refs": ["<integrityRef>", ...] }
|
|
44
|
+
```
|
|
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
|
+
Producers MAY require authentication before serving a response. The response
|
|
50
|
+
semantics defined in this profile are unchanged by the presence or absence of
|
|
51
|
+
authentication; the receiver simply may not be able to reach the endpoint
|
|
52
|
+
without valid credentials.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Response shape
|
|
57
|
+
|
|
58
|
+
The response body is a TrustBundle (see `schemas/trust-bundle.schema.json`)
|
|
59
|
+
scoped to the records matching the requested refs, plus a `metadata` extension
|
|
60
|
+
block. The bundle carries:
|
|
61
|
+
|
|
62
|
+
- **`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.
|
|
65
|
+
- **`evidence`** and **`events`** — the delta since issuance where the producer
|
|
66
|
+
can supply it: new evidence items, new verification events, revocation events,
|
|
67
|
+
dispute events. Producers that do not track a delta MAY return the full current
|
|
68
|
+
set for the matched claims; receivers MUST NOT assume the absence of an item
|
|
69
|
+
means it was never present in the original bundle.
|
|
70
|
+
- **`authorityTrace`** — current authority traces relevant to the matched claims,
|
|
71
|
+
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
|
|
73
|
+
reserved keys within `metadata`:
|
|
74
|
+
|
|
75
|
+
| Key | Type | Required | Meaning |
|
|
76
|
+
|---|---|---|---|
|
|
77
|
+
| `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. |
|
|
79
|
+
| `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. |
|
|
81
|
+
|
|
82
|
+
Unknown refs are reported in `unknownRefs` honestly. A producer MUST NOT silently
|
|
83
|
+
omit a ref it does not recognise; it MUST include it in `unknownRefs` so the
|
|
84
|
+
receiver can distinguish "no changes" from "not found."
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Assurance levels
|
|
89
|
+
|
|
90
|
+
A response may be signed or unsigned. The two carry different weight.
|
|
91
|
+
|
|
92
|
+
**Signed response.** A producer that wraps the response bundle in a DSSE envelope
|
|
93
|
+
(per [interop-in-toto.md](interop-in-toto.md)) or attaches a `proof` block provides
|
|
94
|
+
independently verifiable testimony. The receiver can verify the signature using the
|
|
95
|
+
producer's public key before trusting any of the returned data. A signed response is
|
|
96
|
+
as trustworthy as the key and the in-toto statement it protects.
|
|
97
|
+
|
|
98
|
+
**Unsigned response.** A response delivered without a signature is producer-asserted
|
|
99
|
+
only. Receivers SHOULD treat an unsigned response exactly as they would treat an
|
|
100
|
+
unsigned TrustBundle received over a trusted channel: the transport provides some
|
|
101
|
+
assurance, but the content itself is not independently verifiable. Do not give an
|
|
102
|
+
unsigned response higher trust than the connection that delivered it.
|
|
103
|
+
|
|
104
|
+
Key distribution and rotation are out of scope for this profile. The same backlog
|
|
105
|
+
item that defers cryptographic signing for testimony records (ADR 0004, §Backlog)
|
|
106
|
+
applies here: until key-management infrastructure exists, signing is OPTIONAL and
|
|
107
|
+
unsigned responses are valid.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Receiver rules
|
|
112
|
+
|
|
113
|
+
1. **Re-run the status function.** Merge the response bundle's events and evidence
|
|
114
|
+
with the records in the held bundle, then call the status function over the
|
|
115
|
+
combined input at your own `now`. Do not treat any status value in the response
|
|
116
|
+
as a pre-computed verdict.
|
|
117
|
+
|
|
118
|
+
2. **Fix `now` before evaluating.** The status function is `f(claim, evidence,
|
|
119
|
+
events, policy, authorityTrace, now)`. The response supplies updated inputs; the
|
|
120
|
+
receiver controls `now`. Two receivers evaluating the same response at different
|
|
121
|
+
instants may derive different statuses — that is expected and correct.
|
|
122
|
+
|
|
123
|
+
3. **Cache by `respondedAt`; honour `max-age`.** A receiver MAY cache a response
|
|
124
|
+
keyed on the set of requested refs and the `respondedAt` timestamp. Producers MAY
|
|
125
|
+
include a standard HTTP `Cache-Control: max-age=<seconds>` header as a hint for
|
|
126
|
+
how long the response is expected to remain fresh. The hint is advisory; the
|
|
127
|
+
receiver decides its own staleness policy.
|
|
128
|
+
|
|
129
|
+
4. **Retain the response as a record.** A verification response is itself a record
|
|
130
|
+
worth keeping. It is testimony: evidence of what the producer asserted at
|
|
131
|
+
`respondedAt`. A receiver that retains responses can reconstruct the history of
|
|
132
|
+
its knowledge — when it learned of a revocation, when a dispute event appeared —
|
|
133
|
+
which is useful for audit and for re-evaluating past InquiryRecords.
|
|
134
|
+
|
|
135
|
+
5. **Treat signed and unsigned responses differently.** Apply the assurance-level
|
|
136
|
+
rules above before deciding how to weight the response in a combined evaluation.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Non-goals
|
|
141
|
+
|
|
142
|
+
- **Transport beyond HTTPS.** This profile specifies only the HTTP/HTTPS request
|
|
143
|
+
and response shape. Other transport mechanisms (gRPC, message queues, file
|
|
144
|
+
exchange) are not covered.
|
|
145
|
+
- **Push and webhooks.** This profile is pull-only. Producers notify receivers
|
|
146
|
+
only when asked. Push notification channels are a separate concern.
|
|
147
|
+
- **Transparency-log inclusion.** Including verification responses in a public
|
|
148
|
+
transparency log (Rekor or equivalent) is complementary and encouraged for
|
|
149
|
+
high-assurance scenarios, but it is not required by this profile and is not
|
|
150
|
+
specified here.
|
|
151
|
+
- **Key management.** Public-key distribution, rotation, and revocation are out
|
|
152
|
+
of scope. See the ADR 0004 backlog note above.
|