@colophon-claims/verify 0.0.0 → 0.1.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 +35 -0
- package/dist/admission/application.d.ts +18 -0
- package/dist/admission/application.js +53 -0
- package/dist/admission/contracts.d.ts +264 -0
- package/dist/admission/contracts.js +290 -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 +89 -0
- package/dist/admission/verification.js +709 -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 +52 -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 +324 -0
- package/dist/profile/claim.js +724 -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 +370 -0
- package/dist/schema.js +484 -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
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const BINARY_ITEM_BANK_ENTRY_PROTOCOL: "https://spec.jinn.network/binary-judgment/item-bank-entry/v1";
|
|
3
|
+
export declare const BINARY_SOURCE_MANIFEST_ENTRY_PROTOCOL: "https://spec.jinn.network/binary-judgment/source-manifest-entry/v1";
|
|
4
|
+
export declare const BINARY_ADMISSION_INDEX_ENTRY_PROTOCOL: "https://spec.jinn.network/binary-judgment/admission-index-entry/v1";
|
|
5
|
+
export declare const BINARY_ITEM_BANK_INTAKE_EXTENSION: "https://product.jinn.network/extensions/binary-judgment-intake/v1";
|
|
6
|
+
export declare const BinaryItemBankEntrySchema: z.ZodObject<{
|
|
7
|
+
protocol: z.ZodLiteral<"https://spec.jinn.network/binary-judgment/item-bank-entry/v1">;
|
|
8
|
+
item: z.ZodObject<{
|
|
9
|
+
itemId: z.ZodString;
|
|
10
|
+
question: z.ZodString;
|
|
11
|
+
referenceAnswer: z.ZodString;
|
|
12
|
+
candidateAnswer: z.ZodString;
|
|
13
|
+
evidence: z.ZodOptional<z.ZodString>;
|
|
14
|
+
provenance: z.ZodObject<{
|
|
15
|
+
sourceCommitment: z.ZodPipe<z.ZodString, z.ZodTransform<`sha256:${string}`, string>>;
|
|
16
|
+
timestamp: z.ZodString;
|
|
17
|
+
}, z.core.$strict>;
|
|
18
|
+
sources: z.ZodArray<z.ZodObject<{
|
|
19
|
+
digest: z.ZodObject<{
|
|
20
|
+
sha256: z.ZodString;
|
|
21
|
+
}, z.core.$strict>;
|
|
22
|
+
}, z.core.$strict>>;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
}, z.core.$strict>;
|
|
25
|
+
export type BinaryItemBankEntry = z.infer<typeof BinaryItemBankEntrySchema>;
|
|
26
|
+
export declare const BinarySourceManifestEntrySchema: z.ZodObject<{
|
|
27
|
+
protocol: z.ZodLiteral<"https://spec.jinn.network/binary-judgment/source-manifest-entry/v1">;
|
|
28
|
+
provenanceSha256: z.ZodPipe<z.ZodString, z.ZodTransform<`sha256:${string}`, string>>;
|
|
29
|
+
source: z.ZodObject<{
|
|
30
|
+
name: z.ZodOptional<z.ZodString>;
|
|
31
|
+
uri: z.ZodString;
|
|
32
|
+
digest: z.ZodObject<{
|
|
33
|
+
sha256: z.ZodString;
|
|
34
|
+
}, z.core.$strict>;
|
|
35
|
+
mediaType: z.ZodOptional<z.ZodString>;
|
|
36
|
+
downloadLocation: z.ZodOptional<z.ZodString>;
|
|
37
|
+
}, z.core.$strict>;
|
|
38
|
+
license: z.ZodObject<{
|
|
39
|
+
name: z.ZodOptional<z.ZodString>;
|
|
40
|
+
uri: z.ZodString;
|
|
41
|
+
digest: z.ZodObject<{
|
|
42
|
+
sha256: z.ZodString;
|
|
43
|
+
}, z.core.$strict>;
|
|
44
|
+
mediaType: z.ZodOptional<z.ZodString>;
|
|
45
|
+
downloadLocation: z.ZodOptional<z.ZodString>;
|
|
46
|
+
}, z.core.$strict>;
|
|
47
|
+
attribution: z.ZodObject<{
|
|
48
|
+
name: z.ZodOptional<z.ZodString>;
|
|
49
|
+
uri: z.ZodString;
|
|
50
|
+
digest: z.ZodObject<{
|
|
51
|
+
sha256: z.ZodString;
|
|
52
|
+
}, z.core.$strict>;
|
|
53
|
+
mediaType: z.ZodOptional<z.ZodString>;
|
|
54
|
+
downloadLocation: z.ZodOptional<z.ZodString>;
|
|
55
|
+
}, z.core.$strict>;
|
|
56
|
+
publishedAt: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
|
|
57
|
+
}, z.core.$strict>;
|
|
58
|
+
export type BinarySourceManifestEntry = z.infer<typeof BinarySourceManifestEntrySchema>;
|
|
59
|
+
export declare const BinaryAdmissionIndexEntrySchema: z.ZodObject<{
|
|
60
|
+
protocol: z.ZodLiteral<"https://spec.jinn.network/binary-judgment/admission-index-entry/v1">;
|
|
61
|
+
admissionManifestSha256: z.ZodPipe<z.ZodString, z.ZodTransform<`sha256:${string}`, string>>;
|
|
62
|
+
itemSha256: z.ZodPipe<z.ZodString, z.ZodTransform<`sha256:${string}`, string>>;
|
|
63
|
+
labelResolutionSha256: z.ZodPipe<z.ZodString, z.ZodTransform<`sha256:${string}`, string>>;
|
|
64
|
+
analysisContextSha256: z.ZodPipe<z.ZodString, z.ZodTransform<`sha256:${string}`, string>>;
|
|
65
|
+
}, z.core.$strict>;
|
|
66
|
+
export type BinaryAdmissionIndexEntry = z.infer<typeof BinaryAdmissionIndexEntrySchema>;
|
|
67
|
+
export declare const BinaryItemBankIntakeExtensionSchema: z.ZodObject<{
|
|
68
|
+
profile: z.ZodLiteral<"https://spec.jinn.network/task-profiles/binary-judgment/2.0">;
|
|
69
|
+
itemBankSha256: z.ZodPipe<z.ZodString, z.ZodTransform<`sha256:${string}`, string>>;
|
|
70
|
+
sourceManifestSha256: z.ZodPipe<z.ZodString, z.ZodTransform<`sha256:${string}`, string>>;
|
|
71
|
+
admissionIndexSha256: z.ZodPipe<z.ZodString, z.ZodTransform<`sha256:${string}`, string>>;
|
|
72
|
+
admissionManifestSha256: z.ZodPipe<z.ZodString, z.ZodTransform<`sha256:${string}`, string>>;
|
|
73
|
+
replacementLedgerSha256: z.ZodPipe<z.ZodString, z.ZodTransform<`sha256:${string}`, string>>;
|
|
74
|
+
}, z.core.$strict>;
|
|
75
|
+
export type BinaryItemBankIntakeExtension = z.infer<typeof BinaryItemBankIntakeExtensionSchema>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { isCalendarStrictRfc3339 } from "@jinn-network/benchmarking-records";
|
|
4
|
+
import { BINARY_JUDGMENT_TIMESTAMP_PATTERN } from "@jinn-network/task-execution-profiles";
|
|
5
|
+
export const BINARY_ITEM_BANK_ENTRY_PROTOCOL = "https://spec.jinn.network/binary-judgment/item-bank-entry/v1";
|
|
6
|
+
export const BINARY_SOURCE_MANIFEST_ENTRY_PROTOCOL = "https://spec.jinn.network/binary-judgment/source-manifest-entry/v1";
|
|
7
|
+
export const BINARY_ADMISSION_INDEX_ENTRY_PROTOCOL = "https://spec.jinn.network/binary-judgment/admission-index-entry/v1";
|
|
8
|
+
export const BINARY_ITEM_BANK_INTAKE_EXTENSION = "https://product.jinn.network/extensions/binary-judgment-intake/v1";
|
|
9
|
+
const DigestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/u).transform((value) => value);
|
|
10
|
+
const Sha256HexSchema = z.string().regex(/^[0-9a-f]{64}$/u);
|
|
11
|
+
/**
|
|
12
|
+
* RFC 3339 instant SHAPE, seconds precision, no fractional part. The pattern itself is imported
|
|
13
|
+
* from the profiles package rather than restated, so the payload contract and this manifest row
|
|
14
|
+
* cannot drift apart. Shape only: calendar strictness is the `publishedAt` refinement below.
|
|
15
|
+
*/
|
|
16
|
+
const BinaryJudgmentTimestampShapeSchema = z.string().regex(new RegExp(BINARY_JUDGMENT_TIMESTAMP_PATTERN, "u"));
|
|
17
|
+
const ItemPayloadSchema = z.strictObject({
|
|
18
|
+
itemId: z.string().regex(/^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u),
|
|
19
|
+
question: z.string(),
|
|
20
|
+
referenceAnswer: z.string(),
|
|
21
|
+
candidateAnswer: z.string(),
|
|
22
|
+
evidence: z.string().optional(),
|
|
23
|
+
provenance: z.strictObject({
|
|
24
|
+
sourceCommitment: DigestSchema,
|
|
25
|
+
timestamp: BinaryJudgmentTimestampShapeSchema,
|
|
26
|
+
}),
|
|
27
|
+
sources: z.array(z.strictObject({ digest: z.strictObject({ sha256: Sha256HexSchema }) })).min(1),
|
|
28
|
+
});
|
|
29
|
+
const SourceDescriptorSchema = z.strictObject({
|
|
30
|
+
name: z.string().min(1).optional(),
|
|
31
|
+
uri: z.string().min(1),
|
|
32
|
+
digest: z.strictObject({ sha256: Sha256HexSchema }),
|
|
33
|
+
mediaType: z.string().min(1).optional(),
|
|
34
|
+
downloadLocation: z.string().min(1).optional(),
|
|
35
|
+
});
|
|
36
|
+
export const BinaryItemBankEntrySchema = z.strictObject({
|
|
37
|
+
protocol: z.literal(BINARY_ITEM_BANK_ENTRY_PROTOCOL),
|
|
38
|
+
item: ItemPayloadSchema,
|
|
39
|
+
});
|
|
40
|
+
export const BinarySourceManifestEntrySchema = z.strictObject({
|
|
41
|
+
protocol: z.literal(BINARY_SOURCE_MANIFEST_ENTRY_PROTOCOL),
|
|
42
|
+
provenanceSha256: DigestSchema,
|
|
43
|
+
source: SourceDescriptorSchema,
|
|
44
|
+
license: SourceDescriptorSchema,
|
|
45
|
+
attribution: SourceDescriptorSchema,
|
|
46
|
+
// The source publication instant lives on the source row, not the item: two items drawn from
|
|
47
|
+
// one source then structurally carry the same cluster key (provenanceSha256) and the same
|
|
48
|
+
// publishedAt, instead of a rule someone has to check by hand. A per-item timestamp would let a
|
|
49
|
+
// bank author encode an ordering that correlates with the label.
|
|
50
|
+
publishedAt: BinaryJudgmentTimestampShapeSchema.refine(isCalendarStrictRfc3339, "publishedAt must be a calendar-strict RFC 3339 instant at seconds precision"),
|
|
51
|
+
}).superRefine((entry, ctx) => {
|
|
52
|
+
if (`sha256:${entry.source.digest.sha256}` !== entry.provenanceSha256) {
|
|
53
|
+
ctx.addIssue({ code: "custom", path: ["source", "digest", "sha256"], message: "source digest must equal provenanceSha256" });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
export const BinaryAdmissionIndexEntrySchema = z.strictObject({
|
|
57
|
+
protocol: z.literal(BINARY_ADMISSION_INDEX_ENTRY_PROTOCOL),
|
|
58
|
+
admissionManifestSha256: DigestSchema,
|
|
59
|
+
itemSha256: DigestSchema,
|
|
60
|
+
labelResolutionSha256: DigestSchema,
|
|
61
|
+
analysisContextSha256: DigestSchema,
|
|
62
|
+
});
|
|
63
|
+
export const BinaryItemBankIntakeExtensionSchema = z.strictObject({
|
|
64
|
+
profile: z.literal("https://spec.jinn.network/task-profiles/binary-judgment/2.0"),
|
|
65
|
+
itemBankSha256: DigestSchema,
|
|
66
|
+
sourceManifestSha256: DigestSchema,
|
|
67
|
+
admissionIndexSha256: DigestSchema,
|
|
68
|
+
admissionManifestSha256: DigestSchema,
|
|
69
|
+
replacementLedgerSha256: DigestSchema,
|
|
70
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface AdmissionVerdictStatementView {
|
|
2
|
+
readonly evaluatorId: string;
|
|
3
|
+
readonly verdict: "pass" | "fail" | "inconclusive";
|
|
4
|
+
readonly evaluationSpecificationSha256: string;
|
|
5
|
+
readonly measurements: Readonly<Record<string, boolean | number | string>>;
|
|
6
|
+
readonly evaluatedAt: string;
|
|
7
|
+
readonly evidence?: readonly {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly sha256: string;
|
|
10
|
+
readonly mediaType?: string;
|
|
11
|
+
}[];
|
|
12
|
+
readonly limitations?: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export declare function readAdmissionVerdictEnvelope(envelopeBytes: Uint8Array): AdmissionVerdictStatementView;
|
|
15
|
+
export declare function readOrderedAdmissionVerdictMeasurements(envelopeBytes: Uint8Array): readonly {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly value: boolean | number | string;
|
|
18
|
+
}[];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { VERDICT_DSSE_PAYLOAD_TYPE } from "@jinn-network/task-execution-profiles";
|
|
4
|
+
import { parseExactDsseEnvelope } from "@jinn-network/trust-core";
|
|
5
|
+
const MeasurementValueSchema = z.union([z.string(), z.number(), z.boolean()]);
|
|
6
|
+
const VerdictStatementSchema = z.looseObject({
|
|
7
|
+
predicateType: z.string().min(1),
|
|
8
|
+
predicate: z.looseObject({
|
|
9
|
+
evaluator: z.looseObject({ id: z.string().min(1) }),
|
|
10
|
+
verdict: z.enum(["pass", "fail", "inconclusive"]),
|
|
11
|
+
evaluationSpecification: z.looseObject({
|
|
12
|
+
digest: z.looseObject({ sha256: z.string().regex(/^[a-f0-9]{64}$/u) }),
|
|
13
|
+
}),
|
|
14
|
+
evaluationMethod: z.looseObject({
|
|
15
|
+
name: z.string().min(1),
|
|
16
|
+
digest: z.looseObject({ sha256: z.string().regex(/^[a-f0-9]{64}$/u) }),
|
|
17
|
+
}).optional(),
|
|
18
|
+
measurements: z.array(z.looseObject({ name: z.string().min(1), value: MeasurementValueSchema })).optional(),
|
|
19
|
+
evidence: z.array(z.looseObject({
|
|
20
|
+
name: z.string().min(1),
|
|
21
|
+
digest: z.looseObject({ sha256: z.string().regex(/^[a-f0-9]{64}$/u) }),
|
|
22
|
+
mediaType: z.string().optional(),
|
|
23
|
+
})).optional(),
|
|
24
|
+
evaluatedAt: z.string().min(1),
|
|
25
|
+
limitations: z.array(z.string()).optional(),
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
function statement(envelopeBytes) {
|
|
29
|
+
const envelope = parseExactDsseEnvelope(envelopeBytes);
|
|
30
|
+
if (envelope.payloadType !== VERDICT_DSSE_PAYLOAD_TYPE)
|
|
31
|
+
throw new Error("wrong Result Evaluation payload type");
|
|
32
|
+
let json;
|
|
33
|
+
try {
|
|
34
|
+
json = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(envelope.payloadBytes));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new Error("Result Evaluation payload is not UTF-8 JSON");
|
|
38
|
+
}
|
|
39
|
+
const parsed = VerdictStatementSchema.safeParse(json);
|
|
40
|
+
if (!parsed.success)
|
|
41
|
+
throw new Error("Result Evaluation payload is outside the closed view");
|
|
42
|
+
return parsed.data;
|
|
43
|
+
}
|
|
44
|
+
export function readAdmissionVerdictEnvelope(envelopeBytes) {
|
|
45
|
+
const { predicate } = statement(envelopeBytes);
|
|
46
|
+
const measurements = {};
|
|
47
|
+
for (const measurement of predicate.measurements ?? [])
|
|
48
|
+
measurements[measurement.name] = measurement.value;
|
|
49
|
+
return {
|
|
50
|
+
evaluatorId: predicate.evaluator.id,
|
|
51
|
+
verdict: predicate.verdict,
|
|
52
|
+
evaluationSpecificationSha256: predicate.evaluationSpecification.digest.sha256,
|
|
53
|
+
measurements,
|
|
54
|
+
evaluatedAt: predicate.evaluatedAt,
|
|
55
|
+
...(predicate.evidence === undefined ? {} : {
|
|
56
|
+
evidence: predicate.evidence.map((entry) => ({
|
|
57
|
+
name: entry.name,
|
|
58
|
+
sha256: entry.digest.sha256,
|
|
59
|
+
...(entry.mediaType === undefined ? {} : { mediaType: entry.mediaType }),
|
|
60
|
+
})),
|
|
61
|
+
}),
|
|
62
|
+
...(predicate.limitations === undefined ? {} : { limitations: predicate.limitations }),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function readOrderedAdmissionVerdictMeasurements(envelopeBytes) {
|
|
66
|
+
const measurements = statement(envelopeBytes).predicate.measurements ?? [];
|
|
67
|
+
if (new Set(measurements.map((measurement) => measurement.name)).size !== measurements.length) {
|
|
68
|
+
throw new Error("Result Evaluation carries duplicate measurement names");
|
|
69
|
+
}
|
|
70
|
+
return measurements.map((measurement) => ({ name: measurement.name, value: measurement.value }));
|
|
71
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export declare class ScreeningSampleError extends Error {
|
|
2
|
+
readonly path: string;
|
|
3
|
+
constructor(path: string, detail: string);
|
|
4
|
+
}
|
|
5
|
+
export interface ScreeningSampleParams {
|
|
6
|
+
/** The table's `itemSha256` values -- the row identity set. Order is irrelevant; see R-4. */
|
|
7
|
+
readonly itemSha256s: readonly string[];
|
|
8
|
+
/** Non-empty seed string. Enters the HMAC key as literal UTF-8 text, concatenated with `poolDigest`. */
|
|
9
|
+
readonly sampleSeed: string;
|
|
10
|
+
/** Positive integer, `<= itemSha256s.length`. The sample is the first `sampleSize` of the full order. */
|
|
11
|
+
readonly sampleSize: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ScreeningSampleResult {
|
|
14
|
+
/** `sha256:<64 lowercase hex>` -- the digest of the sorted, unique identity set alone. */
|
|
15
|
+
readonly poolDigest: string;
|
|
16
|
+
/** Every `itemSha256` in the pool, ascending by HMAC stream (unsigned byte order), ties by code-unit order. */
|
|
17
|
+
readonly order: readonly string[];
|
|
18
|
+
/** `order.slice(0, sampleSize)`. */
|
|
19
|
+
readonly sample: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One entry in the HMAC-stream ordering: the value actually compared (`stream`) paired with the
|
|
23
|
+
* tie-break key (`itemSha256`). Exported so the sort itself is directly testable -- a real
|
|
24
|
+
* HMAC-SHA256 collision cannot be constructed to exercise the tie-break in an end-to-end call.
|
|
25
|
+
*/
|
|
26
|
+
export interface ScreeningStreamEntry {
|
|
27
|
+
readonly itemSha256: string;
|
|
28
|
+
readonly stream: Uint8Array;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* `stream` compared as 32 unsigned bytes ascending; ties broken by `itemSha256` code-unit order.
|
|
32
|
+
* `Uint8Array` indexing always yields an unsigned 0-255 number in JavaScript, so a direct numeric
|
|
33
|
+
* subtraction per element is already the unsigned comparison -- no bit-masking is needed, but it
|
|
34
|
+
* must not be replaced by a signed-view read (e.g. `Int8Array`) or by comparing a hex rendering.
|
|
35
|
+
*/
|
|
36
|
+
export declare function compareScreeningStreamEntries(left: ScreeningStreamEntry, right: ScreeningStreamEntry): number;
|
|
37
|
+
/**
|
|
38
|
+
* `sha256:` followed by the 64 lowercase hex digits of the SHA-256 of the canonical-JSON bytes of
|
|
39
|
+
* `itemSha256s`, code-unit sorted AND deduplicated, so the digest depends on the identity SET and
|
|
40
|
+
* not on input order or on repetition. §6.5 says "sorted and unique" and this function is the one
|
|
41
|
+
* that has to mean it: `computeScreeningSample` refuses duplicates before it ever calls here, so
|
|
42
|
+
* the dedupe is a no-op on every in-repo path and moves no digest, but this is exported precisely
|
|
43
|
+
* so a cross-language implementer can check `poolDigest` in ISOLATION, and in isolation "sorted"
|
|
44
|
+
* alone would have silently accepted a duplicated identity and produced a digest no other
|
|
45
|
+
* implementation reproduces. Otherwise does not validate its input; `computeScreeningSample`
|
|
46
|
+
* owns that.
|
|
47
|
+
*/
|
|
48
|
+
export declare function computeScreeningPoolDigest(itemSha256s: readonly string[]): string;
|
|
49
|
+
/**
|
|
50
|
+
* Procedure `screening-sample/1` (§6.5). Refuses (throws `ScreeningSampleError`) when:
|
|
51
|
+
* - `itemSha256s` is empty, contains a duplicate, or contains an entry not matching
|
|
52
|
+
* `^sha256:[0-9a-f]{64}$`;
|
|
53
|
+
* - `sampleSeed` is empty;
|
|
54
|
+
* - `sampleSize` is not a positive integer, or exceeds `itemSha256s.length`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function computeScreeningSample(params: ScreeningSampleParams): ScreeningSampleResult;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* `screening-sample/1` -- the verifier's independent recomputation of binary-judgment human-review
|
|
4
|
+
* sample membership (judge-path program packet P6, spec
|
|
5
|
+
* `docs/superpowers/specs/2026-08-19-judge-path-delta-contracts.md` §6.5 "(1) Sample membership").
|
|
6
|
+
*
|
|
7
|
+
* The verifier does not execute the operator's sealed sampling script; it recomputes membership
|
|
8
|
+
* from this specified procedure instead, and the recomputation wins on disagreement. That only
|
|
9
|
+
* works if the procedure is reimplementable in any language from the spec paragraph alone (R-4), so
|
|
10
|
+
* every encoding choice below is spelled out rather than left to convention:
|
|
11
|
+
*
|
|
12
|
+
* - **`poolDigest` binds the row *identity* set, never the rows.** It is `sha256:` followed by the
|
|
13
|
+
* 64 lowercase hex digits of the SHA-256 of the canonical-JSON bytes of the `itemSha256` values
|
|
14
|
+
* alone, code-unit sorted and unique -- not the rows themselves. Binding the rows is circular and
|
|
15
|
+
* uncomputable: a row carries `handChecked`, which rows carry it depends on the sample, and the
|
|
16
|
+
* sample depends on the digest. Binding the identity set breaks the cycle, because `itemSha256` is
|
|
17
|
+
* exactly the part of a row hand-checking never touches.
|
|
18
|
+
* - **The key is text, not raw digest bytes.** The HMAC key is `utf8(sampleSeed || poolDigest)`,
|
|
19
|
+
* where `poolDigest` enters in its `sha256:`-prefixed lowercase-hex string form -- the exact
|
|
20
|
+
* 64-hex-digit string, concatenated onto `sampleSeed`, then UTF-8 encoded. Keying on the digest's
|
|
21
|
+
* 32 raw bytes instead would introduce the only binary-versus-text distinction in an otherwise
|
|
22
|
+
* all-text procedure, which is exactly the kind of thing a second implementer gets wrong.
|
|
23
|
+
* - **No delimiter separates `sampleSeed` and `poolDigest`.** `poolDigest` is a fixed 71-character
|
|
24
|
+
* suffix (`sha256:` + 64 hex digits), so a delimiter would just be a second convention to get
|
|
25
|
+
* wrong.
|
|
26
|
+
* - **The HMAC message is `utf8(itemSha256)`**, the same `sha256:`-prefixed lowercase-hex string
|
|
27
|
+
* used everywhere else in the spec -- never raw digest bytes.
|
|
28
|
+
* - **Stream comparison is over 32 *unsigned* bytes.** `Uint8Array` elements are unsigned by
|
|
29
|
+
* construction in JavaScript (0-255), so a plain per-element numeric comparison is correct; the
|
|
30
|
+
* classic bug is reading the same bytes through a signed 8-bit view (where 0x80 becomes -128)
|
|
31
|
+
* or via a hex-string shortcut that doesn't preserve this. Ties are broken by `itemSha256` in
|
|
32
|
+
* code-unit order (never `localeCompare`, which is locale-dependent).
|
|
33
|
+
*
|
|
34
|
+
* This module does no filesystem or network I/O, throws `ScreeningSampleError` on any invalid
|
|
35
|
+
* input, and owns exactly its own input validity -- not the four other §6.5 checks (coverage,
|
|
36
|
+
* required hand checks, agreement rate, per-row admission), which read the recomputed sample but
|
|
37
|
+
* live elsewhere in this package.
|
|
38
|
+
*/
|
|
39
|
+
import { createHash, createHmac } from "node:crypto";
|
|
40
|
+
import { canonicalJsonBytes, compareCodeUnitStrings } from "@jinn-network/task-execution-profiles";
|
|
41
|
+
const SHA256_DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
42
|
+
export class ScreeningSampleError extends Error {
|
|
43
|
+
path;
|
|
44
|
+
constructor(path, detail) {
|
|
45
|
+
super(`${path}: ${detail}`);
|
|
46
|
+
this.name = "ScreeningSampleError";
|
|
47
|
+
this.path = path;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function fail(path, detail) {
|
|
51
|
+
throw new ScreeningSampleError(path, detail);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* `stream` compared as 32 unsigned bytes ascending; ties broken by `itemSha256` code-unit order.
|
|
55
|
+
* `Uint8Array` indexing always yields an unsigned 0-255 number in JavaScript, so a direct numeric
|
|
56
|
+
* subtraction per element is already the unsigned comparison -- no bit-masking is needed, but it
|
|
57
|
+
* must not be replaced by a signed-view read (e.g. `Int8Array`) or by comparing a hex rendering.
|
|
58
|
+
*/
|
|
59
|
+
export function compareScreeningStreamEntries(left, right) {
|
|
60
|
+
const length = Math.min(left.stream.length, right.stream.length);
|
|
61
|
+
for (let index = 0; index < length; index += 1) {
|
|
62
|
+
const delta = left.stream[index] - right.stream[index];
|
|
63
|
+
if (delta !== 0)
|
|
64
|
+
return delta;
|
|
65
|
+
}
|
|
66
|
+
if (left.stream.length !== right.stream.length)
|
|
67
|
+
return left.stream.length - right.stream.length;
|
|
68
|
+
return compareCodeUnitStrings(left.itemSha256, right.itemSha256);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* `sha256:` followed by the 64 lowercase hex digits of the SHA-256 of the canonical-JSON bytes of
|
|
72
|
+
* `itemSha256s`, code-unit sorted AND deduplicated, so the digest depends on the identity SET and
|
|
73
|
+
* not on input order or on repetition. §6.5 says "sorted and unique" and this function is the one
|
|
74
|
+
* that has to mean it: `computeScreeningSample` refuses duplicates before it ever calls here, so
|
|
75
|
+
* the dedupe is a no-op on every in-repo path and moves no digest, but this is exported precisely
|
|
76
|
+
* so a cross-language implementer can check `poolDigest` in ISOLATION, and in isolation "sorted"
|
|
77
|
+
* alone would have silently accepted a duplicated identity and produced a digest no other
|
|
78
|
+
* implementation reproduces. Otherwise does not validate its input; `computeScreeningSample`
|
|
79
|
+
* owns that.
|
|
80
|
+
*/
|
|
81
|
+
export function computeScreeningPoolDigest(itemSha256s) {
|
|
82
|
+
const sorted = [...new Set(itemSha256s)].sort(compareCodeUnitStrings);
|
|
83
|
+
const bytes = canonicalJsonBytes(sorted);
|
|
84
|
+
const hex = createHash("sha256").update(bytes).digest("hex");
|
|
85
|
+
return `sha256:${hex}`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Procedure `screening-sample/1` (§6.5). Refuses (throws `ScreeningSampleError`) when:
|
|
89
|
+
* - `itemSha256s` is empty, contains a duplicate, or contains an entry not matching
|
|
90
|
+
* `^sha256:[0-9a-f]{64}$`;
|
|
91
|
+
* - `sampleSeed` is empty;
|
|
92
|
+
* - `sampleSize` is not a positive integer, or exceeds `itemSha256s.length`.
|
|
93
|
+
*/
|
|
94
|
+
export function computeScreeningSample(params) {
|
|
95
|
+
const { itemSha256s, sampleSeed, sampleSize } = params;
|
|
96
|
+
itemSha256s.forEach((itemSha256, index) => {
|
|
97
|
+
if (!SHA256_DIGEST_PATTERN.test(itemSha256)) {
|
|
98
|
+
fail(`itemSha256s[${index}]`, `must match ^sha256:[0-9a-f]{64}$, got ${JSON.stringify(itemSha256)}`);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
if (itemSha256s.length === 0)
|
|
102
|
+
fail("itemSha256s", "identity set must be non-empty");
|
|
103
|
+
if (new Set(itemSha256s).size !== itemSha256s.length) {
|
|
104
|
+
fail("itemSha256s", "identity set must not contain duplicate itemSha256 values");
|
|
105
|
+
}
|
|
106
|
+
if (sampleSeed.length === 0)
|
|
107
|
+
fail("sampleSeed", "must be non-empty");
|
|
108
|
+
if (!Number.isInteger(sampleSize) || sampleSize <= 0) {
|
|
109
|
+
fail("sampleSize", `must be a positive integer, got ${JSON.stringify(sampleSize)}`);
|
|
110
|
+
}
|
|
111
|
+
if (sampleSize > itemSha256s.length) {
|
|
112
|
+
fail("sampleSize", `must not exceed the pool size (${itemSha256s.length}), got ${sampleSize}`);
|
|
113
|
+
}
|
|
114
|
+
const poolDigest = computeScreeningPoolDigest(itemSha256s);
|
|
115
|
+
const key = Buffer.from(`${sampleSeed}${poolDigest}`, "utf8");
|
|
116
|
+
const entries = itemSha256s.map((itemSha256) => ({
|
|
117
|
+
itemSha256,
|
|
118
|
+
stream: new Uint8Array(createHmac("sha256", key).update(Buffer.from(itemSha256, "utf8")).digest()),
|
|
119
|
+
}));
|
|
120
|
+
const order = [...entries].sort(compareScreeningStreamEntries).map((entry) => entry.itemSha256);
|
|
121
|
+
return {
|
|
122
|
+
poolDigest,
|
|
123
|
+
order,
|
|
124
|
+
sample: order.slice(0, sampleSize),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type BinaryJudgmentAdmissionManifest, type HumanReviewReplacementLedger } from "./contracts.js";
|
|
2
|
+
export type AdmissionSha256 = `sha256:${string}`;
|
|
3
|
+
export declare const BINARY_JUDGMENT_ADMISSION_RECORD_ROLES: readonly ["admission-manifest", "replacement-ledger", "source-item", "label-resolution", "analysis-context", "human-review-evaluation-spec", "human-review-form", "human-review-packet", "human-review-response", "human-review-verdict", "reviewer-roster", "review-visibility-receipt", "review-reveal-receipt", "operator-assertion", "screening-table", "screening-reveal-receipt", "screening-instrument", "screening-sampling-script", "screening-raw-outputs"];
|
|
4
|
+
export type BinaryJudgmentAdmissionRecordRole = (typeof BINARY_JUDGMENT_ADMISSION_RECORD_ROLES)[number];
|
|
5
|
+
export type AdmissionAuthorityRole = "roster-attestor" | "truth-reveal-attestor" | "operator-truth-attestor";
|
|
6
|
+
export interface BinaryJudgmentAdmissionClosurePorts {
|
|
7
|
+
/** Resolve immutable bytes by their prefixed digest. The verifier re-hashes every result. */
|
|
8
|
+
resolveExactRecord(digest: AdmissionSha256): Uint8Array;
|
|
9
|
+
/** Verify the one reviewer signature against the evaluator/key binding trusted by the caller. */
|
|
10
|
+
verifyReviewerSignature(input: {
|
|
11
|
+
readonly envelopeBytes: Uint8Array;
|
|
12
|
+
readonly evaluatorId: string;
|
|
13
|
+
readonly keyId: string;
|
|
14
|
+
}): boolean;
|
|
15
|
+
/** Verify the one product-authority signature under its payload-declared, separated role. */
|
|
16
|
+
verifyAuthoritySignature(input: {
|
|
17
|
+
readonly envelopeBytes: Uint8Array;
|
|
18
|
+
readonly keyId: string;
|
|
19
|
+
readonly role: AdmissionAuthorityRole;
|
|
20
|
+
}): boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface VerifyBinaryJudgmentAdmissionClosureInput {
|
|
23
|
+
readonly admissionManifestSha256: AdmissionSha256;
|
|
24
|
+
readonly expectedDraftId: string;
|
|
25
|
+
}
|
|
26
|
+
export interface VerifiedBinaryJudgmentAdmissionItem {
|
|
27
|
+
readonly itemSha256: AdmissionSha256;
|
|
28
|
+
readonly itemId: string;
|
|
29
|
+
readonly truthLabel: "CORRECT" | "WRONG";
|
|
30
|
+
readonly candidateClass: string;
|
|
31
|
+
readonly stratum: string;
|
|
32
|
+
readonly truthAdmission: "two-human-unanimous" | "operator-only" | "screened-operator-sampled";
|
|
33
|
+
readonly labelResolutionSha256: AdmissionSha256;
|
|
34
|
+
readonly analysisContextSha256: AdmissionSha256;
|
|
35
|
+
}
|
|
36
|
+
export interface VerifiedBinaryJudgmentAdmissionExclusion {
|
|
37
|
+
readonly itemSha256: AdmissionSha256;
|
|
38
|
+
readonly itemId: string;
|
|
39
|
+
readonly candidateClass: string;
|
|
40
|
+
readonly stratum: string;
|
|
41
|
+
readonly reason: "review-disagreement" | "review-indeterminate" | "review-incomplete" | "screening-disagreement" | "screening-indeterminate" | "screening-hand-excluded";
|
|
42
|
+
readonly replacementItemSha256: AdmissionSha256;
|
|
43
|
+
}
|
|
44
|
+
export interface VerifiedBinaryJudgmentAdmissionClosure {
|
|
45
|
+
readonly manifestSha256: AdmissionSha256;
|
|
46
|
+
readonly manifest: BinaryJudgmentAdmissionManifest;
|
|
47
|
+
readonly replacementLedger: HumanReviewReplacementLedger;
|
|
48
|
+
readonly publicationGrade: boolean;
|
|
49
|
+
readonly classes: readonly string[];
|
|
50
|
+
readonly strata: readonly string[];
|
|
51
|
+
readonly accepted: readonly VerifiedBinaryJudgmentAdmissionItem[];
|
|
52
|
+
readonly excluded: readonly VerifiedBinaryJudgmentAdmissionExclusion[];
|
|
53
|
+
/** Complete, sorted digest inventory reachable from the manifest closure. */
|
|
54
|
+
readonly reachableSha256s: readonly AdmissionSha256[];
|
|
55
|
+
/** Same closure with semantic roles assigned by the exact traversal that authenticated it. */
|
|
56
|
+
readonly reachableRecords: readonly {
|
|
57
|
+
readonly sha256: AdmissionSha256;
|
|
58
|
+
readonly roles: readonly BinaryJudgmentAdmissionRecordRole[];
|
|
59
|
+
}[];
|
|
60
|
+
/** Present iff `manifest.truthAdmission === "screened-operator-sampled"` (spec §6.5 check (3)). */
|
|
61
|
+
readonly screening?: {
|
|
62
|
+
readonly sampleAgreementRate: number;
|
|
63
|
+
readonly instrumentSha256: AdmissionSha256;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export declare class BinaryJudgmentAdmissionClosureError extends Error {
|
|
67
|
+
readonly path: string;
|
|
68
|
+
constructor(path: string, detail: string, options?: ErrorOptions);
|
|
69
|
+
}
|
|
70
|
+
export interface VerifiedBinaryJudgmentReviewerResult {
|
|
71
|
+
readonly evaluatorId: string;
|
|
72
|
+
readonly keyId: string;
|
|
73
|
+
readonly label: "CORRECT" | "WRONG" | "indeterminate";
|
|
74
|
+
readonly complete: boolean;
|
|
75
|
+
readonly completedAt: string;
|
|
76
|
+
readonly itemId: string;
|
|
77
|
+
readonly packetSha256: AdmissionSha256;
|
|
78
|
+
readonly visibilityReceiptSha256: AdmissionSha256;
|
|
79
|
+
}
|
|
80
|
+
/** The reviewer Result Evaluation verifier shared by admission construction and closure replay. */
|
|
81
|
+
export declare function verifyBinaryJudgmentReviewerResult(input: {
|
|
82
|
+
readonly verdictSha256: AdmissionSha256;
|
|
83
|
+
readonly expectedItemSha256: AdmissionSha256;
|
|
84
|
+
}, ports: BinaryJudgmentAdmissionClosurePorts): VerifiedBinaryJudgmentReviewerResult;
|
|
85
|
+
/**
|
|
86
|
+
* Portable, fail-closed replay of the entire binary-judgment admission graph. The caller supplies
|
|
87
|
+
* immutable record resolution and role-separated trust; no candidate truth is caller-authored.
|
|
88
|
+
*/
|
|
89
|
+
export declare function verifyBinaryJudgmentAdmissionClosure(input: VerifyBinaryJudgmentAdmissionClosureInput, ports: BinaryJudgmentAdmissionClosurePorts): VerifiedBinaryJudgmentAdmissionClosure;
|