@colophon-claims/verify 0.0.0 → 0.2.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 +39 -0
- package/dist/admission/application.d.ts +18 -0
- package/dist/admission/application.js +53 -0
- package/dist/admission/contracts.d.ts +506 -0
- package/dist/admission/contracts.js +459 -0
- package/dist/admission/index.d.ts +5 -0
- package/dist/admission/index.js +5 -0
- package/dist/admission/intake.d.ts +75 -0
- package/dist/admission/intake.js +70 -0
- package/dist/admission/result-evaluation.d.ts +18 -0
- package/dist/admission/result-evaluation.js +71 -0
- package/dist/admission/screening-sample.d.ts +56 -0
- package/dist/admission/screening-sample.js +126 -0
- package/dist/admission/verification.d.ts +102 -0
- package/dist/admission/verification.js +844 -0
- package/dist/anchor/check.d.ts +95 -0
- package/dist/anchor/check.js +146 -0
- package/dist/anchor/ports.d.ts +6 -0
- package/dist/anchor/ports.js +354 -0
- package/dist/assets.d.ts +33 -0
- package/dist/assets.js +885 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +20 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +205 -0
- package/dist/comparison.d.ts +64 -0
- package/dist/comparison.js +235 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +23 -0
- package/dist/manifest.d.ts +54 -0
- package/dist/manifest.js +240 -0
- package/dist/materialize.d.ts +4 -0
- package/dist/materialize.js +13 -0
- package/dist/profile/admission-receipts.d.ts +11 -0
- package/dist/profile/admission-receipts.js +24 -0
- package/dist/profile/anchor-claims.d.ts +157 -0
- package/dist/profile/anchor-claims.js +330 -0
- package/dist/profile/artifacts.d.ts +261 -0
- package/dist/profile/artifacts.js +139 -0
- package/dist/profile/assembly-ports.d.ts +19 -0
- package/dist/profile/assembly-ports.js +42 -0
- package/dist/profile/binary-judge-manifest.d.ts +121 -0
- package/dist/profile/binary-judge-manifest.js +106 -0
- package/dist/profile/binary-qualification.d.ts +9 -0
- package/dist/profile/binary-qualification.js +58 -0
- package/dist/profile/branding-assets.d.ts +5 -0
- package/dist/profile/branding-assets.js +5 -0
- package/dist/profile/branding.d.ts +24 -0
- package/dist/profile/branding.js +14 -0
- package/dist/profile/claim-consistency.d.ts +26 -0
- package/dist/profile/claim-consistency.js +91 -0
- package/dist/profile/claim.d.ts +327 -0
- package/dist/profile/claim.js +742 -0
- package/dist/profile/errors.d.ts +49 -0
- package/dist/profile/errors.js +78 -0
- package/dist/profile/inspect-assurance.d.ts +16 -0
- package/dist/profile/inspect-assurance.js +30 -0
- package/dist/profile/inspect-disclosure.d.ts +28 -0
- package/dist/profile/inspect-disclosure.js +54 -0
- package/dist/profile/inspect-manifest.d.ts +706 -0
- package/dist/profile/inspect-manifest.js +397 -0
- package/dist/profile/isolation.d.ts +21 -0
- package/dist/profile/isolation.js +31 -0
- package/dist/profile/pinning-evidence.d.ts +41 -0
- package/dist/profile/pinning-evidence.js +43 -0
- package/dist/profile/ports.d.ts +3 -0
- package/dist/profile/ports.js +11 -0
- package/dist/profile/preview-log.d.ts +4 -0
- package/dist/profile/preview-log.js +3 -0
- package/dist/profile/run-results.d.ts +17 -0
- package/dist/profile/run-results.js +23 -0
- package/dist/profile/signing.d.ts +10 -0
- package/dist/profile/signing.js +46 -0
- package/dist/profile/trust.d.ts +13 -0
- package/dist/profile/trust.js +35 -0
- package/dist/profile/venue.d.ts +2 -0
- package/dist/profile/venue.js +2 -0
- package/dist/profile/verdict.d.ts +28 -0
- package/dist/profile/verdict.js +51 -0
- package/dist/reader-instructions.d.ts +41 -0
- package/dist/reader-instructions.js +52 -0
- package/dist/schema.d.ts +380 -0
- package/dist/schema.js +498 -0
- package/dist/verify.d.ts +72 -0
- package/dist/verify.js +1573 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +63 -3
- package/schemas/assembly-row.schema.json +194 -0
- package/schemas/bundle-manifest.schema.json +23 -0
- package/schemas/claim-package.schema.json +279 -0
- package/schemas/dsse-envelope.schema.json +25 -0
- package/schemas/evidence-catalog.schema.json +43 -0
- package/schemas/public-trust.schema.json +44 -0
- package/schemas/verdict-catalog.schema.json +27 -0
- package/scripts/external-verify.py +238 -0
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, readdirSync, realpathSync, } from "node:fs";
|
|
3
|
+
import { isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { EvidenceNativeBundleManifestV5Schema } from "@jinn-network/benchmarking-protocol";
|
|
6
|
+
import { canonicalJsonBytes } from "@jinn-network/trust-core";
|
|
7
|
+
import { refuse } from "./profile/errors.js";
|
|
8
|
+
export const BUNDLE_FORMAT = "benchmark-product-public-bundle/2";
|
|
9
|
+
export const BUNDLE_V4_FORMAT = "benchmark-product-public-bundle/4";
|
|
10
|
+
export const BUNDLE_V5_FORMAT = "benchmark-product-public-bundle/5";
|
|
11
|
+
/** The anchored closure (anchor-evidence design §7.4). Additive: v2, v4, and v5 keep their check
|
|
12
|
+
* lists, byte shapes, and values; the `anchors/` member and the `integrity-anchors` check exist
|
|
13
|
+
* only here. */
|
|
14
|
+
export const BUNDLE_V6_FORMAT = "benchmark-product-public-bundle/6";
|
|
15
|
+
export const SUPPORTED_BUNDLE_FORMATS = [
|
|
16
|
+
BUNDLE_FORMAT,
|
|
17
|
+
BUNDLE_V4_FORMAT,
|
|
18
|
+
BUNDLE_V5_FORMAT,
|
|
19
|
+
BUNDLE_V6_FORMAT,
|
|
20
|
+
];
|
|
21
|
+
export const BUNDLE_MANIFEST_FILENAME = "bundle.json";
|
|
22
|
+
const SHA256_HEX = /^[a-f0-9]{64}$/;
|
|
23
|
+
export const BundleManifestFileSchema = z.object({
|
|
24
|
+
path: z.string().min(1),
|
|
25
|
+
sha256: z.string().regex(SHA256_HEX),
|
|
26
|
+
bytes: z.number().int().nonnegative(),
|
|
27
|
+
});
|
|
28
|
+
const LegacyBundleManifestSchema = z.object({
|
|
29
|
+
format: z.union([
|
|
30
|
+
z.literal(BUNDLE_FORMAT),
|
|
31
|
+
z.literal(BUNDLE_V4_FORMAT),
|
|
32
|
+
z.literal(BUNDLE_V6_FORMAT),
|
|
33
|
+
]),
|
|
34
|
+
files: z.array(BundleManifestFileSchema).min(1),
|
|
35
|
+
});
|
|
36
|
+
export const BundleManifestSchema = z.union([
|
|
37
|
+
LegacyBundleManifestSchema,
|
|
38
|
+
EvidenceNativeBundleManifestV5Schema,
|
|
39
|
+
]);
|
|
40
|
+
function sha256(bytes) {
|
|
41
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
42
|
+
}
|
|
43
|
+
function equalBytes(left, right) {
|
|
44
|
+
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
45
|
+
}
|
|
46
|
+
function validateRelativePath(path) {
|
|
47
|
+
if (path === ""
|
|
48
|
+
|| path === "."
|
|
49
|
+
|| path === BUNDLE_MANIFEST_FILENAME
|
|
50
|
+
|| isAbsolute(path)
|
|
51
|
+
|| path.includes("\\")
|
|
52
|
+
|| path.split("/").some((part) => part === "" || part === "." || part === "..")
|
|
53
|
+
|| posix.normalize(path) !== path) {
|
|
54
|
+
refuse("record-integrity", "bundle.manifest.path", `unsafe or reserved bundle path "${path}"`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function isInside(root, candidate) {
|
|
58
|
+
const rel = relative(root, candidate);
|
|
59
|
+
return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel));
|
|
60
|
+
}
|
|
61
|
+
function readRegularFile(bundleDir, path, allowManifest = false) {
|
|
62
|
+
if (allowManifest && path === BUNDLE_MANIFEST_FILENAME) {
|
|
63
|
+
// bundle.json is reserved from the file list but is itself authenticated by the same reader.
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
validateRelativePath(path);
|
|
67
|
+
}
|
|
68
|
+
const absolute = join(bundleDir, ...path.split("/"));
|
|
69
|
+
let stat;
|
|
70
|
+
try {
|
|
71
|
+
stat = lstatSync(absolute);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
refuse("record-integrity", path, `manifest entry "${path}" is missing`);
|
|
75
|
+
}
|
|
76
|
+
if (stat.isSymbolicLink()) {
|
|
77
|
+
refuse("record-integrity", path, `manifest entry "${path}" is a symbolic link`);
|
|
78
|
+
}
|
|
79
|
+
if (!stat.isFile()) {
|
|
80
|
+
refuse("record-integrity", path, `manifest entry "${path}" is not a regular file`);
|
|
81
|
+
}
|
|
82
|
+
if (stat.nlink !== 1) {
|
|
83
|
+
refuse("record-integrity", path, `manifest entry "${path}" must not be hard-linked`);
|
|
84
|
+
}
|
|
85
|
+
const root = realpathSync(bundleDir);
|
|
86
|
+
let resolved;
|
|
87
|
+
try {
|
|
88
|
+
resolved = realpathSync(absolute);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
refuse("record-integrity", path, `manifest entry "${path}" cannot be resolved inside the bundle`);
|
|
92
|
+
}
|
|
93
|
+
if (!isInside(root, resolved)) {
|
|
94
|
+
refuse("record-integrity", path, `manifest entry "${path}" resolves outside the bundle`);
|
|
95
|
+
}
|
|
96
|
+
let fd;
|
|
97
|
+
try {
|
|
98
|
+
fd = openSync(absolute, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
refuse("record-integrity", path, `manifest entry "${path}" could not be opened without following links`);
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const opened = fstatSync(fd);
|
|
105
|
+
if (!opened.isFile() || opened.nlink !== 1 || opened.dev !== stat.dev || opened.ino !== stat.ino) {
|
|
106
|
+
refuse("record-integrity", path, `manifest entry "${path}" changed identity while it was opened`);
|
|
107
|
+
}
|
|
108
|
+
const bytes = new Uint8Array(readFileSync(fd));
|
|
109
|
+
const after = fstatSync(fd);
|
|
110
|
+
if (after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size) {
|
|
111
|
+
refuse("record-integrity", path, `manifest entry "${path}" changed while it was authenticated`);
|
|
112
|
+
}
|
|
113
|
+
return bytes;
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
closeSync(fd);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function walkTree(root, directory = root) {
|
|
120
|
+
const files = [];
|
|
121
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
122
|
+
const absolute = join(directory, entry.name);
|
|
123
|
+
const path = relative(root, absolute).split(sep).join("/");
|
|
124
|
+
const stat = lstatSync(absolute);
|
|
125
|
+
if (stat.isSymbolicLink()) {
|
|
126
|
+
refuse("record-integrity", path, `bundle contains symbolic link "${path}"`);
|
|
127
|
+
}
|
|
128
|
+
if (stat.isDirectory()) {
|
|
129
|
+
const resolvedRoot = realpathSync(root);
|
|
130
|
+
const resolvedDirectory = realpathSync(absolute);
|
|
131
|
+
if (!isInside(resolvedRoot, resolvedDirectory)) {
|
|
132
|
+
refuse("record-integrity", path, `bundle directory "${path}" resolves outside the bundle`);
|
|
133
|
+
}
|
|
134
|
+
files.push(...walkTree(root, absolute));
|
|
135
|
+
}
|
|
136
|
+
else if (stat.isFile()) {
|
|
137
|
+
files.push(path);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
refuse("record-integrity", path, `bundle contains non-regular entry "${path}"`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return files;
|
|
144
|
+
}
|
|
145
|
+
export function buildBundleManifest(bundleDir, filePaths, options = {}) {
|
|
146
|
+
const seen = new Set();
|
|
147
|
+
const files = [...filePaths]
|
|
148
|
+
.map((path) => {
|
|
149
|
+
validateRelativePath(path);
|
|
150
|
+
if (seen.has(path)) {
|
|
151
|
+
refuse("record-integrity", "bundle.manifest.files", `duplicate bundle path "${path}"`);
|
|
152
|
+
}
|
|
153
|
+
seen.add(path);
|
|
154
|
+
const bytes = readRegularFile(bundleDir, path);
|
|
155
|
+
return { path, sha256: sha256(bytes), bytes: bytes.length };
|
|
156
|
+
})
|
|
157
|
+
.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
158
|
+
const manifest = BundleManifestSchema.parse({ format: options.format ?? BUNDLE_FORMAT, files });
|
|
159
|
+
const bytes = canonicalJsonBytes(manifest);
|
|
160
|
+
return { manifest, bytes, identity: sha256(bytes) };
|
|
161
|
+
}
|
|
162
|
+
export function verifyBundleManifest(bundleDir) {
|
|
163
|
+
const snapshot = verifyBundleSnapshot(bundleDir);
|
|
164
|
+
return { manifest: snapshot.manifest, bytes: snapshot.bytes, identity: snapshot.identity };
|
|
165
|
+
}
|
|
166
|
+
/** Authenticates the complete tree once and returns the exact immutable byte snapshot used by
|
|
167
|
+
* every semantic verifier. No consumer may reopen a pathname after this boundary. */
|
|
168
|
+
export function verifyBundleSnapshot(bundleDir, deps = {}) {
|
|
169
|
+
const absoluteRoot = resolve(bundleDir);
|
|
170
|
+
let rootStat;
|
|
171
|
+
try {
|
|
172
|
+
rootStat = lstatSync(absoluteRoot);
|
|
173
|
+
}
|
|
174
|
+
catch (cause) {
|
|
175
|
+
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT") {
|
|
176
|
+
refuse("record-integrity", "bundle", "bundle directory is missing");
|
|
177
|
+
}
|
|
178
|
+
throw cause;
|
|
179
|
+
}
|
|
180
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
181
|
+
refuse("record-integrity", "bundle", "bundle root must be a real directory");
|
|
182
|
+
}
|
|
183
|
+
const canonicalRoot = realpathSync(absoluteRoot);
|
|
184
|
+
const bytes = readRegularFile(canonicalRoot, BUNDLE_MANIFEST_FILENAME, true);
|
|
185
|
+
let raw;
|
|
186
|
+
try {
|
|
187
|
+
raw = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
refuse("record-integrity", BUNDLE_MANIFEST_FILENAME, "bundle.json is not valid UTF-8 JSON");
|
|
191
|
+
}
|
|
192
|
+
const parsed = BundleManifestSchema.safeParse(raw);
|
|
193
|
+
if (!parsed.success) {
|
|
194
|
+
refuse("record-integrity", BUNDLE_MANIFEST_FILENAME, "bundle.json does not satisfy the manifest schema");
|
|
195
|
+
}
|
|
196
|
+
const canonical = canonicalJsonBytes(parsed.data);
|
|
197
|
+
if (!equalBytes(bytes, canonical)) {
|
|
198
|
+
refuse("record-integrity", BUNDLE_MANIFEST_FILENAME, "bundle.json bytes are not the exact canonical manifest encoding");
|
|
199
|
+
}
|
|
200
|
+
const seen = new Set();
|
|
201
|
+
let previous = "";
|
|
202
|
+
for (const entry of parsed.data.files) {
|
|
203
|
+
validateRelativePath(entry.path);
|
|
204
|
+
if (seen.has(entry.path)) {
|
|
205
|
+
refuse("record-integrity", "bundle.manifest.files", `duplicate bundle path "${entry.path}"`);
|
|
206
|
+
}
|
|
207
|
+
if (previous !== "" && entry.path <= previous) {
|
|
208
|
+
refuse("record-integrity", "bundle.manifest.files", "manifest files are not in strict path order");
|
|
209
|
+
}
|
|
210
|
+
seen.add(entry.path);
|
|
211
|
+
previous = entry.path;
|
|
212
|
+
const fileBytes = readRegularFile(canonicalRoot, entry.path);
|
|
213
|
+
if (fileBytes.length !== entry.bytes) {
|
|
214
|
+
refuse("record-integrity", entry.path, `byte length mismatch for "${entry.path}"`);
|
|
215
|
+
}
|
|
216
|
+
if (sha256(fileBytes) !== entry.sha256) {
|
|
217
|
+
refuse("record-integrity", entry.path, `digest mismatch for "${entry.path}"`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const actual = new Set(walkTree(canonicalRoot));
|
|
221
|
+
const expected = new Set([BUNDLE_MANIFEST_FILENAME, ...seen]);
|
|
222
|
+
for (const path of expected) {
|
|
223
|
+
if (!actual.has(path))
|
|
224
|
+
refuse("record-integrity", path, `manifest entry "${path}" is missing`);
|
|
225
|
+
}
|
|
226
|
+
for (const path of actual) {
|
|
227
|
+
if (!expected.has(path))
|
|
228
|
+
refuse("record-integrity", path, `bundle contains unexpected file "${path}"`);
|
|
229
|
+
}
|
|
230
|
+
const fileBytes = new Map([[BUNDLE_MANIFEST_FILENAME, bytes]]);
|
|
231
|
+
for (const entry of parsed.data.files) {
|
|
232
|
+
const authenticated = readRegularFile(canonicalRoot, entry.path);
|
|
233
|
+
if (authenticated.length !== entry.bytes || sha256(authenticated) !== entry.sha256) {
|
|
234
|
+
refuse("record-integrity", entry.path, `manifest entry "${entry.path}" changed during snapshot authentication`);
|
|
235
|
+
}
|
|
236
|
+
fileBytes.set(entry.path, authenticated);
|
|
237
|
+
}
|
|
238
|
+
deps.afterManifestValidated?.();
|
|
239
|
+
return { manifest: parsed.data, bytes, identity: sha256(bytes), fileBytes };
|
|
240
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Public bundle members required by the Colophon bundle profile. */
|
|
2
|
+
export declare const PUBLIC_BUNDLE_FILES: readonly ["static-bundle.json", "benchmark.json", "run.json", "matrix.json", "report.json", "report-envelope.json", "claim-package.json", "verdicts.json", "evidence.json", "verification/assembly.jsonl", "trust/public-keys.json", "index.html", "badge.svg", "social-card.svg", "README.md", "share.txt"];
|
|
3
|
+
/** V4 is the fixed v2 graph with exactly one canonical qualification projection added. */
|
|
4
|
+
export declare const PUBLIC_BUNDLE_V4_FILES: readonly ("benchmark.json" | "run.json" | "matrix.json" | "report.json" | "report-envelope.json" | "claim-package.json" | "qualification.json" | "static-bundle.json" | "evidence.json" | "verdicts.json" | "verification/assembly.jsonl" | "trust/public-keys.json" | "index.html" | "badge.svg" | "social-card.svg" | "README.md" | "share.txt")[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Public bundle members required by the Colophon bundle profile. */
|
|
2
|
+
export const PUBLIC_BUNDLE_FILES = [
|
|
3
|
+
"static-bundle.json", "benchmark.json", "run.json", "matrix.json", "report.json",
|
|
4
|
+
"report-envelope.json", "claim-package.json", "verdicts.json", "evidence.json",
|
|
5
|
+
"verification/assembly.jsonl", "trust/public-keys.json", "index.html", "badge.svg",
|
|
6
|
+
"social-card.svg", "README.md", "share.txt",
|
|
7
|
+
];
|
|
8
|
+
/** V4 is the fixed v2 graph with exactly one canonical qualification projection added. */
|
|
9
|
+
export const PUBLIC_BUNDLE_V4_FILES = [
|
|
10
|
+
...PUBLIC_BUNDLE_FILES.slice(0, 7),
|
|
11
|
+
"qualification.json",
|
|
12
|
+
...PUBLIC_BUNDLE_FILES.slice(7),
|
|
13
|
+
];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface LocalAdmissionReceiptFact {
|
|
2
|
+
readonly zeroReplayVariance: boolean;
|
|
3
|
+
readonly externalCapabilities: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface ParsedLocalAdmissionReceipt {
|
|
6
|
+
readonly sha256: string;
|
|
7
|
+
readonly taskSha256: string;
|
|
8
|
+
readonly fact: LocalAdmissionReceiptFact;
|
|
9
|
+
}
|
|
10
|
+
/** Parses the public, sealed prediction-snapshot admission receipt. */
|
|
11
|
+
export declare function parsePredictionSnapshotAdmissionReceipt(bytes: Uint8Array, sha256?: string): ParsedLocalAdmissionReceipt | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ADMISSION_RECEIPT_MEDIA_TYPE, IN_TOTO_STATEMENT_TYPE, PREDICTION_SNAPSHOT_ADMISSION_POLICY_V1, } from "@jinn-network/task-admission";
|
|
2
|
+
import { parseDsseEnvelope } from "@jinn-network/trust-core";
|
|
3
|
+
/** Parses the public, sealed prediction-snapshot admission receipt. */
|
|
4
|
+
export function parsePredictionSnapshotAdmissionReceipt(bytes, sha256 = "") {
|
|
5
|
+
let envelope;
|
|
6
|
+
try {
|
|
7
|
+
envelope = parseDsseEnvelope(bytes);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
if (envelope.payloadType !== ADMISSION_RECEIPT_MEDIA_TYPE)
|
|
13
|
+
return undefined;
|
|
14
|
+
try {
|
|
15
|
+
const statement = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(envelope.payloadBytes));
|
|
16
|
+
const digest = statement.predicate?.task?.documentDigest;
|
|
17
|
+
if (statement._type !== IN_TOTO_STATEMENT_TYPE || statement.predicateType !== PREDICTION_SNAPSHOT_ADMISSION_POLICY_V1.predicateType || typeof digest !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(digest))
|
|
18
|
+
return undefined;
|
|
19
|
+
return { sha256, taskSha256: digest.slice("sha256:".length), fact: { zeroReplayVariance: true, externalCapabilities: false } };
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The anchored-closure projection (anchor-evidence design §7.4 and §9.2): the pure function that
|
|
3
|
+
* turns a bundle's carried AnchorEvidence bytes into the claim package's `anchors` section, and
|
|
4
|
+
* the conditional honesty copy that section gates.
|
|
5
|
+
*
|
|
6
|
+
* This module is **single-sourced, not mirrored**. `@colophon-claims/core` already depends on this
|
|
7
|
+
* package (it takes `nodeCryptoAnchorPorts` and the reader instructions from here), so producer and
|
|
8
|
+
* verifier import the same function rather than two copies of it. §7.4 requires that "producer and
|
|
9
|
+
* verifier derive the section from the same bundle bytes by the same pure function, so
|
|
10
|
+
* claim-consistency remains an exact byte-compare" — one function is the strongest available form
|
|
11
|
+
* of that requirement, since two mirrored copies can drift and this one cannot.
|
|
12
|
+
*
|
|
13
|
+
* Four disciplines are load-bearing:
|
|
14
|
+
*
|
|
15
|
+
* - **Records are read only through `parseExactAnchorEvidence`, proof content only through
|
|
16
|
+
* `decodeAnchorProofContent`.** Conformance is shape-level and admits alternate JSON spellings;
|
|
17
|
+
* identity is byte-exactness. A record selected through the laxer door would have a digest other
|
|
18
|
+
* than the one it is filed under.
|
|
19
|
+
* - **Subject selectors are digest-keyed.** The lock anchor is any carried anchor whose
|
|
20
|
+
* `subject.digest` equals the digest of the bundle's exact `run.json` bytes; the matrix anchor is
|
|
21
|
+
* any whose digest equals `matrix.json`'s. `subject.kind` is then required to equal the resolved
|
|
22
|
+
* record's actual kind — it is checked, never used to route (§8 step 2). A digest that names no
|
|
23
|
+
* bundle record, or a kind that misdescribes the one it names, is `invalid`: affirmative evidence
|
|
24
|
+
* of substitution, reported louder than absence.
|
|
25
|
+
* - **Byte-embedded facts only.** `genTime`, `policyOid`, `serialNumber`,
|
|
26
|
+
* `signerCertificateSha256` for RFC 3161; the attested block height (or `pending`) for
|
|
27
|
+
* OpenTimestamps. Facts that need external data (block time) or lack a canonical rendering
|
|
28
|
+
* (issuer distinguished names, accuracy intervals) never enter this section — they are
|
|
29
|
+
* verifier-report and presentation content (§7.4).
|
|
30
|
+
* - **No trust material is supplied here.** This projection is a function of the bundle's bytes,
|
|
31
|
+
* never of a verifier's configured roots, because the sealed claim text must be identical for
|
|
32
|
+
* every reader (§8, "The honesty text keys on byte-facts"). Trust evaluation lives in the
|
|
33
|
+
* `integrity-anchors` check's own report.
|
|
34
|
+
*/
|
|
35
|
+
import { z } from "zod";
|
|
36
|
+
/** The two records this product anchors (§7.1). Both are whole sealed-record digests. */
|
|
37
|
+
export type ClaimAnchorSubject = "lock" | "matrix";
|
|
38
|
+
/** RFC 3161's four byte-embedded facts (§7.4). `accuracy` and the issuer DN are deliberately
|
|
39
|
+
* absent: the first has no canonical single rendering, the second is not canonically comparable
|
|
40
|
+
* across implementations. */
|
|
41
|
+
export interface Rfc3161ClaimAnchorFacts {
|
|
42
|
+
readonly genTime: string;
|
|
43
|
+
readonly policyOid: string;
|
|
44
|
+
readonly serialNumber: string;
|
|
45
|
+
readonly signerCertificateSha256: string;
|
|
46
|
+
}
|
|
47
|
+
/** OpenTimestamps' one byte-embedded fact, or the calendar-only state. A `pending` proof carries no
|
|
48
|
+
* height because none has been attested yet — the two shapes are disjoint by construction so no
|
|
49
|
+
* reader can mistake a promise for a commitment (§13 item 5). */
|
|
50
|
+
export type OpenTimestampsClaimAnchorFacts = {
|
|
51
|
+
readonly blockHeight: number;
|
|
52
|
+
} | {
|
|
53
|
+
readonly pending: true;
|
|
54
|
+
};
|
|
55
|
+
export type ClaimAnchorFacts = Rfc3161ClaimAnchorFacts | OpenTimestampsClaimAnchorFacts;
|
|
56
|
+
export interface ClaimAnchor {
|
|
57
|
+
readonly subject: ClaimAnchorSubject;
|
|
58
|
+
/** The resolved record's actual kind IRI — checked against `subject.kind`, never taken from it. */
|
|
59
|
+
readonly kind: string;
|
|
60
|
+
readonly provider: string;
|
|
61
|
+
readonly recordSha256: string;
|
|
62
|
+
/** Present only on the §6.2 upgraded form of a pending proof — see `upgradeEdge` below. */
|
|
63
|
+
readonly upgradesRecordSha256?: string;
|
|
64
|
+
readonly facts: ClaimAnchorFacts;
|
|
65
|
+
}
|
|
66
|
+
export interface CarriedAnchorRecord {
|
|
67
|
+
readonly recordSha256: string;
|
|
68
|
+
readonly bytes: Uint8Array;
|
|
69
|
+
}
|
|
70
|
+
export declare const ClaimAnchorSchema: z.ZodObject<{
|
|
71
|
+
subject: z.ZodEnum<{
|
|
72
|
+
lock: "lock";
|
|
73
|
+
matrix: "matrix";
|
|
74
|
+
}>;
|
|
75
|
+
kind: z.ZodString;
|
|
76
|
+
provider: z.ZodString;
|
|
77
|
+
recordSha256: z.ZodString;
|
|
78
|
+
upgradesRecordSha256: z.ZodOptional<z.ZodString>;
|
|
79
|
+
facts: z.ZodUnion<readonly [z.ZodObject<{
|
|
80
|
+
genTime: z.ZodString;
|
|
81
|
+
policyOid: z.ZodString;
|
|
82
|
+
serialNumber: z.ZodString;
|
|
83
|
+
signerCertificateSha256: z.ZodString;
|
|
84
|
+
}, z.core.$strict>, z.ZodUnion<readonly [z.ZodObject<{
|
|
85
|
+
blockHeight: z.ZodNumber;
|
|
86
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
87
|
+
pending: z.ZodLiteral<true>;
|
|
88
|
+
}, z.core.$strict>]>]>;
|
|
89
|
+
}, z.core.$strict>;
|
|
90
|
+
export interface DeriveClaimAnchorsInput {
|
|
91
|
+
readonly records: readonly CarriedAnchorRecord[];
|
|
92
|
+
/** sha256 hex of the bundle's exact `run.json` bytes. */
|
|
93
|
+
readonly runSha256: string;
|
|
94
|
+
/** sha256 hex of the bundle's exact `matrix.json` bytes. */
|
|
95
|
+
readonly matrixSha256: string;
|
|
96
|
+
}
|
|
97
|
+
/** A record this projection refuses. Every caller converts it into its own typed refusal
|
|
98
|
+
* (`record-integrity` on the producer side, an `invalid` anchor outcome on the verifier side), so
|
|
99
|
+
* the failure is never swallowed and never crosses a package boundary as an untyped throw. */
|
|
100
|
+
export declare class ClaimAnchorProjectionError extends Error {
|
|
101
|
+
readonly name = "ClaimAnchorProjectionError";
|
|
102
|
+
readonly recordSha256: string;
|
|
103
|
+
constructor(recordSha256: string, message: string);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Projects the carried AnchorEvidence records into the claim package's `anchors` section.
|
|
107
|
+
*
|
|
108
|
+
* Ordered by record digest so the section has exactly one spelling for one carried set. Throws
|
|
109
|
+
* `ClaimAnchorProjectionError` on a record that is not exactly-sealed, does not parse, names a
|
|
110
|
+
* digest no bundle record has, misdescribes the kind of the record it does name, carries a provider
|
|
111
|
+
* profile no verifier implements, or carries a proof the rules reject.
|
|
112
|
+
*/
|
|
113
|
+
export declare function deriveClaimAnchors(input: DeriveClaimAnchorsInput): readonly ClaimAnchor[];
|
|
114
|
+
/** The historical value every unanchored bundle keeps (§9.2, "Additive"). */
|
|
115
|
+
export declare const STRUCTURAL_PRE_REGISTRATION: "structural-and-append-order-only";
|
|
116
|
+
/** The widened value an anchored bundle carries. */
|
|
117
|
+
export declare const ANCHORED_PRE_REGISTRATION: "structural-append-order-and-anchored-time";
|
|
118
|
+
export declare const SELF_RUN_TRUST_ROOT = "Signatures verify against the bundle-carried public keys minted by this workspace; there is no third-party trust anchor on the self-run venue.";
|
|
119
|
+
export declare const ANCHORED_TRUST_ROOT: string;
|
|
120
|
+
/**
|
|
121
|
+
* A lock anchor gates copy only when it is carried, structurally complete for its class, and
|
|
122
|
+
* digest- and kind-matching (§9.2). The first and last are guaranteed by `deriveClaimAnchors`,
|
|
123
|
+
* which refuses everything else; completeness is the one condition readable off the section: a
|
|
124
|
+
* `pending`-only proof is not structurally complete and gates nothing.
|
|
125
|
+
*/
|
|
126
|
+
export declare function governingLockAnchors(anchors: readonly ClaimAnchor[]): readonly ClaimAnchor[];
|
|
127
|
+
/** Complete matrix anchors, in record-digest order. A matrix anchor upgrades nothing (§9.2). */
|
|
128
|
+
export declare function carriedMatrixAnchors(anchors: readonly ClaimAnchor[]): readonly ClaimAnchor[];
|
|
129
|
+
/**
|
|
130
|
+
* The replacement for limitation sentence 2, rendered once from the governing anchor.
|
|
131
|
+
*
|
|
132
|
+
* The `authority-time` form asserts, because its cryptography is fully carried and the verifier
|
|
133
|
+
* re-verifies it unconditionally and fails loud on `invalid`. The `chain-time` form is attributive
|
|
134
|
+
* — it says what the proof asserts and what checking it requires — because structural completeness
|
|
135
|
+
* is not chain evaluation; the assertive chain-time statement lives only in the report of a
|
|
136
|
+
* verifier that evaluated headers (§8, §9.2).
|
|
137
|
+
*/
|
|
138
|
+
export declare function anchoredPreRegistrationSentence(governing: ClaimAnchor): string;
|
|
139
|
+
/** One neutral line per additional lock anchor (§9.2). It upgrades nothing; it reports. */
|
|
140
|
+
export declare function additionalLockAnchorLine(anchor: ClaimAnchor): string;
|
|
141
|
+
/** One neutral line per carried matrix anchor (§9.2). It upgrades no claim. */
|
|
142
|
+
export declare function matrixAnchorLine(anchor: ClaimAnchor): string;
|
|
143
|
+
/**
|
|
144
|
+
* The complete venue-limits list for one anchor section: the unconditional list with sentence 2
|
|
145
|
+
* replaced by the governing anchor's form, followed by the neutral lines. Sentences 1, 3/3', 4, and
|
|
146
|
+
* 5 are untouched — anchors have nothing to say about operator control, pinning axes, self-reported
|
|
147
|
+
* cost, or agent distinctness (§9.2).
|
|
148
|
+
*
|
|
149
|
+
* With no governing lock anchor the unconditional list is returned unchanged, including when the
|
|
150
|
+
* bundle carries only pending or only matrix anchors: absent, pending, or invalid anchors change
|
|
151
|
+
* nothing.
|
|
152
|
+
*/
|
|
153
|
+
export declare function anchoredVenueLimits(unconditional: readonly string[], anchors: readonly ClaimAnchor[]): readonly string[];
|
|
154
|
+
/** `venueHonesty.preRegistration` for one anchor section (§9.2). */
|
|
155
|
+
export declare function anchoredPreRegistration(anchors: readonly ClaimAnchor[]): typeof STRUCTURAL_PRE_REGISTRATION | typeof ANCHORED_PRE_REGISTRATION;
|
|
156
|
+
/** `claim.verification.trustRoot` for one anchor section (§9.2). */
|
|
157
|
+
export declare function anchoredTrustRoot(anchors: readonly ClaimAnchor[]): string;
|