@openprose/reactor 0.1.0-rc.1
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 +21 -0
- package/README.md +231 -0
- package/dist/adapters/agent-sdk-passthrough/index.d.ts +8 -0
- package/dist/adapters/agent-sdk-passthrough/index.d.ts.map +1 -0
- package/dist/adapters/agent-sdk-passthrough/index.js +25 -0
- package/dist/adapters/clock-system/index.d.ts +9 -0
- package/dist/adapters/clock-system/index.d.ts.map +1 -0
- package/dist/adapters/clock-system/index.js +39 -0
- package/dist/adapters/connector-static/index.d.ts +11 -0
- package/dist/adapters/connector-static/index.d.ts.map +1 -0
- package/dist/adapters/connector-static/index.js +35 -0
- package/dist/adapters/event-sink-memory/index.d.ts +10 -0
- package/dist/adapters/event-sink-memory/index.d.ts.map +1 -0
- package/dist/adapters/event-sink-memory/index.js +20 -0
- package/dist/adapters/index.d.ts +10 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +25 -0
- package/dist/adapters/json.d.ts +3 -0
- package/dist/adapters/json.d.ts.map +1 -0
- package/dist/adapters/json.js +61 -0
- package/dist/adapters/model-gateway-record-replay/index.d.ts +24 -0
- package/dist/adapters/model-gateway-record-replay/index.d.ts.map +1 -0
- package/dist/adapters/model-gateway-record-replay/index.js +55 -0
- package/dist/adapters/sandbox-null/index.d.ts +3 -0
- package/dist/adapters/sandbox-null/index.d.ts.map +1 -0
- package/dist/adapters/sandbox-null/index.js +8 -0
- package/dist/adapters/storage-fs/index.d.ts +14 -0
- package/dist/adapters/storage-fs/index.d.ts.map +1 -0
- package/dist/adapters/storage-fs/index.js +65 -0
- package/dist/adapters/storage-memory/index.d.ts +11 -0
- package/dist/adapters/storage-memory/index.d.ts.map +1 -0
- package/dist/adapters/storage-memory/index.js +34 -0
- package/dist/adapters/types.d.ts +22 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +97 -0
- package/dist/composition/index.d.ts +79 -0
- package/dist/composition/index.d.ts.map +1 -0
- package/dist/composition/index.js +280 -0
- package/dist/cost/index.d.ts +49 -0
- package/dist/cost/index.d.ts.map +1 -0
- package/dist/cost/index.js +206 -0
- package/dist/evidence-plan/index.d.ts +57 -0
- package/dist/evidence-plan/index.d.ts.map +1 -0
- package/dist/evidence-plan/index.js +164 -0
- package/dist/forecast/index.d.ts +39 -0
- package/dist/forecast/index.d.ts.map +1 -0
- package/dist/forecast/index.js +119 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/judge/index.d.ts +29 -0
- package/dist/judge/index.d.ts.map +1 -0
- package/dist/judge/index.js +138 -0
- package/dist/kernel/index.d.ts +170 -0
- package/dist/kernel/index.d.ts.map +1 -0
- package/dist/kernel/index.js +637 -0
- package/dist/memo/index.d.ts +59 -0
- package/dist/memo/index.d.ts.map +1 -0
- package/dist/memo/index.js +189 -0
- package/dist/policy/index.d.ts +249 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +1463 -0
- package/dist/projection/index.d.ts +119 -0
- package/dist/projection/index.d.ts.map +1 -0
- package/dist/projection/index.js +506 -0
- package/dist/reactor/index.d.ts +54 -0
- package/dist/reactor/index.d.ts.map +1 -0
- package/dist/reactor/index.js +1861 -0
- package/dist/receipt/index.d.ts +190 -0
- package/dist/receipt/index.d.ts.map +1 -0
- package/dist/receipt/index.js +646 -0
- package/dist/sdk/exit-bundle.d.ts +144 -0
- package/dist/sdk/exit-bundle.d.ts.map +1 -0
- package/dist/sdk/exit-bundle.js +1034 -0
- package/dist/sdk/index.d.ts +201 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +418 -0
- package/package.json +89 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ReactorRuntimeRegistrySnapshotV0, type ReactorStorageRuntimeAdapterV0 } from "../types";
|
|
2
|
+
export interface FileSystemStorageAdapterInputV0 {
|
|
3
|
+
readonly directory: string;
|
|
4
|
+
readonly registry_file?: string;
|
|
5
|
+
readonly receipts_file?: string;
|
|
6
|
+
readonly initial_registry?: ReactorRuntimeRegistrySnapshotV0;
|
|
7
|
+
}
|
|
8
|
+
export interface FileSystemStorageAdapterV0 extends ReactorStorageRuntimeAdapterV0 {
|
|
9
|
+
readonly directory: string;
|
|
10
|
+
readonly registryPath: string;
|
|
11
|
+
readonly receiptsPath: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function createFileSystemStorageAdapterV0(input: FileSystemStorageAdapterInputV0): FileSystemStorageAdapterV0;
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/storage-fs/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAIL,KAAK,gCAAgC,EACrC,KAAK,8BAA8B,EACpC,MAAM,UAAU,CAAC;AAElB,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gCAAgC,CAAC;CAC9D;AAED,MAAM,WAAW,0BAA2B,SAAQ,8BAA8B;IAChF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,+BAA+B,GACrC,0BAA0B,CAoC5B"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createFileSystemStorageAdapterV0 = createFileSystemStorageAdapterV0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const receipt_1 = require("../../receipt");
|
|
7
|
+
const json_1 = require("../json");
|
|
8
|
+
const types_1 = require("../types");
|
|
9
|
+
function createFileSystemStorageAdapterV0(input) {
|
|
10
|
+
if (input.directory.length === 0) {
|
|
11
|
+
throw new Error("filesystem storage directory must be non-empty");
|
|
12
|
+
}
|
|
13
|
+
const registryPath = (0, node_path_1.join)(input.directory, input.registry_file ?? "registry.json");
|
|
14
|
+
const receiptsPath = (0, node_path_1.join)(input.directory, input.receipts_file ?? "receipts.json");
|
|
15
|
+
(0, node_fs_1.mkdirSync)(input.directory, { recursive: true });
|
|
16
|
+
initializeFile(registryPath, input.initial_registry ?? types_1.EMPTY_RUNTIME_REGISTRY_V0);
|
|
17
|
+
initializeFile(receiptsPath, []);
|
|
18
|
+
return {
|
|
19
|
+
directory: input.directory,
|
|
20
|
+
registryPath,
|
|
21
|
+
receiptsPath,
|
|
22
|
+
appendReceipt(receipt) {
|
|
23
|
+
(0, receipt_1.assertReceiptV0)(receipt);
|
|
24
|
+
const receipts = readReceiptLog(receiptsPath);
|
|
25
|
+
writeJsonFile(receiptsPath, [...receipts, cloneReceipt(receipt)]);
|
|
26
|
+
},
|
|
27
|
+
listReceipts() {
|
|
28
|
+
return readReceiptLog(receiptsPath);
|
|
29
|
+
},
|
|
30
|
+
readRegistry() {
|
|
31
|
+
const value = readJsonFile(registryPath);
|
|
32
|
+
(0, types_1.assertRuntimeRegistrySnapshotV0)(value);
|
|
33
|
+
return (0, types_1.cloneRuntimeRegistrySnapshotV0)(value);
|
|
34
|
+
},
|
|
35
|
+
writeRegistry(registry) {
|
|
36
|
+
writeJsonFile(registryPath, (0, types_1.cloneRuntimeRegistrySnapshotV0)(registry));
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function initializeFile(path, value) {
|
|
41
|
+
if (!(0, node_fs_1.existsSync)(path)) {
|
|
42
|
+
writeJsonFile(path, value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function readReceiptLog(path) {
|
|
46
|
+
const value = readJsonFile(path);
|
|
47
|
+
if (!Array.isArray(value)) {
|
|
48
|
+
throw new Error("filesystem receipts file must contain an array");
|
|
49
|
+
}
|
|
50
|
+
return value.map((receipt) => cloneReceipt(receipt));
|
|
51
|
+
}
|
|
52
|
+
function cloneReceipt(receipt) {
|
|
53
|
+
(0, receipt_1.assertReceiptV0)(receipt);
|
|
54
|
+
const clone = (0, json_1.cloneAdapterJsonValueV0)(receipt);
|
|
55
|
+
(0, receipt_1.assertReceiptV0)(clone);
|
|
56
|
+
return clone;
|
|
57
|
+
}
|
|
58
|
+
function readJsonFile(path) {
|
|
59
|
+
return JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
|
|
60
|
+
}
|
|
61
|
+
function writeJsonFile(path, value) {
|
|
62
|
+
const tempPath = `${path}.tmp-${process.pid}`;
|
|
63
|
+
(0, node_fs_1.writeFileSync)(tempPath, `${(0, json_1.renderAdapterJsonV0)(value)}\n`, "utf8");
|
|
64
|
+
(0, node_fs_1.renameSync)(tempPath, path);
|
|
65
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ReceiptV0 } from "../../receipt";
|
|
2
|
+
import { type ReactorRuntimeRegistrySnapshotV0, type ReactorStorageRuntimeAdapterV0 } from "../types";
|
|
3
|
+
export interface MemoryStorageAdapterInputV0 {
|
|
4
|
+
readonly registry?: ReactorRuntimeRegistrySnapshotV0;
|
|
5
|
+
readonly receipts?: readonly ReceiptV0[];
|
|
6
|
+
}
|
|
7
|
+
export interface MemoryStorageAdapterV0 extends ReactorStorageRuntimeAdapterV0 {
|
|
8
|
+
readonly clear: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function createMemoryStorageAdapterV0(input?: MemoryStorageAdapterInputV0): MemoryStorageAdapterV0;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/storage-memory/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAGL,KAAK,gCAAgC,EACrC,KAAK,8BAA8B,EACpC,MAAM,UAAU,CAAC;AAGlB,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,gCAAgC,CAAC;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,sBAAuB,SAAQ,8BAA8B;IAC5E,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,wBAAgB,4BAA4B,CAC1C,KAAK,GAAE,2BAAgC,GACtC,sBAAsB,CAwBxB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMemoryStorageAdapterV0 = createMemoryStorageAdapterV0;
|
|
4
|
+
const receipt_1 = require("../../receipt");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
const json_1 = require("../json");
|
|
7
|
+
function createMemoryStorageAdapterV0(input = {}) {
|
|
8
|
+
let registry = (0, types_1.cloneRuntimeRegistrySnapshotV0)(input.registry ?? types_1.EMPTY_RUNTIME_REGISTRY_V0);
|
|
9
|
+
let receipts = (input.receipts ?? []).map((receipt) => cloneReceipt(receipt));
|
|
10
|
+
return {
|
|
11
|
+
appendReceipt(receipt) {
|
|
12
|
+
receipts = [...receipts, cloneReceipt(receipt)];
|
|
13
|
+
},
|
|
14
|
+
listReceipts() {
|
|
15
|
+
return receipts.map((receipt) => cloneReceipt(receipt));
|
|
16
|
+
},
|
|
17
|
+
readRegistry() {
|
|
18
|
+
return (0, types_1.cloneRuntimeRegistrySnapshotV0)(registry);
|
|
19
|
+
},
|
|
20
|
+
writeRegistry(nextRegistry) {
|
|
21
|
+
registry = (0, types_1.cloneRuntimeRegistrySnapshotV0)(nextRegistry);
|
|
22
|
+
},
|
|
23
|
+
clear() {
|
|
24
|
+
registry = (0, types_1.cloneRuntimeRegistrySnapshotV0)(types_1.EMPTY_RUNTIME_REGISTRY_V0);
|
|
25
|
+
receipts = [];
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function cloneReceipt(receipt) {
|
|
30
|
+
(0, receipt_1.assertReceiptV0)(receipt);
|
|
31
|
+
const clone = (0, json_1.cloneAdapterJsonValueV0)(receipt);
|
|
32
|
+
(0, receipt_1.assertReceiptV0)(clone);
|
|
33
|
+
return clone;
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactorModelGatewayAdapterV0, ReactorModelGatewayRequestV0, ReactorModelGatewayResponseV0, ReactorModelGatewayUsageV0, ReactorRegistrySnapshotV0, ReactorStorageAdapterV0 } from "../sdk";
|
|
2
|
+
export interface ReactorRuntimeRegistrySnapshotV0 extends ReactorRegistrySnapshotV0 {
|
|
3
|
+
readonly [key: string]: unknown;
|
|
4
|
+
readonly compiled_evidence_plan?: unknown;
|
|
5
|
+
readonly forecast_schedule?: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface ReactorStorageRuntimeAdapterV0 extends ReactorStorageAdapterV0 {
|
|
8
|
+
readonly readRegistry: () => ReactorRuntimeRegistrySnapshotV0;
|
|
9
|
+
readonly writeRegistry: (registry: ReactorRuntimeRegistrySnapshotV0) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface ReactorModelGatewayResponseWithUsageV0 extends ReactorModelGatewayResponseV0 {
|
|
12
|
+
readonly usage: ReactorModelGatewayUsageV0;
|
|
13
|
+
}
|
|
14
|
+
export interface ReactorModelGatewayRuntimeAdapterV0 extends ReactorModelGatewayAdapterV0 {
|
|
15
|
+
readonly invoke: (request: ReactorModelGatewayRequestV0) => ReactorModelGatewayResponseWithUsageV0;
|
|
16
|
+
}
|
|
17
|
+
export declare const EMPTY_RUNTIME_REGISTRY_V0: ReactorRuntimeRegistrySnapshotV0;
|
|
18
|
+
export declare function cloneRuntimeRegistrySnapshotV0(registry: ReactorRuntimeRegistrySnapshotV0): ReactorRuntimeRegistrySnapshotV0;
|
|
19
|
+
export declare function cloneModelGatewayUsageV0(usage: ReactorModelGatewayUsageV0): ReactorModelGatewayUsageV0;
|
|
20
|
+
export declare function assertRuntimeRegistrySnapshotV0(value: unknown): asserts value is ReactorRuntimeRegistrySnapshotV0;
|
|
21
|
+
export declare function assertModelGatewayUsageV0(value: unknown): asserts value is ReactorModelGatewayUsageV0;
|
|
22
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,4BAA4B,EAC5B,4BAA4B,EAC5B,6BAA6B,EAC7B,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACxB,MAAM,QAAQ,CAAC;AAKhB,MAAM,WAAW,gCACf,SAAQ,yBAAyB;IACjC,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAC1C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CACtC;AAED,MAAM,WAAW,8BACf,SAAQ,uBAAuB;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,gCAAgC,CAAC;IAC9D,QAAQ,CAAC,aAAa,EAAE,CAAC,QAAQ,EAAE,gCAAgC,KAAK,IAAI,CAAC;CAC9E;AAED,MAAM,WAAW,sCACf,SAAQ,6BAA6B;IACrC,QAAQ,CAAC,KAAK,EAAE,0BAA0B,CAAC;CAC5C;AAED,MAAM,WAAW,mCACf,SAAQ,4BAA4B;IACpC,QAAQ,CAAC,MAAM,EAAE,CACf,OAAO,EAAE,4BAA4B,KAClC,sCAAsC,CAAC;CAC7C;AAED,eAAO,MAAM,yBAAyB,EAAE,gCAGvC,CAAC;AAEF,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,gCAAgC,GACzC,gCAAgC,CAIlC;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,0BAA0B,GAChC,0BAA0B,CAI5B;AAED,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,KAAK,IAAI,gCAAgC,CAgCnD;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,KAAK,IAAI,0BAA0B,CA8B7C"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EMPTY_RUNTIME_REGISTRY_V0 = void 0;
|
|
4
|
+
exports.cloneRuntimeRegistrySnapshotV0 = cloneRuntimeRegistrySnapshotV0;
|
|
5
|
+
exports.cloneModelGatewayUsageV0 = cloneModelGatewayUsageV0;
|
|
6
|
+
exports.assertRuntimeRegistrySnapshotV0 = assertRuntimeRegistrySnapshotV0;
|
|
7
|
+
exports.assertModelGatewayUsageV0 = assertModelGatewayUsageV0;
|
|
8
|
+
const json_1 = require("./json");
|
|
9
|
+
const CONTENT_HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
10
|
+
exports.EMPTY_RUNTIME_REGISTRY_V0 = {
|
|
11
|
+
policy_artifact_namespace: "policy.uninitialized",
|
|
12
|
+
policy_artifact_revision: "0",
|
|
13
|
+
};
|
|
14
|
+
function cloneRuntimeRegistrySnapshotV0(registry) {
|
|
15
|
+
const clone = (0, json_1.cloneAdapterJsonValueV0)(registry);
|
|
16
|
+
assertRuntimeRegistrySnapshotV0(clone);
|
|
17
|
+
return clone;
|
|
18
|
+
}
|
|
19
|
+
function cloneModelGatewayUsageV0(usage) {
|
|
20
|
+
const clone = (0, json_1.cloneAdapterJsonValueV0)(usage);
|
|
21
|
+
assertModelGatewayUsageV0(clone);
|
|
22
|
+
return clone;
|
|
23
|
+
}
|
|
24
|
+
function assertRuntimeRegistrySnapshotV0(value) {
|
|
25
|
+
if (!isRecord(value)) {
|
|
26
|
+
throw new TypeError("registry snapshot must be an object");
|
|
27
|
+
}
|
|
28
|
+
const namespace = value["policy_artifact_namespace"];
|
|
29
|
+
if (typeof namespace !== "string" || namespace.length === 0) {
|
|
30
|
+
throw new TypeError("registry.policy_artifact_namespace must be a non-empty string");
|
|
31
|
+
}
|
|
32
|
+
const revision = value["policy_artifact_revision"];
|
|
33
|
+
if (typeof revision !== "string" || revision.length === 0) {
|
|
34
|
+
throw new TypeError("registry.policy_artifact_revision must be a non-empty string");
|
|
35
|
+
}
|
|
36
|
+
assertOptionalContentHash(value, "contract_revision");
|
|
37
|
+
assertOptionalContentHash(value, "policy_artifact_content_hash");
|
|
38
|
+
for (const key of [
|
|
39
|
+
"policy_artifact_id",
|
|
40
|
+
"policy_artifact_identity",
|
|
41
|
+
"policy_artifact_bytes",
|
|
42
|
+
]) {
|
|
43
|
+
const item = value[key];
|
|
44
|
+
if (item !== undefined && typeof item !== "string") {
|
|
45
|
+
throw new TypeError(`registry.${key} must be a string when present`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function assertModelGatewayUsageV0(value) {
|
|
50
|
+
if (!isRecord(value)) {
|
|
51
|
+
throw new TypeError("model gateway usage must be an object");
|
|
52
|
+
}
|
|
53
|
+
for (const key of ["provider", "model"]) {
|
|
54
|
+
const item = value[key];
|
|
55
|
+
if (typeof item !== "string" || item.length === 0) {
|
|
56
|
+
throw new TypeError(`usage.${key} must be a non-empty string`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const tokens = value["tokens"];
|
|
60
|
+
if (!isRecord(tokens)) {
|
|
61
|
+
throw new TypeError("usage.tokens must be an object");
|
|
62
|
+
}
|
|
63
|
+
assertNonNegativeSafeInteger(tokens, "fresh");
|
|
64
|
+
assertNonNegativeSafeInteger(tokens, "reused");
|
|
65
|
+
const providerNorm = value["provider_norm"];
|
|
66
|
+
if (providerNorm !== undefined) {
|
|
67
|
+
if (!isRecord(providerNorm)) {
|
|
68
|
+
throw new TypeError("usage.provider_norm must be an object when present");
|
|
69
|
+
}
|
|
70
|
+
const schema = providerNorm["schema"];
|
|
71
|
+
if (typeof schema !== "string" || schema.length === 0) {
|
|
72
|
+
throw new TypeError("usage.provider_norm.schema must be non-empty");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function assertOptionalContentHash(record, key) {
|
|
77
|
+
const value = record[key];
|
|
78
|
+
if (value !== undefined && !isContentHash(value)) {
|
|
79
|
+
throw new TypeError(`registry.${key} must be a sha256 content address`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function assertNonNegativeSafeInteger(record, key) {
|
|
83
|
+
const value = record[key];
|
|
84
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
85
|
+
throw new TypeError(`usage.tokens.${key} must be a non-negative safe integer`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function isContentHash(value) {
|
|
89
|
+
return typeof value === "string" && CONTENT_HASH_PATTERN.test(value);
|
|
90
|
+
}
|
|
91
|
+
function isRecord(value) {
|
|
92
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const prototype = Object.getPrototypeOf(value);
|
|
96
|
+
return prototype === Object.prototype || prototype === null;
|
|
97
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type MemoLookupResultV0, type PolicyArtifactMemoNamespaceV0 } from "../memo";
|
|
2
|
+
import { type PolicyTransitiveFreshnessFunctionV0 } from "../policy";
|
|
3
|
+
import { type ConsumedFreshnessEvaluationV0, type ConsumedReceiptPinV0, type ContentHashV0, type ReceiptFreshnessV0, type ReceiptEventCauseV0, type ReceiptRecheckKindV0, type ReceiptV0 } from "../receipt";
|
|
4
|
+
export interface UpstreamReceiptDependencyPinInputV0 {
|
|
5
|
+
readonly upstream_receipt: unknown;
|
|
6
|
+
readonly acceptable_signer_set: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export interface UpstreamReceiptDependencyPinVerificationInputV0 {
|
|
9
|
+
readonly upstream_receipt: unknown;
|
|
10
|
+
readonly expected_dependency_pin: unknown;
|
|
11
|
+
}
|
|
12
|
+
export interface VerifiedUpstreamReceiptDependencyPinV0 {
|
|
13
|
+
readonly receipt: ReceiptV0;
|
|
14
|
+
readonly content_hash: ContentHashV0;
|
|
15
|
+
readonly dependency_pin: ConsumedReceiptPinV0;
|
|
16
|
+
readonly signer_posture: string;
|
|
17
|
+
}
|
|
18
|
+
export type TransitiveFreshnessOutcomeV0 = "fresh" | "stale-blocked";
|
|
19
|
+
export interface TransitiveFreshnessConsumedReceiptInputV0 {
|
|
20
|
+
readonly upstream_receipt: unknown;
|
|
21
|
+
readonly dependency_pin: unknown;
|
|
22
|
+
readonly refetched_from_receipt?: unknown;
|
|
23
|
+
readonly refetched_from_dependency_pin?: unknown;
|
|
24
|
+
}
|
|
25
|
+
export interface TransitiveFreshnessEvaluationInputV0 {
|
|
26
|
+
readonly as_of: string;
|
|
27
|
+
readonly transitive_freshness_policy_ref: string;
|
|
28
|
+
readonly transitive_freshness_function?: PolicyTransitiveFreshnessFunctionV0;
|
|
29
|
+
readonly consumed_receipts: readonly TransitiveFreshnessConsumedReceiptInputV0[];
|
|
30
|
+
}
|
|
31
|
+
export interface TransitiveFreshnessEvaluationResultV0 {
|
|
32
|
+
readonly outcome: TransitiveFreshnessOutcomeV0;
|
|
33
|
+
readonly as_of: string;
|
|
34
|
+
readonly transitive_freshness_policy_ref: string;
|
|
35
|
+
readonly consumed_freshness_evaluated: readonly ConsumedFreshnessEvaluationV0[];
|
|
36
|
+
readonly stale_receipt_hashes: readonly ContentHashV0[];
|
|
37
|
+
}
|
|
38
|
+
export interface ComposedReceiptFreshnessInputV0 extends TransitiveFreshnessEvaluationInputV0 {
|
|
39
|
+
readonly next_forecast_recheck: string;
|
|
40
|
+
}
|
|
41
|
+
export interface DownstreamComposedMemoKeyInputV0 {
|
|
42
|
+
readonly contract_revision: ContentHashV0;
|
|
43
|
+
readonly evidence_receipts: readonly ContentHashV0[];
|
|
44
|
+
readonly dependency_receipts: readonly ConsumedReceiptPinV0[];
|
|
45
|
+
}
|
|
46
|
+
export interface CompositionMemoStoreV0 {
|
|
47
|
+
readonly lookup: (namespace: PolicyArtifactMemoNamespaceV0, memoKey: ContentHashV0) => MemoLookupResultV0;
|
|
48
|
+
}
|
|
49
|
+
export interface CompositionPropagationPlanInputV0 extends DownstreamComposedMemoKeyInputV0 {
|
|
50
|
+
readonly memo_namespace: PolicyArtifactMemoNamespaceV0;
|
|
51
|
+
readonly memo_store: CompositionMemoStoreV0;
|
|
52
|
+
readonly as_of: string;
|
|
53
|
+
readonly next_forecast_recheck: string;
|
|
54
|
+
readonly event_cause?: ReceiptEventCauseV0;
|
|
55
|
+
readonly recheck_kind?: ReceiptRecheckKindV0;
|
|
56
|
+
}
|
|
57
|
+
export type CompositionPropagationPlanV0 = {
|
|
58
|
+
readonly outcome: "rejudge-required";
|
|
59
|
+
readonly memo_key: ContentHashV0;
|
|
60
|
+
readonly memo_lookup: Extract<MemoLookupResultV0, {
|
|
61
|
+
outcome: "miss";
|
|
62
|
+
}>;
|
|
63
|
+
readonly dependency_receipts: readonly ConsumedReceiptPinV0[];
|
|
64
|
+
} | {
|
|
65
|
+
readonly outcome: "stop-memo-hit";
|
|
66
|
+
readonly memo_key: ContentHashV0;
|
|
67
|
+
readonly memo_lookup: Extract<MemoLookupResultV0, {
|
|
68
|
+
outcome: "hit";
|
|
69
|
+
}>;
|
|
70
|
+
readonly memo_hit_receipt: ReceiptV0;
|
|
71
|
+
readonly dependency_receipts: readonly ConsumedReceiptPinV0[];
|
|
72
|
+
};
|
|
73
|
+
export declare function dependencyReceiptPinFromVerifiedReceiptV0(input: UpstreamReceiptDependencyPinInputV0): ConsumedReceiptPinV0;
|
|
74
|
+
export declare function verifyUpstreamReceiptDependencyPinV0(input: UpstreamReceiptDependencyPinVerificationInputV0): VerifiedUpstreamReceiptDependencyPinV0;
|
|
75
|
+
export declare function evaluateTransitiveFreshnessV0(input: TransitiveFreshnessEvaluationInputV0): TransitiveFreshnessEvaluationResultV0;
|
|
76
|
+
export declare function createComposedReceiptFreshnessV0(input: ComposedReceiptFreshnessInputV0): ReceiptFreshnessV0;
|
|
77
|
+
export declare function computeDownstreamComposedMemoKeyV0(input: DownstreamComposedMemoKeyInputV0): ContentHashV0;
|
|
78
|
+
export declare function planCompositionPropagationV0(input: CompositionPropagationPlanInputV0): CompositionPropagationPlanV0;
|
|
79
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/composition/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,6BAA6B,EAGnC,MAAM,SAAS,CAAC;AACjB,OAAO,EAEL,KAAK,mCAAmC,EACzC,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,SAAS,EAGf,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,mCAAmC;IAClD,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;CACnD;AAED,MAAM,WAAW,+CAA+C;IAC9D,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;CAC3C;AAED,MAAM,WAAW,sCAAsC;IACrD,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,oBAAoB,CAAC;IAC9C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,MAAM,4BAA4B,GAAG,OAAO,GAAG,eAAe,CAAC;AAErE,MAAM,WAAW,yCAAyC;IACxD,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAC1C,QAAQ,CAAC,6BAA6B,CAAC,EAAE,OAAO,CAAC;CAClD;AAED,MAAM,WAAW,oCAAoC;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,+BAA+B,EAAE,MAAM,CAAC;IACjD,QAAQ,CAAC,6BAA6B,CAAC,EAAE,mCAAmC,CAAC;IAC7E,QAAQ,CAAC,iBAAiB,EAAE,SAAS,yCAAyC,EAAE,CAAC;CAClF;AAED,MAAM,WAAW,qCAAqC;IACpD,QAAQ,CAAC,OAAO,EAAE,4BAA4B,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,+BAA+B,EAAE,MAAM,CAAC;IACjD,QAAQ,CAAC,4BAA4B,EAAE,SAAS,6BAA6B,EAAE,CAAC;IAChF,QAAQ,CAAC,oBAAoB,EAAE,SAAS,aAAa,EAAE,CAAC;CACzD;AAED,MAAM,WAAW,+BACf,SAAQ,oCAAoC;IAC5C,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,iBAAiB,EAAE,aAAa,CAAC;IAC1C,QAAQ,CAAC,iBAAiB,EAAE,SAAS,aAAa,EAAE,CAAC;IACrD,QAAQ,CAAC,mBAAmB,EAAE,SAAS,oBAAoB,EAAE,CAAC;CAC/D;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,EAAE,CACf,SAAS,EAAE,6BAA6B,EACxC,OAAO,EAAE,aAAa,KACnB,kBAAkB,CAAC;CACzB;AAED,MAAM,WAAW,iCACf,SAAQ,gCAAgC;IACxC,QAAQ,CAAC,cAAc,EAAE,6BAA6B,CAAC;IACvD,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,YAAY,CAAC,EAAE,oBAAoB,CAAC;CAC9C;AAED,MAAM,MAAM,4BAA4B,GACpC;IACE,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,kBAAkB,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvE,QAAQ,CAAC,mBAAmB,EAAE,SAAS,oBAAoB,EAAE,CAAC;CAC/D,GACD;IACE,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,kBAAkB,EAAE;QAAE,OAAO,EAAE,KAAK,CAAA;KAAE,CAAC,CAAC;IACtE,QAAQ,CAAC,gBAAgB,EAAE,SAAS,CAAC;IACrC,QAAQ,CAAC,mBAAmB,EAAE,SAAS,oBAAoB,EAAE,CAAC;CAC/D,CAAC;AAEN,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,mCAAmC,GACzC,oBAAoB,CAqBtB;AAED,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,+CAA+C,GACrD,sCAAsC,CA8BxC;AAED,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,oCAAoC,GAC1C,qCAAqC,CA2DvC;AAiDD,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,+BAA+B,GACrC,kBAAkB,CAkBpB;AAED,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,gCAAgC,GACtC,aAAa,CAUf;AAED,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,iCAAiC,GACvC,4BAA4B,CAkC9B"}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dependencyReceiptPinFromVerifiedReceiptV0 = dependencyReceiptPinFromVerifiedReceiptV0;
|
|
4
|
+
exports.verifyUpstreamReceiptDependencyPinV0 = verifyUpstreamReceiptDependencyPinV0;
|
|
5
|
+
exports.evaluateTransitiveFreshnessV0 = evaluateTransitiveFreshnessV0;
|
|
6
|
+
exports.createComposedReceiptFreshnessV0 = createComposedReceiptFreshnessV0;
|
|
7
|
+
exports.computeDownstreamComposedMemoKeyV0 = computeDownstreamComposedMemoKeyV0;
|
|
8
|
+
exports.planCompositionPropagationV0 = planCompositionPropagationV0;
|
|
9
|
+
const memo_1 = require("../memo");
|
|
10
|
+
const policy_1 = require("../policy");
|
|
11
|
+
const receipt_1 = require("../receipt");
|
|
12
|
+
function dependencyReceiptPinFromVerifiedReceiptV0(input) {
|
|
13
|
+
const verification = (0, receipt_1.verifyReceiptV0)(input.upstream_receipt);
|
|
14
|
+
if (!verification.ok) {
|
|
15
|
+
throw new Error("upstream receipt must verify before composition");
|
|
16
|
+
}
|
|
17
|
+
const upstreamReceipt = input.upstream_receipt;
|
|
18
|
+
const acceptableSignerSet = normalizeAcceptableSignerSetV0(input.acceptable_signer_set, "acceptable_signer_set");
|
|
19
|
+
const signerPosture = signerPostureForVerifiedReceiptV0(upstreamReceipt);
|
|
20
|
+
if (!acceptableSignerSet.includes(signerPosture)) {
|
|
21
|
+
throw new Error("upstream receipt signer posture is not allowed by dependency pin");
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
upstream_content_hash: verification.content_hash,
|
|
25
|
+
contract_revision: upstreamReceipt.core.contract_revision,
|
|
26
|
+
acceptable_signer_set: acceptableSignerSet,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function verifyUpstreamReceiptDependencyPinV0(input) {
|
|
30
|
+
const verification = (0, receipt_1.verifyReceiptV0)(input.upstream_receipt);
|
|
31
|
+
if (!verification.ok) {
|
|
32
|
+
throw new Error("upstream receipt must verify before dependency pin check");
|
|
33
|
+
}
|
|
34
|
+
const upstreamReceipt = input.upstream_receipt;
|
|
35
|
+
const dependencyPin = normalizeDependencyReceiptPinV0(input.expected_dependency_pin, "expected_dependency_pin");
|
|
36
|
+
if (verification.content_hash !== dependencyPin.upstream_content_hash) {
|
|
37
|
+
throw new Error("upstream receipt content hash must match dependency pin");
|
|
38
|
+
}
|
|
39
|
+
if (upstreamReceipt.core.contract_revision !== dependencyPin.contract_revision) {
|
|
40
|
+
throw new Error("upstream receipt contract revision must match dependency pin");
|
|
41
|
+
}
|
|
42
|
+
const signerPosture = signerPostureForVerifiedReceiptV0(upstreamReceipt);
|
|
43
|
+
if (!dependencyPin.acceptable_signer_set.includes(signerPosture)) {
|
|
44
|
+
throw new Error("upstream receipt signer posture is not allowed by dependency pin");
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
receipt: upstreamReceipt,
|
|
48
|
+
content_hash: verification.content_hash,
|
|
49
|
+
dependency_pin: dependencyPin,
|
|
50
|
+
signer_posture: signerPosture,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function evaluateTransitiveFreshnessV0(input) {
|
|
54
|
+
const asOfMs = parseReplayableInstantMsV0(input.as_of, "as_of");
|
|
55
|
+
assertNonEmptyStringV0(input.transitive_freshness_policy_ref, "transitive_freshness_policy_ref");
|
|
56
|
+
const transitiveFreshnessFunction = (0, policy_1.normalizePolicyTransitiveFreshnessFunctionV0)(input.transitive_freshness_function);
|
|
57
|
+
if (!Array.isArray(input.consumed_receipts)) {
|
|
58
|
+
throw new Error("consumed_receipts must be an array");
|
|
59
|
+
}
|
|
60
|
+
const staleReceiptHashes = [];
|
|
61
|
+
const consumedFreshnessEvaluated = input.consumed_receipts.map((entry, index) => {
|
|
62
|
+
if (!isRecord(entry)) {
|
|
63
|
+
throw new Error(`consumed_receipts[${index}] must be an object`);
|
|
64
|
+
}
|
|
65
|
+
const verified = verifyUpstreamReceiptDependencyPinV0({
|
|
66
|
+
upstream_receipt: entry.upstream_receipt,
|
|
67
|
+
expected_dependency_pin: entry.dependency_pin,
|
|
68
|
+
});
|
|
69
|
+
const refetchedFrom = readRefetchedFromReceiptV0(entry, index, asOfMs);
|
|
70
|
+
const nextForecastRecheck = verified.receipt.freshness.next_forecast_recheck;
|
|
71
|
+
const nextForecastRecheckMs = parseReplayableInstantMsV0(nextForecastRecheck, `consumed_receipts[${index}].upstream_receipt.freshness.next_forecast_recheck`);
|
|
72
|
+
const isStale = nextForecastRecheckMs <= asOfMs ||
|
|
73
|
+
isStaleByAuthoredTransitiveFreshnessFunctionV0(transitiveFreshnessFunction, nextForecastRecheckMs, asOfMs);
|
|
74
|
+
if (isStale) {
|
|
75
|
+
staleReceiptHashes.push(verified.content_hash);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
receipt_hash: verified.content_hash,
|
|
79
|
+
next_forecast_recheck: nextForecastRecheck,
|
|
80
|
+
staleness_outcome: isStale
|
|
81
|
+
? "stale-blocked"
|
|
82
|
+
: refetchedFrom === undefined
|
|
83
|
+
? "fresh"
|
|
84
|
+
: "stale-refetched",
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
outcome: staleReceiptHashes.length === 0 ? "fresh" : "stale-blocked",
|
|
89
|
+
as_of: input.as_of,
|
|
90
|
+
transitive_freshness_policy_ref: input.transitive_freshness_policy_ref,
|
|
91
|
+
consumed_freshness_evaluated: consumedFreshnessEvaluated,
|
|
92
|
+
stale_receipt_hashes: staleReceiptHashes,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function isStaleByAuthoredTransitiveFreshnessFunctionV0(fn, nextForecastRecheckMs, asOfMs) {
|
|
96
|
+
switch (fn.kind) {
|
|
97
|
+
case "kernel-default":
|
|
98
|
+
return false;
|
|
99
|
+
case "minimum-remaining-freshness-ms":
|
|
100
|
+
return nextForecastRecheckMs <= asOfMs + fn.minimum_remaining_ms;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function readRefetchedFromReceiptV0(entry, index, asOfMs) {
|
|
104
|
+
const refetchedReceipt = entry["refetched_from_receipt"];
|
|
105
|
+
const refetchedPin = entry["refetched_from_dependency_pin"];
|
|
106
|
+
if (refetchedReceipt === undefined && refetchedPin === undefined) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
if (refetchedReceipt === undefined || refetchedPin === undefined) {
|
|
110
|
+
throw new Error(`consumed_receipts[${index}] refetch evidence requires both receipt and dependency pin`);
|
|
111
|
+
}
|
|
112
|
+
const verified = verifyUpstreamReceiptDependencyPinV0({
|
|
113
|
+
upstream_receipt: refetchedReceipt,
|
|
114
|
+
expected_dependency_pin: refetchedPin,
|
|
115
|
+
});
|
|
116
|
+
const previousNextForecastRecheckMs = parseReplayableInstantMsV0(verified.receipt.freshness.next_forecast_recheck, `consumed_receipts[${index}].refetched_from_receipt.freshness.next_forecast_recheck`);
|
|
117
|
+
if (previousNextForecastRecheckMs > asOfMs) {
|
|
118
|
+
throw new Error(`consumed_receipts[${index}].refetched_from_receipt must be stale at as_of`);
|
|
119
|
+
}
|
|
120
|
+
return verified;
|
|
121
|
+
}
|
|
122
|
+
function createComposedReceiptFreshnessV0(input) {
|
|
123
|
+
const asOfMs = parseReplayableInstantMsV0(input.as_of, "as_of");
|
|
124
|
+
const nextForecastRecheckMs = parseReplayableInstantMsV0(input.next_forecast_recheck, "next_forecast_recheck");
|
|
125
|
+
if (nextForecastRecheckMs < asOfMs) {
|
|
126
|
+
throw new Error("next_forecast_recheck must not be before as_of");
|
|
127
|
+
}
|
|
128
|
+
const evaluation = evaluateTransitiveFreshnessV0(input);
|
|
129
|
+
return {
|
|
130
|
+
as_of: input.as_of,
|
|
131
|
+
next_forecast_recheck: input.next_forecast_recheck,
|
|
132
|
+
transitive_freshness_policy_ref: input.transitive_freshness_policy_ref,
|
|
133
|
+
consumed_freshness_evaluated: evaluation.consumed_freshness_evaluated,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function computeDownstreamComposedMemoKeyV0(input) {
|
|
137
|
+
const dependencyReceipts = assertNoDuplicateDependencyPinsV0(input.dependency_receipts);
|
|
138
|
+
return (0, memo_1.computeMemoKeyV0)({
|
|
139
|
+
contract_revision: input.contract_revision,
|
|
140
|
+
evidence_receipts: input.evidence_receipts,
|
|
141
|
+
dependency_receipts: dependencyReceipts,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function planCompositionPropagationV0(input) {
|
|
145
|
+
const dependencyReceipts = assertNoDuplicateDependencyPinsV0(input.dependency_receipts);
|
|
146
|
+
const memoKey = computeDownstreamComposedMemoKeyV0({
|
|
147
|
+
contract_revision: input.contract_revision,
|
|
148
|
+
evidence_receipts: input.evidence_receipts,
|
|
149
|
+
dependency_receipts: dependencyReceipts,
|
|
150
|
+
});
|
|
151
|
+
const memoLookup = input.memo_store.lookup(input.memo_namespace, memoKey);
|
|
152
|
+
if (memoLookup.outcome === "miss") {
|
|
153
|
+
return {
|
|
154
|
+
outcome: "rejudge-required",
|
|
155
|
+
memo_key: memoKey,
|
|
156
|
+
memo_lookup: memoLookup,
|
|
157
|
+
dependency_receipts: dependencyReceipts,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
assertMemoHitMatchesComposedInputV0(memoLookup, memoKey, dependencyReceipts);
|
|
161
|
+
return {
|
|
162
|
+
outcome: "stop-memo-hit",
|
|
163
|
+
memo_key: memoKey,
|
|
164
|
+
memo_lookup: memoLookup,
|
|
165
|
+
memo_hit_receipt: (0, memo_1.createMemoHitReceiptV0)({
|
|
166
|
+
source_receipt: memoLookup.entry.receipt,
|
|
167
|
+
as_of: input.as_of,
|
|
168
|
+
next_forecast_recheck: input.next_forecast_recheck,
|
|
169
|
+
...(input.event_cause === undefined ? {} : { event_cause: input.event_cause }),
|
|
170
|
+
...(input.recheck_kind === undefined ? {} : { recheck_kind: input.recheck_kind }),
|
|
171
|
+
}),
|
|
172
|
+
dependency_receipts: dependencyReceipts,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function assertMemoHitMatchesComposedInputV0(memoLookup, memoKey, dependencyReceipts) {
|
|
176
|
+
if (memoLookup.entry.memo_key !== memoKey) {
|
|
177
|
+
throw new Error("composition memo hit entry key must match composed memo key");
|
|
178
|
+
}
|
|
179
|
+
if (memoLookup.entry.receipt.core.memo_key !== memoKey) {
|
|
180
|
+
throw new Error("composition memo hit receipt key must match composed memo key");
|
|
181
|
+
}
|
|
182
|
+
if (canonicalizeDependencyPinsForComparisonV0(memoLookup.entry.receipt.composition.consumed_receipts) !== canonicalizeDependencyPinsForComparisonV0(dependencyReceipts)) {
|
|
183
|
+
throw new Error("composition memo hit receipt dependencies must match composed input");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function assertNoDuplicateDependencyPinsV0(dependencyReceipts) {
|
|
187
|
+
const seen = new Set();
|
|
188
|
+
for (const dependencyReceipt of dependencyReceipts) {
|
|
189
|
+
if (seen.has(dependencyReceipt.upstream_content_hash)) {
|
|
190
|
+
throw new Error(`duplicate dependency receipt pin ${dependencyReceipt.upstream_content_hash}`);
|
|
191
|
+
}
|
|
192
|
+
seen.add(dependencyReceipt.upstream_content_hash);
|
|
193
|
+
}
|
|
194
|
+
return dependencyReceipts;
|
|
195
|
+
}
|
|
196
|
+
function canonicalizeDependencyPinsForComparisonV0(dependencyReceipts) {
|
|
197
|
+
return (0, receipt_1.canonicalizeForReceiptV0)(dependencyReceipts
|
|
198
|
+
.map((dependencyReceipt, index) => ({
|
|
199
|
+
upstream_content_hash: dependencyReceipt.upstream_content_hash,
|
|
200
|
+
contract_revision: dependencyReceipt.contract_revision,
|
|
201
|
+
acceptable_signer_set: normalizeAcceptableSignerSetV0(dependencyReceipt.acceptable_signer_set, `dependency_receipts[${index}].acceptable_signer_set`),
|
|
202
|
+
}))
|
|
203
|
+
.sort((left, right) => left.upstream_content_hash.localeCompare(right.upstream_content_hash)));
|
|
204
|
+
}
|
|
205
|
+
function normalizeAcceptableSignerSetV0(acceptableSignerSet, path) {
|
|
206
|
+
if (!Array.isArray(acceptableSignerSet)) {
|
|
207
|
+
throw new Error(`${path} must be an array`);
|
|
208
|
+
}
|
|
209
|
+
if (acceptableSignerSet.length === 0) {
|
|
210
|
+
throw new Error(`${path} must not be empty`);
|
|
211
|
+
}
|
|
212
|
+
const seen = new Set();
|
|
213
|
+
for (const [index, signer] of acceptableSignerSet.entries()) {
|
|
214
|
+
if (typeof signer !== "string") {
|
|
215
|
+
throw new Error(`${path}[${index}] must be a non-empty string`);
|
|
216
|
+
}
|
|
217
|
+
if (signer.length === 0) {
|
|
218
|
+
throw new Error(`${path} contains an empty signer`);
|
|
219
|
+
}
|
|
220
|
+
if (seen.has(signer)) {
|
|
221
|
+
throw new Error(`${path} contains duplicate signer ${signer}`);
|
|
222
|
+
}
|
|
223
|
+
seen.add(signer);
|
|
224
|
+
}
|
|
225
|
+
return [...acceptableSignerSet].sort((left, right) => left.localeCompare(right));
|
|
226
|
+
}
|
|
227
|
+
function normalizeDependencyReceiptPinV0(value, path) {
|
|
228
|
+
if (!isRecord(value)) {
|
|
229
|
+
throw new Error(`${path} must be an object`);
|
|
230
|
+
}
|
|
231
|
+
assertExactPinKeysV0(value, path);
|
|
232
|
+
assertContentHashV0(value["upstream_content_hash"], `${path}.upstream_content_hash`);
|
|
233
|
+
assertContentHashV0(value["contract_revision"], `${path}.contract_revision`);
|
|
234
|
+
return {
|
|
235
|
+
upstream_content_hash: value["upstream_content_hash"],
|
|
236
|
+
contract_revision: value["contract_revision"],
|
|
237
|
+
acceptable_signer_set: normalizeAcceptableSignerSetV0(value["acceptable_signer_set"], `${path}.acceptable_signer_set`),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function signerPostureForVerifiedReceiptV0(receipt) {
|
|
241
|
+
if (receipt.sig.scheme === "none") {
|
|
242
|
+
return "none";
|
|
243
|
+
}
|
|
244
|
+
throw new Error("upstream receipt non-null signer posture is rejected in receipt v0.1; null signer is the only honest v0.1 dependency posture");
|
|
245
|
+
}
|
|
246
|
+
function assertExactPinKeysV0(value, path) {
|
|
247
|
+
const allowed = new Set([
|
|
248
|
+
"upstream_content_hash",
|
|
249
|
+
"contract_revision",
|
|
250
|
+
"acceptable_signer_set",
|
|
251
|
+
]);
|
|
252
|
+
for (const key of Object.keys(value)) {
|
|
253
|
+
if (!allowed.has(key)) {
|
|
254
|
+
throw new Error(`${path}.${key} is not pinned in dependency receipt v0`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function assertContentHashV0(value, path) {
|
|
259
|
+
if (typeof value !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value)) {
|
|
260
|
+
throw new Error(`${path} must be a sha256 content address`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function assertNonEmptyStringV0(value, path) {
|
|
264
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
265
|
+
throw new Error(`${path} must be a non-empty string`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function parseReplayableInstantMsV0(value, path) {
|
|
269
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(value)) {
|
|
270
|
+
throw new Error(`${path} must be a replayable ISO instant`);
|
|
271
|
+
}
|
|
272
|
+
const parsed = Date.parse(value);
|
|
273
|
+
if (!Number.isFinite(parsed)) {
|
|
274
|
+
throw new Error(`${path} must be a replayable ISO instant`);
|
|
275
|
+
}
|
|
276
|
+
return parsed;
|
|
277
|
+
}
|
|
278
|
+
function isRecord(value) {
|
|
279
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
280
|
+
}
|