@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,1463 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_TRANSITIVE_FRESHNESS_FUNCTION_V0 = exports.POLICY_DRIFT_FACT_IDS_V0 = exports.POLICY_ROLLBACK_DECISION_SCHEMA = exports.POLICY_RECOMPILE_EXECUTION_SCHEMA = exports.POLICY_RECOMPILE_DECISION_SCHEMA = exports.POLICY_DRIFT_EVALUATION_SCHEMA = exports.POLICY_DRIFT_FACTS_SCHEMA = exports.POLICY_ARTIFACT_VALIDATOR_ID = exports.POLICY_AUTHOR_ARTIFACT_RESPONSE_SCHEMA = exports.POLICY_AUTHOR_HISTORY_QUERY_SCHEMA = exports.POLICY_AUTHOR_REQUEST_SCHEMA = exports.POLICY_ARTIFACT_VERSION = exports.POLICY_ARTIFACT_SCHEMA = void 0;
|
|
4
|
+
exports.authorPolicyArtifactV0 = authorPolicyArtifactV0;
|
|
5
|
+
exports.validatePolicyArtifactV0 = validatePolicyArtifactV0;
|
|
6
|
+
exports.canonicalizePolicyArtifactV0 = canonicalizePolicyArtifactV0;
|
|
7
|
+
exports.normalizePolicyTransitiveFreshnessFunctionV0 = normalizePolicyTransitiveFreshnessFunctionV0;
|
|
8
|
+
exports.derivePolicyDriftFactsV0 = derivePolicyDriftFactsV0;
|
|
9
|
+
exports.evaluatePolicyDriftV0 = evaluatePolicyDriftV0;
|
|
10
|
+
exports.planPolicyRecompileV0 = planPolicyRecompileV0;
|
|
11
|
+
exports.executePolicyRecompileV0 = executePolicyRecompileV0;
|
|
12
|
+
exports.planPolicyRollbackV0 = planPolicyRollbackV0;
|
|
13
|
+
const kernel_1 = require("../kernel");
|
|
14
|
+
const receipt_1 = require("../receipt");
|
|
15
|
+
exports.POLICY_ARTIFACT_SCHEMA = "openprose.reactor.policy-artifact";
|
|
16
|
+
exports.POLICY_ARTIFACT_VERSION = 0;
|
|
17
|
+
exports.POLICY_AUTHOR_REQUEST_SCHEMA = "openprose.reactor.policy-author.request";
|
|
18
|
+
exports.POLICY_AUTHOR_HISTORY_QUERY_SCHEMA = "openprose.reactor.policy-author.history-query";
|
|
19
|
+
exports.POLICY_AUTHOR_ARTIFACT_RESPONSE_SCHEMA = "openprose.reactor.policy-author.artifact-response";
|
|
20
|
+
exports.POLICY_ARTIFACT_VALIDATOR_ID = "@openprose/reactor/policy.validatePolicyArtifactV0";
|
|
21
|
+
exports.POLICY_DRIFT_FACTS_SCHEMA = "openprose.reactor.policy-drift.facts";
|
|
22
|
+
exports.POLICY_DRIFT_EVALUATION_SCHEMA = "openprose.reactor.policy-drift.evaluation";
|
|
23
|
+
exports.POLICY_RECOMPILE_DECISION_SCHEMA = "openprose.reactor.policy-recompile.decision";
|
|
24
|
+
exports.POLICY_RECOMPILE_EXECUTION_SCHEMA = "openprose.reactor.policy-recompile.execution";
|
|
25
|
+
exports.POLICY_ROLLBACK_DECISION_SCHEMA = "openprose.reactor.policy-rollback.decision";
|
|
26
|
+
exports.POLICY_DRIFT_FACT_IDS_V0 = [
|
|
27
|
+
"cost.fresh_tokens_per_maintained_day",
|
|
28
|
+
"receipt.escalation_precision_7d",
|
|
29
|
+
"kernel.deep_shallow_contradiction_count_7d",
|
|
30
|
+
];
|
|
31
|
+
exports.DEFAULT_TRANSITIVE_FRESHNESS_FUNCTION_V0 = Object.freeze({
|
|
32
|
+
kind: "kernel-default",
|
|
33
|
+
});
|
|
34
|
+
const CONTENT_HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
35
|
+
const ISO_INSTANT_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
36
|
+
const LIVE_OBSERVABLE_SOURCES = new Set([
|
|
37
|
+
"connector",
|
|
38
|
+
"receipt-log",
|
|
39
|
+
"kernel-backstop",
|
|
40
|
+
"cost-ledger",
|
|
41
|
+
"human-label-stream",
|
|
42
|
+
]);
|
|
43
|
+
const POLICY_DRIFT_FACT_ID_SET = new Set(exports.POLICY_DRIFT_FACT_IDS_V0);
|
|
44
|
+
const ESCALATION_CONFIRMED_TAGS = new Set([
|
|
45
|
+
"escalation:confirmed",
|
|
46
|
+
"escalation:true-positive",
|
|
47
|
+
"escalation:true_positive",
|
|
48
|
+
"escalation-confirmed",
|
|
49
|
+
"escalation-needed",
|
|
50
|
+
"escalation:needed",
|
|
51
|
+
]);
|
|
52
|
+
const ESCALATION_REFUTED_TAGS = new Set([
|
|
53
|
+
"escalation:refuted",
|
|
54
|
+
"escalation:false-positive",
|
|
55
|
+
"escalation:false_positive",
|
|
56
|
+
"escalation-refuted",
|
|
57
|
+
"escalation-unneeded",
|
|
58
|
+
"escalation:unneeded",
|
|
59
|
+
]);
|
|
60
|
+
const DEEP_SHALLOW_CONTRADICTION_TAGS = new Set([
|
|
61
|
+
"backstop-divergence",
|
|
62
|
+
"deep-shallow-contradiction",
|
|
63
|
+
"kernel:deep-shallow-contradiction",
|
|
64
|
+
]);
|
|
65
|
+
function authorPolicyArtifactV0(input) {
|
|
66
|
+
validateAuthorInput(input);
|
|
67
|
+
const receiptHistory = assertReceiptHistory(input.receipt_history);
|
|
68
|
+
const receiptHistorySummary = receiptHistory.map((receipt) => summarizeReceiptForPolicyAuthor(receipt));
|
|
69
|
+
const receiptHistorySummaryHash = (0, receipt_1.hashCanonicalReceiptV0)((0, receipt_1.canonicalizeForReceiptV0)(receiptHistorySummary));
|
|
70
|
+
const historyRequest = {
|
|
71
|
+
schema: exports.POLICY_AUTHOR_REQUEST_SCHEMA,
|
|
72
|
+
v: exports.POLICY_ARTIFACT_VERSION,
|
|
73
|
+
step: "history-query",
|
|
74
|
+
responsibility_id: input.responsibility_id,
|
|
75
|
+
contract_revision: input.contract_revision,
|
|
76
|
+
contract_summary: input.contract_summary,
|
|
77
|
+
no_anchor: input.no_anchor,
|
|
78
|
+
live_observables: canonicalLiveObservables(input.live_observables),
|
|
79
|
+
receipt_history_summary: receiptHistorySummary,
|
|
80
|
+
};
|
|
81
|
+
const historyResponse = input.agentSdk.launch({
|
|
82
|
+
kind: "policy-author",
|
|
83
|
+
payload: historyRequest,
|
|
84
|
+
});
|
|
85
|
+
const historyQuery = readHistoryQueryResponse(historyResponse.payload);
|
|
86
|
+
assertSelectedReceiptsExist(historyQuery, receiptHistory);
|
|
87
|
+
const selectedReceipts = selectReceipts(receiptHistory, historyQuery);
|
|
88
|
+
const artifactRequest = {
|
|
89
|
+
schema: exports.POLICY_AUTHOR_REQUEST_SCHEMA,
|
|
90
|
+
v: exports.POLICY_ARTIFACT_VERSION,
|
|
91
|
+
step: "author-artifact",
|
|
92
|
+
responsibility_id: input.responsibility_id,
|
|
93
|
+
contract_revision: input.contract_revision,
|
|
94
|
+
contract_summary: input.contract_summary,
|
|
95
|
+
no_anchor: input.no_anchor,
|
|
96
|
+
live_observables: canonicalLiveObservables(input.live_observables),
|
|
97
|
+
receipt_history_summary_hash: receiptHistorySummaryHash,
|
|
98
|
+
history_query: historyQuery,
|
|
99
|
+
selected_receipts: selectedReceipts,
|
|
100
|
+
};
|
|
101
|
+
const artifactResponse = input.agentSdk.launch({
|
|
102
|
+
kind: "policy-author",
|
|
103
|
+
payload: artifactRequest,
|
|
104
|
+
});
|
|
105
|
+
const authoredArtifact = normalizeAuthoredPolicyArtifactV0(readAuthoredArtifactResponse(artifactResponse.payload), {
|
|
106
|
+
responsibility_id: input.responsibility_id,
|
|
107
|
+
contract_revision: input.contract_revision,
|
|
108
|
+
no_anchor: input.no_anchor,
|
|
109
|
+
live_observables: input.live_observables,
|
|
110
|
+
receipt_history_summary_hash: receiptHistorySummaryHash,
|
|
111
|
+
history_query: historyQuery,
|
|
112
|
+
});
|
|
113
|
+
const validation = validatePolicyArtifactV0(authoredArtifact);
|
|
114
|
+
if (!validation.ok) {
|
|
115
|
+
throw new Error(`policy artifact validation failed: ${validation.errors.join("; ")}`);
|
|
116
|
+
}
|
|
117
|
+
const namespace = input.policy_artifact_namespace ?? validation.artifact.registry_id;
|
|
118
|
+
const validationState = {
|
|
119
|
+
status: "validated",
|
|
120
|
+
validator_id: exports.POLICY_ARTIFACT_VALIDATOR_ID,
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
contract_revision: input.contract_revision,
|
|
124
|
+
policy_artifact_id: validation.artifact.registry_id,
|
|
125
|
+
policy_artifact_identity: validation.artifact.registry_id,
|
|
126
|
+
policy_artifact_namespace: namespace,
|
|
127
|
+
policy_artifact_revision: validation.artifact.policy_revision,
|
|
128
|
+
policy_artifact_validation_state: validationState,
|
|
129
|
+
validation_state: validationState,
|
|
130
|
+
policy_artifact_bytes: validation.bytes,
|
|
131
|
+
policy_artifact_content_hash: validation.content_hash,
|
|
132
|
+
transitive_freshness_function: validation.artifact.transitive_freshness_function,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function validatePolicyArtifactV0(value) {
|
|
136
|
+
const errors = [];
|
|
137
|
+
if (!isRecord(value)) {
|
|
138
|
+
return {
|
|
139
|
+
ok: false,
|
|
140
|
+
errors: ["policy artifact must be an object"],
|
|
141
|
+
live_observable_refs: [],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
validateKnownKeys(value, "policy_artifact", [
|
|
145
|
+
"schema",
|
|
146
|
+
"v",
|
|
147
|
+
"responsibility_id",
|
|
148
|
+
"registry_id",
|
|
149
|
+
"policy_revision",
|
|
150
|
+
"no_anchor",
|
|
151
|
+
"live_observables",
|
|
152
|
+
"cadence",
|
|
153
|
+
"hysteresis",
|
|
154
|
+
"thresholds",
|
|
155
|
+
"transitive_freshness_function",
|
|
156
|
+
"falsification_predicate",
|
|
157
|
+
"backstop_divergence_predicate",
|
|
158
|
+
"provenance",
|
|
159
|
+
], errors);
|
|
160
|
+
expectLiteral(value["schema"], exports.POLICY_ARTIFACT_SCHEMA, "policy_artifact.schema", errors);
|
|
161
|
+
expectLiteral(value["v"], exports.POLICY_ARTIFACT_VERSION, "policy_artifact.v", errors);
|
|
162
|
+
const responsibilityId = readNonEmptyString(value["responsibility_id"], "policy_artifact.responsibility_id", errors);
|
|
163
|
+
const registryId = readNonEmptyString(value["registry_id"], "policy_artifact.registry_id", errors);
|
|
164
|
+
const policyRevision = readNonEmptyString(value["policy_revision"], "policy_artifact.policy_revision", errors);
|
|
165
|
+
const noAnchor = readBoolean(value["no_anchor"], "policy_artifact.no_anchor", errors);
|
|
166
|
+
const liveObservables = readLiveObservables(value["live_observables"], "policy_artifact.live_observables", errors);
|
|
167
|
+
const cadence = readCadence(value["cadence"], noAnchor, errors);
|
|
168
|
+
const hysteresis = readHysteresis(value["hysteresis"], errors);
|
|
169
|
+
const thresholds = readThresholds(value["thresholds"], errors);
|
|
170
|
+
const transitiveFreshnessFunction = readTransitiveFreshnessFunction(value["transitive_freshness_function"], "policy_artifact.transitive_freshness_function", errors);
|
|
171
|
+
const falsificationPredicate = readPredicate(value["falsification_predicate"], "policy_artifact.falsification_predicate", errors);
|
|
172
|
+
const backstopDivergencePredicate = value["backstop_divergence_predicate"] === undefined
|
|
173
|
+
? undefined
|
|
174
|
+
: readPredicate(value["backstop_divergence_predicate"], "policy_artifact.backstop_divergence_predicate", errors);
|
|
175
|
+
const provenance = readProvenance(value["provenance"], errors);
|
|
176
|
+
if (provenance !== undefined &&
|
|
177
|
+
provenance.explored_receipt_hashes.join("\0") !==
|
|
178
|
+
provenance.history_query.selected_receipt_hashes.join("\0")) {
|
|
179
|
+
errors.push("policy_artifact.provenance.explored_receipt_hashes must match history_query.selected_receipt_hashes");
|
|
180
|
+
}
|
|
181
|
+
let kernelValidation;
|
|
182
|
+
if (noAnchor !== undefined &&
|
|
183
|
+
liveObservables.length > 0 &&
|
|
184
|
+
falsificationPredicate !== undefined) {
|
|
185
|
+
const liveObservableIds = liveObservables.map((observable) => observable.id);
|
|
186
|
+
rejectOffLivePredicateFacts(falsificationPredicate, "policy_artifact.falsification_predicate", liveObservableIds, errors);
|
|
187
|
+
if (backstopDivergencePredicate !== undefined) {
|
|
188
|
+
rejectOffLivePredicateFacts(backstopDivergencePredicate, "policy_artifact.backstop_divergence_predicate", liveObservableIds, errors);
|
|
189
|
+
}
|
|
190
|
+
kernelValidation = (0, kernel_1.validateKernelPolicyArtifact)({
|
|
191
|
+
no_anchor: noAnchor,
|
|
192
|
+
falsification_predicate: falsificationPredicate,
|
|
193
|
+
live_observables: liveObservableIds,
|
|
194
|
+
...(backstopDivergencePredicate === undefined
|
|
195
|
+
? {}
|
|
196
|
+
: { backstop_divergence_predicate: backstopDivergencePredicate }),
|
|
197
|
+
});
|
|
198
|
+
if (!kernelValidation.ok) {
|
|
199
|
+
errors.push(...kernelValidation.errors.map((error) => `kernel validation: ${error}`));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (errors.length > 0 ||
|
|
203
|
+
responsibilityId === undefined ||
|
|
204
|
+
registryId === undefined ||
|
|
205
|
+
policyRevision === undefined ||
|
|
206
|
+
noAnchor === undefined ||
|
|
207
|
+
cadence === undefined ||
|
|
208
|
+
hysteresis === undefined ||
|
|
209
|
+
thresholds === undefined ||
|
|
210
|
+
transitiveFreshnessFunction === undefined ||
|
|
211
|
+
falsificationPredicate === undefined ||
|
|
212
|
+
provenance === undefined ||
|
|
213
|
+
kernelValidation === undefined ||
|
|
214
|
+
!kernelValidation.ok) {
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
errors,
|
|
218
|
+
live_observable_refs: kernelValidation?.live_observable_refs ?? [],
|
|
219
|
+
...(kernelValidation === undefined ? {} : { kernel_validation: kernelValidation }),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const artifact = {
|
|
223
|
+
schema: exports.POLICY_ARTIFACT_SCHEMA,
|
|
224
|
+
v: exports.POLICY_ARTIFACT_VERSION,
|
|
225
|
+
responsibility_id: responsibilityId,
|
|
226
|
+
registry_id: registryId,
|
|
227
|
+
policy_revision: policyRevision,
|
|
228
|
+
no_anchor: noAnchor,
|
|
229
|
+
live_observables: liveObservables,
|
|
230
|
+
cadence,
|
|
231
|
+
hysteresis,
|
|
232
|
+
thresholds,
|
|
233
|
+
transitive_freshness_function: transitiveFreshnessFunction,
|
|
234
|
+
falsification_predicate: falsificationPredicate,
|
|
235
|
+
...(backstopDivergencePredicate === undefined
|
|
236
|
+
? {}
|
|
237
|
+
: { backstop_divergence_predicate: backstopDivergencePredicate }),
|
|
238
|
+
provenance,
|
|
239
|
+
};
|
|
240
|
+
const bytes = canonicalizePolicyArtifactV0(artifact);
|
|
241
|
+
return {
|
|
242
|
+
ok: true,
|
|
243
|
+
artifact,
|
|
244
|
+
bytes,
|
|
245
|
+
content_hash: (0, receipt_1.hashCanonicalReceiptV0)(bytes),
|
|
246
|
+
kernel_validation: kernelValidation,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function canonicalizePolicyArtifactV0(artifact) {
|
|
250
|
+
return (0, receipt_1.canonicalizeForReceiptV0)(artifact);
|
|
251
|
+
}
|
|
252
|
+
function normalizePolicyTransitiveFreshnessFunctionV0(value, path = "transitive_freshness_function") {
|
|
253
|
+
const errors = [];
|
|
254
|
+
const fn = readTransitiveFreshnessFunction(value, path, errors);
|
|
255
|
+
if (fn === undefined || errors.length > 0) {
|
|
256
|
+
throw new Error(`${path} is malformed: ${errors.join("; ")}`);
|
|
257
|
+
}
|
|
258
|
+
return fn;
|
|
259
|
+
}
|
|
260
|
+
function derivePolicyDriftFactsV0(inputOrReceipts) {
|
|
261
|
+
const input = normalizePolicyDriftFactInput(inputOrReceipts);
|
|
262
|
+
const receipts = assertPolicyDriftReceiptLog(input.receipts);
|
|
263
|
+
const scopedReceipts = sortReceiptsForPolicyDrift(receipts.filter((receipt) => receiptBelongsToPolicyDriftScope(receipt, input)));
|
|
264
|
+
const asOf = resolvePolicyDriftAsOf(scopedReceipts, input.as_of);
|
|
265
|
+
const facts = {};
|
|
266
|
+
const unsupportedFactIds = new Set(exports.POLICY_DRIFT_FACT_IDS_V0);
|
|
267
|
+
if (asOf !== null) {
|
|
268
|
+
const asOfMs = parseReplayableInstantMs(asOf, "policy drift as_of");
|
|
269
|
+
const freshTokensPerDay = deriveFreshTokensPerMaintainedDay(scopedReceipts, asOfMs);
|
|
270
|
+
if (freshTokensPerDay !== undefined) {
|
|
271
|
+
facts["cost.fresh_tokens_per_maintained_day"] = freshTokensPerDay;
|
|
272
|
+
unsupportedFactIds.delete("cost.fresh_tokens_per_maintained_day");
|
|
273
|
+
}
|
|
274
|
+
const escalationPrecision = deriveEscalationPrecision7d(scopedReceipts, asOfMs);
|
|
275
|
+
if (escalationPrecision !== undefined) {
|
|
276
|
+
facts["receipt.escalation_precision_7d"] = escalationPrecision;
|
|
277
|
+
unsupportedFactIds.delete("receipt.escalation_precision_7d");
|
|
278
|
+
}
|
|
279
|
+
const contradictionCount = deriveDeepShallowContradictionCount7d(scopedReceipts, asOfMs);
|
|
280
|
+
if (contradictionCount !== undefined) {
|
|
281
|
+
facts["kernel.deep_shallow_contradiction_count_7d"] = contradictionCount;
|
|
282
|
+
unsupportedFactIds.delete("kernel.deep_shallow_contradiction_count_7d");
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const supportedFactIds = Object.keys(facts).sort((left, right) => left.localeCompare(right));
|
|
286
|
+
return {
|
|
287
|
+
schema: exports.POLICY_DRIFT_FACTS_SCHEMA,
|
|
288
|
+
v: exports.POLICY_ARTIFACT_VERSION,
|
|
289
|
+
as_of: asOf,
|
|
290
|
+
facts: Object.freeze({ ...facts }),
|
|
291
|
+
supported_fact_ids: Object.freeze(supportedFactIds),
|
|
292
|
+
unsupported_fact_ids: Object.freeze([...unsupportedFactIds].sort((left, right) => left.localeCompare(right))),
|
|
293
|
+
evidence_receipt_hashes: Object.freeze(evidenceReceiptHashesForPolicyDrift(scopedReceipts, asOf)),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function evaluatePolicyDriftV0(inputOrArtifact, receipts, options = {}) {
|
|
297
|
+
const input = normalizePolicyDriftEvaluationInput(inputOrArtifact, receipts, options);
|
|
298
|
+
const validation = validatePolicyArtifactV0(input.artifact);
|
|
299
|
+
if (!validation.ok) {
|
|
300
|
+
throw new Error(`policy drift evaluation requires a validated policy artifact: ${validation.errors.join("; ")}`);
|
|
301
|
+
}
|
|
302
|
+
const artifact = validation.artifact;
|
|
303
|
+
const derivation = derivePolicyDriftFactsV0({
|
|
304
|
+
receipts: input.receipts,
|
|
305
|
+
responsibility_id: artifact.responsibility_id,
|
|
306
|
+
contract_revision: artifact.provenance.contract_revision,
|
|
307
|
+
...(input.as_of === undefined ? {} : { as_of: input.as_of }),
|
|
308
|
+
});
|
|
309
|
+
const predicateFactIds = collectPredicateFacts(artifact.falsification_predicate);
|
|
310
|
+
const missingFactIds = predicateFactIds.filter((fact) => !Object.prototype.hasOwnProperty.call(derivation.facts, fact));
|
|
311
|
+
if (missingFactIds.length > 0) {
|
|
312
|
+
const unsupportedFactIds = missingFactIds.filter((fact) => !POLICY_DRIFT_FACT_ID_SET.has(fact) ||
|
|
313
|
+
derivation.unsupported_fact_ids.includes(fact));
|
|
314
|
+
return {
|
|
315
|
+
schema: exports.POLICY_DRIFT_EVALUATION_SCHEMA,
|
|
316
|
+
v: exports.POLICY_ARTIFACT_VERSION,
|
|
317
|
+
outcome: "indeterminate",
|
|
318
|
+
as_of: derivation.as_of,
|
|
319
|
+
policy_artifact_content_hash: validation.content_hash,
|
|
320
|
+
policy_artifact_revision: artifact.policy_revision,
|
|
321
|
+
facts: derivation.facts,
|
|
322
|
+
evidence_receipt_hashes: derivation.evidence_receipt_hashes,
|
|
323
|
+
predicate: {
|
|
324
|
+
outcome: "indeterminate",
|
|
325
|
+
reason: `unsupported or missing policy drift fact(s): ${missingFactIds.join(", ")}`,
|
|
326
|
+
},
|
|
327
|
+
missing_fact_ids: Object.freeze([...missingFactIds]),
|
|
328
|
+
unsupported_fact_ids: Object.freeze(unsupportedFactIds.sort((left, right) => left.localeCompare(right))),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
const predicate = (0, kernel_1.evaluatePredicate)(artifact.falsification_predicate, derivation.facts);
|
|
332
|
+
return {
|
|
333
|
+
schema: exports.POLICY_DRIFT_EVALUATION_SCHEMA,
|
|
334
|
+
v: exports.POLICY_ARTIFACT_VERSION,
|
|
335
|
+
outcome: predicate.outcome,
|
|
336
|
+
as_of: derivation.as_of,
|
|
337
|
+
policy_artifact_content_hash: validation.content_hash,
|
|
338
|
+
policy_artifact_revision: artifact.policy_revision,
|
|
339
|
+
facts: derivation.facts,
|
|
340
|
+
evidence_receipt_hashes: derivation.evidence_receipt_hashes,
|
|
341
|
+
predicate,
|
|
342
|
+
missing_fact_ids: Object.freeze([]),
|
|
343
|
+
unsupported_fact_ids: Object.freeze([]),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function planPolicyRecompileV0(input) {
|
|
347
|
+
validatePolicyRecompileInput(input);
|
|
348
|
+
const validation = validatePolicyArtifactV0(input.artifact);
|
|
349
|
+
if (!validation.ok) {
|
|
350
|
+
throw new Error(`policy recompile planning requires a validated policy artifact: ${validation.errors.join("; ")}`);
|
|
351
|
+
}
|
|
352
|
+
const artifact = validation.artifact;
|
|
353
|
+
const drift = evaluatePolicyDriftV0({
|
|
354
|
+
artifact,
|
|
355
|
+
receipts: input.receipts,
|
|
356
|
+
as_of: input.as_of,
|
|
357
|
+
});
|
|
358
|
+
const preliminaryRecompileRequested = drift.outcome === "tripped";
|
|
359
|
+
let backstops = evaluatePolicyRecompileBackstops(input, artifact, validation, preliminaryRecompileRequested);
|
|
360
|
+
let requestedBy = collectPolicyRecompileRequestSources(drift, backstops);
|
|
361
|
+
if (requestedBy.length > 0 && !preliminaryRecompileRequested) {
|
|
362
|
+
backstops = evaluatePolicyRecompileBackstops(input, artifact, validation, true);
|
|
363
|
+
requestedBy = collectPolicyRecompileRequestSources(drift, backstops);
|
|
364
|
+
}
|
|
365
|
+
const base = {
|
|
366
|
+
schema: exports.POLICY_RECOMPILE_DECISION_SCHEMA,
|
|
367
|
+
v: exports.POLICY_ARTIFACT_VERSION,
|
|
368
|
+
as_of: input.as_of,
|
|
369
|
+
responsibility_id: artifact.responsibility_id,
|
|
370
|
+
contract_revision: artifact.provenance.contract_revision,
|
|
371
|
+
policy_artifact_content_hash: validation.content_hash,
|
|
372
|
+
policy_artifact_revision: artifact.policy_revision,
|
|
373
|
+
requested_by: Object.freeze([...requestedBy]),
|
|
374
|
+
drift,
|
|
375
|
+
evidence_receipt_hashes: drift.evidence_receipt_hashes,
|
|
376
|
+
backstops,
|
|
377
|
+
};
|
|
378
|
+
if (drift.outcome === "indeterminate") {
|
|
379
|
+
return {
|
|
380
|
+
...base,
|
|
381
|
+
outcome: "needs-judgment",
|
|
382
|
+
reasons: Object.freeze([
|
|
383
|
+
drift.predicate.reason ??
|
|
384
|
+
"policy drift predicate was indeterminate against receipt evidence",
|
|
385
|
+
]),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
if (requestedBy.length === 0) {
|
|
389
|
+
return {
|
|
390
|
+
...base,
|
|
391
|
+
outcome: "no-recompile-needed",
|
|
392
|
+
reasons: Object.freeze(["policy drift and recompile backstops did not trip"]),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
if (hasMinRecompileDelay(backstops)) {
|
|
396
|
+
return {
|
|
397
|
+
...base,
|
|
398
|
+
outcome: "recompile-delayed",
|
|
399
|
+
reasons: Object.freeze([
|
|
400
|
+
"recompile request is held by the fixed min_recompile_interval backstop",
|
|
401
|
+
]),
|
|
402
|
+
delayed_by: "min_recompile_interval",
|
|
403
|
+
retry_after_ms: remainingMinRecompileIntervalMs(input),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
...base,
|
|
408
|
+
outcome: "recompile-requested",
|
|
409
|
+
reasons: Object.freeze(["policy drift or a fixed backstop requested recompile"]),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function executePolicyRecompileV0(input) {
|
|
413
|
+
if (input.decision.outcome !== "recompile-requested") {
|
|
414
|
+
return {
|
|
415
|
+
schema: exports.POLICY_RECOMPILE_EXECUTION_SCHEMA,
|
|
416
|
+
v: exports.POLICY_ARTIFACT_VERSION,
|
|
417
|
+
outcome: "not-executed",
|
|
418
|
+
decision: input.decision,
|
|
419
|
+
reason: `policy author is not allowed for ${input.decision.outcome}`,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
if (input.author_input === undefined) {
|
|
423
|
+
throw new Error("policy recompile execution requires author_input");
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
schema: exports.POLICY_RECOMPILE_EXECUTION_SCHEMA,
|
|
427
|
+
v: exports.POLICY_ARTIFACT_VERSION,
|
|
428
|
+
outcome: "recompile-authored",
|
|
429
|
+
decision: input.decision,
|
|
430
|
+
registry: authorPolicyArtifactV0(input.author_input),
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function planPolicyRollbackV0(input) {
|
|
434
|
+
validatePolicyRollbackInput(input);
|
|
435
|
+
const freshJudgedActivations = (0, kernel_1.judgedActivations)(input.fresh_policy_judged_activations_before_trip, "fresh_policy_judged_activations_before_trip");
|
|
436
|
+
const lastKnownGoodJudgedActivations = input.last_known_good_judged_activations_before_trip === undefined
|
|
437
|
+
? undefined
|
|
438
|
+
: (0, kernel_1.judgedActivations)(input.last_known_good_judged_activations_before_trip, "last_known_good_judged_activations_before_trip");
|
|
439
|
+
const comparison = (0, kernel_1.compareRollback)({
|
|
440
|
+
fresh_policy_revision: input.fresh_policy_revision,
|
|
441
|
+
fresh_policy_judged_activations_before_trip: freshJudgedActivations,
|
|
442
|
+
...(input.last_known_good_revision === undefined
|
|
443
|
+
? {}
|
|
444
|
+
: { last_known_good_revision: input.last_known_good_revision }),
|
|
445
|
+
...(lastKnownGoodJudgedActivations === undefined
|
|
446
|
+
? {}
|
|
447
|
+
: {
|
|
448
|
+
last_known_good_judged_activations_before_trip: lastKnownGoodJudgedActivations,
|
|
449
|
+
}),
|
|
450
|
+
});
|
|
451
|
+
return {
|
|
452
|
+
schema: exports.POLICY_ROLLBACK_DECISION_SCHEMA,
|
|
453
|
+
v: exports.POLICY_ARTIFACT_VERSION,
|
|
454
|
+
fresh_policy_revision: input.fresh_policy_revision,
|
|
455
|
+
...(input.last_known_good_revision === undefined
|
|
456
|
+
? {}
|
|
457
|
+
: { last_known_good_revision: input.last_known_good_revision }),
|
|
458
|
+
fresh_policy_judged_activations_before_trip: freshJudgedActivations,
|
|
459
|
+
...(lastKnownGoodJudgedActivations === undefined
|
|
460
|
+
? {}
|
|
461
|
+
: {
|
|
462
|
+
last_known_good_judged_activations_before_trip: lastKnownGoodJudgedActivations,
|
|
463
|
+
}),
|
|
464
|
+
outcome: comparison.outcome,
|
|
465
|
+
reason: comparison.reason,
|
|
466
|
+
...(comparison.target_policy_revision === undefined
|
|
467
|
+
? {}
|
|
468
|
+
: { target_policy_revision: comparison.target_policy_revision }),
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function validatePolicyRollbackInput(input) {
|
|
472
|
+
if (!isRecord(input)) {
|
|
473
|
+
throw new Error("policy rollback input must be an object");
|
|
474
|
+
}
|
|
475
|
+
const errors = [];
|
|
476
|
+
readNonEmptyString(input.fresh_policy_revision, "fresh_policy_revision", errors);
|
|
477
|
+
if (input.last_known_good_revision !== undefined) {
|
|
478
|
+
readNonEmptyString(input.last_known_good_revision, "last_known_good_revision", errors);
|
|
479
|
+
}
|
|
480
|
+
if (errors.length > 0) {
|
|
481
|
+
throw new Error(`policy rollback input is malformed: ${errors.join("; ")}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function validatePolicyRecompileInput(input) {
|
|
485
|
+
const asOfMs = parseReplayableInstantMs(input.as_of, "policy recompile as_of");
|
|
486
|
+
const lastPolicyRevalidatedMs = parseReplayableInstantMs(input.last_policy_revalidated_at, "policy recompile last_policy_revalidated_at");
|
|
487
|
+
const lastRecompileMs = parseReplayableInstantMs(input.last_recompile_at, "policy recompile last_recompile_at");
|
|
488
|
+
if (lastPolicyRevalidatedMs > asOfMs) {
|
|
489
|
+
throw new Error("policy recompile last_policy_revalidated_at must not be after as_of");
|
|
490
|
+
}
|
|
491
|
+
if (lastRecompileMs > asOfMs) {
|
|
492
|
+
throw new Error("policy recompile last_recompile_at must not be after as_of");
|
|
493
|
+
}
|
|
494
|
+
if (input.last_unforced_deep_at !== undefined) {
|
|
495
|
+
const lastUnforcedDeepMs = parseReplayableInstantMs(input.last_unforced_deep_at, "policy recompile last_unforced_deep_at");
|
|
496
|
+
if (lastUnforcedDeepMs > asOfMs) {
|
|
497
|
+
throw new Error("policy recompile last_unforced_deep_at must not be after as_of");
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (input.calibration_evidence_as_of !== undefined) {
|
|
501
|
+
const calibrationEvidenceMs = parseReplayableInstantMs(input.calibration_evidence_as_of, "policy recompile calibration_evidence_as_of");
|
|
502
|
+
if (calibrationEvidenceMs > asOfMs) {
|
|
503
|
+
throw new Error("policy recompile calibration_evidence_as_of must not be after as_of");
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (input.observed_calibration_divergence_multiplier !== undefined &&
|
|
507
|
+
(!Number.isFinite(input.observed_calibration_divergence_multiplier) ||
|
|
508
|
+
input.observed_calibration_divergence_multiplier <= 0)) {
|
|
509
|
+
throw new Error("policy recompile observed_calibration_divergence_multiplier must be positive");
|
|
510
|
+
}
|
|
511
|
+
if (input.max_calibration_evidence_age_ms !== undefined &&
|
|
512
|
+
(!Number.isSafeInteger(input.max_calibration_evidence_age_ms) ||
|
|
513
|
+
input.max_calibration_evidence_age_ms <= 0)) {
|
|
514
|
+
throw new Error("policy recompile max_calibration_evidence_age_ms must be a positive safe integer");
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function evaluatePolicyRecompileBackstops(input, artifact, validation, recompileRequested) {
|
|
518
|
+
return (0, kernel_1.evaluateBackstops)({
|
|
519
|
+
token: validation.kernel_validation.token,
|
|
520
|
+
as_of: input.as_of,
|
|
521
|
+
last_policy_revalidated_at: input.last_policy_revalidated_at,
|
|
522
|
+
last_recompile_at: input.last_recompile_at,
|
|
523
|
+
recompile_requested: recompileRequested,
|
|
524
|
+
...(input.observed_calibration_divergence_multiplier === undefined
|
|
525
|
+
? {}
|
|
526
|
+
: {
|
|
527
|
+
observed_calibration_divergence_multiplier: input.observed_calibration_divergence_multiplier,
|
|
528
|
+
}),
|
|
529
|
+
...(input.calibration_evidence_as_of === undefined
|
|
530
|
+
? {}
|
|
531
|
+
: { calibration_evidence_as_of: input.calibration_evidence_as_of }),
|
|
532
|
+
...(input.max_calibration_evidence_age_ms === undefined
|
|
533
|
+
? {}
|
|
534
|
+
: { max_calibration_evidence_age_ms: input.max_calibration_evidence_age_ms }),
|
|
535
|
+
warmup_length: (0, kernel_1.judgedActivations)(input.warmup_judged_activations ??
|
|
536
|
+
artifact.hysteresis.warmup_judged_activations, "warmup_judged_activations"),
|
|
537
|
+
policy_warmup_judged_activations: (0, kernel_1.judgedActivations)(input.policy_warmup_judged_activations ??
|
|
538
|
+
artifact.hysteresis.warmup_judged_activations, "policy_warmup_judged_activations"),
|
|
539
|
+
...(input.last_unforced_deep_at === undefined
|
|
540
|
+
? {}
|
|
541
|
+
: { last_unforced_deep_at: input.last_unforced_deep_at }),
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
function collectPolicyRecompileRequestSources(drift, backstops) {
|
|
545
|
+
const sources = [];
|
|
546
|
+
if (drift.outcome === "tripped") {
|
|
547
|
+
sources.push("policy-drift");
|
|
548
|
+
}
|
|
549
|
+
for (const outcome of backstops.outcomes) {
|
|
550
|
+
const source = policyRecompileSourceForBackstop(outcome.backstop);
|
|
551
|
+
if (source !== undefined &&
|
|
552
|
+
(outcome.action === "force-policy-recompile" ||
|
|
553
|
+
outcome.action === "force-policy-revalidation")) {
|
|
554
|
+
sources.push(source);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return Object.freeze([...new Set(sources)].sort((left, right) => left.localeCompare(right)));
|
|
558
|
+
}
|
|
559
|
+
function policyRecompileSourceForBackstop(backstop) {
|
|
560
|
+
switch (backstop) {
|
|
561
|
+
case "max_policy_age":
|
|
562
|
+
return "backstop:max_policy_age";
|
|
563
|
+
case "max_policy_age_no_anchor":
|
|
564
|
+
return "backstop:max_policy_age_no_anchor";
|
|
565
|
+
case "max_calibration_divergence":
|
|
566
|
+
return "backstop:max_calibration_divergence";
|
|
567
|
+
case "min_recompile_interval":
|
|
568
|
+
case "max_calibration_evidence_age":
|
|
569
|
+
case "warmup_length":
|
|
570
|
+
case "max_unforced_deep_interval":
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function hasMinRecompileDelay(backstops) {
|
|
575
|
+
return backstops.outcomes.some((outcome) => outcome.action === "delay-recompile-for-min-interval");
|
|
576
|
+
}
|
|
577
|
+
function remainingMinRecompileIntervalMs(input) {
|
|
578
|
+
const asOfMs = parseReplayableInstantMs(input.as_of, "policy recompile as_of");
|
|
579
|
+
const lastRecompileMs = parseReplayableInstantMs(input.last_recompile_at, "policy recompile last_recompile_at");
|
|
580
|
+
const elapsedMs = asOfMs - lastRecompileMs;
|
|
581
|
+
return Math.max(0, kernel_1.KERNEL_BACKSTOPS.minRecompileIntervalMs - elapsedMs);
|
|
582
|
+
}
|
|
583
|
+
function validateAuthorInput(input) {
|
|
584
|
+
const errors = [];
|
|
585
|
+
readNonEmptyString(input.responsibility_id, "responsibility_id", errors);
|
|
586
|
+
if (!isContentHash(input.contract_revision)) {
|
|
587
|
+
errors.push("contract_revision must be a sha256 content address");
|
|
588
|
+
}
|
|
589
|
+
readNonEmptyString(input.contract_summary, "contract_summary", errors);
|
|
590
|
+
if (typeof input.no_anchor !== "boolean") {
|
|
591
|
+
errors.push("no_anchor must be boolean");
|
|
592
|
+
}
|
|
593
|
+
readLiveObservables(input.live_observables, "live_observables", errors);
|
|
594
|
+
if (typeof input.agentSdk?.launch !== "function") {
|
|
595
|
+
errors.push("agentSdk.launch adapter is required");
|
|
596
|
+
}
|
|
597
|
+
if (!Array.isArray(input.receipt_history)) {
|
|
598
|
+
errors.push("receipt_history must be an array");
|
|
599
|
+
}
|
|
600
|
+
if (input.policy_artifact_namespace !== undefined &&
|
|
601
|
+
input.policy_artifact_namespace.length === 0) {
|
|
602
|
+
errors.push("policy_artifact_namespace must be non-empty when supplied");
|
|
603
|
+
}
|
|
604
|
+
if (errors.length > 0) {
|
|
605
|
+
throw new Error(`policy author input is malformed: ${errors.join("; ")}`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
function assertReceiptHistory(receipts) {
|
|
609
|
+
return assertVerifiedReceiptLog(receipts, "policy author receipt history", "receipt_history");
|
|
610
|
+
}
|
|
611
|
+
function assertPolicyDriftReceiptLog(receipts) {
|
|
612
|
+
return assertVerifiedReceiptLog(receipts, "policy drift receipt log", "receipts");
|
|
613
|
+
}
|
|
614
|
+
function assertVerifiedReceiptLog(receipts, context, path) {
|
|
615
|
+
const errors = [];
|
|
616
|
+
const seen = new Set();
|
|
617
|
+
receipts.forEach((receipt, index) => {
|
|
618
|
+
const verification = (0, receipt_1.verifyReceiptV0)(receipt);
|
|
619
|
+
if (!verification.ok) {
|
|
620
|
+
errors.push(`${path}[${index}] is invalid: ${verification.errors.join("; ")}`);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (seen.has(receipt.content_hash)) {
|
|
624
|
+
errors.push(`${path}[${index}].content_hash is duplicated`);
|
|
625
|
+
}
|
|
626
|
+
seen.add(receipt.content_hash);
|
|
627
|
+
});
|
|
628
|
+
if (errors.length > 0) {
|
|
629
|
+
throw new Error(`${context} is malformed: ${errors.join("; ")}`);
|
|
630
|
+
}
|
|
631
|
+
return Object.freeze([...receipts]);
|
|
632
|
+
}
|
|
633
|
+
function normalizePolicyDriftFactInput(inputOrReceipts) {
|
|
634
|
+
if (Array.isArray(inputOrReceipts)) {
|
|
635
|
+
return { receipts: inputOrReceipts };
|
|
636
|
+
}
|
|
637
|
+
if (!isRecord(inputOrReceipts) || !Array.isArray(inputOrReceipts["receipts"])) {
|
|
638
|
+
throw new Error("policy drift fact input must include receipts");
|
|
639
|
+
}
|
|
640
|
+
const input = inputOrReceipts;
|
|
641
|
+
if (input.as_of !== undefined) {
|
|
642
|
+
parseReplayableInstantMs(input.as_of, "policy drift as_of");
|
|
643
|
+
}
|
|
644
|
+
if (input.responsibility_id !== undefined &&
|
|
645
|
+
input.responsibility_id.length === 0) {
|
|
646
|
+
throw new Error("policy drift responsibility_id must be non-empty when supplied");
|
|
647
|
+
}
|
|
648
|
+
if (input.contract_revision !== undefined &&
|
|
649
|
+
!isContentHash(input.contract_revision)) {
|
|
650
|
+
throw new Error("policy drift contract_revision must be a sha256 content address");
|
|
651
|
+
}
|
|
652
|
+
return input;
|
|
653
|
+
}
|
|
654
|
+
function normalizePolicyDriftEvaluationInput(inputOrArtifact, receipts, options) {
|
|
655
|
+
if (receipts !== undefined) {
|
|
656
|
+
return {
|
|
657
|
+
artifact: inputOrArtifact,
|
|
658
|
+
receipts,
|
|
659
|
+
...(options.as_of === undefined ? {} : { as_of: options.as_of }),
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
if (!isRecord(inputOrArtifact) ||
|
|
663
|
+
!isRecord(inputOrArtifact["artifact"]) ||
|
|
664
|
+
!Array.isArray(inputOrArtifact["receipts"])) {
|
|
665
|
+
throw new Error("policy drift evaluation input must include artifact and receipts");
|
|
666
|
+
}
|
|
667
|
+
const input = inputOrArtifact;
|
|
668
|
+
if (input.as_of !== undefined) {
|
|
669
|
+
parseReplayableInstantMs(input.as_of, "policy drift as_of");
|
|
670
|
+
}
|
|
671
|
+
return input;
|
|
672
|
+
}
|
|
673
|
+
function receiptBelongsToPolicyDriftScope(receipt, input) {
|
|
674
|
+
if (input.responsibility_id !== undefined &&
|
|
675
|
+
receipt.core.responsibility_id !== input.responsibility_id) {
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
678
|
+
if (input.contract_revision !== undefined &&
|
|
679
|
+
receipt.core.contract_revision !== input.contract_revision) {
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
684
|
+
function sortReceiptsForPolicyDrift(receipts) {
|
|
685
|
+
return Object.freeze(receipts
|
|
686
|
+
.map((receipt) => ({
|
|
687
|
+
receipt,
|
|
688
|
+
as_of_ms: parseReplayableInstantMs(receipt.core.as_of, `receipt ${receipt.content_hash} core.as_of`),
|
|
689
|
+
}))
|
|
690
|
+
.sort((left, right) => {
|
|
691
|
+
const byTime = left.as_of_ms - right.as_of_ms;
|
|
692
|
+
return byTime === 0
|
|
693
|
+
? left.receipt.content_hash.localeCompare(right.receipt.content_hash)
|
|
694
|
+
: byTime;
|
|
695
|
+
}));
|
|
696
|
+
}
|
|
697
|
+
function resolvePolicyDriftAsOf(receipts, suppliedAsOf) {
|
|
698
|
+
if (suppliedAsOf !== undefined) {
|
|
699
|
+
parseReplayableInstantMs(suppliedAsOf, "policy drift as_of");
|
|
700
|
+
return suppliedAsOf;
|
|
701
|
+
}
|
|
702
|
+
return receipts.at(-1)?.receipt.core.as_of ?? null;
|
|
703
|
+
}
|
|
704
|
+
function deriveFreshTokensPerMaintainedDay(receipts, asOfMs) {
|
|
705
|
+
const eligible = receipts.filter((entry) => entry.as_of_ms <= asOfMs);
|
|
706
|
+
const first = eligible[0];
|
|
707
|
+
if (first === undefined) {
|
|
708
|
+
return undefined;
|
|
709
|
+
}
|
|
710
|
+
const elapsedMs = asOfMs - first.as_of_ms;
|
|
711
|
+
if (elapsedMs <= 0) {
|
|
712
|
+
return undefined;
|
|
713
|
+
}
|
|
714
|
+
const freshTokens = eligible.reduce((sum, entry) => sum + entry.receipt.cost.tokens.fresh, 0);
|
|
715
|
+
return freshTokens / (elapsedMs / kernel_1.KERNEL_DAY_MS);
|
|
716
|
+
}
|
|
717
|
+
function deriveEscalationPrecision7d(receipts, asOfMs) {
|
|
718
|
+
let confirmed = 0;
|
|
719
|
+
let refuted = 0;
|
|
720
|
+
for (const entry of policyDriftSevenDayWindow(receipts, asOfMs)) {
|
|
721
|
+
const label = escalationPrecisionLabel(entry.receipt);
|
|
722
|
+
if (label === "confirmed") {
|
|
723
|
+
confirmed += 1;
|
|
724
|
+
}
|
|
725
|
+
else if (label === "refuted") {
|
|
726
|
+
refuted += 1;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
const labeledEscalations = confirmed + refuted;
|
|
730
|
+
return labeledEscalations === 0 ? undefined : confirmed / labeledEscalations;
|
|
731
|
+
}
|
|
732
|
+
function deriveDeepShallowContradictionCount7d(receipts, asOfMs) {
|
|
733
|
+
const windowReceipts = policyDriftSevenDayWindow(receipts, asOfMs);
|
|
734
|
+
if (windowReceipts.length === 0) {
|
|
735
|
+
return undefined;
|
|
736
|
+
}
|
|
737
|
+
return windowReceipts.filter((entry) => isDeepShallowContradictionReceipt(entry.receipt)).length;
|
|
738
|
+
}
|
|
739
|
+
function policyDriftSevenDayWindow(receipts, asOfMs) {
|
|
740
|
+
const windowStartMs = asOfMs - 7 * kernel_1.KERNEL_DAY_MS;
|
|
741
|
+
return receipts.filter((entry) => entry.as_of_ms >= windowStartMs && entry.as_of_ms <= asOfMs);
|
|
742
|
+
}
|
|
743
|
+
function escalationPrecisionLabel(receipt) {
|
|
744
|
+
const confirmed = receipt.cost.tags.some((tag) => ESCALATION_CONFIRMED_TAGS.has(tag));
|
|
745
|
+
const refuted = receipt.cost.tags.some((tag) => ESCALATION_REFUTED_TAGS.has(tag));
|
|
746
|
+
if (confirmed && refuted) {
|
|
747
|
+
throw new Error(`policy drift receipt ${receipt.content_hash} has conflicting escalation precision labels`);
|
|
748
|
+
}
|
|
749
|
+
if (!confirmed && !refuted) {
|
|
750
|
+
return undefined;
|
|
751
|
+
}
|
|
752
|
+
if (receipt.core.event_cause !== "escalation" &&
|
|
753
|
+
!receipt.cost.tags.includes("escalation")) {
|
|
754
|
+
throw new Error(`policy drift receipt ${receipt.content_hash} labels escalation precision without escalation evidence`);
|
|
755
|
+
}
|
|
756
|
+
return confirmed ? "confirmed" : "refuted";
|
|
757
|
+
}
|
|
758
|
+
function isDeepShallowContradictionReceipt(receipt) {
|
|
759
|
+
return (receipt.verdict.blocked?.reason === "backstop-divergence" ||
|
|
760
|
+
receipt.cost.tags.some((tag) => DEEP_SHALLOW_CONTRADICTION_TAGS.has(tag)));
|
|
761
|
+
}
|
|
762
|
+
function evidenceReceiptHashesForPolicyDrift(receipts, asOf) {
|
|
763
|
+
if (asOf === null) {
|
|
764
|
+
return [];
|
|
765
|
+
}
|
|
766
|
+
const asOfMs = parseReplayableInstantMs(asOf, "policy drift as_of");
|
|
767
|
+
return receipts
|
|
768
|
+
.filter((entry) => entry.as_of_ms <= asOfMs)
|
|
769
|
+
.map((entry) => entry.receipt.content_hash);
|
|
770
|
+
}
|
|
771
|
+
function summarizeReceiptForPolicyAuthor(receipt) {
|
|
772
|
+
return {
|
|
773
|
+
content_hash: receipt.content_hash,
|
|
774
|
+
contract_revision: receipt.core.contract_revision,
|
|
775
|
+
as_of: receipt.core.as_of,
|
|
776
|
+
role: receipt.core.role,
|
|
777
|
+
event_cause: receipt.core.event_cause,
|
|
778
|
+
verdict_status: receipt.verdict.status,
|
|
779
|
+
next_forecast_recheck: receipt.freshness.next_forecast_recheck,
|
|
780
|
+
surprise_cause: receipt.cost.surprise_cause,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
function readHistoryQueryResponse(payload) {
|
|
784
|
+
if (looksLikeAuthoredPolicy(payload)) {
|
|
785
|
+
throw new Error("policy author must query receipt history before authoring an artifact");
|
|
786
|
+
}
|
|
787
|
+
if (!isRecord(payload)) {
|
|
788
|
+
throw new Error("policy author history query response must be an object");
|
|
789
|
+
}
|
|
790
|
+
const errors = [];
|
|
791
|
+
validateKnownKeys(payload, "policy author history query", ["schema", "v", "selected_receipt_hashes", "rationale"], errors);
|
|
792
|
+
expectLiteral(payload["schema"], exports.POLICY_AUTHOR_HISTORY_QUERY_SCHEMA, "history_query.schema", errors);
|
|
793
|
+
expectLiteral(payload["v"], exports.POLICY_ARTIFACT_VERSION, "history_query.v", errors);
|
|
794
|
+
const selectedReceiptHashes = readContentHashArray(payload["selected_receipt_hashes"], "history_query.selected_receipt_hashes", errors);
|
|
795
|
+
const rationale = payload["rationale"] === undefined
|
|
796
|
+
? undefined
|
|
797
|
+
: readNonEmptyString(payload["rationale"], "history_query.rationale", errors);
|
|
798
|
+
if (errors.length > 0) {
|
|
799
|
+
throw new Error(`policy author history query is malformed: ${errors.join("; ")}`);
|
|
800
|
+
}
|
|
801
|
+
return {
|
|
802
|
+
schema: exports.POLICY_AUTHOR_HISTORY_QUERY_SCHEMA,
|
|
803
|
+
v: exports.POLICY_ARTIFACT_VERSION,
|
|
804
|
+
selected_receipt_hashes: selectedReceiptHashes,
|
|
805
|
+
...(rationale === undefined ? {} : { rationale }),
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
function assertSelectedReceiptsExist(query, receipts) {
|
|
809
|
+
if (query.selected_receipt_hashes.length === 0) {
|
|
810
|
+
if (receipts.length > 0) {
|
|
811
|
+
throw new Error("policy author must select at least one receipt for dynamic history exploration");
|
|
812
|
+
}
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
const available = new Set(receipts.map((receipt) => receipt.content_hash));
|
|
816
|
+
const unknown = query.selected_receipt_hashes.filter((hash) => !available.has(hash));
|
|
817
|
+
if (unknown.length > 0) {
|
|
818
|
+
throw new Error(`policy author selected unknown receipt hashes: ${unknown.join(", ")}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
function selectReceipts(receipts, query) {
|
|
822
|
+
const byHash = new Map(receipts.map((receipt) => [receipt.content_hash, receipt]));
|
|
823
|
+
return Object.freeze(query.selected_receipt_hashes.map((hash) => {
|
|
824
|
+
const receipt = byHash.get(hash);
|
|
825
|
+
if (receipt === undefined) {
|
|
826
|
+
throw new Error(`selected receipt ${hash} disappeared from history`);
|
|
827
|
+
}
|
|
828
|
+
return receipt;
|
|
829
|
+
}));
|
|
830
|
+
}
|
|
831
|
+
function readAuthoredArtifactResponse(payload) {
|
|
832
|
+
if (!isRecord(payload)) {
|
|
833
|
+
throw new Error("policy author artifact response must be an object");
|
|
834
|
+
}
|
|
835
|
+
if (payload["schema"] === exports.POLICY_AUTHOR_HISTORY_QUERY_SCHEMA) {
|
|
836
|
+
throw new Error("policy author returned a second history query instead of an artifact");
|
|
837
|
+
}
|
|
838
|
+
if (payload["schema"] === exports.POLICY_ARTIFACT_SCHEMA) {
|
|
839
|
+
return payload;
|
|
840
|
+
}
|
|
841
|
+
if (payload["schema"] === exports.POLICY_AUTHOR_ARTIFACT_RESPONSE_SCHEMA) {
|
|
842
|
+
const artifact = payload["artifact"] ?? payload["authored_policy"];
|
|
843
|
+
if (artifact === undefined) {
|
|
844
|
+
throw new Error("policy author artifact response is missing artifact");
|
|
845
|
+
}
|
|
846
|
+
return artifact;
|
|
847
|
+
}
|
|
848
|
+
const authoredPolicy = payload["authored_policy"] ?? payload["artifact"];
|
|
849
|
+
if (authoredPolicy !== undefined) {
|
|
850
|
+
return authoredPolicy;
|
|
851
|
+
}
|
|
852
|
+
if (looksLikeAuthoredPolicy(payload)) {
|
|
853
|
+
return payload;
|
|
854
|
+
}
|
|
855
|
+
throw new Error("policy author artifact response is malformed");
|
|
856
|
+
}
|
|
857
|
+
function normalizeAuthoredPolicyArtifactV0(value, context) {
|
|
858
|
+
if (!isRecord(value)) {
|
|
859
|
+
throw new Error("authored policy artifact must be an object");
|
|
860
|
+
}
|
|
861
|
+
const responsibilityId = value["responsibility_id"] === undefined
|
|
862
|
+
? context.responsibility_id
|
|
863
|
+
: readRequiredString(value["responsibility_id"], "artifact.responsibility_id");
|
|
864
|
+
const noAnchor = value["no_anchor"] === undefined
|
|
865
|
+
? context.no_anchor
|
|
866
|
+
: readRequiredBoolean(value["no_anchor"], "artifact.no_anchor");
|
|
867
|
+
const liveObservables = value["live_observables"] === undefined
|
|
868
|
+
? canonicalLiveObservables(context.live_observables)
|
|
869
|
+
: readRequiredLiveObservables(value["live_observables"], "artifact.live_observables");
|
|
870
|
+
if (responsibilityId !== context.responsibility_id) {
|
|
871
|
+
throw new Error(`authored policy targets ${responsibilityId}, expected ${context.responsibility_id}`);
|
|
872
|
+
}
|
|
873
|
+
if (noAnchor !== context.no_anchor) {
|
|
874
|
+
throw new Error("authored policy no_anchor flag does not match responsibility");
|
|
875
|
+
}
|
|
876
|
+
assertSameLiveObservableIds(liveObservables, context.live_observables);
|
|
877
|
+
const artifact = {
|
|
878
|
+
schema: exports.POLICY_ARTIFACT_SCHEMA,
|
|
879
|
+
v: exports.POLICY_ARTIFACT_VERSION,
|
|
880
|
+
responsibility_id: responsibilityId,
|
|
881
|
+
registry_id: readRequiredString(value["registry_id"], "artifact.registry_id"),
|
|
882
|
+
policy_revision: readRequiredString(value["policy_revision"], "artifact.policy_revision"),
|
|
883
|
+
no_anchor: noAnchor,
|
|
884
|
+
live_observables: liveObservables,
|
|
885
|
+
cadence: readRequiredCadence(value["cadence"], noAnchor),
|
|
886
|
+
hysteresis: readRequiredHysteresis(value["hysteresis"]),
|
|
887
|
+
thresholds: readRequiredThresholds(value["thresholds"]),
|
|
888
|
+
transitive_freshness_function: value["transitive_freshness_function"] === undefined
|
|
889
|
+
? exports.DEFAULT_TRANSITIVE_FRESHNESS_FUNCTION_V0
|
|
890
|
+
: readRequiredTransitiveFreshnessFunction(value["transitive_freshness_function"], "artifact.transitive_freshness_function"),
|
|
891
|
+
falsification_predicate: readRequiredPredicate(value["falsification_predicate"], "artifact.falsification_predicate"),
|
|
892
|
+
...(value["backstop_divergence_predicate"] === undefined
|
|
893
|
+
? {}
|
|
894
|
+
: {
|
|
895
|
+
backstop_divergence_predicate: readRequiredPredicate(value["backstop_divergence_predicate"], "artifact.backstop_divergence_predicate"),
|
|
896
|
+
}),
|
|
897
|
+
provenance: normalizeProvenance(value["provenance"], context),
|
|
898
|
+
};
|
|
899
|
+
return artifact;
|
|
900
|
+
}
|
|
901
|
+
function normalizeProvenance(value, context) {
|
|
902
|
+
if (value !== undefined) {
|
|
903
|
+
const provenance = readRequiredProvenance(value, "artifact.provenance");
|
|
904
|
+
if (provenance.contract_revision !== context.contract_revision) {
|
|
905
|
+
throw new Error("artifact provenance contract_revision does not match input");
|
|
906
|
+
}
|
|
907
|
+
if (provenance.receipt_history_summary_hash !==
|
|
908
|
+
context.receipt_history_summary_hash) {
|
|
909
|
+
throw new Error("artifact provenance receipt_history_summary_hash does not match query");
|
|
910
|
+
}
|
|
911
|
+
if ((0, receipt_1.canonicalizeForReceiptV0)(provenance.history_query) !==
|
|
912
|
+
(0, receipt_1.canonicalizeForReceiptV0)(context.history_query)) {
|
|
913
|
+
throw new Error("artifact provenance history_query does not match query");
|
|
914
|
+
}
|
|
915
|
+
if (provenance.explored_receipt_hashes.join("\0") !==
|
|
916
|
+
context.history_query.selected_receipt_hashes.join("\0")) {
|
|
917
|
+
throw new Error("artifact provenance explored receipt hashes do not match query");
|
|
918
|
+
}
|
|
919
|
+
return provenance;
|
|
920
|
+
}
|
|
921
|
+
return {
|
|
922
|
+
contract_revision: context.contract_revision,
|
|
923
|
+
receipt_history_summary_hash: context.receipt_history_summary_hash,
|
|
924
|
+
explored_receipt_hashes: context.history_query.selected_receipt_hashes,
|
|
925
|
+
history_query: context.history_query,
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
function looksLikeAuthoredPolicy(value) {
|
|
929
|
+
if (!isRecord(value)) {
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
return (value["schema"] === exports.POLICY_ARTIFACT_SCHEMA ||
|
|
933
|
+
value["authored_policy"] !== undefined ||
|
|
934
|
+
value["artifact"] !== undefined ||
|
|
935
|
+
value["registry_id"] !== undefined ||
|
|
936
|
+
value["policy_revision"] !== undefined ||
|
|
937
|
+
value["falsification_predicate"] !== undefined);
|
|
938
|
+
}
|
|
939
|
+
function readCadence(value, noAnchor, errors) {
|
|
940
|
+
if (!isRecord(value)) {
|
|
941
|
+
errors.push("policy_artifact.cadence must be an object");
|
|
942
|
+
return undefined;
|
|
943
|
+
}
|
|
944
|
+
validateKnownKeys(value, "policy_artifact.cadence", ["shallow_recheck_ms", "plan_audit_ms", "deep_revalidation_ms"], errors);
|
|
945
|
+
const shallow = readPositiveSafeInteger(value["shallow_recheck_ms"], "policy_artifact.cadence.shallow_recheck_ms", errors);
|
|
946
|
+
const planAudit = readPositiveSafeInteger(value["plan_audit_ms"], "policy_artifact.cadence.plan_audit_ms", errors);
|
|
947
|
+
const deep = readPositiveSafeInteger(value["deep_revalidation_ms"], "policy_artifact.cadence.deep_revalidation_ms", errors);
|
|
948
|
+
validateCadenceValues(shallow, planAudit, deep, noAnchor, errors);
|
|
949
|
+
if (shallow === undefined || planAudit === undefined || deep === undefined) {
|
|
950
|
+
return undefined;
|
|
951
|
+
}
|
|
952
|
+
return {
|
|
953
|
+
shallow_recheck_ms: shallow,
|
|
954
|
+
plan_audit_ms: planAudit,
|
|
955
|
+
deep_revalidation_ms: deep,
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
function readHysteresis(value, errors) {
|
|
959
|
+
if (!isRecord(value)) {
|
|
960
|
+
errors.push("policy_artifact.hysteresis must be an object");
|
|
961
|
+
return undefined;
|
|
962
|
+
}
|
|
963
|
+
validateKnownKeys(value, "policy_artifact.hysteresis", [
|
|
964
|
+
"min_recompile_interval_ms",
|
|
965
|
+
"enter_degraded_threshold",
|
|
966
|
+
"exit_degraded_threshold",
|
|
967
|
+
"warmup_judged_activations",
|
|
968
|
+
], errors);
|
|
969
|
+
const minRecompile = readPositiveSafeInteger(value["min_recompile_interval_ms"], "policy_artifact.hysteresis.min_recompile_interval_ms", errors);
|
|
970
|
+
const enter = readUnitInterval(value["enter_degraded_threshold"], "policy_artifact.hysteresis.enter_degraded_threshold", errors);
|
|
971
|
+
const exit = readUnitInterval(value["exit_degraded_threshold"], "policy_artifact.hysteresis.exit_degraded_threshold", errors);
|
|
972
|
+
const warmup = readPositiveSafeInteger(value["warmup_judged_activations"], "policy_artifact.hysteresis.warmup_judged_activations", errors);
|
|
973
|
+
validateHysteresisValues(minRecompile, enter, exit, errors);
|
|
974
|
+
if (minRecompile === undefined ||
|
|
975
|
+
enter === undefined ||
|
|
976
|
+
exit === undefined ||
|
|
977
|
+
warmup === undefined) {
|
|
978
|
+
return undefined;
|
|
979
|
+
}
|
|
980
|
+
return {
|
|
981
|
+
min_recompile_interval_ms: minRecompile,
|
|
982
|
+
enter_degraded_threshold: enter,
|
|
983
|
+
exit_degraded_threshold: exit,
|
|
984
|
+
warmup_judged_activations: warmup,
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
function readThresholds(value, errors) {
|
|
988
|
+
if (!isRecord(value)) {
|
|
989
|
+
errors.push("policy_artifact.thresholds must be an object");
|
|
990
|
+
return undefined;
|
|
991
|
+
}
|
|
992
|
+
validateKnownKeys(value, "policy_artifact.thresholds", [
|
|
993
|
+
"max_calibration_divergence_multiplier",
|
|
994
|
+
"escalation_precision_floor",
|
|
995
|
+
"backstop_deep_contradiction_count",
|
|
996
|
+
"stale_brief_minutes",
|
|
997
|
+
"fresh_tokens_per_day_ceiling",
|
|
998
|
+
], errors);
|
|
999
|
+
const divergence = readPositiveFiniteNumber(value["max_calibration_divergence_multiplier"], "policy_artifact.thresholds.max_calibration_divergence_multiplier", errors);
|
|
1000
|
+
const precision = readUnitInterval(value["escalation_precision_floor"], "policy_artifact.thresholds.escalation_precision_floor", errors);
|
|
1001
|
+
const contradictionCount = readPositiveSafeInteger(value["backstop_deep_contradiction_count"], "policy_artifact.thresholds.backstop_deep_contradiction_count", errors);
|
|
1002
|
+
const staleBriefMinutes = readPositiveSafeInteger(value["stale_brief_minutes"], "policy_artifact.thresholds.stale_brief_minutes", errors);
|
|
1003
|
+
const freshTokensCeiling = readPositiveFiniteNumber(value["fresh_tokens_per_day_ceiling"], "policy_artifact.thresholds.fresh_tokens_per_day_ceiling", errors);
|
|
1004
|
+
validateThresholdValues(divergence, staleBriefMinutes, errors);
|
|
1005
|
+
if (divergence === undefined ||
|
|
1006
|
+
precision === undefined ||
|
|
1007
|
+
contradictionCount === undefined ||
|
|
1008
|
+
staleBriefMinutes === undefined ||
|
|
1009
|
+
freshTokensCeiling === undefined) {
|
|
1010
|
+
return undefined;
|
|
1011
|
+
}
|
|
1012
|
+
return {
|
|
1013
|
+
max_calibration_divergence_multiplier: divergence,
|
|
1014
|
+
escalation_precision_floor: precision,
|
|
1015
|
+
backstop_deep_contradiction_count: contradictionCount,
|
|
1016
|
+
stale_brief_minutes: staleBriefMinutes,
|
|
1017
|
+
fresh_tokens_per_day_ceiling: freshTokensCeiling,
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
function readTransitiveFreshnessFunction(value, path, errors) {
|
|
1021
|
+
if (value === undefined) {
|
|
1022
|
+
return exports.DEFAULT_TRANSITIVE_FRESHNESS_FUNCTION_V0;
|
|
1023
|
+
}
|
|
1024
|
+
if (!isRecord(value)) {
|
|
1025
|
+
errors.push(`${path} must be an object`);
|
|
1026
|
+
return undefined;
|
|
1027
|
+
}
|
|
1028
|
+
const kind = value["kind"];
|
|
1029
|
+
if (kind === "kernel-default") {
|
|
1030
|
+
validateKnownKeys(value, path, ["kind"], errors);
|
|
1031
|
+
return { kind };
|
|
1032
|
+
}
|
|
1033
|
+
if (kind === "minimum-remaining-freshness-ms") {
|
|
1034
|
+
validateKnownKeys(value, path, ["kind", "minimum_remaining_ms"], errors);
|
|
1035
|
+
const minimumRemainingMs = readNonNegativeSafeInteger(value["minimum_remaining_ms"], `${path}.minimum_remaining_ms`, errors);
|
|
1036
|
+
if (minimumRemainingMs === undefined) {
|
|
1037
|
+
return undefined;
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
kind,
|
|
1041
|
+
minimum_remaining_ms: minimumRemainingMs,
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
errors.push(`${path}.kind must be kernel-default or minimum-remaining-freshness-ms`);
|
|
1045
|
+
return undefined;
|
|
1046
|
+
}
|
|
1047
|
+
function readProvenance(value, errors) {
|
|
1048
|
+
if (!isRecord(value)) {
|
|
1049
|
+
errors.push("policy_artifact.provenance must be an object");
|
|
1050
|
+
return undefined;
|
|
1051
|
+
}
|
|
1052
|
+
validateKnownKeys(value, "policy_artifact.provenance", [
|
|
1053
|
+
"contract_revision",
|
|
1054
|
+
"receipt_history_summary_hash",
|
|
1055
|
+
"explored_receipt_hashes",
|
|
1056
|
+
"history_query",
|
|
1057
|
+
], errors);
|
|
1058
|
+
const contractRevision = readContentHash(value["contract_revision"], "policy_artifact.provenance.contract_revision", errors);
|
|
1059
|
+
const summaryHash = readContentHash(value["receipt_history_summary_hash"], "policy_artifact.provenance.receipt_history_summary_hash", errors);
|
|
1060
|
+
const exploredReceiptHashes = readContentHashArray(value["explored_receipt_hashes"], "policy_artifact.provenance.explored_receipt_hashes", errors);
|
|
1061
|
+
const historyQuery = readHistoryQuery(value["history_query"], "policy_artifact.provenance.history_query", errors);
|
|
1062
|
+
if (contractRevision === undefined ||
|
|
1063
|
+
summaryHash === undefined ||
|
|
1064
|
+
historyQuery === undefined) {
|
|
1065
|
+
return undefined;
|
|
1066
|
+
}
|
|
1067
|
+
return {
|
|
1068
|
+
contract_revision: contractRevision,
|
|
1069
|
+
receipt_history_summary_hash: summaryHash,
|
|
1070
|
+
explored_receipt_hashes: exploredReceiptHashes,
|
|
1071
|
+
history_query: historyQuery,
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
function readHistoryQuery(value, path, errors) {
|
|
1075
|
+
if (!isRecord(value)) {
|
|
1076
|
+
errors.push(`${path} must be an object`);
|
|
1077
|
+
return undefined;
|
|
1078
|
+
}
|
|
1079
|
+
validateKnownKeys(value, path, ["schema", "v", "selected_receipt_hashes", "rationale"], errors);
|
|
1080
|
+
expectLiteral(value["schema"], exports.POLICY_AUTHOR_HISTORY_QUERY_SCHEMA, `${path}.schema`, errors);
|
|
1081
|
+
expectLiteral(value["v"], exports.POLICY_ARTIFACT_VERSION, `${path}.v`, errors);
|
|
1082
|
+
const selectedReceiptHashes = readContentHashArray(value["selected_receipt_hashes"], `${path}.selected_receipt_hashes`, errors);
|
|
1083
|
+
const rationale = value["rationale"] === undefined
|
|
1084
|
+
? undefined
|
|
1085
|
+
: readNonEmptyString(value["rationale"], `${path}.rationale`, errors);
|
|
1086
|
+
return {
|
|
1087
|
+
schema: exports.POLICY_AUTHOR_HISTORY_QUERY_SCHEMA,
|
|
1088
|
+
v: exports.POLICY_ARTIFACT_VERSION,
|
|
1089
|
+
selected_receipt_hashes: selectedReceiptHashes,
|
|
1090
|
+
...(rationale === undefined ? {} : { rationale }),
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
function readLiveObservables(value, path, errors) {
|
|
1094
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
1095
|
+
errors.push(`${path} must be a non-empty array`);
|
|
1096
|
+
return [];
|
|
1097
|
+
}
|
|
1098
|
+
const observables = [];
|
|
1099
|
+
const seen = new Set();
|
|
1100
|
+
value.forEach((observable, index) => {
|
|
1101
|
+
const observablePath = `${path}[${index}]`;
|
|
1102
|
+
if (!isRecord(observable)) {
|
|
1103
|
+
errors.push(`${observablePath} must be an object`);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
validateKnownKeys(observable, observablePath, ["id", "source", "description"], errors);
|
|
1107
|
+
const id = readNonEmptyString(observable["id"], `${observablePath}.id`, errors);
|
|
1108
|
+
const source = readLiveObservableSource(observable["source"], `${observablePath}.source`, errors);
|
|
1109
|
+
const description = readNonEmptyString(observable["description"], `${observablePath}.description`, errors);
|
|
1110
|
+
if (id !== undefined) {
|
|
1111
|
+
if (seen.has(id)) {
|
|
1112
|
+
errors.push(`${observablePath}.id must be unique`);
|
|
1113
|
+
}
|
|
1114
|
+
seen.add(id);
|
|
1115
|
+
}
|
|
1116
|
+
if (id !== undefined && source !== undefined && description !== undefined) {
|
|
1117
|
+
observables.push({ id, source, description });
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
1120
|
+
return Object.freeze(observables.sort((left, right) => left.id.localeCompare(right.id)));
|
|
1121
|
+
}
|
|
1122
|
+
function canonicalLiveObservables(value) {
|
|
1123
|
+
return readRequiredLiveObservables(value, "live_observables");
|
|
1124
|
+
}
|
|
1125
|
+
function readRequiredLiveObservables(value, path) {
|
|
1126
|
+
const errors = [];
|
|
1127
|
+
const observables = readLiveObservables(value, path, errors);
|
|
1128
|
+
if (errors.length > 0) {
|
|
1129
|
+
throw new Error(`${path} is malformed: ${errors.join("; ")}`);
|
|
1130
|
+
}
|
|
1131
|
+
return observables;
|
|
1132
|
+
}
|
|
1133
|
+
function assertSameLiveObservableIds(actual, expected) {
|
|
1134
|
+
const actualIds = actual.map((observable) => observable.id).sort();
|
|
1135
|
+
const expectedIds = expected.map((observable) => observable.id).sort();
|
|
1136
|
+
if (actualIds.join("\0") !== expectedIds.join("\0")) {
|
|
1137
|
+
throw new Error("authored policy live_observables do not match replayable inputs");
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
function readRequiredCadence(value, noAnchor) {
|
|
1141
|
+
const errors = [];
|
|
1142
|
+
const cadence = readCadence(value, noAnchor, errors);
|
|
1143
|
+
if (cadence === undefined || errors.length > 0) {
|
|
1144
|
+
throw new Error(`artifact.cadence is malformed: ${errors.join("; ")}`);
|
|
1145
|
+
}
|
|
1146
|
+
return cadence;
|
|
1147
|
+
}
|
|
1148
|
+
function readRequiredHysteresis(value) {
|
|
1149
|
+
const errors = [];
|
|
1150
|
+
const hysteresis = readHysteresis(value, errors);
|
|
1151
|
+
if (hysteresis === undefined || errors.length > 0) {
|
|
1152
|
+
throw new Error(`artifact.hysteresis is malformed: ${errors.join("; ")}`);
|
|
1153
|
+
}
|
|
1154
|
+
return hysteresis;
|
|
1155
|
+
}
|
|
1156
|
+
function readRequiredThresholds(value) {
|
|
1157
|
+
const errors = [];
|
|
1158
|
+
const thresholds = readThresholds(value, errors);
|
|
1159
|
+
if (thresholds === undefined || errors.length > 0) {
|
|
1160
|
+
throw new Error(`artifact.thresholds is malformed: ${errors.join("; ")}`);
|
|
1161
|
+
}
|
|
1162
|
+
return thresholds;
|
|
1163
|
+
}
|
|
1164
|
+
function readRequiredTransitiveFreshnessFunction(value, path) {
|
|
1165
|
+
const errors = [];
|
|
1166
|
+
const fn = readTransitiveFreshnessFunction(value, path, errors);
|
|
1167
|
+
if (fn === undefined || errors.length > 0) {
|
|
1168
|
+
throw new Error(`${path} is malformed: ${errors.join("; ")}`);
|
|
1169
|
+
}
|
|
1170
|
+
return fn;
|
|
1171
|
+
}
|
|
1172
|
+
function readRequiredProvenance(value, path) {
|
|
1173
|
+
const errors = [];
|
|
1174
|
+
const provenance = readProvenance(value, errors);
|
|
1175
|
+
if (provenance === undefined || errors.length > 0) {
|
|
1176
|
+
throw new Error(`${path} is malformed: ${errors.join("; ")}`);
|
|
1177
|
+
}
|
|
1178
|
+
return provenance;
|
|
1179
|
+
}
|
|
1180
|
+
function validateCadenceValues(shallow, planAudit, deep, noAnchor, errors) {
|
|
1181
|
+
if (shallow !== undefined && shallow < 5 * 60 * 1000) {
|
|
1182
|
+
errors.push("policy_artifact.cadence.shallow_recheck_ms is too tight");
|
|
1183
|
+
}
|
|
1184
|
+
if (shallow !== undefined && planAudit !== undefined && shallow > planAudit) {
|
|
1185
|
+
errors.push("policy_artifact.cadence.plan_audit_ms must be at least shallow_recheck_ms");
|
|
1186
|
+
}
|
|
1187
|
+
if (planAudit !== undefined && deep !== undefined && planAudit > deep) {
|
|
1188
|
+
errors.push("policy_artifact.cadence.deep_revalidation_ms must be at least plan_audit_ms");
|
|
1189
|
+
}
|
|
1190
|
+
if (noAnchor === true &&
|
|
1191
|
+
deep !== undefined &&
|
|
1192
|
+
deep > kernel_1.KERNEL_BACKSTOPS.maxUnforcedDeepIntervalMs) {
|
|
1193
|
+
errors.push("policy_artifact.cadence.deep_revalidation_ms may not exceed the B2 deep interval");
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
function validateHysteresisValues(minRecompile, enter, exit, errors) {
|
|
1197
|
+
if (minRecompile !== undefined &&
|
|
1198
|
+
minRecompile < kernel_1.KERNEL_BACKSTOPS.minRecompileIntervalMs) {
|
|
1199
|
+
errors.push("policy_artifact.hysteresis.min_recompile_interval_ms may not shorten the kernel floor");
|
|
1200
|
+
}
|
|
1201
|
+
if (enter !== undefined && exit !== undefined && exit >= enter) {
|
|
1202
|
+
errors.push("policy_artifact.hysteresis.exit_degraded_threshold must be below enter_degraded_threshold");
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
function validateThresholdValues(divergence, staleBriefMinutes, errors) {
|
|
1206
|
+
if (divergence !== undefined &&
|
|
1207
|
+
(divergence <= 1 ||
|
|
1208
|
+
divergence > kernel_1.KERNEL_BACKSTOPS.maxCalibrationDivergenceMultiplier)) {
|
|
1209
|
+
errors.push("policy_artifact.thresholds.max_calibration_divergence_multiplier must trip before the kernel ceiling");
|
|
1210
|
+
}
|
|
1211
|
+
if (staleBriefMinutes !== undefined &&
|
|
1212
|
+
staleBriefMinutes * 60 * 1000 > kernel_1.KERNEL_DAY_MS) {
|
|
1213
|
+
errors.push("policy_artifact.thresholds.stale_brief_minutes must fit inside a day");
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
function rejectOffLivePredicateFacts(predicate, path, liveObservableIds, errors) {
|
|
1217
|
+
const liveObservableSet = new Set(liveObservableIds);
|
|
1218
|
+
const offLiveFacts = collectPredicateFacts(predicate).filter((fact) => !liveObservableSet.has(fact));
|
|
1219
|
+
if (offLiveFacts.length > 0) {
|
|
1220
|
+
errors.push(`${path} references non-live observable facts: ${offLiveFacts.join(", ")}`);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
function readPredicate(value, path, errors) {
|
|
1224
|
+
if (!isRecord(value)) {
|
|
1225
|
+
errors.push(`${path} must be an object`);
|
|
1226
|
+
return undefined;
|
|
1227
|
+
}
|
|
1228
|
+
const kind = value["kind"];
|
|
1229
|
+
switch (kind) {
|
|
1230
|
+
case "equals":
|
|
1231
|
+
case "not-equals":
|
|
1232
|
+
return readFactPredicate(value, path, errors, false);
|
|
1233
|
+
case "greater-than-or-equal":
|
|
1234
|
+
case "less-than":
|
|
1235
|
+
return readFactPredicate(value, path, errors, true);
|
|
1236
|
+
case "and":
|
|
1237
|
+
case "or":
|
|
1238
|
+
return readVariadicPredicate(value, path, errors);
|
|
1239
|
+
case "not": {
|
|
1240
|
+
validateKnownKeys(value, path, ["kind", "predicate"], errors);
|
|
1241
|
+
const predicate = readPredicate(value["predicate"], `${path}.predicate`, errors);
|
|
1242
|
+
return predicate === undefined ? undefined : { kind, predicate };
|
|
1243
|
+
}
|
|
1244
|
+
default:
|
|
1245
|
+
errors.push(`${path}.kind is not supported`);
|
|
1246
|
+
return undefined;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
function readRequiredPredicate(value, path) {
|
|
1250
|
+
const errors = [];
|
|
1251
|
+
const predicate = readPredicate(value, path, errors);
|
|
1252
|
+
if (predicate === undefined || errors.length > 0) {
|
|
1253
|
+
throw new Error(`${path} is malformed: ${errors.join("; ")}`);
|
|
1254
|
+
}
|
|
1255
|
+
return predicate;
|
|
1256
|
+
}
|
|
1257
|
+
function readFactPredicate(value, path, errors, numeric) {
|
|
1258
|
+
validateKnownKeys(value, path, ["kind", "fact", "value"], errors);
|
|
1259
|
+
const kind = value["kind"];
|
|
1260
|
+
const fact = readNonEmptyString(value["fact"], `${path}.fact`, errors);
|
|
1261
|
+
const expected = value["value"];
|
|
1262
|
+
if (kind !== "equals" &&
|
|
1263
|
+
kind !== "not-equals" &&
|
|
1264
|
+
kind !== "greater-than-or-equal" &&
|
|
1265
|
+
kind !== "less-than") {
|
|
1266
|
+
errors.push(`${path}.kind is not a fact predicate`);
|
|
1267
|
+
return undefined;
|
|
1268
|
+
}
|
|
1269
|
+
if (numeric) {
|
|
1270
|
+
if (typeof expected !== "number" || !Number.isFinite(expected)) {
|
|
1271
|
+
errors.push(`${path}.value must be a finite numeric threshold`);
|
|
1272
|
+
return undefined;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
else if (!isKernelFactValue(expected)) {
|
|
1276
|
+
errors.push(`${path}.value must be a kernel fact value`);
|
|
1277
|
+
return undefined;
|
|
1278
|
+
}
|
|
1279
|
+
if (fact === undefined) {
|
|
1280
|
+
return undefined;
|
|
1281
|
+
}
|
|
1282
|
+
return {
|
|
1283
|
+
kind,
|
|
1284
|
+
fact,
|
|
1285
|
+
value: expected,
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
function readVariadicPredicate(value, path, errors) {
|
|
1289
|
+
validateKnownKeys(value, path, ["kind", "predicates"], errors);
|
|
1290
|
+
const kind = value["kind"];
|
|
1291
|
+
const predicates = value["predicates"];
|
|
1292
|
+
if (kind !== "and" && kind !== "or") {
|
|
1293
|
+
errors.push(`${path}.kind is not a variadic predicate`);
|
|
1294
|
+
return undefined;
|
|
1295
|
+
}
|
|
1296
|
+
if (!Array.isArray(predicates) || predicates.length === 0) {
|
|
1297
|
+
errors.push(`${path}.predicates must be a non-empty array`);
|
|
1298
|
+
return undefined;
|
|
1299
|
+
}
|
|
1300
|
+
const parsed = [];
|
|
1301
|
+
predicates.forEach((predicate, index) => {
|
|
1302
|
+
const parsedPredicate = readPredicate(predicate, `${path}.predicates[${index}]`, errors);
|
|
1303
|
+
if (parsedPredicate !== undefined) {
|
|
1304
|
+
parsed.push(parsedPredicate);
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
if (parsed.length !== predicates.length) {
|
|
1308
|
+
return undefined;
|
|
1309
|
+
}
|
|
1310
|
+
return { kind, predicates: parsed };
|
|
1311
|
+
}
|
|
1312
|
+
function collectPredicateFacts(expression) {
|
|
1313
|
+
const facts = new Set();
|
|
1314
|
+
collectFacts(expression, facts);
|
|
1315
|
+
return [...facts].sort((left, right) => left.localeCompare(right));
|
|
1316
|
+
}
|
|
1317
|
+
function collectFacts(expression, facts) {
|
|
1318
|
+
switch (expression.kind) {
|
|
1319
|
+
case "equals":
|
|
1320
|
+
case "not-equals":
|
|
1321
|
+
case "greater-than-or-equal":
|
|
1322
|
+
case "less-than":
|
|
1323
|
+
facts.add(expression.fact);
|
|
1324
|
+
return;
|
|
1325
|
+
case "and":
|
|
1326
|
+
case "or":
|
|
1327
|
+
expression.predicates.forEach((predicate) => collectFacts(predicate, facts));
|
|
1328
|
+
return;
|
|
1329
|
+
case "not":
|
|
1330
|
+
collectFacts(expression.predicate, facts);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
function readRequiredString(value, path) {
|
|
1335
|
+
const errors = [];
|
|
1336
|
+
const result = readNonEmptyString(value, path, errors);
|
|
1337
|
+
if (result === undefined) {
|
|
1338
|
+
throw new Error(`${path} must be a non-empty string`);
|
|
1339
|
+
}
|
|
1340
|
+
return result;
|
|
1341
|
+
}
|
|
1342
|
+
function readRequiredBoolean(value, path) {
|
|
1343
|
+
if (typeof value !== "boolean") {
|
|
1344
|
+
throw new Error(`${path} must be boolean`);
|
|
1345
|
+
}
|
|
1346
|
+
return value;
|
|
1347
|
+
}
|
|
1348
|
+
function readBoolean(value, path, errors) {
|
|
1349
|
+
if (typeof value !== "boolean") {
|
|
1350
|
+
errors.push(`${path} must be boolean`);
|
|
1351
|
+
return undefined;
|
|
1352
|
+
}
|
|
1353
|
+
return value;
|
|
1354
|
+
}
|
|
1355
|
+
function readNonEmptyString(value, path, errors) {
|
|
1356
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1357
|
+
errors.push(`${path} must be a non-empty string`);
|
|
1358
|
+
return undefined;
|
|
1359
|
+
}
|
|
1360
|
+
return value;
|
|
1361
|
+
}
|
|
1362
|
+
function readPositiveSafeInteger(value, path, errors) {
|
|
1363
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
1364
|
+
errors.push(`${path} must be a positive safe integer`);
|
|
1365
|
+
return undefined;
|
|
1366
|
+
}
|
|
1367
|
+
return value;
|
|
1368
|
+
}
|
|
1369
|
+
function readNonNegativeSafeInteger(value, path, errors) {
|
|
1370
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
1371
|
+
errors.push(`${path} must be a non-negative safe integer`);
|
|
1372
|
+
return undefined;
|
|
1373
|
+
}
|
|
1374
|
+
return value;
|
|
1375
|
+
}
|
|
1376
|
+
function readPositiveFiniteNumber(value, path, errors) {
|
|
1377
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
1378
|
+
errors.push(`${path} must be a positive finite number`);
|
|
1379
|
+
return undefined;
|
|
1380
|
+
}
|
|
1381
|
+
return value;
|
|
1382
|
+
}
|
|
1383
|
+
function readUnitInterval(value, path, errors) {
|
|
1384
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value >= 1) {
|
|
1385
|
+
errors.push(`${path} must be inside the open unit interval`);
|
|
1386
|
+
return undefined;
|
|
1387
|
+
}
|
|
1388
|
+
return value;
|
|
1389
|
+
}
|
|
1390
|
+
function readLiveObservableSource(value, path, errors) {
|
|
1391
|
+
if (typeof value !== "string" || !LIVE_OBSERVABLE_SOURCES.has(value)) {
|
|
1392
|
+
errors.push(`${path} must be a replayable live observable source`);
|
|
1393
|
+
return undefined;
|
|
1394
|
+
}
|
|
1395
|
+
return value;
|
|
1396
|
+
}
|
|
1397
|
+
function readContentHash(value, path, errors) {
|
|
1398
|
+
if (!isContentHash(value)) {
|
|
1399
|
+
errors.push(`${path} must be a sha256 content address`);
|
|
1400
|
+
return undefined;
|
|
1401
|
+
}
|
|
1402
|
+
return value;
|
|
1403
|
+
}
|
|
1404
|
+
function readContentHashArray(value, path, errors) {
|
|
1405
|
+
if (!Array.isArray(value)) {
|
|
1406
|
+
errors.push(`${path} must be an array`);
|
|
1407
|
+
return [];
|
|
1408
|
+
}
|
|
1409
|
+
const hashes = [];
|
|
1410
|
+
const seen = new Set();
|
|
1411
|
+
value.forEach((item, index) => {
|
|
1412
|
+
const hash = readContentHash(item, `${path}[${index}]`, errors);
|
|
1413
|
+
if (hash === undefined) {
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
if (seen.has(hash)) {
|
|
1417
|
+
errors.push(`${path}[${index}] must not duplicate a receipt hash`);
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
seen.add(hash);
|
|
1421
|
+
hashes.push(hash);
|
|
1422
|
+
});
|
|
1423
|
+
return Object.freeze(hashes.sort((left, right) => left.localeCompare(right)));
|
|
1424
|
+
}
|
|
1425
|
+
function expectLiteral(value, expected, path, errors) {
|
|
1426
|
+
if (value !== expected) {
|
|
1427
|
+
errors.push(`${path} must be ${JSON.stringify(expected)}`);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
function validateKnownKeys(value, path, allowed, errors) {
|
|
1431
|
+
const allowedSet = new Set(allowed);
|
|
1432
|
+
for (const key of Object.keys(value)) {
|
|
1433
|
+
if (!allowedSet.has(key)) {
|
|
1434
|
+
errors.push(`${path}.${key} is not part of v0`);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
function isContentHash(value) {
|
|
1439
|
+
return typeof value === "string" && CONTENT_HASH_PATTERN.test(value);
|
|
1440
|
+
}
|
|
1441
|
+
function parseReplayableInstantMs(value, path) {
|
|
1442
|
+
if (!ISO_INSTANT_PATTERN.test(value)) {
|
|
1443
|
+
throw new Error(`${path} must be a replayable ISO instant`);
|
|
1444
|
+
}
|
|
1445
|
+
const parsed = Date.parse(value);
|
|
1446
|
+
if (!Number.isFinite(parsed)) {
|
|
1447
|
+
throw new Error(`${path} must be a replayable ISO instant`);
|
|
1448
|
+
}
|
|
1449
|
+
return parsed;
|
|
1450
|
+
}
|
|
1451
|
+
function isKernelFactValue(value) {
|
|
1452
|
+
return (value === null ||
|
|
1453
|
+
typeof value === "string" ||
|
|
1454
|
+
typeof value === "number" ||
|
|
1455
|
+
typeof value === "boolean");
|
|
1456
|
+
}
|
|
1457
|
+
function isRecord(value) {
|
|
1458
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1459
|
+
return false;
|
|
1460
|
+
}
|
|
1461
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1462
|
+
return prototype === Object.prototype || prototype === null;
|
|
1463
|
+
}
|