@hol-org/cigar 0.9.4
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/LICENSE +201 -0
- package/NOTICE +5 -0
- package/README.md +68 -0
- package/dist/client.d.ts +16 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +839 -0
- package/dist/client.js.map +1 -0
- package/dist/digest.d.ts +21 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +406 -0
- package/dist/digest.js.map +1 -0
- package/dist/errors.d.ts +35 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +54 -0
- package/dist/errors.js.map +1 -0
- package/dist/examples/quickstart.d.ts +2 -0
- package/dist/examples/quickstart.d.ts.map +1 -0
- package/dist/examples/quickstart.js +32 -0
- package/dist/examples/quickstart.js.map +1 -0
- package/dist/examples/two-agent-observer.d.ts +9 -0
- package/dist/examples/two-agent-observer.d.ts.map +1 -0
- package/dist/examples/two-agent-observer.js +59 -0
- package/dist/examples/two-agent-observer.js.map +1 -0
- package/dist/examples/verify-shared-bundle.d.ts +2 -0
- package/dist/examples/verify-shared-bundle.d.ts.map +1 -0
- package/dist/examples/verify-shared-bundle.js +13 -0
- package/dist/examples/verify-shared-bundle.js.map +1 -0
- package/dist/generated/cigar_service_pb.d.ts +642 -0
- package/dist/generated/cigar_service_pb.d.ts.map +1 -0
- package/dist/generated/cigar_service_pb.js +55 -0
- package/dist/generated/cigar_service_pb.js.map +1 -0
- package/dist/generated/context_abi_pb.d.ts +3429 -0
- package/dist/generated/context_abi_pb.d.ts.map +1 -0
- package/dist/generated/context_abi_pb.js +1386 -0
- package/dist/generated/context_abi_pb.js.map +1 -0
- package/dist/generated/errors.d.ts +242 -0
- package/dist/generated/errors.d.ts.map +1 -0
- package/dist/generated/errors.js +38 -0
- package/dist/generated/errors.js.map +1 -0
- package/dist/generated/generated/error_codes_pb.d.ts +188 -0
- package/dist/generated/generated/error_codes_pb.d.ts.map +1 -0
- package/dist/generated/generated/error_codes_pb.js +191 -0
- package/dist/generated/generated/error_codes_pb.js.map +1 -0
- package/dist/generated/models.d.ts +472 -0
- package/dist/generated/models.d.ts.map +1 -0
- package/dist/generated/models.js +314 -0
- package/dist/generated/models.js.map +1 -0
- package/dist/generated/operations.d.ts +912 -0
- package/dist/generated/operations.d.ts.map +1 -0
- package/dist/generated/operations.js +50 -0
- package/dist/generated/operations.js.map +1 -0
- package/dist/idempotency.d.ts +3 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +16 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/release.json +6 -0
- package/dist/types.d.ts +115 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/verify-replay.d.ts +3 -0
- package/dist/verify-replay.d.ts.map +1 -0
- package/dist/verify-replay.js +391 -0
- package/dist/verify-replay.js.map +1 -0
- package/dist/verify-vectors.d.ts +3 -0
- package/dist/verify-vectors.d.ts.map +1 -0
- package/dist/verify-vectors.js +383 -0
- package/dist/verify-vectors.js.map +1 -0
- package/dist/workflow-session.d.ts +93 -0
- package/dist/workflow-session.d.ts.map +1 -0
- package/dist/workflow-session.js +550 -0
- package/dist/workflow-session.js.map +1 -0
- package/fixtures/semantic-bundle-v1.json +13 -0
- package/package.json +60 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/** Copy-safe high-level HTTP wire records. */
|
|
2
|
+
export interface PathParameter {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly value: string;
|
|
5
|
+
}
|
|
6
|
+
export interface OperationRequest {
|
|
7
|
+
readonly payloadCbor?: Uint8Array;
|
|
8
|
+
readonly pathParameters?: readonly PathParameter[];
|
|
9
|
+
readonly idempotencyKey?: string;
|
|
10
|
+
readonly expectedRevision?: string;
|
|
11
|
+
readonly dryRun?: boolean;
|
|
12
|
+
readonly pageCursor?: string;
|
|
13
|
+
readonly pageSize?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface TypedOperationRequest<T> {
|
|
16
|
+
readonly payload: T;
|
|
17
|
+
readonly idempotencyKey?: string;
|
|
18
|
+
readonly expectedRevision?: string;
|
|
19
|
+
readonly dryRun?: boolean;
|
|
20
|
+
readonly pageCursor?: string;
|
|
21
|
+
readonly pageSize?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface OperationResponse {
|
|
24
|
+
readonly operationId: string;
|
|
25
|
+
readonly payloadCbor: Uint8Array;
|
|
26
|
+
readonly semanticEtag?: string;
|
|
27
|
+
readonly nextPageCursor?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface TypedOperationResponse<T> {
|
|
30
|
+
readonly operationId: string;
|
|
31
|
+
readonly payload: Readonly<T>;
|
|
32
|
+
readonly payloadCbor: Uint8Array;
|
|
33
|
+
readonly semanticEtag?: string;
|
|
34
|
+
readonly nextPageCursor?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface OperationEvent {
|
|
37
|
+
readonly operationId: string;
|
|
38
|
+
readonly eventId: string;
|
|
39
|
+
readonly payloadCbor: Uint8Array;
|
|
40
|
+
}
|
|
41
|
+
export interface TypedOperationEvent<T> {
|
|
42
|
+
readonly operationId: string;
|
|
43
|
+
readonly eventId: string;
|
|
44
|
+
readonly payload: Readonly<T>;
|
|
45
|
+
readonly payloadCbor: Uint8Array;
|
|
46
|
+
}
|
|
47
|
+
export type BearerTokenProvider = string | ((signal?: AbortSignal) => string | Promise<string>);
|
|
48
|
+
export interface CallOptions {
|
|
49
|
+
/** End-to-end deadline in milliseconds. The SDK caps this at five minutes. */
|
|
50
|
+
readonly timeoutMs?: number;
|
|
51
|
+
readonly signal?: AbortSignal;
|
|
52
|
+
/** Maximum total attempts, including the first. Dispatch is always one attempt. */
|
|
53
|
+
readonly maxAttempts?: number;
|
|
54
|
+
/** Exact Last-Event-ID for a server stream; distinct from opaque pagination cursors. */
|
|
55
|
+
readonly resumeFrom?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface ClientOptions {
|
|
58
|
+
readonly baseUrl: string | URL;
|
|
59
|
+
/** Mandatory for remote HTTPS; no ambient or inherited credential source is consulted. */
|
|
60
|
+
readonly bearerToken?: BearerTokenProvider;
|
|
61
|
+
readonly defaultTimeoutMs?: number;
|
|
62
|
+
readonly maxAttempts?: number;
|
|
63
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
64
|
+
/** Required when injecting a custom fetch implementation with caller-owned proxy/redirect policy. */
|
|
65
|
+
readonly trustCustomFetch?: boolean;
|
|
66
|
+
readonly apiVersion?: "1";
|
|
67
|
+
/** Allows cleartext only for localhost/loopback development endpoints. */
|
|
68
|
+
readonly allowInsecureLoopback?: boolean;
|
|
69
|
+
}
|
|
70
|
+
export interface EventStream extends AsyncIterable<OperationEvent>, AsyncDisposable {
|
|
71
|
+
readonly lastEventId: string | undefined;
|
|
72
|
+
close(): void;
|
|
73
|
+
}
|
|
74
|
+
export interface TypedEventStream<T> extends AsyncIterable<TypedOperationEvent<T>>, AsyncDisposable {
|
|
75
|
+
readonly lastEventId: string | undefined;
|
|
76
|
+
close(): void;
|
|
77
|
+
}
|
|
78
|
+
export interface Compatibility {
|
|
79
|
+
readonly apiVersion: "1";
|
|
80
|
+
readonly version: OperationResponse;
|
|
81
|
+
readonly capabilities: OperationResponse;
|
|
82
|
+
}
|
|
83
|
+
interface SemanticBundleBlockBase {
|
|
84
|
+
readonly block_id: string;
|
|
85
|
+
readonly lane: "rules" | "task" | "evidence" | "history" | "tools";
|
|
86
|
+
readonly content_digest: string;
|
|
87
|
+
readonly token_count: number;
|
|
88
|
+
readonly provenance: readonly string[];
|
|
89
|
+
}
|
|
90
|
+
export type SemanticBundleBlock = SemanticBundleBlockBase & ({
|
|
91
|
+
readonly representation: "exact" | "redacted";
|
|
92
|
+
readonly transform_receipt?: never;
|
|
93
|
+
} | {
|
|
94
|
+
readonly representation: "extracted" | "summarized";
|
|
95
|
+
readonly transform_receipt: string;
|
|
96
|
+
});
|
|
97
|
+
export interface SemanticContextBundle {
|
|
98
|
+
readonly schema_version: "cigar.context-bundle.v1";
|
|
99
|
+
readonly bundle_id: string;
|
|
100
|
+
readonly contract_digest: string;
|
|
101
|
+
readonly manifest_digest: string;
|
|
102
|
+
readonly blocks: readonly SemanticBundleBlock[];
|
|
103
|
+
readonly total_tokens: number;
|
|
104
|
+
readonly extensions: Readonly<Record<string, unknown>>;
|
|
105
|
+
}
|
|
106
|
+
export interface SemanticContextDelta {
|
|
107
|
+
readonly schema_version: "cigar.context-delta.v1";
|
|
108
|
+
readonly base_bundle_id: string;
|
|
109
|
+
readonly target_bundle_id: string;
|
|
110
|
+
readonly added_blocks: readonly SemanticBundleBlock[];
|
|
111
|
+
readonly removed_block_ids: readonly string[];
|
|
112
|
+
readonly resulting_tokens: number;
|
|
113
|
+
}
|
|
114
|
+
export {};
|
|
115
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["/** Copy-safe high-level HTTP wire records. */\n\nexport interface PathParameter {\n readonly name: string;\n readonly value: string;\n}\n\nexport interface OperationRequest {\n readonly payloadCbor?: Uint8Array;\n readonly pathParameters?: readonly PathParameter[];\n readonly idempotencyKey?: string;\n readonly expectedRevision?: string;\n readonly dryRun?: boolean;\n readonly pageCursor?: string;\n readonly pageSize?: number;\n}\n\nexport interface TypedOperationRequest<T> {\n readonly payload: T;\n readonly idempotencyKey?: string;\n readonly expectedRevision?: string;\n readonly dryRun?: boolean;\n readonly pageCursor?: string;\n readonly pageSize?: number;\n}\n\nexport interface OperationResponse {\n readonly operationId: string;\n readonly payloadCbor: Uint8Array;\n readonly semanticEtag?: string;\n readonly nextPageCursor?: string;\n}\n\nexport interface TypedOperationResponse<T> {\n readonly operationId: string;\n readonly payload: Readonly<T>;\n readonly payloadCbor: Uint8Array;\n readonly semanticEtag?: string;\n readonly nextPageCursor?: string;\n}\n\nexport interface OperationEvent {\n readonly operationId: string;\n readonly eventId: string;\n readonly payloadCbor: Uint8Array;\n}\n\nexport interface TypedOperationEvent<T> {\n readonly operationId: string;\n readonly eventId: string;\n readonly payload: Readonly<T>;\n readonly payloadCbor: Uint8Array;\n}\n\nexport type BearerTokenProvider = string | ((signal?: AbortSignal) => string | Promise<string>);\n\nexport interface CallOptions {\n /** End-to-end deadline in milliseconds. The SDK caps this at five minutes. */\n readonly timeoutMs?: number;\n readonly signal?: AbortSignal;\n /** Maximum total attempts, including the first. Dispatch is always one attempt. */\n readonly maxAttempts?: number;\n /** Exact Last-Event-ID for a server stream; distinct from opaque pagination cursors. */\n readonly resumeFrom?: string;\n}\n\nexport interface ClientOptions {\n readonly baseUrl: string | URL;\n /** Mandatory for remote HTTPS; no ambient or inherited credential source is consulted. */\n readonly bearerToken?: BearerTokenProvider;\n readonly defaultTimeoutMs?: number;\n readonly maxAttempts?: number;\n readonly fetch?: typeof globalThis.fetch;\n /** Required when injecting a custom fetch implementation with caller-owned proxy/redirect policy. */\n readonly trustCustomFetch?: boolean;\n readonly apiVersion?: \"1\";\n /** Allows cleartext only for localhost/loopback development endpoints. */\n readonly allowInsecureLoopback?: boolean;\n}\n\nexport interface EventStream extends AsyncIterable<OperationEvent>, AsyncDisposable {\n readonly lastEventId: string | undefined;\n close(): void;\n}\n\nexport interface TypedEventStream<T> extends AsyncIterable<TypedOperationEvent<T>>, AsyncDisposable {\n readonly lastEventId: string | undefined;\n close(): void;\n}\n\nexport interface Compatibility {\n readonly apiVersion: \"1\";\n readonly version: OperationResponse;\n readonly capabilities: OperationResponse;\n}\n\ninterface SemanticBundleBlockBase {\n readonly block_id: string;\n readonly lane: \"rules\" | \"task\" | \"evidence\" | \"history\" | \"tools\";\n readonly content_digest: string;\n readonly token_count: number;\n readonly provenance: readonly string[];\n}\n\nexport type SemanticBundleBlock = SemanticBundleBlockBase & (\n | {\n readonly representation: \"exact\" | \"redacted\";\n readonly transform_receipt?: never;\n }\n | {\n readonly representation: \"extracted\" | \"summarized\";\n readonly transform_receipt: string;\n }\n);\n\nexport interface SemanticContextBundle {\n readonly schema_version: \"cigar.context-bundle.v1\";\n readonly bundle_id: string;\n readonly contract_digest: string;\n readonly manifest_digest: string;\n readonly blocks: readonly SemanticBundleBlock[];\n readonly total_tokens: number;\n readonly extensions: Readonly<Record<string, unknown>>;\n}\n\nexport interface SemanticContextDelta {\n readonly schema_version: \"cigar.context-delta.v1\";\n readonly base_bundle_id: string;\n readonly target_bundle_id: string;\n readonly added_blocks: readonly SemanticBundleBlock[];\n readonly removed_block_ids: readonly string[];\n readonly resulting_tokens: number;\n}\n"]}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8CAA8C","sourcesContent":["/** Copy-safe high-level HTTP wire records. */\n\nexport interface PathParameter {\n readonly name: string;\n readonly value: string;\n}\n\nexport interface OperationRequest {\n readonly payloadCbor?: Uint8Array;\n readonly pathParameters?: readonly PathParameter[];\n readonly idempotencyKey?: string;\n readonly expectedRevision?: string;\n readonly dryRun?: boolean;\n readonly pageCursor?: string;\n readonly pageSize?: number;\n}\n\nexport interface TypedOperationRequest<T> {\n readonly payload: T;\n readonly idempotencyKey?: string;\n readonly expectedRevision?: string;\n readonly dryRun?: boolean;\n readonly pageCursor?: string;\n readonly pageSize?: number;\n}\n\nexport interface OperationResponse {\n readonly operationId: string;\n readonly payloadCbor: Uint8Array;\n readonly semanticEtag?: string;\n readonly nextPageCursor?: string;\n}\n\nexport interface TypedOperationResponse<T> {\n readonly operationId: string;\n readonly payload: Readonly<T>;\n readonly payloadCbor: Uint8Array;\n readonly semanticEtag?: string;\n readonly nextPageCursor?: string;\n}\n\nexport interface OperationEvent {\n readonly operationId: string;\n readonly eventId: string;\n readonly payloadCbor: Uint8Array;\n}\n\nexport interface TypedOperationEvent<T> {\n readonly operationId: string;\n readonly eventId: string;\n readonly payload: Readonly<T>;\n readonly payloadCbor: Uint8Array;\n}\n\nexport type BearerTokenProvider = string | ((signal?: AbortSignal) => string | Promise<string>);\n\nexport interface CallOptions {\n /** End-to-end deadline in milliseconds. The SDK caps this at five minutes. */\n readonly timeoutMs?: number;\n readonly signal?: AbortSignal;\n /** Maximum total attempts, including the first. Dispatch is always one attempt. */\n readonly maxAttempts?: number;\n /** Exact Last-Event-ID for a server stream; distinct from opaque pagination cursors. */\n readonly resumeFrom?: string;\n}\n\nexport interface ClientOptions {\n readonly baseUrl: string | URL;\n /** Mandatory for remote HTTPS; no ambient or inherited credential source is consulted. */\n readonly bearerToken?: BearerTokenProvider;\n readonly defaultTimeoutMs?: number;\n readonly maxAttempts?: number;\n readonly fetch?: typeof globalThis.fetch;\n /** Required when injecting a custom fetch implementation with caller-owned proxy/redirect policy. */\n readonly trustCustomFetch?: boolean;\n readonly apiVersion?: \"1\";\n /** Allows cleartext only for localhost/loopback development endpoints. */\n readonly allowInsecureLoopback?: boolean;\n}\n\nexport interface EventStream extends AsyncIterable<OperationEvent>, AsyncDisposable {\n readonly lastEventId: string | undefined;\n close(): void;\n}\n\nexport interface TypedEventStream<T> extends AsyncIterable<TypedOperationEvent<T>>, AsyncDisposable {\n readonly lastEventId: string | undefined;\n close(): void;\n}\n\nexport interface Compatibility {\n readonly apiVersion: \"1\";\n readonly version: OperationResponse;\n readonly capabilities: OperationResponse;\n}\n\ninterface SemanticBundleBlockBase {\n readonly block_id: string;\n readonly lane: \"rules\" | \"task\" | \"evidence\" | \"history\" | \"tools\";\n readonly content_digest: string;\n readonly token_count: number;\n readonly provenance: readonly string[];\n}\n\nexport type SemanticBundleBlock = SemanticBundleBlockBase & (\n | {\n readonly representation: \"exact\" | \"redacted\";\n readonly transform_receipt?: never;\n }\n | {\n readonly representation: \"extracted\" | \"summarized\";\n readonly transform_receipt: string;\n }\n);\n\nexport interface SemanticContextBundle {\n readonly schema_version: \"cigar.context-bundle.v1\";\n readonly bundle_id: string;\n readonly contract_digest: string;\n readonly manifest_digest: string;\n readonly blocks: readonly SemanticBundleBlock[];\n readonly total_tokens: number;\n readonly extensions: Readonly<Record<string, unknown>>;\n}\n\nexport interface SemanticContextDelta {\n readonly schema_version: \"cigar.context-delta.v1\";\n readonly base_bundle_id: string;\n readonly target_bundle_id: string;\n readonly added_blocks: readonly SemanticBundleBlock[];\n readonly removed_block_ids: readonly string[];\n readonly resulting_tokens: number;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-replay.d.ts","sourceRoot":"","sources":["../src/verify-replay.ts"],"names":[],"mappings":"AAAA,2EAA2E","sourcesContent":["/** Independently verifies the bounded CIGAR replay reproduction vector. */\n\nimport { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\n\nconst MAX_FIXTURE_BYTES = 1_048_576;\nconst MAX_RETAINED_BYTES = 1_048_576;\nconst MAX_ENCODED_RETAINED_BYTES = 1_398_104;\nconst MAX_ARTIFACTS = 64;\nconst MAX_OBSERVATIONS = 1_024;\nconst MAX_JSON_DEPTH = 64;\nconst DEPENDENCY_ORDER = [\n \"source\",\n \"blob\",\n \"policy\",\n \"index\",\n \"manifest\",\n \"bundle\",\n \"tokenizer\",\n \"adapter\",\n \"consumer\",\n \"tool_schema\",\n \"environment\",\n] as const;\n\ntype JsonObject = Record<string, unknown>;\ntype RetainedArtifact = {\n kind: string;\n bytesBase64url: string;\n digestMultihash: string;\n};\ntype ArtifactVerification = {\n verifiedBytes: Map<string, Buffer>;\n missing: string[];\n};\n\nfunction fail(message: string): never {\n throw new Error(`replay vector verification failed: ${message}`);\n}\n\nfunction strictObject(value: unknown, keys: readonly string[], context: string): JsonObject {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n fail(`${context} must be an object`);\n }\n const record = value as JsonObject;\n const actual = Object.keys(record).sort();\n const expected = [...keys].sort();\n if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {\n fail(`${context} has unknown or missing fields`);\n }\n return record;\n}\n\nfunction stringField(\n record: JsonObject,\n key: string,\n context: string,\n maximum = 256,\n allowEmpty = false,\n): string {\n const value = record[key];\n if (typeof value !== \"string\" || (!allowEmpty && value.length === 0) || value.length > maximum) {\n fail(`${context}.${key} must be a bounded string`);\n }\n return value;\n}\n\nfunction booleanField(record: JsonObject, key: string, context: string): boolean {\n const value = record[key];\n if (typeof value !== \"boolean\") fail(`${context}.${key} must be a boolean`);\n return value;\n}\n\nfunction stringArray(value: unknown, context: string, maximum = 64): string[] {\n if (!Array.isArray(value) || value.length > maximum) fail(`${context} must be a bounded array`);\n return value.map((item, index) => {\n if (typeof item !== \"string\" || item.length === 0 || item.length > 256) {\n fail(`${context}[${index}] must be a bounded string`);\n }\n return item;\n });\n}\n\nfunction sameStrings(actual: readonly string[], expected: readonly string[], context: string): void {\n if (actual.length !== expected.length || actual.some((value, index) => value !== expected[index])) {\n fail(`${context} differs`);\n }\n}\n\nfunction decodeExactBase64url(value: string, context: string, allowEmpty: boolean): Buffer {\n if (value.length === 0) {\n if (allowEmpty) return Buffer.alloc(0);\n fail(`${context} must not be empty`);\n }\n if (value.length > MAX_ENCODED_RETAINED_BYTES || !/^[A-Za-z0-9_-]+$/u.test(value)) {\n fail(`${context} is not bounded unpadded base64url`);\n }\n const decoded = Buffer.from(value, \"base64url\");\n if (decoded.length > MAX_RETAINED_BYTES || decoded.toString(\"base64url\") !== value) {\n fail(`${context} is non-canonical or exceeds its bound`);\n }\n return decoded;\n}\n\nfunction multihash(bytes: Uint8Array): string {\n return `1220${createHash(\"sha256\").update(bytes).digest(\"hex\")}`;\n}\n\nfunction observationMultihash(observations: readonly Buffer[]): string {\n const hash = createHash(\"sha256\");\n for (const observation of observations) {\n if (observation.length > 0xffff_ffff) fail(\"recorded observation exceeds u32 framing\");\n const length = Buffer.alloc(4);\n length.writeUInt32BE(observation.length);\n hash.update(length);\n hash.update(observation);\n }\n return `1220${hash.digest(\"hex\")}`;\n}\n\nfunction verifyDigest(value: string, context: string): void {\n if (!/^1220[0-9a-f]{64}$/u.test(value)) fail(`${context} is not a lowercase SHA-256 multihash`);\n}\n\nfunction parseArtifacts(required: readonly string[], value: unknown): RetainedArtifact[] {\n if (!Array.isArray(value) || value.length > MAX_ARTIFACTS) {\n fail(\"retained artifact table must be a bounded array\");\n }\n const seen = new Set<string>();\n return value.map((raw, index) => {\n const entry = strictObject(\n raw,\n [\"kind\", \"bytes_base64url\", \"digest_multihash\"],\n `retained artifact ${index}`,\n );\n const kind = stringField(entry, \"kind\", `retained artifact ${index}`);\n if (!required.includes(kind) || seen.has(kind)) fail(\"retained artifact kind is unknown or duplicated\");\n seen.add(kind);\n const bytesBase64url = stringField(\n entry,\n \"bytes_base64url\",\n `retained artifact ${index}`,\n MAX_ENCODED_RETAINED_BYTES,\n );\n const digestMultihash = stringField(entry, \"digest_multihash\", `retained artifact ${index}`, 68);\n verifyDigest(digestMultihash, `retained artifact ${index} digest`);\n return { kind, bytesBase64url, digestMultihash };\n });\n}\n\nfunction verifyArtifacts(required: readonly string[], artifacts: readonly RetainedArtifact[]): ArtifactVerification {\n const verifiedBytes = new Map<string, Buffer>();\n for (const artifact of artifacts) {\n const bytes = decodeExactBase64url(artifact.bytesBase64url, `${artifact.kind} artifact`, false);\n if (multihash(bytes) === artifact.digestMultihash) verifiedBytes.set(artifact.kind, bytes);\n }\n return {\n verifiedBytes,\n missing: required.filter((kind) => !verifiedBytes.has(kind)),\n };\n}\n\nfunction rejectDuplicateJsonKeys(source: string): void {\n let offset = 0;\n\n function whitespace(): void {\n while (offset < source.length && /[\\u0009\\u000a\\u000d\\u0020]/u.test(source[offset] ?? \"\")) offset += 1;\n }\n\n function parseString(): string {\n if (source[offset] !== '\"') fail(\"invalid JSON string\");\n const start = offset;\n offset += 1;\n while (offset < source.length) {\n const character = source[offset];\n if (character === '\"') {\n offset += 1;\n const decoded = JSON.parse(source.slice(start, offset)) as unknown;\n if (typeof decoded !== \"string\") fail(\"invalid JSON string\");\n return decoded;\n }\n if (character === \"\\\\\") {\n offset += 1;\n const escape = source[offset];\n if (escape === \"u\") {\n if (!/^[0-9a-fA-F]{4}$/u.test(source.slice(offset + 1, offset + 5))) fail(\"invalid JSON escape\");\n offset += 5;\n continue;\n }\n if (escape === undefined || !'\"\\\\/bfnrt'.includes(escape)) fail(\"invalid JSON escape\");\n offset += 1;\n continue;\n }\n if (character === undefined || character.charCodeAt(0) < 0x20) fail(\"invalid JSON string character\");\n offset += 1;\n }\n fail(\"unterminated JSON string\");\n }\n\n function parseValue(depth: number): void {\n if (depth > MAX_JSON_DEPTH) fail(\"JSON nesting exceeds its bound\");\n whitespace();\n const character = source[offset];\n if (character === \"{\") {\n offset += 1;\n whitespace();\n const keys = new Set<string>();\n if (source[offset] === \"}\") {\n offset += 1;\n return;\n }\n while (true) {\n whitespace();\n const key = parseString();\n if (keys.has(key)) fail(\"duplicate JSON object key\");\n keys.add(key);\n whitespace();\n if (source[offset] !== \":\") fail(\"JSON object key lacks a value\");\n offset += 1;\n parseValue(depth + 1);\n whitespace();\n if (source[offset] === \"}\") {\n offset += 1;\n return;\n }\n if (source[offset] !== \",\") fail(\"invalid JSON object separator\");\n offset += 1;\n }\n }\n if (character === \"[\") {\n offset += 1;\n whitespace();\n if (source[offset] === \"]\") {\n offset += 1;\n return;\n }\n while (true) {\n parseValue(depth + 1);\n whitespace();\n if (source[offset] === \"]\") {\n offset += 1;\n return;\n }\n if (source[offset] !== \",\") fail(\"invalid JSON array separator\");\n offset += 1;\n }\n }\n if (character === '\"') {\n parseString();\n return;\n }\n for (const literal of [\"true\", \"false\", \"null\"] as const) {\n if (source.startsWith(literal, offset)) {\n offset += literal.length;\n return;\n }\n }\n const number = /^-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/u.exec(source.slice(offset));\n if (number === null) fail(\"invalid JSON value\");\n offset += number[0].length;\n }\n\n parseValue(0);\n whitespace();\n if (offset !== source.length) fail(\"fixture contains trailing JSON\");\n}\n\nfunction main(): void {\n const path = process.argv[2] ?? \"schemas/vectors/replay-v1.json\";\n const source = readFileSync(path);\n if (source.length === 0 || source.length > MAX_FIXTURE_BYTES) fail(\"fixture size is invalid\");\n let text: string;\n try {\n text = new TextDecoder(\"utf-8\", { fatal: true }).decode(source);\n } catch {\n fail(\"fixture is not valid UTF-8\");\n }\n rejectDuplicateJsonKeys(text);\n const root = strictObject(JSON.parse(text) as unknown, [\n \"schema_version\",\n \"digest_algorithm\",\n \"observation_framing\",\n \"retained\",\n \"required_dependencies\",\n \"retained_artifacts\",\n \"missing_artifact_probe\",\n \"tampered_artifact_probe\",\n \"empty_recorded_response_probe\",\n \"expected\",\n ], \"fixture\");\n if (stringField(root, \"schema_version\", \"fixture\") !== \"cigar.replay-vector.v1\"\n || stringField(root, \"digest_algorithm\", \"fixture\") !== \"sha256-multihash-raw-v1\"\n || stringField(root, \"observation_framing\", \"fixture\") !== \"u32be-length-prefixed-v1\") {\n fail(\"fixture declares an unsupported profile\");\n }\n\n const retained = strictObject(root.retained, [\n \"bundle_bytes_base64url\",\n \"invocation_bytes_base64url\",\n \"recorded_observation_bytes_base64url\",\n ], \"retained\");\n const bundle = decodeExactBase64url(\n stringField(retained, \"bundle_bytes_base64url\", \"retained\", MAX_ENCODED_RETAINED_BYTES),\n \"bundle\",\n false,\n );\n const invocation = decodeExactBase64url(\n stringField(retained, \"invocation_bytes_base64url\", \"retained\", MAX_ENCODED_RETAINED_BYTES),\n \"invocation\",\n false,\n );\n const encodedObservations = stringArray(\n retained.recorded_observation_bytes_base64url,\n \"recorded observations\",\n MAX_OBSERVATIONS,\n );\n if (encodedObservations.length === 0) fail(\"recorded observations must not be empty\");\n const observations = encodedObservations.map((value, index) =>\n decodeExactBase64url(value, `recorded observation ${index}`, true));\n\n const required = stringArray(root.required_dependencies, \"required dependencies\");\n sameStrings(required, DEPENDENCY_ORDER, \"required dependency order\");\n const artifacts = parseArtifacts(required, root.retained_artifacts);\n\n const expected = strictObject(root.expected, [\n \"bundle_digest_multihash\",\n \"invocation_digest_multihash\",\n \"observation_digest_multihash\",\n \"complete\",\n \"missing_dependencies\",\n ], \"expected\");\n const expectedBundle = stringField(expected, \"bundle_digest_multihash\", \"expected\", 68);\n const expectedInvocation = stringField(expected, \"invocation_digest_multihash\", \"expected\", 68);\n const expectedObservations = stringField(expected, \"observation_digest_multihash\", \"expected\", 68);\n for (const [context, value] of [\n [\"bundle digest\", expectedBundle],\n [\"invocation digest\", expectedInvocation],\n [\"observation digest\", expectedObservations],\n ] as const) verifyDigest(value, context);\n const bundleDigest = multihash(bundle);\n const invocationDigest = multihash(invocation);\n const observationDigest = observationMultihash(observations);\n if (bundleDigest !== expectedBundle || invocationDigest !== expectedInvocation\n || observationDigest !== expectedObservations) fail(\"retained replay digest mismatch\");\n\n const verification = verifyArtifacts(required, artifacts);\n const complete = verification.missing.length === 0;\n if (complete !== booleanField(expected, \"complete\", \"expected\")) fail(\"completeness mismatch\");\n sameStrings(\n verification.missing,\n stringArray(expected.missing_dependencies, \"expected missing dependencies\"),\n \"missing dependencies\",\n );\n const artifactBundle = verification.verifiedBytes.get(\"bundle\");\n if (artifactBundle === undefined || !artifactBundle.equals(bundle)) {\n fail(\"retained bundle and bundle dependency artifact differ\");\n }\n\n const missingProbe = strictObject(root.missing_artifact_probe, [\n \"kind\",\n \"expected_complete\",\n \"expected_missing_dependencies\",\n ], \"missing artifact probe\");\n const missingKind = stringField(missingProbe, \"kind\", \"missing artifact probe\");\n if (!required.includes(missingKind)) fail(\"missing artifact probe names an unknown dependency\");\n const missingVerification = verifyArtifacts(\n required,\n artifacts.filter((artifact) => artifact.kind !== missingKind),\n );\n const missingComplete = missingVerification.missing.length === 0;\n if (missingComplete !== booleanField(missingProbe, \"expected_complete\", \"missing artifact probe\")) {\n fail(\"missing artifact probe completeness differs\");\n }\n sameStrings(\n missingVerification.missing,\n stringArray(missingProbe.expected_missing_dependencies, \"missing artifact probe dependencies\"),\n \"missing artifact probe dependencies\",\n );\n\n const tamperProbe = strictObject(root.tampered_artifact_probe, [\n \"kind\",\n \"replacement_bytes_base64url\",\n \"expected_accepted\",\n \"expected_missing_dependencies\",\n ], \"tampered artifact probe\");\n const tamperKind = stringField(tamperProbe, \"kind\", \"tampered artifact probe\");\n if (!required.includes(tamperKind)) fail(\"tampered artifact probe names an unknown dependency\");\n const replacement = stringField(\n tamperProbe,\n \"replacement_bytes_base64url\",\n \"tampered artifact probe\",\n MAX_ENCODED_RETAINED_BYTES,\n );\n let replacements = 0;\n const tampered = artifacts.map((artifact) => {\n if (artifact.kind !== tamperKind) return artifact;\n replacements += 1;\n return { ...artifact, bytesBase64url: replacement };\n });\n if (replacements !== 1) fail(\"tampered artifact probe must identify exactly one artifact\");\n const tamperedVerification = verifyArtifacts(required, tampered);\n const tamperAccepted = tamperedVerification.missing.length === 0;\n if (tamperAccepted !== booleanField(tamperProbe, \"expected_accepted\", \"tampered artifact probe\")) {\n fail(\"tampered artifact probe acceptance differs\");\n }\n sameStrings(\n tamperedVerification.missing,\n stringArray(tamperProbe.expected_missing_dependencies, \"tampered artifact probe dependencies\"),\n \"tampered artifact probe dependencies\",\n );\n\n const emptyProbe = strictObject(root.empty_recorded_response_probe, [\n \"bytes_base64url\",\n \"digest_multihash\",\n \"expected_accepted\",\n ], \"empty recorded response probe\");\n const emptyResponse = decodeExactBase64url(\n stringField(\n emptyProbe,\n \"bytes_base64url\",\n \"empty recorded response probe\",\n MAX_ENCODED_RETAINED_BYTES,\n true,\n ),\n \"empty recorded response\",\n true,\n );\n const expectedEmptyDigest = stringField(emptyProbe, \"digest_multihash\", \"empty recorded response probe\", 68);\n verifyDigest(expectedEmptyDigest, \"empty recorded response digest\");\n const emptyDigest = multihash(emptyResponse);\n const emptyAccepted = emptyResponse.length === 0 && emptyDigest === expectedEmptyDigest;\n if (emptyAccepted !== booleanField(emptyProbe, \"expected_accepted\", \"empty recorded response probe\")) {\n fail(\"empty recorded response probe acceptance differs\");\n }\n\n process.stdout.write(`${JSON.stringify({\n schema_version: \"cigar.replay-reproduction-result.v1\",\n bundle_digest_multihash: bundleDigest,\n invocation_digest_multihash: invocationDigest,\n observation_digest_multihash: observationDigest,\n complete,\n missing_dependencies: verification.missing,\n missing_artifact_probe: {\n complete: missingComplete,\n missing_dependencies: missingVerification.missing,\n },\n tampered_artifact_probe: {\n accepted: tamperAccepted,\n missing_dependencies: tamperedVerification.missing,\n },\n empty_recorded_response_probe: {\n accepted: emptyAccepted,\n digest_multihash: emptyDigest,\n },\n })}\\n`);\n}\n\nmain();\n"]}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/** Independently verifies the bounded CIGAR replay reproduction vector. */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
const MAX_FIXTURE_BYTES = 1_048_576;
|
|
5
|
+
const MAX_RETAINED_BYTES = 1_048_576;
|
|
6
|
+
const MAX_ENCODED_RETAINED_BYTES = 1_398_104;
|
|
7
|
+
const MAX_ARTIFACTS = 64;
|
|
8
|
+
const MAX_OBSERVATIONS = 1_024;
|
|
9
|
+
const MAX_JSON_DEPTH = 64;
|
|
10
|
+
const DEPENDENCY_ORDER = [
|
|
11
|
+
"source",
|
|
12
|
+
"blob",
|
|
13
|
+
"policy",
|
|
14
|
+
"index",
|
|
15
|
+
"manifest",
|
|
16
|
+
"bundle",
|
|
17
|
+
"tokenizer",
|
|
18
|
+
"adapter",
|
|
19
|
+
"consumer",
|
|
20
|
+
"tool_schema",
|
|
21
|
+
"environment",
|
|
22
|
+
];
|
|
23
|
+
function fail(message) {
|
|
24
|
+
throw new Error(`replay vector verification failed: ${message}`);
|
|
25
|
+
}
|
|
26
|
+
function strictObject(value, keys, context) {
|
|
27
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
28
|
+
fail(`${context} must be an object`);
|
|
29
|
+
}
|
|
30
|
+
const record = value;
|
|
31
|
+
const actual = Object.keys(record).sort();
|
|
32
|
+
const expected = [...keys].sort();
|
|
33
|
+
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
|
|
34
|
+
fail(`${context} has unknown or missing fields`);
|
|
35
|
+
}
|
|
36
|
+
return record;
|
|
37
|
+
}
|
|
38
|
+
function stringField(record, key, context, maximum = 256, allowEmpty = false) {
|
|
39
|
+
const value = record[key];
|
|
40
|
+
if (typeof value !== "string" || (!allowEmpty && value.length === 0) || value.length > maximum) {
|
|
41
|
+
fail(`${context}.${key} must be a bounded string`);
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
function booleanField(record, key, context) {
|
|
46
|
+
const value = record[key];
|
|
47
|
+
if (typeof value !== "boolean")
|
|
48
|
+
fail(`${context}.${key} must be a boolean`);
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
function stringArray(value, context, maximum = 64) {
|
|
52
|
+
if (!Array.isArray(value) || value.length > maximum)
|
|
53
|
+
fail(`${context} must be a bounded array`);
|
|
54
|
+
return value.map((item, index) => {
|
|
55
|
+
if (typeof item !== "string" || item.length === 0 || item.length > 256) {
|
|
56
|
+
fail(`${context}[${index}] must be a bounded string`);
|
|
57
|
+
}
|
|
58
|
+
return item;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function sameStrings(actual, expected, context) {
|
|
62
|
+
if (actual.length !== expected.length || actual.some((value, index) => value !== expected[index])) {
|
|
63
|
+
fail(`${context} differs`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function decodeExactBase64url(value, context, allowEmpty) {
|
|
67
|
+
if (value.length === 0) {
|
|
68
|
+
if (allowEmpty)
|
|
69
|
+
return Buffer.alloc(0);
|
|
70
|
+
fail(`${context} must not be empty`);
|
|
71
|
+
}
|
|
72
|
+
if (value.length > MAX_ENCODED_RETAINED_BYTES || !/^[A-Za-z0-9_-]+$/u.test(value)) {
|
|
73
|
+
fail(`${context} is not bounded unpadded base64url`);
|
|
74
|
+
}
|
|
75
|
+
const decoded = Buffer.from(value, "base64url");
|
|
76
|
+
if (decoded.length > MAX_RETAINED_BYTES || decoded.toString("base64url") !== value) {
|
|
77
|
+
fail(`${context} is non-canonical or exceeds its bound`);
|
|
78
|
+
}
|
|
79
|
+
return decoded;
|
|
80
|
+
}
|
|
81
|
+
function multihash(bytes) {
|
|
82
|
+
return `1220${createHash("sha256").update(bytes).digest("hex")}`;
|
|
83
|
+
}
|
|
84
|
+
function observationMultihash(observations) {
|
|
85
|
+
const hash = createHash("sha256");
|
|
86
|
+
for (const observation of observations) {
|
|
87
|
+
if (observation.length > 0xffff_ffff)
|
|
88
|
+
fail("recorded observation exceeds u32 framing");
|
|
89
|
+
const length = Buffer.alloc(4);
|
|
90
|
+
length.writeUInt32BE(observation.length);
|
|
91
|
+
hash.update(length);
|
|
92
|
+
hash.update(observation);
|
|
93
|
+
}
|
|
94
|
+
return `1220${hash.digest("hex")}`;
|
|
95
|
+
}
|
|
96
|
+
function verifyDigest(value, context) {
|
|
97
|
+
if (!/^1220[0-9a-f]{64}$/u.test(value))
|
|
98
|
+
fail(`${context} is not a lowercase SHA-256 multihash`);
|
|
99
|
+
}
|
|
100
|
+
function parseArtifacts(required, value) {
|
|
101
|
+
if (!Array.isArray(value) || value.length > MAX_ARTIFACTS) {
|
|
102
|
+
fail("retained artifact table must be a bounded array");
|
|
103
|
+
}
|
|
104
|
+
const seen = new Set();
|
|
105
|
+
return value.map((raw, index) => {
|
|
106
|
+
const entry = strictObject(raw, ["kind", "bytes_base64url", "digest_multihash"], `retained artifact ${index}`);
|
|
107
|
+
const kind = stringField(entry, "kind", `retained artifact ${index}`);
|
|
108
|
+
if (!required.includes(kind) || seen.has(kind))
|
|
109
|
+
fail("retained artifact kind is unknown or duplicated");
|
|
110
|
+
seen.add(kind);
|
|
111
|
+
const bytesBase64url = stringField(entry, "bytes_base64url", `retained artifact ${index}`, MAX_ENCODED_RETAINED_BYTES);
|
|
112
|
+
const digestMultihash = stringField(entry, "digest_multihash", `retained artifact ${index}`, 68);
|
|
113
|
+
verifyDigest(digestMultihash, `retained artifact ${index} digest`);
|
|
114
|
+
return { kind, bytesBase64url, digestMultihash };
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function verifyArtifacts(required, artifacts) {
|
|
118
|
+
const verifiedBytes = new Map();
|
|
119
|
+
for (const artifact of artifacts) {
|
|
120
|
+
const bytes = decodeExactBase64url(artifact.bytesBase64url, `${artifact.kind} artifact`, false);
|
|
121
|
+
if (multihash(bytes) === artifact.digestMultihash)
|
|
122
|
+
verifiedBytes.set(artifact.kind, bytes);
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
verifiedBytes,
|
|
126
|
+
missing: required.filter((kind) => !verifiedBytes.has(kind)),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function rejectDuplicateJsonKeys(source) {
|
|
130
|
+
let offset = 0;
|
|
131
|
+
function whitespace() {
|
|
132
|
+
while (offset < source.length && /[\u0009\u000a\u000d\u0020]/u.test(source[offset] ?? ""))
|
|
133
|
+
offset += 1;
|
|
134
|
+
}
|
|
135
|
+
function parseString() {
|
|
136
|
+
if (source[offset] !== '"')
|
|
137
|
+
fail("invalid JSON string");
|
|
138
|
+
const start = offset;
|
|
139
|
+
offset += 1;
|
|
140
|
+
while (offset < source.length) {
|
|
141
|
+
const character = source[offset];
|
|
142
|
+
if (character === '"') {
|
|
143
|
+
offset += 1;
|
|
144
|
+
const decoded = JSON.parse(source.slice(start, offset));
|
|
145
|
+
if (typeof decoded !== "string")
|
|
146
|
+
fail("invalid JSON string");
|
|
147
|
+
return decoded;
|
|
148
|
+
}
|
|
149
|
+
if (character === "\\") {
|
|
150
|
+
offset += 1;
|
|
151
|
+
const escape = source[offset];
|
|
152
|
+
if (escape === "u") {
|
|
153
|
+
if (!/^[0-9a-fA-F]{4}$/u.test(source.slice(offset + 1, offset + 5)))
|
|
154
|
+
fail("invalid JSON escape");
|
|
155
|
+
offset += 5;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (escape === undefined || !'"\\/bfnrt'.includes(escape))
|
|
159
|
+
fail("invalid JSON escape");
|
|
160
|
+
offset += 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (character === undefined || character.charCodeAt(0) < 0x20)
|
|
164
|
+
fail("invalid JSON string character");
|
|
165
|
+
offset += 1;
|
|
166
|
+
}
|
|
167
|
+
fail("unterminated JSON string");
|
|
168
|
+
}
|
|
169
|
+
function parseValue(depth) {
|
|
170
|
+
if (depth > MAX_JSON_DEPTH)
|
|
171
|
+
fail("JSON nesting exceeds its bound");
|
|
172
|
+
whitespace();
|
|
173
|
+
const character = source[offset];
|
|
174
|
+
if (character === "{") {
|
|
175
|
+
offset += 1;
|
|
176
|
+
whitespace();
|
|
177
|
+
const keys = new Set();
|
|
178
|
+
if (source[offset] === "}") {
|
|
179
|
+
offset += 1;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
while (true) {
|
|
183
|
+
whitespace();
|
|
184
|
+
const key = parseString();
|
|
185
|
+
if (keys.has(key))
|
|
186
|
+
fail("duplicate JSON object key");
|
|
187
|
+
keys.add(key);
|
|
188
|
+
whitespace();
|
|
189
|
+
if (source[offset] !== ":")
|
|
190
|
+
fail("JSON object key lacks a value");
|
|
191
|
+
offset += 1;
|
|
192
|
+
parseValue(depth + 1);
|
|
193
|
+
whitespace();
|
|
194
|
+
if (source[offset] === "}") {
|
|
195
|
+
offset += 1;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (source[offset] !== ",")
|
|
199
|
+
fail("invalid JSON object separator");
|
|
200
|
+
offset += 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (character === "[") {
|
|
204
|
+
offset += 1;
|
|
205
|
+
whitespace();
|
|
206
|
+
if (source[offset] === "]") {
|
|
207
|
+
offset += 1;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
while (true) {
|
|
211
|
+
parseValue(depth + 1);
|
|
212
|
+
whitespace();
|
|
213
|
+
if (source[offset] === "]") {
|
|
214
|
+
offset += 1;
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (source[offset] !== ",")
|
|
218
|
+
fail("invalid JSON array separator");
|
|
219
|
+
offset += 1;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (character === '"') {
|
|
223
|
+
parseString();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
for (const literal of ["true", "false", "null"]) {
|
|
227
|
+
if (source.startsWith(literal, offset)) {
|
|
228
|
+
offset += literal.length;
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const number = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/u.exec(source.slice(offset));
|
|
233
|
+
if (number === null)
|
|
234
|
+
fail("invalid JSON value");
|
|
235
|
+
offset += number[0].length;
|
|
236
|
+
}
|
|
237
|
+
parseValue(0);
|
|
238
|
+
whitespace();
|
|
239
|
+
if (offset !== source.length)
|
|
240
|
+
fail("fixture contains trailing JSON");
|
|
241
|
+
}
|
|
242
|
+
function main() {
|
|
243
|
+
const path = process.argv[2] ?? "schemas/vectors/replay-v1.json";
|
|
244
|
+
const source = readFileSync(path);
|
|
245
|
+
if (source.length === 0 || source.length > MAX_FIXTURE_BYTES)
|
|
246
|
+
fail("fixture size is invalid");
|
|
247
|
+
let text;
|
|
248
|
+
try {
|
|
249
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(source);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
fail("fixture is not valid UTF-8");
|
|
253
|
+
}
|
|
254
|
+
rejectDuplicateJsonKeys(text);
|
|
255
|
+
const root = strictObject(JSON.parse(text), [
|
|
256
|
+
"schema_version",
|
|
257
|
+
"digest_algorithm",
|
|
258
|
+
"observation_framing",
|
|
259
|
+
"retained",
|
|
260
|
+
"required_dependencies",
|
|
261
|
+
"retained_artifacts",
|
|
262
|
+
"missing_artifact_probe",
|
|
263
|
+
"tampered_artifact_probe",
|
|
264
|
+
"empty_recorded_response_probe",
|
|
265
|
+
"expected",
|
|
266
|
+
], "fixture");
|
|
267
|
+
if (stringField(root, "schema_version", "fixture") !== "cigar.replay-vector.v1"
|
|
268
|
+
|| stringField(root, "digest_algorithm", "fixture") !== "sha256-multihash-raw-v1"
|
|
269
|
+
|| stringField(root, "observation_framing", "fixture") !== "u32be-length-prefixed-v1") {
|
|
270
|
+
fail("fixture declares an unsupported profile");
|
|
271
|
+
}
|
|
272
|
+
const retained = strictObject(root.retained, [
|
|
273
|
+
"bundle_bytes_base64url",
|
|
274
|
+
"invocation_bytes_base64url",
|
|
275
|
+
"recorded_observation_bytes_base64url",
|
|
276
|
+
], "retained");
|
|
277
|
+
const bundle = decodeExactBase64url(stringField(retained, "bundle_bytes_base64url", "retained", MAX_ENCODED_RETAINED_BYTES), "bundle", false);
|
|
278
|
+
const invocation = decodeExactBase64url(stringField(retained, "invocation_bytes_base64url", "retained", MAX_ENCODED_RETAINED_BYTES), "invocation", false);
|
|
279
|
+
const encodedObservations = stringArray(retained.recorded_observation_bytes_base64url, "recorded observations", MAX_OBSERVATIONS);
|
|
280
|
+
if (encodedObservations.length === 0)
|
|
281
|
+
fail("recorded observations must not be empty");
|
|
282
|
+
const observations = encodedObservations.map((value, index) => decodeExactBase64url(value, `recorded observation ${index}`, true));
|
|
283
|
+
const required = stringArray(root.required_dependencies, "required dependencies");
|
|
284
|
+
sameStrings(required, DEPENDENCY_ORDER, "required dependency order");
|
|
285
|
+
const artifacts = parseArtifacts(required, root.retained_artifacts);
|
|
286
|
+
const expected = strictObject(root.expected, [
|
|
287
|
+
"bundle_digest_multihash",
|
|
288
|
+
"invocation_digest_multihash",
|
|
289
|
+
"observation_digest_multihash",
|
|
290
|
+
"complete",
|
|
291
|
+
"missing_dependencies",
|
|
292
|
+
], "expected");
|
|
293
|
+
const expectedBundle = stringField(expected, "bundle_digest_multihash", "expected", 68);
|
|
294
|
+
const expectedInvocation = stringField(expected, "invocation_digest_multihash", "expected", 68);
|
|
295
|
+
const expectedObservations = stringField(expected, "observation_digest_multihash", "expected", 68);
|
|
296
|
+
for (const [context, value] of [
|
|
297
|
+
["bundle digest", expectedBundle],
|
|
298
|
+
["invocation digest", expectedInvocation],
|
|
299
|
+
["observation digest", expectedObservations],
|
|
300
|
+
])
|
|
301
|
+
verifyDigest(value, context);
|
|
302
|
+
const bundleDigest = multihash(bundle);
|
|
303
|
+
const invocationDigest = multihash(invocation);
|
|
304
|
+
const observationDigest = observationMultihash(observations);
|
|
305
|
+
if (bundleDigest !== expectedBundle || invocationDigest !== expectedInvocation
|
|
306
|
+
|| observationDigest !== expectedObservations)
|
|
307
|
+
fail("retained replay digest mismatch");
|
|
308
|
+
const verification = verifyArtifacts(required, artifacts);
|
|
309
|
+
const complete = verification.missing.length === 0;
|
|
310
|
+
if (complete !== booleanField(expected, "complete", "expected"))
|
|
311
|
+
fail("completeness mismatch");
|
|
312
|
+
sameStrings(verification.missing, stringArray(expected.missing_dependencies, "expected missing dependencies"), "missing dependencies");
|
|
313
|
+
const artifactBundle = verification.verifiedBytes.get("bundle");
|
|
314
|
+
if (artifactBundle === undefined || !artifactBundle.equals(bundle)) {
|
|
315
|
+
fail("retained bundle and bundle dependency artifact differ");
|
|
316
|
+
}
|
|
317
|
+
const missingProbe = strictObject(root.missing_artifact_probe, [
|
|
318
|
+
"kind",
|
|
319
|
+
"expected_complete",
|
|
320
|
+
"expected_missing_dependencies",
|
|
321
|
+
], "missing artifact probe");
|
|
322
|
+
const missingKind = stringField(missingProbe, "kind", "missing artifact probe");
|
|
323
|
+
if (!required.includes(missingKind))
|
|
324
|
+
fail("missing artifact probe names an unknown dependency");
|
|
325
|
+
const missingVerification = verifyArtifacts(required, artifacts.filter((artifact) => artifact.kind !== missingKind));
|
|
326
|
+
const missingComplete = missingVerification.missing.length === 0;
|
|
327
|
+
if (missingComplete !== booleanField(missingProbe, "expected_complete", "missing artifact probe")) {
|
|
328
|
+
fail("missing artifact probe completeness differs");
|
|
329
|
+
}
|
|
330
|
+
sameStrings(missingVerification.missing, stringArray(missingProbe.expected_missing_dependencies, "missing artifact probe dependencies"), "missing artifact probe dependencies");
|
|
331
|
+
const tamperProbe = strictObject(root.tampered_artifact_probe, [
|
|
332
|
+
"kind",
|
|
333
|
+
"replacement_bytes_base64url",
|
|
334
|
+
"expected_accepted",
|
|
335
|
+
"expected_missing_dependencies",
|
|
336
|
+
], "tampered artifact probe");
|
|
337
|
+
const tamperKind = stringField(tamperProbe, "kind", "tampered artifact probe");
|
|
338
|
+
if (!required.includes(tamperKind))
|
|
339
|
+
fail("tampered artifact probe names an unknown dependency");
|
|
340
|
+
const replacement = stringField(tamperProbe, "replacement_bytes_base64url", "tampered artifact probe", MAX_ENCODED_RETAINED_BYTES);
|
|
341
|
+
let replacements = 0;
|
|
342
|
+
const tampered = artifacts.map((artifact) => {
|
|
343
|
+
if (artifact.kind !== tamperKind)
|
|
344
|
+
return artifact;
|
|
345
|
+
replacements += 1;
|
|
346
|
+
return { ...artifact, bytesBase64url: replacement };
|
|
347
|
+
});
|
|
348
|
+
if (replacements !== 1)
|
|
349
|
+
fail("tampered artifact probe must identify exactly one artifact");
|
|
350
|
+
const tamperedVerification = verifyArtifacts(required, tampered);
|
|
351
|
+
const tamperAccepted = tamperedVerification.missing.length === 0;
|
|
352
|
+
if (tamperAccepted !== booleanField(tamperProbe, "expected_accepted", "tampered artifact probe")) {
|
|
353
|
+
fail("tampered artifact probe acceptance differs");
|
|
354
|
+
}
|
|
355
|
+
sameStrings(tamperedVerification.missing, stringArray(tamperProbe.expected_missing_dependencies, "tampered artifact probe dependencies"), "tampered artifact probe dependencies");
|
|
356
|
+
const emptyProbe = strictObject(root.empty_recorded_response_probe, [
|
|
357
|
+
"bytes_base64url",
|
|
358
|
+
"digest_multihash",
|
|
359
|
+
"expected_accepted",
|
|
360
|
+
], "empty recorded response probe");
|
|
361
|
+
const emptyResponse = decodeExactBase64url(stringField(emptyProbe, "bytes_base64url", "empty recorded response probe", MAX_ENCODED_RETAINED_BYTES, true), "empty recorded response", true);
|
|
362
|
+
const expectedEmptyDigest = stringField(emptyProbe, "digest_multihash", "empty recorded response probe", 68);
|
|
363
|
+
verifyDigest(expectedEmptyDigest, "empty recorded response digest");
|
|
364
|
+
const emptyDigest = multihash(emptyResponse);
|
|
365
|
+
const emptyAccepted = emptyResponse.length === 0 && emptyDigest === expectedEmptyDigest;
|
|
366
|
+
if (emptyAccepted !== booleanField(emptyProbe, "expected_accepted", "empty recorded response probe")) {
|
|
367
|
+
fail("empty recorded response probe acceptance differs");
|
|
368
|
+
}
|
|
369
|
+
process.stdout.write(`${JSON.stringify({
|
|
370
|
+
schema_version: "cigar.replay-reproduction-result.v1",
|
|
371
|
+
bundle_digest_multihash: bundleDigest,
|
|
372
|
+
invocation_digest_multihash: invocationDigest,
|
|
373
|
+
observation_digest_multihash: observationDigest,
|
|
374
|
+
complete,
|
|
375
|
+
missing_dependencies: verification.missing,
|
|
376
|
+
missing_artifact_probe: {
|
|
377
|
+
complete: missingComplete,
|
|
378
|
+
missing_dependencies: missingVerification.missing,
|
|
379
|
+
},
|
|
380
|
+
tampered_artifact_probe: {
|
|
381
|
+
accepted: tamperAccepted,
|
|
382
|
+
missing_dependencies: tamperedVerification.missing,
|
|
383
|
+
},
|
|
384
|
+
empty_recorded_response_probe: {
|
|
385
|
+
accepted: emptyAccepted,
|
|
386
|
+
digest_multihash: emptyDigest,
|
|
387
|
+
},
|
|
388
|
+
})}\n`);
|
|
389
|
+
}
|
|
390
|
+
main();
|
|
391
|
+
//# sourceMappingURL=verify-replay.js.map
|