@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,1861 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createRuntimeReactorV0 = createRuntimeReactorV0;
|
|
4
|
+
const memo_1 = require("../memo");
|
|
5
|
+
const composition_1 = require("../composition");
|
|
6
|
+
const forecast_1 = require("../forecast");
|
|
7
|
+
const evidence_plan_1 = require("../evidence-plan");
|
|
8
|
+
const kernel_1 = require("../kernel");
|
|
9
|
+
const policy_1 = require("../policy");
|
|
10
|
+
const receipt_1 = require("../receipt");
|
|
11
|
+
const judge_1 = require("../judge");
|
|
12
|
+
const CONTENT_HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
13
|
+
const RUNTIME_MEMO_SOURCE_TAG_PREFIX = "runtime-memo-source:";
|
|
14
|
+
const UNINITIALIZED_POLICY_NAMESPACE = "policy.uninitialized";
|
|
15
|
+
const UNINITIALIZED_POLICY_REVISION = "0";
|
|
16
|
+
const POLICY_LIVE_OBSERVABLE_SOURCES = new Set([
|
|
17
|
+
"connector",
|
|
18
|
+
"receipt-log",
|
|
19
|
+
"kernel-backstop",
|
|
20
|
+
"cost-ledger",
|
|
21
|
+
"human-label-stream",
|
|
22
|
+
]);
|
|
23
|
+
const POLICY_ROLLBACK_METADATA_SCHEMA = "openprose.reactor.policy-rollback.metadata";
|
|
24
|
+
const POLICY_ROLLBACK_RECEIPT_SCHEMA = "openprose.reactor.policy-rollback.receipt";
|
|
25
|
+
class PolicyRollbackFailure extends Error {
|
|
26
|
+
name = "PolicyRollbackFailure";
|
|
27
|
+
}
|
|
28
|
+
function createRuntimeReactorV0(input) {
|
|
29
|
+
return {
|
|
30
|
+
ingest(event) {
|
|
31
|
+
const asOf = input.adapters.clock.now();
|
|
32
|
+
return ingestEvent({
|
|
33
|
+
responsibility_id: input.responsibility_id,
|
|
34
|
+
adapters: input.adapters,
|
|
35
|
+
as_of: asOf,
|
|
36
|
+
event,
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
tick(as_of) {
|
|
40
|
+
const asOf = as_of ?? input.adapters.clock.now();
|
|
41
|
+
return tickRuntimeScheduler({
|
|
42
|
+
responsibility_id: input.responsibility_id,
|
|
43
|
+
adapters: input.adapters,
|
|
44
|
+
as_of: asOf,
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
receipts() {
|
|
48
|
+
return input.adapters.storage.listReceipts();
|
|
49
|
+
},
|
|
50
|
+
registry() {
|
|
51
|
+
return input.adapters.storage.readRegistry();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function ingestEvent(input) {
|
|
56
|
+
emitIngest(input.responsibility_id, input.adapters, input.as_of, input.event);
|
|
57
|
+
try {
|
|
58
|
+
const turn = normalizeTurnInputV0(input.event);
|
|
59
|
+
if (turn === undefined) {
|
|
60
|
+
return ingestUnsupportedInput({
|
|
61
|
+
responsibility_id: input.responsibility_id,
|
|
62
|
+
adapters: input.adapters,
|
|
63
|
+
as_of: input.as_of,
|
|
64
|
+
event: input.event,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return ingestTypedTurn({
|
|
68
|
+
responsibility_id: input.responsibility_id,
|
|
69
|
+
adapters: input.adapters,
|
|
70
|
+
as_of: input.as_of,
|
|
71
|
+
turn,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return {
|
|
76
|
+
accepted: false,
|
|
77
|
+
responsibility_id: input.responsibility_id,
|
|
78
|
+
as_of: input.as_of,
|
|
79
|
+
outcome: "failed-before-write",
|
|
80
|
+
errors: [
|
|
81
|
+
error instanceof Error ? error.message : "reactor ingest failed",
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function tickRuntimeScheduler(input) {
|
|
87
|
+
if (!isReplayableInstant(input.as_of)) {
|
|
88
|
+
return {
|
|
89
|
+
accepted: false,
|
|
90
|
+
responsibility_id: input.responsibility_id,
|
|
91
|
+
as_of: input.as_of,
|
|
92
|
+
outcome: "failed-before-write",
|
|
93
|
+
receipts_appended: 0,
|
|
94
|
+
receipt_hashes: [],
|
|
95
|
+
errors: ["tick as_of must be a replayable instant"],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const beforeReceiptCount = input.adapters.storage.listReceipts().length;
|
|
99
|
+
let registry;
|
|
100
|
+
try {
|
|
101
|
+
registry = readRuntimeRegistry(input.adapters.storage.readRegistry(), input.responsibility_id, undefined, input.as_of, false);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
accepted: false,
|
|
106
|
+
responsibility_id: input.responsibility_id,
|
|
107
|
+
as_of: input.as_of,
|
|
108
|
+
outcome: "failed-before-write",
|
|
109
|
+
receipts_appended: input.adapters.storage.listReceipts().length -
|
|
110
|
+
beforeReceiptCount,
|
|
111
|
+
receipt_hashes: [],
|
|
112
|
+
errors: [
|
|
113
|
+
error instanceof Error
|
|
114
|
+
? error.message
|
|
115
|
+
: "reactor scheduler tick failed",
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (registry.forecast.due_rechecks.length === 0) {
|
|
120
|
+
return {
|
|
121
|
+
accepted: true,
|
|
122
|
+
responsibility_id: input.responsibility_id,
|
|
123
|
+
as_of: input.as_of,
|
|
124
|
+
outcome: "no-work",
|
|
125
|
+
receipts_appended: 0,
|
|
126
|
+
receipt_hashes: [],
|
|
127
|
+
...(registry.forecast.next_due_at === undefined
|
|
128
|
+
? {}
|
|
129
|
+
: { next_due_at: registry.forecast.next_due_at }),
|
|
130
|
+
due_rechecks: [],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const dueRechecks = pendingDueRechecks({
|
|
134
|
+
registry,
|
|
135
|
+
receipts: input.adapters.storage.listReceipts(),
|
|
136
|
+
});
|
|
137
|
+
if (dueRechecks.length === 0) {
|
|
138
|
+
return {
|
|
139
|
+
accepted: true,
|
|
140
|
+
responsibility_id: input.responsibility_id,
|
|
141
|
+
as_of: input.as_of,
|
|
142
|
+
outcome: "no-work",
|
|
143
|
+
receipts_appended: 0,
|
|
144
|
+
receipt_hashes: [],
|
|
145
|
+
...(registry.forecast.next_due_at === undefined
|
|
146
|
+
? {}
|
|
147
|
+
: { next_due_at: registry.forecast.next_due_at }),
|
|
148
|
+
due_rechecks: [],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const results = [];
|
|
152
|
+
try {
|
|
153
|
+
for (const recheckKind of dueRechecks) {
|
|
154
|
+
const evidence = readPlannedAdapterEvidence({
|
|
155
|
+
plan: registry.compiled_evidence_plan,
|
|
156
|
+
adapters: input.adapters,
|
|
157
|
+
as_of: input.as_of,
|
|
158
|
+
});
|
|
159
|
+
const result = ingestEvent({
|
|
160
|
+
responsibility_id: input.responsibility_id,
|
|
161
|
+
adapters: input.adapters,
|
|
162
|
+
as_of: input.as_of,
|
|
163
|
+
event: {
|
|
164
|
+
kind: "forecast-recheck",
|
|
165
|
+
recheck_kind: recheckKind,
|
|
166
|
+
evidence,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
results.push(result);
|
|
170
|
+
if (!result.accepted) {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return {
|
|
177
|
+
accepted: false,
|
|
178
|
+
responsibility_id: input.responsibility_id,
|
|
179
|
+
as_of: input.as_of,
|
|
180
|
+
outcome: "failed-before-write",
|
|
181
|
+
receipts_appended: input.adapters.storage.listReceipts().length -
|
|
182
|
+
beforeReceiptCount,
|
|
183
|
+
receipt_hashes: results.flatMap((result) => result.receipt_hash === undefined ? [] : [result.receipt_hash]),
|
|
184
|
+
...(registry.forecast.next_due_at === undefined
|
|
185
|
+
? {}
|
|
186
|
+
: { next_due_at: registry.forecast.next_due_at }),
|
|
187
|
+
due_rechecks: dueRechecks,
|
|
188
|
+
recheck_results: results,
|
|
189
|
+
errors: [
|
|
190
|
+
error instanceof Error
|
|
191
|
+
? error.message
|
|
192
|
+
: "reactor scheduler tick failed",
|
|
193
|
+
],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const afterRecheckReceiptCount = input.adapters.storage.listReceipts().length;
|
|
197
|
+
const receiptHashes = results.flatMap((result) => result.receipt_hash === undefined ? [] : [result.receipt_hash]);
|
|
198
|
+
const errors = results.flatMap((result) => result.errors ?? []);
|
|
199
|
+
const last = results.at(-1);
|
|
200
|
+
const accepted = results.every((result) => result.accepted);
|
|
201
|
+
let policyLoop;
|
|
202
|
+
if (accepted && afterRecheckReceiptCount > beforeReceiptCount) {
|
|
203
|
+
try {
|
|
204
|
+
policyLoop = planAndMaybeExecutePolicyRecompileAfterTick({
|
|
205
|
+
responsibility_id: input.responsibility_id,
|
|
206
|
+
adapters: input.adapters,
|
|
207
|
+
as_of: input.as_of,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
return {
|
|
212
|
+
accepted: false,
|
|
213
|
+
responsibility_id: input.responsibility_id,
|
|
214
|
+
as_of: input.as_of,
|
|
215
|
+
outcome: error instanceof PolicyRollbackFailure
|
|
216
|
+
? "policy-rollback-failed"
|
|
217
|
+
: "policy-recompile-failed",
|
|
218
|
+
receipts_appended: input.adapters.storage.listReceipts().length -
|
|
219
|
+
beforeReceiptCount,
|
|
220
|
+
receipt_hashes: receiptHashes,
|
|
221
|
+
...(last?.next_due_at === undefined
|
|
222
|
+
? registry.forecast.next_due_at === undefined
|
|
223
|
+
? {}
|
|
224
|
+
: { next_due_at: registry.forecast.next_due_at }
|
|
225
|
+
: { next_due_at: last.next_due_at }),
|
|
226
|
+
due_rechecks: dueRechecks,
|
|
227
|
+
recheck_results: results,
|
|
228
|
+
...(policyLoop?.policy_recompile === undefined
|
|
229
|
+
? {}
|
|
230
|
+
: { policy_recompile: policyLoop.policy_recompile }),
|
|
231
|
+
...(policyLoop?.policy_rollback === undefined
|
|
232
|
+
? {}
|
|
233
|
+
: { policy_rollback: policyLoop.policy_rollback }),
|
|
234
|
+
errors: [
|
|
235
|
+
...errors,
|
|
236
|
+
error instanceof Error
|
|
237
|
+
? error.message
|
|
238
|
+
: "reactor policy loop planning failed",
|
|
239
|
+
],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const finalReceiptCount = input.adapters.storage.listReceipts().length;
|
|
244
|
+
const policyReceiptHashes = policyLoop?.policy_rollback?.receipt_hash === undefined
|
|
245
|
+
? []
|
|
246
|
+
: [policyLoop.policy_rollback.receipt_hash];
|
|
247
|
+
return {
|
|
248
|
+
accepted,
|
|
249
|
+
responsibility_id: input.responsibility_id,
|
|
250
|
+
as_of: input.as_of,
|
|
251
|
+
outcome: accepted ? "rechecks-completed" : "failed-before-write",
|
|
252
|
+
receipts_appended: finalReceiptCount - beforeReceiptCount,
|
|
253
|
+
receipt_hashes: [...receiptHashes, ...policyReceiptHashes],
|
|
254
|
+
...(last?.next_due_at === undefined
|
|
255
|
+
? registry.forecast.next_due_at === undefined
|
|
256
|
+
? {}
|
|
257
|
+
: { next_due_at: registry.forecast.next_due_at }
|
|
258
|
+
: { next_due_at: last.next_due_at }),
|
|
259
|
+
due_rechecks: dueRechecks,
|
|
260
|
+
recheck_results: results,
|
|
261
|
+
...(policyLoop?.policy_recompile === undefined
|
|
262
|
+
? {}
|
|
263
|
+
: { policy_recompile: policyLoop.policy_recompile }),
|
|
264
|
+
...(policyLoop?.policy_rollback === undefined
|
|
265
|
+
? {}
|
|
266
|
+
: { policy_rollback: policyLoop.policy_rollback }),
|
|
267
|
+
...(errors.length === 0 ? {} : { errors }),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function planAndMaybeExecutePolicyRecompileAfterTick(input) {
|
|
271
|
+
const registry = input.adapters.storage.readRegistry();
|
|
272
|
+
const activePolicy = readActivePolicyArtifactFromRegistry({
|
|
273
|
+
responsibility_id: input.responsibility_id,
|
|
274
|
+
registry,
|
|
275
|
+
});
|
|
276
|
+
const loopMetadata = readPolicyLoopMetadata(registry, activePolicy.artifact.provenance.contract_revision);
|
|
277
|
+
const receipts = input.adapters.storage.listReceipts();
|
|
278
|
+
const decision = (0, policy_1.planPolicyRecompileV0)({
|
|
279
|
+
artifact: activePolicy.artifact,
|
|
280
|
+
receipts,
|
|
281
|
+
as_of: input.as_of,
|
|
282
|
+
last_policy_revalidated_at: loopMetadata.last_policy_revalidated_at,
|
|
283
|
+
last_recompile_at: loopMetadata.last_recompile_at,
|
|
284
|
+
...(loopMetadata.last_unforced_deep_at === undefined
|
|
285
|
+
? {}
|
|
286
|
+
: { last_unforced_deep_at: loopMetadata.last_unforced_deep_at }),
|
|
287
|
+
});
|
|
288
|
+
const rollback = decision.drift.outcome === "tripped"
|
|
289
|
+
? planAndMaybeExecutePolicyRollbackAfterSelfTrip({
|
|
290
|
+
responsibility_id: input.responsibility_id,
|
|
291
|
+
adapters: input.adapters,
|
|
292
|
+
as_of: input.as_of,
|
|
293
|
+
registry,
|
|
294
|
+
active_policy: activePolicy.artifact,
|
|
295
|
+
self_trip: decision,
|
|
296
|
+
})
|
|
297
|
+
: undefined;
|
|
298
|
+
if (decision.outcome !== "recompile-requested") {
|
|
299
|
+
return {
|
|
300
|
+
policy_recompile: {
|
|
301
|
+
policy_artifact_revision_before: activePolicy.artifact.policy_revision,
|
|
302
|
+
decision,
|
|
303
|
+
},
|
|
304
|
+
...(rollback === undefined ? {} : { policy_rollback: rollback }),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (rollback?.decision.outcome === "rollback") {
|
|
308
|
+
return {
|
|
309
|
+
policy_recompile: {
|
|
310
|
+
policy_artifact_revision_before: activePolicy.artifact.policy_revision,
|
|
311
|
+
decision,
|
|
312
|
+
},
|
|
313
|
+
policy_rollback: rollback,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const execution = (0, policy_1.executePolicyRecompileV0)({
|
|
317
|
+
decision,
|
|
318
|
+
author_input: {
|
|
319
|
+
responsibility_id: input.responsibility_id,
|
|
320
|
+
contract_revision: activePolicy.artifact.provenance.contract_revision,
|
|
321
|
+
contract_summary: loopMetadata.contract_summary,
|
|
322
|
+
no_anchor: activePolicy.artifact.no_anchor,
|
|
323
|
+
live_observables: activePolicy.artifact.live_observables,
|
|
324
|
+
receipt_history: receipts,
|
|
325
|
+
agentSdk: input.adapters.agentSdk,
|
|
326
|
+
policy_artifact_namespace: registry.policy_artifact_namespace,
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
if (execution.outcome === "recompile-authored") {
|
|
330
|
+
persistPolicyRecompileExecution({
|
|
331
|
+
responsibility_id: input.responsibility_id,
|
|
332
|
+
adapters: input.adapters,
|
|
333
|
+
as_of: input.as_of,
|
|
334
|
+
current_registry: registry,
|
|
335
|
+
execution,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
policy_recompile: {
|
|
340
|
+
policy_artifact_revision_before: activePolicy.artifact.policy_revision,
|
|
341
|
+
...(execution.outcome === "recompile-authored"
|
|
342
|
+
? {
|
|
343
|
+
policy_artifact_revision_after: execution.registry.policy_artifact_revision,
|
|
344
|
+
compiled_evidence_plan_strategy: "carried-forward",
|
|
345
|
+
}
|
|
346
|
+
: {}),
|
|
347
|
+
decision,
|
|
348
|
+
execution,
|
|
349
|
+
},
|
|
350
|
+
...(rollback === undefined ? {} : { policy_rollback: rollback }),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function planAndMaybeExecutePolicyRollbackAfterSelfTrip(input) {
|
|
354
|
+
const metadata = readPolicyRollbackMetadata(input.registry);
|
|
355
|
+
if (metadata === undefined) {
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
if (metadata.fresh_policy_revision !== input.active_policy.policy_revision) {
|
|
359
|
+
throw new PolicyRollbackFailure("registry.policy_rollback fresh_policy_revision conflicts with active policy");
|
|
360
|
+
}
|
|
361
|
+
const receipts = input.adapters.storage.listReceipts();
|
|
362
|
+
const freshActivations = countJudgedActivationsForPolicy({
|
|
363
|
+
receipts,
|
|
364
|
+
responsibility_id: input.responsibility_id,
|
|
365
|
+
contract_revision: input.active_policy.provenance.contract_revision,
|
|
366
|
+
policy_artifact_namespace: input.registry.policy_artifact_namespace,
|
|
367
|
+
policy_artifact_revision: input.active_policy.policy_revision,
|
|
368
|
+
since: metadata.fresh_policy_installed_at,
|
|
369
|
+
through: input.as_of,
|
|
370
|
+
});
|
|
371
|
+
const targetRegistry = validateRollbackTargetRegistry({
|
|
372
|
+
responsibility_id: input.responsibility_id,
|
|
373
|
+
as_of: input.as_of,
|
|
374
|
+
active_contract_revision: input.active_policy.provenance.contract_revision,
|
|
375
|
+
metadata,
|
|
376
|
+
});
|
|
377
|
+
const decision = (0, policy_1.planPolicyRollbackV0)({
|
|
378
|
+
fresh_policy_revision: input.active_policy.policy_revision,
|
|
379
|
+
fresh_policy_judged_activations_before_trip: freshActivations,
|
|
380
|
+
last_known_good_revision: metadata.last_known_good.policy_artifact_revision,
|
|
381
|
+
last_known_good_judged_activations_before_trip: metadata.last_known_good.judged_activations_before_trip,
|
|
382
|
+
});
|
|
383
|
+
const reportBase = {
|
|
384
|
+
policy_artifact_revision_before: input.active_policy.policy_revision,
|
|
385
|
+
fresh_policy_installed_at: metadata.fresh_policy_installed_at,
|
|
386
|
+
fresh_policy_judged_activations_before_trip: freshActivations,
|
|
387
|
+
last_known_good_judged_activations_before_trip: metadata.last_known_good.judged_activations_before_trip,
|
|
388
|
+
decision,
|
|
389
|
+
self_trip: input.self_trip,
|
|
390
|
+
};
|
|
391
|
+
if (decision.outcome !== "rollback") {
|
|
392
|
+
return reportBase;
|
|
393
|
+
}
|
|
394
|
+
const receipt = createPolicyRollbackReceipt({
|
|
395
|
+
responsibility_id: input.responsibility_id,
|
|
396
|
+
contract_revision: input.active_policy.provenance.contract_revision,
|
|
397
|
+
as_of: input.as_of,
|
|
398
|
+
target_registry: targetRegistry,
|
|
399
|
+
decision,
|
|
400
|
+
self_trip: input.self_trip,
|
|
401
|
+
});
|
|
402
|
+
const verification = (0, receipt_1.verifyReceiptV0)(receipt);
|
|
403
|
+
if (!verification.ok) {
|
|
404
|
+
throw new PolicyRollbackFailure(`policy rollback receipt failed verification: ${verification.errors.join("; ")}`);
|
|
405
|
+
}
|
|
406
|
+
persistPolicyRollbackTargetRegistry({
|
|
407
|
+
responsibility_id: input.responsibility_id,
|
|
408
|
+
adapters: input.adapters,
|
|
409
|
+
as_of: input.as_of,
|
|
410
|
+
target_registry: targetRegistry,
|
|
411
|
+
});
|
|
412
|
+
input.adapters.storage.appendReceipt(receipt);
|
|
413
|
+
return {
|
|
414
|
+
...reportBase,
|
|
415
|
+
policy_artifact_revision_after: targetRegistry.policy_artifact_revision,
|
|
416
|
+
receipt_hash: verification.content_hash,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function readPolicyRollbackMetadata(registry) {
|
|
420
|
+
const value = registry.policy_rollback;
|
|
421
|
+
if (value === undefined) {
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
424
|
+
if (!isRecord(value)) {
|
|
425
|
+
throw new PolicyRollbackFailure("registry.policy_rollback must be an object");
|
|
426
|
+
}
|
|
427
|
+
if (value["schema"] !== POLICY_ROLLBACK_METADATA_SCHEMA) {
|
|
428
|
+
throw new PolicyRollbackFailure("registry.policy_rollback.schema is malformed");
|
|
429
|
+
}
|
|
430
|
+
if (value["v"] !== 0) {
|
|
431
|
+
throw new PolicyRollbackFailure("registry.policy_rollback.v must be 0");
|
|
432
|
+
}
|
|
433
|
+
const freshPolicyRevision = readNonEmptyString(value["fresh_policy_revision"]);
|
|
434
|
+
if (freshPolicyRevision === undefined) {
|
|
435
|
+
throw new PolicyRollbackFailure("registry.policy_rollback.fresh_policy_revision must be non-empty");
|
|
436
|
+
}
|
|
437
|
+
const freshPolicyInstalledAt = readNonEmptyString(value["fresh_policy_installed_at"]);
|
|
438
|
+
if (freshPolicyInstalledAt === undefined ||
|
|
439
|
+
!isReplayableInstant(freshPolicyInstalledAt)) {
|
|
440
|
+
throw new PolicyRollbackFailure("registry.policy_rollback.fresh_policy_installed_at must be replayable");
|
|
441
|
+
}
|
|
442
|
+
const lastKnownGood = value["last_known_good"];
|
|
443
|
+
if (!isRecord(lastKnownGood)) {
|
|
444
|
+
throw new PolicyRollbackFailure("registry.policy_rollback.last_known_good must be an object");
|
|
445
|
+
}
|
|
446
|
+
const lastKnownGoodRevision = readNonEmptyString(lastKnownGood["policy_artifact_revision"]);
|
|
447
|
+
if (lastKnownGoodRevision === undefined) {
|
|
448
|
+
throw new PolicyRollbackFailure("registry.policy_rollback.last_known_good.policy_artifact_revision must be non-empty");
|
|
449
|
+
}
|
|
450
|
+
const lastKnownGoodActivations = lastKnownGood["judged_activations_before_trip"];
|
|
451
|
+
if (!isNonNegativeSafeInteger(lastKnownGoodActivations)) {
|
|
452
|
+
throw new PolicyRollbackFailure("registry.policy_rollback.last_known_good.judged_activations_before_trip must be a non-negative safe integer");
|
|
453
|
+
}
|
|
454
|
+
const lastKnownGoodRegistry = lastKnownGood["registry"];
|
|
455
|
+
if (!isRecord(lastKnownGoodRegistry)) {
|
|
456
|
+
throw new PolicyRollbackFailure("registry.policy_rollback.last_known_good.registry must be an object");
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
schema: POLICY_ROLLBACK_METADATA_SCHEMA,
|
|
460
|
+
v: 0,
|
|
461
|
+
fresh_policy_revision: freshPolicyRevision,
|
|
462
|
+
fresh_policy_installed_at: freshPolicyInstalledAt,
|
|
463
|
+
last_known_good: {
|
|
464
|
+
policy_artifact_revision: lastKnownGoodRevision,
|
|
465
|
+
judged_activations_before_trip: lastKnownGoodActivations,
|
|
466
|
+
registry: lastKnownGoodRegistry,
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function validateRollbackTargetRegistry(input) {
|
|
471
|
+
const targetRegistry = cloneRegistryWithoutRollbackMetadata(input.metadata.last_known_good.registry);
|
|
472
|
+
try {
|
|
473
|
+
const runtimeRegistry = readRuntimeRegistry(targetRegistry, input.responsibility_id, undefined, input.as_of, false);
|
|
474
|
+
if (runtimeRegistry.contract_revision !== input.active_contract_revision) {
|
|
475
|
+
throw new Error("rollback target contract_revision conflicts with active policy");
|
|
476
|
+
}
|
|
477
|
+
if (runtimeRegistry.policy_artifact_revision !==
|
|
478
|
+
input.metadata.last_known_good.policy_artifact_revision) {
|
|
479
|
+
throw new Error("rollback target revision conflicts with rollback metadata");
|
|
480
|
+
}
|
|
481
|
+
readActivePolicyArtifactFromRegistry({
|
|
482
|
+
responsibility_id: input.responsibility_id,
|
|
483
|
+
registry: targetRegistry,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
throw new PolicyRollbackFailure(`registry.policy_rollback.last_known_good.registry cannot be validated: ${error instanceof Error ? error.message : "unknown validation failure"}`);
|
|
488
|
+
}
|
|
489
|
+
return targetRegistry;
|
|
490
|
+
}
|
|
491
|
+
function persistPolicyRollbackTargetRegistry(input) {
|
|
492
|
+
const writeRegistry = input.adapters.storage.writeRegistry;
|
|
493
|
+
if (typeof writeRegistry !== "function") {
|
|
494
|
+
throw new PolicyRollbackFailure("storage.writeRegistry is required for policy rollback persistence");
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
writeRegistry(input.target_registry);
|
|
498
|
+
const persisted = input.adapters.storage.readRegistry();
|
|
499
|
+
validateRollbackTargetRegistry({
|
|
500
|
+
responsibility_id: input.responsibility_id,
|
|
501
|
+
as_of: input.as_of,
|
|
502
|
+
active_contract_revision: input.target_registry.contract_revision,
|
|
503
|
+
metadata: {
|
|
504
|
+
schema: POLICY_ROLLBACK_METADATA_SCHEMA,
|
|
505
|
+
v: 0,
|
|
506
|
+
fresh_policy_revision: input.target_registry.policy_artifact_revision,
|
|
507
|
+
fresh_policy_installed_at: input.as_of,
|
|
508
|
+
last_known_good: {
|
|
509
|
+
policy_artifact_revision: input.target_registry.policy_artifact_revision,
|
|
510
|
+
judged_activations_before_trip: 0,
|
|
511
|
+
registry: persisted,
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
throw new PolicyRollbackFailure(`policy rollback persistence failed: ${error instanceof Error ? error.message : "unknown persistence failure"}`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function createPolicyRollbackReceipt(input) {
|
|
521
|
+
const lastKnownGoodActivations = input.decision.last_known_good_judged_activations_before_trip;
|
|
522
|
+
if (lastKnownGoodActivations === undefined) {
|
|
523
|
+
throw new PolicyRollbackFailure("policy rollback decision must include last-known-good judged activations");
|
|
524
|
+
}
|
|
525
|
+
if (input.decision.target_policy_revision === undefined) {
|
|
526
|
+
throw new PolicyRollbackFailure("policy rollback decision must include target_policy_revision");
|
|
527
|
+
}
|
|
528
|
+
const evidenceInputId = policyRollbackEvidenceInputId(input);
|
|
529
|
+
return (0, receipt_1.createReceiptV0)({
|
|
530
|
+
core: {
|
|
531
|
+
responsibility_id: input.responsibility_id,
|
|
532
|
+
contract_revision: input.contract_revision,
|
|
533
|
+
event_cause: "escalation",
|
|
534
|
+
memo_key: `policy-rollback:${evidenceInputId}`,
|
|
535
|
+
evidence_input_ids: [evidenceInputId],
|
|
536
|
+
as_of: input.as_of,
|
|
537
|
+
role: "policy-compile",
|
|
538
|
+
},
|
|
539
|
+
sig: (0, receipt_1.createNullSignerReceiptSignatureV0)(),
|
|
540
|
+
verdict: {
|
|
541
|
+
status: "up",
|
|
542
|
+
confidence: {
|
|
543
|
+
value: 1,
|
|
544
|
+
derivation_method: "deterministic-policy-rollback",
|
|
545
|
+
calibration_grade: "none",
|
|
546
|
+
label_source: "runtime-policy-rollback",
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
freshness: {
|
|
550
|
+
as_of: input.as_of,
|
|
551
|
+
next_forecast_recheck: optionalNextForecastRecheck(input.target_registry) ?? input.as_of,
|
|
552
|
+
},
|
|
553
|
+
composition: {
|
|
554
|
+
consumed_receipts: [],
|
|
555
|
+
cycle_checked: true,
|
|
556
|
+
},
|
|
557
|
+
cost: {
|
|
558
|
+
provider: "runtime",
|
|
559
|
+
model: "deterministic-policy-rollback",
|
|
560
|
+
role: "policy-compile",
|
|
561
|
+
tags: [
|
|
562
|
+
"policy-rollback",
|
|
563
|
+
`policy-rollback:fresh:${input.decision.fresh_policy_revision}`,
|
|
564
|
+
`policy-rollback:target:${input.decision.target_policy_revision}`,
|
|
565
|
+
`policy-rollback:fresh-judged-activations:${input.decision.fresh_policy_judged_activations_before_trip}`,
|
|
566
|
+
`policy-rollback:last-known-good-judged-activations:${lastKnownGoodActivations}`,
|
|
567
|
+
],
|
|
568
|
+
responsibility_id: input.responsibility_id,
|
|
569
|
+
run_id: `policy-rollback-${input.as_of}`,
|
|
570
|
+
as_of: input.as_of,
|
|
571
|
+
tokens: { fresh: 0, reused: 0 },
|
|
572
|
+
surprise_cause: "escalation",
|
|
573
|
+
provider_norm: {
|
|
574
|
+
schema: POLICY_ROLLBACK_RECEIPT_SCHEMA,
|
|
575
|
+
fresh_policy_revision: input.decision.fresh_policy_revision,
|
|
576
|
+
target_policy_revision: input.decision.target_policy_revision,
|
|
577
|
+
fresh_policy_judged_activations_before_trip: input.decision.fresh_policy_judged_activations_before_trip,
|
|
578
|
+
last_known_good_judged_activations_before_trip: lastKnownGoodActivations,
|
|
579
|
+
self_trip_outcome: input.self_trip.outcome,
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
function policyRollbackEvidenceInputId(input) {
|
|
585
|
+
return (0, receipt_1.hashCanonicalReceiptV0)((0, receipt_1.canonicalizeForReceiptV0)({
|
|
586
|
+
schema: "openprose.reactor.policy-rollback.evidence",
|
|
587
|
+
v: 0,
|
|
588
|
+
decision: input.decision,
|
|
589
|
+
self_trip: {
|
|
590
|
+
outcome: input.self_trip.outcome,
|
|
591
|
+
policy_artifact_revision: input.self_trip.policy_artifact_revision,
|
|
592
|
+
policy_artifact_content_hash: input.self_trip.policy_artifact_content_hash,
|
|
593
|
+
requested_by: input.self_trip.requested_by,
|
|
594
|
+
drift_outcome: input.self_trip.drift.outcome,
|
|
595
|
+
evidence_receipt_hashes: input.self_trip.evidence_receipt_hashes,
|
|
596
|
+
},
|
|
597
|
+
}));
|
|
598
|
+
}
|
|
599
|
+
function readActivePolicyArtifactFromRegistry(input) {
|
|
600
|
+
const bytes = input.registry.policy_artifact_bytes;
|
|
601
|
+
if (typeof bytes !== "string" || bytes.length === 0) {
|
|
602
|
+
throw new Error("registry.policy_artifact_bytes is required for policy recompile planning");
|
|
603
|
+
}
|
|
604
|
+
let parsed;
|
|
605
|
+
try {
|
|
606
|
+
parsed = JSON.parse(bytes);
|
|
607
|
+
}
|
|
608
|
+
catch {
|
|
609
|
+
throw new Error("registry.policy_artifact_bytes must contain a JSON policy artifact");
|
|
610
|
+
}
|
|
611
|
+
const validation = (0, policy_1.validatePolicyArtifactV0)(parsed);
|
|
612
|
+
if (!validation.ok) {
|
|
613
|
+
throw new Error(`registry.policy_artifact_bytes failed policy validation: ${validation.errors.join("; ")}`);
|
|
614
|
+
}
|
|
615
|
+
if (validation.bytes !== bytes) {
|
|
616
|
+
throw new Error("registry.policy_artifact_bytes must be canonical policy artifact bytes");
|
|
617
|
+
}
|
|
618
|
+
if (validation.artifact.responsibility_id !== input.responsibility_id) {
|
|
619
|
+
throw new Error("registry.policy_artifact_bytes responsibility_id conflicts with runtime responsibility_id");
|
|
620
|
+
}
|
|
621
|
+
if (isContentHash(input.registry.contract_revision) &&
|
|
622
|
+
validation.artifact.provenance.contract_revision !== input.registry.contract_revision) {
|
|
623
|
+
throw new Error("registry.policy_artifact_bytes contract_revision conflicts with registry.contract_revision");
|
|
624
|
+
}
|
|
625
|
+
if (input.registry.policy_artifact_revision !==
|
|
626
|
+
validation.artifact.policy_revision) {
|
|
627
|
+
throw new Error("registry.policy_artifact_bytes policy_revision conflicts with registry.policy_artifact_revision");
|
|
628
|
+
}
|
|
629
|
+
if (input.registry.policy_artifact_id !== undefined &&
|
|
630
|
+
input.registry.policy_artifact_id !== validation.artifact.registry_id) {
|
|
631
|
+
throw new Error("registry.policy_artifact_id conflicts with policy artifact registry_id");
|
|
632
|
+
}
|
|
633
|
+
if (input.registry.policy_artifact_identity !== undefined &&
|
|
634
|
+
input.registry.policy_artifact_identity !== validation.artifact.registry_id) {
|
|
635
|
+
throw new Error("registry.policy_artifact_identity conflicts with policy artifact registry_id");
|
|
636
|
+
}
|
|
637
|
+
if (input.registry.policy_artifact_content_hash !== undefined &&
|
|
638
|
+
input.registry.policy_artifact_content_hash !== validation.content_hash) {
|
|
639
|
+
throw new Error("registry.policy_artifact_content_hash conflicts with policy_artifact_bytes");
|
|
640
|
+
}
|
|
641
|
+
return {
|
|
642
|
+
artifact: validation.artifact,
|
|
643
|
+
content_hash: validation.content_hash,
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
function readPolicyLoopMetadata(registry, contractRevision) {
|
|
647
|
+
return {
|
|
648
|
+
contract_summary: readRegistryContractSummary(registry, contractRevision),
|
|
649
|
+
last_policy_revalidated_at: readRegistryReplayableInstant(registry, "last_policy_revalidated_at"),
|
|
650
|
+
last_recompile_at: readRegistryReplayableInstant(registry, "last_recompile_at"),
|
|
651
|
+
...(registry.last_unforced_deep_at === undefined
|
|
652
|
+
? {}
|
|
653
|
+
: {
|
|
654
|
+
last_unforced_deep_at: readRegistryReplayableInstant(registry, "last_unforced_deep_at"),
|
|
655
|
+
}),
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function readRegistryContractSummary(registry, contractRevision) {
|
|
659
|
+
const value = registry.contract_summary;
|
|
660
|
+
if (typeof value === "string" && value.length > 0) {
|
|
661
|
+
return value;
|
|
662
|
+
}
|
|
663
|
+
if (!isRecord(value)) {
|
|
664
|
+
throw new Error("registry.contract_summary is required for policy recompile planning");
|
|
665
|
+
}
|
|
666
|
+
const summary = readNonEmptyString(value["summary"]);
|
|
667
|
+
if (summary === undefined) {
|
|
668
|
+
throw new Error("registry.contract_summary.summary must be non-empty for policy recompile planning");
|
|
669
|
+
}
|
|
670
|
+
if (value["source_contract_revision"] !== contractRevision) {
|
|
671
|
+
throw new Error("registry.contract_summary source_contract_revision conflicts with policy artifact");
|
|
672
|
+
}
|
|
673
|
+
if (!isContentHash(value["projection_hash"])) {
|
|
674
|
+
throw new Error("registry.contract_summary.projection_hash must be a sha256 content address");
|
|
675
|
+
}
|
|
676
|
+
return summary;
|
|
677
|
+
}
|
|
678
|
+
function readRegistryReplayableInstant(registry, field) {
|
|
679
|
+
const value = registry[field];
|
|
680
|
+
if (typeof value !== "string" || !isReplayableInstant(value)) {
|
|
681
|
+
throw new Error(`registry.${field} must be a replayable instant for policy recompile planning`);
|
|
682
|
+
}
|
|
683
|
+
return value;
|
|
684
|
+
}
|
|
685
|
+
function persistPolicyRecompileExecution(input) {
|
|
686
|
+
const writeRegistry = input.adapters.storage.writeRegistry;
|
|
687
|
+
if (typeof writeRegistry !== "function") {
|
|
688
|
+
throw new Error("storage.writeRegistry is required for policy recompile persistence");
|
|
689
|
+
}
|
|
690
|
+
if (input.execution.registry.contract_revision !==
|
|
691
|
+
input.current_registry.contract_revision) {
|
|
692
|
+
throw new Error("authored policy contract_revision conflicts with active registry");
|
|
693
|
+
}
|
|
694
|
+
const currentPlan = readCompiledEvidencePlan(input.current_registry.compiled_evidence_plan, "registry.compiled_evidence_plan");
|
|
695
|
+
const currentSchedule = readForecastSchedule(input.current_registry.forecast_schedule);
|
|
696
|
+
const currentPolicySnapshot = createRollbackPolicySnapshot({
|
|
697
|
+
responsibility_id: input.responsibility_id,
|
|
698
|
+
registry: input.current_registry,
|
|
699
|
+
as_of: input.as_of,
|
|
700
|
+
});
|
|
701
|
+
const currentPolicyActivations = countJudgedActivationsForPolicy({
|
|
702
|
+
receipts: input.adapters.storage.listReceipts(),
|
|
703
|
+
responsibility_id: input.responsibility_id,
|
|
704
|
+
contract_revision: input.current_registry.contract_revision,
|
|
705
|
+
policy_artifact_namespace: input.current_registry.policy_artifact_namespace,
|
|
706
|
+
policy_artifact_revision: input.current_registry.policy_artifact_revision,
|
|
707
|
+
through: input.as_of,
|
|
708
|
+
});
|
|
709
|
+
const nextPlan = carryForwardCompiledEvidencePlanForPolicyRecompile({
|
|
710
|
+
plan: currentPlan,
|
|
711
|
+
registry: input.execution.registry,
|
|
712
|
+
as_of: input.as_of,
|
|
713
|
+
});
|
|
714
|
+
const nextRegistry = {
|
|
715
|
+
...input.current_registry,
|
|
716
|
+
...input.execution.registry,
|
|
717
|
+
compiled_evidence_plan: nextPlan,
|
|
718
|
+
forecast_schedule: currentSchedule,
|
|
719
|
+
...(input.current_registry.contract_summary === undefined
|
|
720
|
+
? {}
|
|
721
|
+
: { contract_summary: input.current_registry.contract_summary }),
|
|
722
|
+
last_policy_revalidated_at: input.as_of,
|
|
723
|
+
last_recompile_at: input.as_of,
|
|
724
|
+
last_policy_recompile_at: input.as_of,
|
|
725
|
+
...(input.current_registry.last_unforced_deep_at === undefined
|
|
726
|
+
? {}
|
|
727
|
+
: { last_unforced_deep_at: input.current_registry.last_unforced_deep_at }),
|
|
728
|
+
policy_rollback: {
|
|
729
|
+
schema: POLICY_ROLLBACK_METADATA_SCHEMA,
|
|
730
|
+
v: 0,
|
|
731
|
+
fresh_policy_revision: input.execution.registry.policy_artifact_revision,
|
|
732
|
+
fresh_policy_installed_at: input.as_of,
|
|
733
|
+
last_known_good: {
|
|
734
|
+
policy_artifact_revision: input.current_registry.policy_artifact_revision,
|
|
735
|
+
judged_activations_before_trip: currentPolicyActivations,
|
|
736
|
+
registry: currentPolicySnapshot,
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
};
|
|
740
|
+
readRuntimeRegistry(nextRegistry, input.responsibility_id, undefined, input.as_of, false);
|
|
741
|
+
readActivePolicyArtifactFromRegistry({
|
|
742
|
+
responsibility_id: input.responsibility_id,
|
|
743
|
+
registry: nextRegistry,
|
|
744
|
+
});
|
|
745
|
+
writeRegistry(nextRegistry);
|
|
746
|
+
const persistedRegistry = input.adapters.storage.readRegistry();
|
|
747
|
+
readRuntimeRegistry(persistedRegistry, input.responsibility_id, undefined, input.as_of, false);
|
|
748
|
+
readActivePolicyArtifactFromRegistry({
|
|
749
|
+
responsibility_id: input.responsibility_id,
|
|
750
|
+
registry: persistedRegistry,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
function createRollbackPolicySnapshot(input) {
|
|
754
|
+
const snapshot = cloneRegistryWithoutRollbackMetadata(input.registry);
|
|
755
|
+
readRuntimeRegistry(snapshot, input.responsibility_id, undefined, input.as_of, false);
|
|
756
|
+
readActivePolicyArtifactFromRegistry({
|
|
757
|
+
responsibility_id: input.responsibility_id,
|
|
758
|
+
registry: snapshot,
|
|
759
|
+
});
|
|
760
|
+
return snapshot;
|
|
761
|
+
}
|
|
762
|
+
function cloneRegistryWithoutRollbackMetadata(registry) {
|
|
763
|
+
const snapshot = JSON.parse((0, receipt_1.canonicalizeForReceiptV0)(registry));
|
|
764
|
+
delete snapshot["policy_rollback"];
|
|
765
|
+
return snapshot;
|
|
766
|
+
}
|
|
767
|
+
function countJudgedActivationsForPolicy(input) {
|
|
768
|
+
const throughMs = parseRuntimeInstantMs(input.through, "rollback through");
|
|
769
|
+
const sinceMs = input.since === undefined
|
|
770
|
+
? Number.NEGATIVE_INFINITY
|
|
771
|
+
: parseRuntimeInstantMs(input.since, "rollback since");
|
|
772
|
+
const tag = `${RUNTIME_MEMO_SOURCE_TAG_PREFIX}${input.policy_artifact_namespace}@${input.policy_artifact_revision}`;
|
|
773
|
+
return input.receipts.filter((receipt) => {
|
|
774
|
+
const verification = (0, receipt_1.verifyReceiptV0)(receipt);
|
|
775
|
+
if (!verification.ok) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
const asOfMs = Date.parse(receipt.core.as_of);
|
|
779
|
+
return (receipt.core.responsibility_id === input.responsibility_id &&
|
|
780
|
+
receipt.core.contract_revision === input.contract_revision &&
|
|
781
|
+
receipt.core.role === "judge" &&
|
|
782
|
+
receipt.cost.tags.includes(tag) &&
|
|
783
|
+
asOfMs >= sinceMs &&
|
|
784
|
+
asOfMs <= throughMs);
|
|
785
|
+
}).length;
|
|
786
|
+
}
|
|
787
|
+
function parseRuntimeInstantMs(value, name) {
|
|
788
|
+
const ms = Date.parse(value);
|
|
789
|
+
if (!Number.isFinite(ms)) {
|
|
790
|
+
throw new PolicyRollbackFailure(`${name} must be a replayable instant`);
|
|
791
|
+
}
|
|
792
|
+
return ms;
|
|
793
|
+
}
|
|
794
|
+
function carryForwardCompiledEvidencePlanForPolicyRecompile(input) {
|
|
795
|
+
return {
|
|
796
|
+
...input.plan,
|
|
797
|
+
policy_artifact_namespace: input.registry.policy_artifact_namespace,
|
|
798
|
+
policy_artifact_revision: input.registry.policy_artifact_revision,
|
|
799
|
+
plan_revision: `${input.plan.plan_revision}+policy-recompile-carry-forward:${input.registry.policy_artifact_revision}`,
|
|
800
|
+
as_of: input.as_of,
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
function pendingDueRechecks(input) {
|
|
804
|
+
return input.registry.forecast.due_rechecks.filter((recheckKind) => {
|
|
805
|
+
const scheduledAt = recheckKind === "evidence-age"
|
|
806
|
+
? input.registry.forecast_schedule.next_evidence_recheck
|
|
807
|
+
: input.registry.forecast_schedule.next_plan_recheck;
|
|
808
|
+
return !input.receipts.some((receipt) => satisfiesScheduledRecheck(receipt, input.registry, recheckKind, scheduledAt));
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
function satisfiesScheduledRecheck(receipt, registry, recheckKind, scheduledAt) {
|
|
812
|
+
if (!(0, receipt_1.verifyReceiptV0)(receipt).ok) {
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
return (receipt.core.responsibility_id === registry.forecast_schedule.responsibility_id &&
|
|
816
|
+
receipt.core.contract_revision === registry.contract_revision &&
|
|
817
|
+
receipt.core.event_cause === "forecast-recheck" &&
|
|
818
|
+
receipt.core.recheck_kind === recheckKind &&
|
|
819
|
+
Date.parse(receipt.core.as_of) >= Date.parse(scheduledAt));
|
|
820
|
+
}
|
|
821
|
+
function ingestTypedTurn(input) {
|
|
822
|
+
if (input.turn.kind === "escalation") {
|
|
823
|
+
return ingestEscalationTurn({
|
|
824
|
+
responsibility_id: input.responsibility_id,
|
|
825
|
+
adapters: input.adapters,
|
|
826
|
+
as_of: input.as_of,
|
|
827
|
+
turn: input.turn,
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
const eventCause = eventCauseForTurn(input.turn);
|
|
831
|
+
const recheckKind = recheckKindForTurn(input.turn);
|
|
832
|
+
const registrySnapshot = ensurePolicyRegistryForTurn({
|
|
833
|
+
responsibility_id: input.responsibility_id,
|
|
834
|
+
adapters: input.adapters,
|
|
835
|
+
as_of: input.as_of,
|
|
836
|
+
turn: input.turn,
|
|
837
|
+
});
|
|
838
|
+
let registry;
|
|
839
|
+
try {
|
|
840
|
+
registry = readRuntimeRegistry(registrySnapshot, input.responsibility_id, input.turn.contract_revision, input.as_of, eventCause === "real-input");
|
|
841
|
+
}
|
|
842
|
+
catch (error) {
|
|
843
|
+
const contractRevision = receiptContractRevisionForTurn(registrySnapshot, input.turn.contract_revision);
|
|
844
|
+
if (contractRevision === undefined) {
|
|
845
|
+
throw error;
|
|
846
|
+
}
|
|
847
|
+
return appendRuntimeBlockedReceipt({
|
|
848
|
+
responsibility_id: input.responsibility_id,
|
|
849
|
+
adapters: input.adapters,
|
|
850
|
+
as_of: input.as_of,
|
|
851
|
+
contract_revision: contractRevision,
|
|
852
|
+
reason: error instanceof Error
|
|
853
|
+
? error.message
|
|
854
|
+
: "active runtime policy is not executable",
|
|
855
|
+
fix_target: "policy-artifact",
|
|
856
|
+
interrupt_cause: "needs-judgment",
|
|
857
|
+
event_cause: "escalation",
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
const evidenceSelection = selectPlannedEvidenceInputs(registry.compiled_evidence_plan, input.turn.evidence ?? []);
|
|
861
|
+
if (!evidenceSelection.ok) {
|
|
862
|
+
return appendRuntimeBlockedReceipt({
|
|
863
|
+
responsibility_id: input.responsibility_id,
|
|
864
|
+
adapters: input.adapters,
|
|
865
|
+
as_of: input.as_of,
|
|
866
|
+
contract_revision: registry.contract_revision,
|
|
867
|
+
reason: evidenceSelection.reason,
|
|
868
|
+
fix_target: evidenceSelection.fix_target,
|
|
869
|
+
interrupt_cause: evidenceSelection.interrupt_cause,
|
|
870
|
+
event_cause: "escalation",
|
|
871
|
+
next_forecast_recheck: registry.forecast.receipt_next_forecast_recheck,
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
const evidence = evidenceSelection.evidence;
|
|
875
|
+
const dependencies = verifyDependencyReceipts(input.turn.dependency_receipts ?? []);
|
|
876
|
+
const cycleCheck = (0, kernel_1.detectReceiptCycles)(receiptCycleEdgesFromDependencies(dependencies));
|
|
877
|
+
if (cycleCheck.has_cycle) {
|
|
878
|
+
return appendRuntimeBlockedReceipt({
|
|
879
|
+
responsibility_id: input.responsibility_id,
|
|
880
|
+
adapters: input.adapters,
|
|
881
|
+
as_of: input.as_of,
|
|
882
|
+
contract_revision: registry.contract_revision,
|
|
883
|
+
reason: "cycle-detected",
|
|
884
|
+
fix_target: "composition.dependency_receipts",
|
|
885
|
+
interrupt_cause: "needs-judgment",
|
|
886
|
+
event_cause: "escalation",
|
|
887
|
+
next_forecast_recheck: registry.forecast.receipt_next_forecast_recheck,
|
|
888
|
+
cycle_checked: cycleCheck.cycle_checked,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
const memoKey = (0, memo_1.computeMemoKeyV0)({
|
|
892
|
+
contract_revision: registry.contract_revision,
|
|
893
|
+
evidence_receipts: evidence.map((item) => item.content_hash),
|
|
894
|
+
dependency_receipts: dependencies.map((item) => item.memo_ref),
|
|
895
|
+
});
|
|
896
|
+
const memoSource = shouldCheckMemo(input.turn)
|
|
897
|
+
? findReusableReceipt({
|
|
898
|
+
receipts: input.adapters.storage.listReceipts(),
|
|
899
|
+
responsibility_id: input.responsibility_id,
|
|
900
|
+
contract_revision: registry.contract_revision,
|
|
901
|
+
memo_key: memoKey,
|
|
902
|
+
registry,
|
|
903
|
+
})
|
|
904
|
+
: undefined;
|
|
905
|
+
if (memoSource !== undefined) {
|
|
906
|
+
const memoHitReceipt = (0, memo_1.createMemoHitReceiptV0)({
|
|
907
|
+
source_receipt: memoSource,
|
|
908
|
+
as_of: input.as_of,
|
|
909
|
+
next_forecast_recheck: registry.forecast.receipt_next_forecast_recheck,
|
|
910
|
+
event_cause: eventCause,
|
|
911
|
+
...(recheckKind === undefined ? {} : { recheck_kind: recheckKind }),
|
|
912
|
+
});
|
|
913
|
+
const verification = (0, receipt_1.verifyReceiptV0)(memoHitReceipt);
|
|
914
|
+
if (!verification.ok) {
|
|
915
|
+
throw new Error(`memo-hit receipt failed verification: ${verification.errors.join("; ")}`);
|
|
916
|
+
}
|
|
917
|
+
input.adapters.storage.appendReceipt(memoHitReceipt);
|
|
918
|
+
return withForecastResult({
|
|
919
|
+
accepted: true,
|
|
920
|
+
responsibility_id: input.responsibility_id,
|
|
921
|
+
as_of: input.as_of,
|
|
922
|
+
receipt_hash: verification.content_hash,
|
|
923
|
+
outcome: "memo-hit-receipt",
|
|
924
|
+
}, registry.forecast);
|
|
925
|
+
}
|
|
926
|
+
const judge = (0, judge_1.runShallowJudgeV0)({
|
|
927
|
+
responsibility_id: input.responsibility_id,
|
|
928
|
+
contract_revision: registry.contract_revision,
|
|
929
|
+
policy_artifact_namespace: registry.policy_artifact_namespace,
|
|
930
|
+
policy_artifact_revision: registry.policy_artifact_revision,
|
|
931
|
+
...(registry.policy_artifact_content_hash === undefined
|
|
932
|
+
? {}
|
|
933
|
+
: { policy_artifact_content_hash: registry.policy_artifact_content_hash }),
|
|
934
|
+
evidence,
|
|
935
|
+
as_of: input.as_of,
|
|
936
|
+
event_cause: eventCause,
|
|
937
|
+
...(recheckKind === undefined ? {} : { recheck_kind: recheckKind }),
|
|
938
|
+
depth: "shallow",
|
|
939
|
+
modelGateway: input.adapters.modelGateway,
|
|
940
|
+
});
|
|
941
|
+
const usage = judge.model_usage;
|
|
942
|
+
const freshness = receiptFreshnessForRuntimeJudge({
|
|
943
|
+
registry,
|
|
944
|
+
dependencies,
|
|
945
|
+
as_of: input.as_of,
|
|
946
|
+
});
|
|
947
|
+
const receipt = (0, receipt_1.createReceiptV0)({
|
|
948
|
+
core: {
|
|
949
|
+
responsibility_id: input.responsibility_id,
|
|
950
|
+
contract_revision: registry.contract_revision,
|
|
951
|
+
event_cause: eventCause,
|
|
952
|
+
...(recheckKind === undefined ? {} : { recheck_kind: recheckKind }),
|
|
953
|
+
memo_key: memoKey,
|
|
954
|
+
evidence_input_ids: evidence.map((item) => item.content_hash),
|
|
955
|
+
as_of: input.as_of,
|
|
956
|
+
role: "judge",
|
|
957
|
+
},
|
|
958
|
+
sig: (0, receipt_1.createNullSignerReceiptSignatureV0)(),
|
|
959
|
+
verdict: judge.verdict,
|
|
960
|
+
freshness: {
|
|
961
|
+
...freshness,
|
|
962
|
+
},
|
|
963
|
+
composition: {
|
|
964
|
+
consumed_receipts: dependencies.map((item) => item.pin),
|
|
965
|
+
cycle_checked: cycleCheck.cycle_checked,
|
|
966
|
+
},
|
|
967
|
+
cost: {
|
|
968
|
+
provider: usage.provider,
|
|
969
|
+
model: usage.model,
|
|
970
|
+
role: "judge",
|
|
971
|
+
tags: runtimeCostTags(judge.cost_tags.tags, registry, recheckKind),
|
|
972
|
+
responsibility_id: input.responsibility_id,
|
|
973
|
+
run_id: `judge-${input.as_of}`,
|
|
974
|
+
as_of: input.as_of,
|
|
975
|
+
tokens: usage.tokens,
|
|
976
|
+
surprise_cause: eventCause,
|
|
977
|
+
...(usage.provider_norm === undefined
|
|
978
|
+
? {}
|
|
979
|
+
: { provider_norm: usage.provider_norm }),
|
|
980
|
+
},
|
|
981
|
+
});
|
|
982
|
+
const verification = (0, receipt_1.verifyReceiptV0)(receipt);
|
|
983
|
+
if (!verification.ok) {
|
|
984
|
+
throw new Error(`runtime receipt failed verification: ${verification.errors.join("; ")}`);
|
|
985
|
+
}
|
|
986
|
+
input.adapters.storage.appendReceipt(receipt);
|
|
987
|
+
return withForecastResult({
|
|
988
|
+
accepted: true,
|
|
989
|
+
responsibility_id: input.responsibility_id,
|
|
990
|
+
as_of: input.as_of,
|
|
991
|
+
receipt_hash: verification.content_hash,
|
|
992
|
+
outcome: eventCause === "forecast-recheck"
|
|
993
|
+
? "forecast-recheck-receipt"
|
|
994
|
+
: "fresh-judge-receipt",
|
|
995
|
+
}, registry.forecast);
|
|
996
|
+
}
|
|
997
|
+
function ingestEscalationTurn(input) {
|
|
998
|
+
const registry = input.adapters.storage.readRegistry();
|
|
999
|
+
const contractRevision = receiptContractRevisionForTurn(registry, input.turn.contract_revision);
|
|
1000
|
+
if (contractRevision === undefined) {
|
|
1001
|
+
return {
|
|
1002
|
+
accepted: false,
|
|
1003
|
+
responsibility_id: input.responsibility_id,
|
|
1004
|
+
as_of: input.as_of,
|
|
1005
|
+
outcome: "failed-before-write",
|
|
1006
|
+
errors: ["escalation requires contract_revision or registry.contract_revision"],
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
const conflict = registry.contract_revision !== undefined &&
|
|
1010
|
+
input.turn.contract_revision !== undefined &&
|
|
1011
|
+
registry.contract_revision !== input.turn.contract_revision;
|
|
1012
|
+
return appendRuntimeBlockedReceipt({
|
|
1013
|
+
responsibility_id: input.responsibility_id,
|
|
1014
|
+
adapters: input.adapters,
|
|
1015
|
+
as_of: input.as_of,
|
|
1016
|
+
contract_revision: contractRevision,
|
|
1017
|
+
reason: conflict
|
|
1018
|
+
? "escalation contract_revision conflicts with registry.contract_revision"
|
|
1019
|
+
: input.turn.reason,
|
|
1020
|
+
fix_target: conflict ? "contract-revision" : input.turn.fix_target,
|
|
1021
|
+
interrupt_cause: conflict ? "needs-judgment" : input.turn.interrupt_cause,
|
|
1022
|
+
event_cause: "escalation",
|
|
1023
|
+
next_forecast_recheck: optionalNextForecastRecheck(registry) ?? input.as_of,
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
function ingestUnsupportedInput(input) {
|
|
1027
|
+
const registry = input.adapters.storage.readRegistry();
|
|
1028
|
+
const contractRevision = contractRevisionFromUnknownEvent(input.event) ??
|
|
1029
|
+
receiptContractRevisionForTurn(registry, undefined);
|
|
1030
|
+
const reason = unsupportedInputReason(input.event);
|
|
1031
|
+
if (contractRevision === undefined) {
|
|
1032
|
+
return {
|
|
1033
|
+
accepted: false,
|
|
1034
|
+
responsibility_id: input.responsibility_id,
|
|
1035
|
+
as_of: input.as_of,
|
|
1036
|
+
outcome: "failed-before-write",
|
|
1037
|
+
errors: [reason],
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
return appendRuntimeBlockedReceipt({
|
|
1041
|
+
responsibility_id: input.responsibility_id,
|
|
1042
|
+
adapters: input.adapters,
|
|
1043
|
+
as_of: input.as_of,
|
|
1044
|
+
contract_revision: contractRevision,
|
|
1045
|
+
reason,
|
|
1046
|
+
fix_target: "event.kind",
|
|
1047
|
+
interrupt_cause: "needs-judgment",
|
|
1048
|
+
event_cause: "escalation",
|
|
1049
|
+
next_forecast_recheck: optionalNextForecastRecheck(registry) ?? input.as_of,
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
function appendRuntimeBlockedReceipt(input) {
|
|
1053
|
+
const evidenceInputId = runtimeFailSafeEvidenceInputId({
|
|
1054
|
+
responsibility_id: input.responsibility_id,
|
|
1055
|
+
contract_revision: input.contract_revision,
|
|
1056
|
+
as_of: input.as_of,
|
|
1057
|
+
reason: input.reason,
|
|
1058
|
+
fix_target: input.fix_target,
|
|
1059
|
+
event_cause: input.event_cause,
|
|
1060
|
+
});
|
|
1061
|
+
const receipt = (0, kernel_1.createKernelSafetyReceipt)({
|
|
1062
|
+
responsibility_id: input.responsibility_id,
|
|
1063
|
+
contract_revision: input.contract_revision,
|
|
1064
|
+
memo_key: `runtime-fail-safe:${evidenceInputId}`,
|
|
1065
|
+
evidence_input_ids: [evidenceInputId],
|
|
1066
|
+
as_of: input.as_of,
|
|
1067
|
+
reason: input.reason,
|
|
1068
|
+
fix_target: input.fix_target,
|
|
1069
|
+
interrupt_cause: input.interrupt_cause,
|
|
1070
|
+
event_cause: input.event_cause,
|
|
1071
|
+
...(input.next_forecast_recheck === undefined
|
|
1072
|
+
? {}
|
|
1073
|
+
: { next_forecast_recheck: input.next_forecast_recheck }),
|
|
1074
|
+
...(input.cycle_checked === undefined
|
|
1075
|
+
? {}
|
|
1076
|
+
: { cycle_checked: input.cycle_checked }),
|
|
1077
|
+
});
|
|
1078
|
+
const verification = (0, receipt_1.verifyReceiptV0)(receipt);
|
|
1079
|
+
if (!verification.ok) {
|
|
1080
|
+
throw new Error(`blocked receipt failed verification: ${verification.errors.join("; ")}`);
|
|
1081
|
+
}
|
|
1082
|
+
input.adapters.storage.appendReceipt(receipt);
|
|
1083
|
+
return {
|
|
1084
|
+
accepted: true,
|
|
1085
|
+
responsibility_id: input.responsibility_id,
|
|
1086
|
+
as_of: input.as_of,
|
|
1087
|
+
receipt_hash: verification.content_hash,
|
|
1088
|
+
outcome: "blocked-escalation-receipt",
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function normalizeTurnInputV0(event) {
|
|
1092
|
+
if (!isRecord(event)) {
|
|
1093
|
+
return undefined;
|
|
1094
|
+
}
|
|
1095
|
+
if (event["kind"] === "real-input") {
|
|
1096
|
+
return {
|
|
1097
|
+
kind: "real-input",
|
|
1098
|
+
...readTurnBase(event),
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
if (event["kind"] === "forecast-recheck") {
|
|
1102
|
+
const recheckKind = event["recheck_kind"];
|
|
1103
|
+
if (recheckKind !== "evidence-age" && recheckKind !== "plan-age") {
|
|
1104
|
+
throw new Error("forecast-recheck requires recheck_kind");
|
|
1105
|
+
}
|
|
1106
|
+
return {
|
|
1107
|
+
kind: "forecast-recheck",
|
|
1108
|
+
recheck_kind: recheckKind,
|
|
1109
|
+
...readTurnBase(event),
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
if (event["kind"] === "escalation") {
|
|
1113
|
+
const interruptCause = event["interrupt_cause"];
|
|
1114
|
+
if (interruptCause !== "needs-judgment" &&
|
|
1115
|
+
interruptCause !== "needs-input" &&
|
|
1116
|
+
interruptCause !== "contract-declared") {
|
|
1117
|
+
throw new Error("escalation requires a valid interrupt_cause");
|
|
1118
|
+
}
|
|
1119
|
+
return {
|
|
1120
|
+
kind: "escalation",
|
|
1121
|
+
interrupt_cause: interruptCause,
|
|
1122
|
+
reason: readNonEmptyString(event["reason"]) ?? "runtime escalation",
|
|
1123
|
+
fix_target: readNonEmptyString(event["fix_target"]) ?? "author",
|
|
1124
|
+
...readTurnBase(event),
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
return undefined;
|
|
1128
|
+
}
|
|
1129
|
+
function readTurnBase(event) {
|
|
1130
|
+
const contractRevision = event["contract_revision"];
|
|
1131
|
+
const base = {};
|
|
1132
|
+
if (isContentHash(contractRevision)) {
|
|
1133
|
+
base.contract_revision = contractRevision;
|
|
1134
|
+
}
|
|
1135
|
+
if (event["cold_start"] !== undefined) {
|
|
1136
|
+
base.cold_start = readColdStartPolicyInput(event["cold_start"]);
|
|
1137
|
+
}
|
|
1138
|
+
if (event["evidence"] !== undefined) {
|
|
1139
|
+
base.evidence = readEvidenceArray(event["evidence"]);
|
|
1140
|
+
}
|
|
1141
|
+
if (event["dependency_receipts"] !== undefined) {
|
|
1142
|
+
base.dependency_receipts = readDependencyReceiptArray(event["dependency_receipts"]);
|
|
1143
|
+
}
|
|
1144
|
+
return base;
|
|
1145
|
+
}
|
|
1146
|
+
function ensurePolicyRegistryForTurn(input) {
|
|
1147
|
+
const registry = input.adapters.storage.readRegistry();
|
|
1148
|
+
if (hasActivePolicyArtifact(registry)) {
|
|
1149
|
+
return registry;
|
|
1150
|
+
}
|
|
1151
|
+
if (input.turn.kind !== "real-input") {
|
|
1152
|
+
return registry;
|
|
1153
|
+
}
|
|
1154
|
+
return authorAndPersistColdStartPolicyRegistry({
|
|
1155
|
+
responsibility_id: input.responsibility_id,
|
|
1156
|
+
adapters: input.adapters,
|
|
1157
|
+
as_of: input.as_of,
|
|
1158
|
+
registry,
|
|
1159
|
+
turn: input.turn,
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
function authorAndPersistColdStartPolicyRegistry(input) {
|
|
1163
|
+
const coldStart = input.turn.cold_start;
|
|
1164
|
+
if (coldStart === undefined) {
|
|
1165
|
+
throw new Error("cold_start is required before first runtime policy ingest");
|
|
1166
|
+
}
|
|
1167
|
+
const writeRegistry = input.adapters.storage.writeRegistry;
|
|
1168
|
+
if (typeof writeRegistry !== "function") {
|
|
1169
|
+
throw new Error("storage.writeRegistry is required for cold_start policy persistence");
|
|
1170
|
+
}
|
|
1171
|
+
if (input.turn.contract_revision !== undefined &&
|
|
1172
|
+
input.turn.contract_revision !== coldStart.contract_revision) {
|
|
1173
|
+
throw new Error("cold_start contract_revision conflicts with event contract_revision");
|
|
1174
|
+
}
|
|
1175
|
+
if (input.registry.contract_revision !== undefined &&
|
|
1176
|
+
input.registry.contract_revision !== coldStart.contract_revision) {
|
|
1177
|
+
throw new Error("cold_start contract_revision conflicts with registry.contract_revision");
|
|
1178
|
+
}
|
|
1179
|
+
if (coldStart.compiled_evidence_plan.responsibility_id !== input.responsibility_id) {
|
|
1180
|
+
throw new Error("cold_start compiled_evidence_plan responsibility_id conflicts");
|
|
1181
|
+
}
|
|
1182
|
+
if (coldStart.forecast_schedule.responsibility_id !== input.responsibility_id) {
|
|
1183
|
+
throw new Error("cold_start forecast_schedule responsibility_id conflicts");
|
|
1184
|
+
}
|
|
1185
|
+
const authoredRegistry = (0, policy_1.authorPolicyArtifactV0)({
|
|
1186
|
+
responsibility_id: input.responsibility_id,
|
|
1187
|
+
contract_revision: coldStart.contract_revision,
|
|
1188
|
+
contract_summary: coldStart.contract_summary.summary,
|
|
1189
|
+
no_anchor: coldStart.no_anchor,
|
|
1190
|
+
live_observables: coldStart.live_observables,
|
|
1191
|
+
receipt_history: input.adapters.storage.listReceipts(),
|
|
1192
|
+
agentSdk: input.adapters.agentSdk,
|
|
1193
|
+
...(coldStart.policy_artifact_namespace === undefined
|
|
1194
|
+
? {}
|
|
1195
|
+
: { policy_artifact_namespace: coldStart.policy_artifact_namespace }),
|
|
1196
|
+
});
|
|
1197
|
+
assertColdStartTargetsAuthoredPolicy(coldStart, authoredRegistry);
|
|
1198
|
+
writeRegistry({
|
|
1199
|
+
...input.registry,
|
|
1200
|
+
...authoredRegistry,
|
|
1201
|
+
contract_summary: coldStart.contract_summary,
|
|
1202
|
+
compiled_evidence_plan: coldStart.compiled_evidence_plan,
|
|
1203
|
+
forecast_schedule: coldStart.forecast_schedule,
|
|
1204
|
+
last_policy_revalidated_at: input.as_of,
|
|
1205
|
+
last_recompile_at: input.as_of,
|
|
1206
|
+
last_policy_recompile_at: input.as_of,
|
|
1207
|
+
});
|
|
1208
|
+
const persistedRegistry = input.adapters.storage.readRegistry();
|
|
1209
|
+
if (!hasActivePolicyArtifact(persistedRegistry)) {
|
|
1210
|
+
throw new Error("storage.writeRegistry did not persist cold_start policy registry");
|
|
1211
|
+
}
|
|
1212
|
+
if (persistedRegistry.compiled_evidence_plan === undefined) {
|
|
1213
|
+
throw new Error("storage.writeRegistry did not persist cold_start compiled_evidence_plan");
|
|
1214
|
+
}
|
|
1215
|
+
if (persistedRegistry.forecast_schedule === undefined) {
|
|
1216
|
+
throw new Error("storage.writeRegistry did not persist cold_start forecast_schedule");
|
|
1217
|
+
}
|
|
1218
|
+
return persistedRegistry;
|
|
1219
|
+
}
|
|
1220
|
+
function hasActivePolicyArtifact(registry) {
|
|
1221
|
+
return (typeof registry.policy_artifact_namespace === "string" &&
|
|
1222
|
+
registry.policy_artifact_namespace.length > 0 &&
|
|
1223
|
+
registry.policy_artifact_namespace !== UNINITIALIZED_POLICY_NAMESPACE &&
|
|
1224
|
+
typeof registry.policy_artifact_revision === "string" &&
|
|
1225
|
+
registry.policy_artifact_revision.length > 0 &&
|
|
1226
|
+
registry.policy_artifact_revision !== UNINITIALIZED_POLICY_REVISION);
|
|
1227
|
+
}
|
|
1228
|
+
function hasValidatedPolicyPosture(registry) {
|
|
1229
|
+
const validationState = registry.policy_artifact_validation_state ?? registry.validation_state;
|
|
1230
|
+
if (validationState === "validated") {
|
|
1231
|
+
return true;
|
|
1232
|
+
}
|
|
1233
|
+
return isRecord(validationState) && validationState["status"] === "validated";
|
|
1234
|
+
}
|
|
1235
|
+
function assertColdStartTargetsAuthoredPolicy(coldStart, authoredRegistry) {
|
|
1236
|
+
if (authoredRegistry.contract_revision !== coldStart.contract_revision) {
|
|
1237
|
+
throw new Error("authored policy contract_revision conflicts with cold_start");
|
|
1238
|
+
}
|
|
1239
|
+
if (coldStart.compiled_evidence_plan.policy_artifact_namespace !==
|
|
1240
|
+
authoredRegistry.policy_artifact_namespace) {
|
|
1241
|
+
throw new Error("cold_start compiled_evidence_plan namespace conflicts with authored policy");
|
|
1242
|
+
}
|
|
1243
|
+
if (coldStart.compiled_evidence_plan.policy_artifact_revision !==
|
|
1244
|
+
authoredRegistry.policy_artifact_revision) {
|
|
1245
|
+
throw new Error("cold_start compiled_evidence_plan revision conflicts with authored policy");
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
function assertCompiledEvidencePlanMatchesRegistry(input) {
|
|
1249
|
+
if (input.plan.responsibility_id !== input.responsibility_id) {
|
|
1250
|
+
throw new Error("registry.compiled_evidence_plan responsibility_id conflicts with runtime responsibility_id");
|
|
1251
|
+
}
|
|
1252
|
+
if (input.plan.contract_revision !== input.contract_revision) {
|
|
1253
|
+
throw new Error("registry.compiled_evidence_plan contract_revision conflicts with registry.contract_revision");
|
|
1254
|
+
}
|
|
1255
|
+
if (input.plan.policy_artifact_namespace !==
|
|
1256
|
+
input.policy_artifact_namespace) {
|
|
1257
|
+
throw new Error("registry.compiled_evidence_plan namespace conflicts with active policy");
|
|
1258
|
+
}
|
|
1259
|
+
if (input.plan.policy_artifact_revision !== input.policy_artifact_revision) {
|
|
1260
|
+
throw new Error("registry.compiled_evidence_plan revision conflicts with active policy");
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
function readRuntimeRegistry(registry, responsibilityId, eventContractRevision, asOf, realInputObserved) {
|
|
1264
|
+
const contractRevision = registry.contract_revision ?? eventContractRevision;
|
|
1265
|
+
if (!isContentHash(contractRevision)) {
|
|
1266
|
+
throw new Error("registry.contract_revision is required for runtime ingest");
|
|
1267
|
+
}
|
|
1268
|
+
if (eventContractRevision !== undefined &&
|
|
1269
|
+
registry.contract_revision !== undefined &&
|
|
1270
|
+
registry.contract_revision !== eventContractRevision) {
|
|
1271
|
+
throw new Error("event contract_revision conflicts with registry.contract_revision");
|
|
1272
|
+
}
|
|
1273
|
+
if (!hasActivePolicyArtifact(registry)) {
|
|
1274
|
+
throw new Error("registry active policy artifact is required for runtime ingest");
|
|
1275
|
+
}
|
|
1276
|
+
if (!hasValidatedPolicyPosture(registry)) {
|
|
1277
|
+
throw new Error("registry.policy_artifact_validation_state must be validated for runtime ingest");
|
|
1278
|
+
}
|
|
1279
|
+
const compiledEvidencePlan = readCompiledEvidencePlan(registry.compiled_evidence_plan, "registry.compiled_evidence_plan");
|
|
1280
|
+
assertCompiledEvidencePlanMatchesRegistry({
|
|
1281
|
+
plan: compiledEvidencePlan,
|
|
1282
|
+
responsibility_id: responsibilityId,
|
|
1283
|
+
contract_revision: contractRevision,
|
|
1284
|
+
policy_artifact_namespace: registry.policy_artifact_namespace,
|
|
1285
|
+
policy_artifact_revision: registry.policy_artifact_revision,
|
|
1286
|
+
});
|
|
1287
|
+
const forecastSchedule = readForecastSchedule(registry.forecast_schedule);
|
|
1288
|
+
if (forecastSchedule.responsibility_id !== responsibilityId) {
|
|
1289
|
+
throw new Error("registry.forecast_schedule responsibility_id conflicts with runtime responsibility_id");
|
|
1290
|
+
}
|
|
1291
|
+
if (forecastSchedule.contract_revision !== contractRevision) {
|
|
1292
|
+
throw new Error("registry.forecast_schedule contract_revision conflicts with registry.contract_revision");
|
|
1293
|
+
}
|
|
1294
|
+
const forecastResult = (0, forecast_1.evaluateForecastScheduleV0)({
|
|
1295
|
+
as_of: asOf,
|
|
1296
|
+
schedule: forecastSchedule,
|
|
1297
|
+
real_input_observed: realInputObserved,
|
|
1298
|
+
});
|
|
1299
|
+
const receiptNextForecastRecheck = forecastResult.next_due_at ??
|
|
1300
|
+
earliestInstant([
|
|
1301
|
+
forecastSchedule.next_evidence_recheck,
|
|
1302
|
+
forecastSchedule.next_plan_recheck,
|
|
1303
|
+
]) ??
|
|
1304
|
+
asOf;
|
|
1305
|
+
return {
|
|
1306
|
+
contract_revision: contractRevision,
|
|
1307
|
+
policy_artifact_namespace: registry.policy_artifact_namespace,
|
|
1308
|
+
policy_artifact_revision: registry.policy_artifact_revision,
|
|
1309
|
+
...(registry.policy_artifact_content_hash === undefined
|
|
1310
|
+
? {}
|
|
1311
|
+
: { policy_artifact_content_hash: registry.policy_artifact_content_hash }),
|
|
1312
|
+
transitive_freshness_function: (0, policy_1.normalizePolicyTransitiveFreshnessFunctionV0)(registry.transitive_freshness_function, "registry.transitive_freshness_function"),
|
|
1313
|
+
compiled_evidence_plan: compiledEvidencePlan,
|
|
1314
|
+
forecast_schedule: forecastSchedule,
|
|
1315
|
+
forecast: {
|
|
1316
|
+
...(forecastResult.next_due_at === undefined
|
|
1317
|
+
? {}
|
|
1318
|
+
: { next_due_at: forecastResult.next_due_at }),
|
|
1319
|
+
due_rechecks: forecastResult.due_rechecks,
|
|
1320
|
+
receipt_next_forecast_recheck: receiptNextForecastRecheck,
|
|
1321
|
+
},
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
function eventCauseForTurn(turn) {
|
|
1325
|
+
return turn.kind;
|
|
1326
|
+
}
|
|
1327
|
+
function recheckKindForTurn(turn) {
|
|
1328
|
+
return turn.kind === "forecast-recheck" ? turn.recheck_kind : undefined;
|
|
1329
|
+
}
|
|
1330
|
+
function shouldCheckMemo(turn) {
|
|
1331
|
+
return !(turn.kind === "forecast-recheck" && turn.recheck_kind === "plan-age");
|
|
1332
|
+
}
|
|
1333
|
+
function readPlannedAdapterEvidence(input) {
|
|
1334
|
+
const evidence = [];
|
|
1335
|
+
for (const source of input.plan.sources) {
|
|
1336
|
+
if (source.kind !== "adapter") {
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
const response = input.adapters.connectors.read({
|
|
1340
|
+
source_id: source.id,
|
|
1341
|
+
as_of: input.as_of,
|
|
1342
|
+
});
|
|
1343
|
+
evidence.push(evidenceFromConnectorResponse(source.id, response));
|
|
1344
|
+
}
|
|
1345
|
+
return evidence;
|
|
1346
|
+
}
|
|
1347
|
+
function evidenceFromConnectorResponse(sourceId, response) {
|
|
1348
|
+
const responseContentHash = response.content_hash ??
|
|
1349
|
+
response.payload_hash ??
|
|
1350
|
+
contentHashFromPayload(response.payload);
|
|
1351
|
+
const receipt = response.receipt;
|
|
1352
|
+
if (receipt !== undefined) {
|
|
1353
|
+
return {
|
|
1354
|
+
source_id: sourceId,
|
|
1355
|
+
receipt,
|
|
1356
|
+
...(responseContentHash === undefined
|
|
1357
|
+
? {}
|
|
1358
|
+
: { content_hash: responseContentHash }),
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
if (responseContentHash !== undefined) {
|
|
1362
|
+
return {
|
|
1363
|
+
source_id: sourceId,
|
|
1364
|
+
content_hash: responseContentHash,
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
return {
|
|
1368
|
+
source_id: sourceId,
|
|
1369
|
+
payload: response.payload,
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
function contentHashFromPayload(payload) {
|
|
1373
|
+
if (!isRecord(payload)) {
|
|
1374
|
+
return undefined;
|
|
1375
|
+
}
|
|
1376
|
+
const payloadHash = payload["payload_hash"] ?? payload["content_hash"];
|
|
1377
|
+
return isContentHash(payloadHash) ? payloadHash : undefined;
|
|
1378
|
+
}
|
|
1379
|
+
function selectPlannedEvidenceInputs(plan, evidence) {
|
|
1380
|
+
const plannedSourceIds = new Set(plan.sources.map((source) => source.id));
|
|
1381
|
+
const evidenceBySourceId = new Map();
|
|
1382
|
+
for (const item of evidence) {
|
|
1383
|
+
if (!plannedSourceIds.has(item.source_id)) {
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
if (evidenceBySourceId.has(item.source_id)) {
|
|
1387
|
+
return {
|
|
1388
|
+
ok: false,
|
|
1389
|
+
reason: `event evidence duplicates planned source ${item.source_id}`,
|
|
1390
|
+
fix_target: `evidence.${item.source_id}`,
|
|
1391
|
+
interrupt_cause: "needs-input",
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
evidenceBySourceId.set(item.source_id, item);
|
|
1395
|
+
}
|
|
1396
|
+
const selected = [];
|
|
1397
|
+
for (const source of plan.sources) {
|
|
1398
|
+
const item = evidenceBySourceId.get(source.id);
|
|
1399
|
+
if (item === undefined) {
|
|
1400
|
+
if (!source.required) {
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
return {
|
|
1404
|
+
ok: false,
|
|
1405
|
+
reason: `planned source ${source.id} is required but missing from event evidence`,
|
|
1406
|
+
fix_target: `evidence.${source.id}`,
|
|
1407
|
+
interrupt_cause: "needs-input",
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
try {
|
|
1411
|
+
selected.push({
|
|
1412
|
+
source_id: source.id,
|
|
1413
|
+
content_hash: evidenceContentHash(item),
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
catch (error) {
|
|
1417
|
+
return {
|
|
1418
|
+
ok: false,
|
|
1419
|
+
reason: error instanceof Error
|
|
1420
|
+
? error.message
|
|
1421
|
+
: `planned source ${source.id} is malformed`,
|
|
1422
|
+
fix_target: `evidence.${source.id}`,
|
|
1423
|
+
interrupt_cause: "needs-input",
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
return { ok: true, evidence: selected };
|
|
1428
|
+
}
|
|
1429
|
+
function evidenceContentHash(input) {
|
|
1430
|
+
if (input.receipt !== undefined) {
|
|
1431
|
+
const verification = (0, receipt_1.verifyReceiptV0)(input.receipt);
|
|
1432
|
+
if (!verification.ok) {
|
|
1433
|
+
throw new Error(`evidence receipt ${input.source_id} failed verification`);
|
|
1434
|
+
}
|
|
1435
|
+
if (input.content_hash !== undefined &&
|
|
1436
|
+
input.content_hash !== verification.content_hash) {
|
|
1437
|
+
throw new Error(`evidence receipt ${input.source_id} content_hash conflicts`);
|
|
1438
|
+
}
|
|
1439
|
+
return verification.content_hash;
|
|
1440
|
+
}
|
|
1441
|
+
if (isContentHash(input.content_hash)) {
|
|
1442
|
+
return input.content_hash;
|
|
1443
|
+
}
|
|
1444
|
+
if (Object.hasOwn(input, "payload")) {
|
|
1445
|
+
return (0, receipt_1.hashCanonicalReceiptV0)((0, receipt_1.canonicalizeForReceiptV0)({
|
|
1446
|
+
schema: "openprose.reactor.evidence-input",
|
|
1447
|
+
v: 0,
|
|
1448
|
+
source_id: input.source_id,
|
|
1449
|
+
payload: input.payload,
|
|
1450
|
+
}));
|
|
1451
|
+
}
|
|
1452
|
+
throw new Error(`evidence ${input.source_id} must carry a receipt, content_hash, or payload`);
|
|
1453
|
+
}
|
|
1454
|
+
function verifyDependencyReceipts(receipts) {
|
|
1455
|
+
return receipts.map((receipt) => {
|
|
1456
|
+
const verification = (0, receipt_1.verifyReceiptV0)(receipt);
|
|
1457
|
+
if (!verification.ok) {
|
|
1458
|
+
throw new Error("dependency receipt failed verification");
|
|
1459
|
+
}
|
|
1460
|
+
const signerPosture = receipt.sig.scheme === "none" ? "none" : receipt.sig.scheme;
|
|
1461
|
+
return {
|
|
1462
|
+
receipt,
|
|
1463
|
+
content_hash: verification.content_hash,
|
|
1464
|
+
memo_ref: {
|
|
1465
|
+
upstream_content_hash: verification.content_hash,
|
|
1466
|
+
contract_revision: receipt.core.contract_revision,
|
|
1467
|
+
acceptable_signer_set: [signerPosture],
|
|
1468
|
+
},
|
|
1469
|
+
pin: {
|
|
1470
|
+
upstream_content_hash: verification.content_hash,
|
|
1471
|
+
contract_revision: receipt.core.contract_revision,
|
|
1472
|
+
acceptable_signer_set: [signerPosture],
|
|
1473
|
+
},
|
|
1474
|
+
};
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
function receiptCycleEdgesFromDependencies(dependencies) {
|
|
1478
|
+
const receiptEdges = dependencies.flatMap((dependency) => dependency.receipt.composition.consumed_receipts.map((upstream) => ({
|
|
1479
|
+
from: dependency.content_hash,
|
|
1480
|
+
to: upstream.upstream_content_hash,
|
|
1481
|
+
})));
|
|
1482
|
+
// The current receipt hash is not available before creation, so B4 also
|
|
1483
|
+
// checks the contract-revision lineage present in dependency pins. This makes
|
|
1484
|
+
// A->B->A composition cycles observable before accepting a new receipt.
|
|
1485
|
+
const contractRevisionEdges = dependencies.flatMap((dependency) => dependency.receipt.composition.consumed_receipts.map((upstream) => ({
|
|
1486
|
+
from: dependency.receipt.core.contract_revision,
|
|
1487
|
+
to: upstream.contract_revision,
|
|
1488
|
+
})));
|
|
1489
|
+
return [...receiptEdges, ...contractRevisionEdges];
|
|
1490
|
+
}
|
|
1491
|
+
function receiptFreshnessForRuntimeJudge(input) {
|
|
1492
|
+
const nextForecastRecheck = input.registry.forecast.receipt_next_forecast_recheck;
|
|
1493
|
+
if (input.dependencies.length === 0) {
|
|
1494
|
+
return {
|
|
1495
|
+
as_of: input.as_of,
|
|
1496
|
+
next_forecast_recheck: nextForecastRecheck,
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
return (0, composition_1.createComposedReceiptFreshnessV0)({
|
|
1500
|
+
as_of: input.as_of,
|
|
1501
|
+
next_forecast_recheck: nextForecastRecheck,
|
|
1502
|
+
transitive_freshness_policy_ref: transitiveFreshnessPolicyRef(input.registry),
|
|
1503
|
+
transitive_freshness_function: input.registry.transitive_freshness_function,
|
|
1504
|
+
consumed_receipts: input.dependencies.map((dependency) => ({
|
|
1505
|
+
upstream_receipt: dependency.receipt,
|
|
1506
|
+
dependency_pin: dependency.pin,
|
|
1507
|
+
})),
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
function transitiveFreshnessPolicyRef(registry) {
|
|
1511
|
+
return `${registry.policy_artifact_namespace}@${registry.policy_artifact_revision}`;
|
|
1512
|
+
}
|
|
1513
|
+
function findReusableReceipt(input) {
|
|
1514
|
+
const candidates = input.receipts.filter((receipt) => {
|
|
1515
|
+
const verification = (0, receipt_1.verifyReceiptV0)(receipt);
|
|
1516
|
+
if (!verification.ok) {
|
|
1517
|
+
return false;
|
|
1518
|
+
}
|
|
1519
|
+
return (receipt.core.responsibility_id === input.responsibility_id &&
|
|
1520
|
+
receipt.core.contract_revision === input.contract_revision &&
|
|
1521
|
+
receipt.core.memo_key === input.memo_key &&
|
|
1522
|
+
receipt.core.role === "judge" &&
|
|
1523
|
+
receipt.cost.provider !== "memo" &&
|
|
1524
|
+
receipt.cost.tags.includes(memoSourceTag(input.registry)) &&
|
|
1525
|
+
receipt.cost.tokens.fresh + receipt.cost.tokens.reused > 0);
|
|
1526
|
+
});
|
|
1527
|
+
return candidates.sort(compareReceiptsNewestFirst)[0];
|
|
1528
|
+
}
|
|
1529
|
+
function compareReceiptsNewestFirst(left, right) {
|
|
1530
|
+
const byTime = Date.parse(right.core.as_of) - Date.parse(left.core.as_of);
|
|
1531
|
+
if (byTime !== 0) {
|
|
1532
|
+
return byTime;
|
|
1533
|
+
}
|
|
1534
|
+
return right.content_hash.localeCompare(left.content_hash);
|
|
1535
|
+
}
|
|
1536
|
+
function memoSourceTag(registry) {
|
|
1537
|
+
return `${RUNTIME_MEMO_SOURCE_TAG_PREFIX}${registry.policy_artifact_namespace}@${registry.policy_artifact_revision}`;
|
|
1538
|
+
}
|
|
1539
|
+
function runtimeCostTags(judgeTags, registry, recheckKind) {
|
|
1540
|
+
return [
|
|
1541
|
+
...judgeTags,
|
|
1542
|
+
...(recheckKind === undefined ? [] : ["forecast-recheck", recheckKind]),
|
|
1543
|
+
memoSourceTag(registry),
|
|
1544
|
+
];
|
|
1545
|
+
}
|
|
1546
|
+
function withForecastResult(result, forecast) {
|
|
1547
|
+
return {
|
|
1548
|
+
...result,
|
|
1549
|
+
...(forecast.next_due_at === undefined
|
|
1550
|
+
? {}
|
|
1551
|
+
: { next_due_at: forecast.next_due_at }),
|
|
1552
|
+
due_rechecks: forecast.due_rechecks,
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
function receiptContractRevisionForTurn(registry, eventContractRevision) {
|
|
1556
|
+
if (isContentHash(registry.contract_revision)) {
|
|
1557
|
+
return registry.contract_revision;
|
|
1558
|
+
}
|
|
1559
|
+
return eventContractRevision;
|
|
1560
|
+
}
|
|
1561
|
+
function contractRevisionFromUnknownEvent(event) {
|
|
1562
|
+
if (!isRecord(event)) {
|
|
1563
|
+
return undefined;
|
|
1564
|
+
}
|
|
1565
|
+
if (isContentHash(event["contract_revision"])) {
|
|
1566
|
+
return event["contract_revision"];
|
|
1567
|
+
}
|
|
1568
|
+
const coldStart = event["cold_start"];
|
|
1569
|
+
if (isRecord(coldStart) && isContentHash(coldStart["contract_revision"])) {
|
|
1570
|
+
return coldStart["contract_revision"];
|
|
1571
|
+
}
|
|
1572
|
+
return undefined;
|
|
1573
|
+
}
|
|
1574
|
+
function optionalNextForecastRecheck(registry) {
|
|
1575
|
+
try {
|
|
1576
|
+
const schedule = readForecastSchedule(registry.forecast_schedule);
|
|
1577
|
+
return earliestInstant([
|
|
1578
|
+
schedule.next_evidence_recheck,
|
|
1579
|
+
schedule.next_plan_recheck,
|
|
1580
|
+
]);
|
|
1581
|
+
}
|
|
1582
|
+
catch {
|
|
1583
|
+
return undefined;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
function unsupportedInputReason(event) {
|
|
1587
|
+
if (!isRecord(event)) {
|
|
1588
|
+
return "unsupported reactor ingest input";
|
|
1589
|
+
}
|
|
1590
|
+
const kind = event["kind"];
|
|
1591
|
+
return typeof kind === "string" && kind.length > 0
|
|
1592
|
+
? `unsupported reactor ingest kind ${kind}`
|
|
1593
|
+
: "unsupported reactor ingest input";
|
|
1594
|
+
}
|
|
1595
|
+
function runtimeFailSafeEvidenceInputId(input) {
|
|
1596
|
+
return (0, receipt_1.hashCanonicalReceiptV0)((0, receipt_1.canonicalizeForReceiptV0)({
|
|
1597
|
+
schema: "openprose.reactor.runtime-fail-safe-input",
|
|
1598
|
+
v: 0,
|
|
1599
|
+
responsibility_id: input.responsibility_id,
|
|
1600
|
+
contract_revision: input.contract_revision,
|
|
1601
|
+
as_of: input.as_of,
|
|
1602
|
+
reason: input.reason,
|
|
1603
|
+
fix_target: input.fix_target,
|
|
1604
|
+
event_cause: input.event_cause,
|
|
1605
|
+
}));
|
|
1606
|
+
}
|
|
1607
|
+
function emitIngest(responsibilityId, adapters, asOf, event) {
|
|
1608
|
+
const sdkEvent = {
|
|
1609
|
+
type: "ingest",
|
|
1610
|
+
responsibility_id: responsibilityId,
|
|
1611
|
+
as_of: asOf,
|
|
1612
|
+
payload: event,
|
|
1613
|
+
};
|
|
1614
|
+
adapters.eventSink.emit(sdkEvent);
|
|
1615
|
+
}
|
|
1616
|
+
function isEvidenceInput(value) {
|
|
1617
|
+
return (isRecord(value) &&
|
|
1618
|
+
typeof value["source_id"] === "string" &&
|
|
1619
|
+
value["source_id"].length > 0);
|
|
1620
|
+
}
|
|
1621
|
+
function readEvidenceArray(value) {
|
|
1622
|
+
if (!Array.isArray(value)) {
|
|
1623
|
+
throw new Error("real-input evidence must be an array when present");
|
|
1624
|
+
}
|
|
1625
|
+
return value.map((item, index) => {
|
|
1626
|
+
if (!isEvidenceInput(item)) {
|
|
1627
|
+
throw new Error(`evidence[${index}] must carry a non-empty source_id`);
|
|
1628
|
+
}
|
|
1629
|
+
return item;
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
function readDependencyReceiptArray(value) {
|
|
1633
|
+
if (!Array.isArray(value)) {
|
|
1634
|
+
throw new Error("dependency_receipts must be an array when present");
|
|
1635
|
+
}
|
|
1636
|
+
return value.map((item, index) => {
|
|
1637
|
+
const verification = (0, receipt_1.verifyReceiptV0)(item);
|
|
1638
|
+
if (!verification.ok) {
|
|
1639
|
+
throw new Error(`dependency_receipts[${index}] failed verification`);
|
|
1640
|
+
}
|
|
1641
|
+
return item;
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
function readColdStartPolicyInput(value) {
|
|
1645
|
+
if (!isRecord(value)) {
|
|
1646
|
+
throw new Error("cold_start must be an object");
|
|
1647
|
+
}
|
|
1648
|
+
const contractRevision = readContentHashField(value, "contract_revision", "cold_start");
|
|
1649
|
+
const contractSummary = readContractSummaryProjection(value["contract_summary"]);
|
|
1650
|
+
if (contractSummary.source_contract_revision !== contractRevision) {
|
|
1651
|
+
throw new Error("cold_start contract_summary conflicts with contract_revision");
|
|
1652
|
+
}
|
|
1653
|
+
const noAnchor = value["no_anchor"];
|
|
1654
|
+
if (typeof noAnchor !== "boolean") {
|
|
1655
|
+
throw new Error("cold_start.no_anchor must be boolean");
|
|
1656
|
+
}
|
|
1657
|
+
const liveObservables = readPolicyLiveObservables(value["live_observables"]);
|
|
1658
|
+
const compiledEvidencePlan = readCompiledEvidencePlan(value["compiled_evidence_plan"]);
|
|
1659
|
+
if (compiledEvidencePlan.contract_revision !== contractRevision) {
|
|
1660
|
+
throw new Error("cold_start compiled_evidence_plan contract_revision conflicts");
|
|
1661
|
+
}
|
|
1662
|
+
const forecastSchedule = readColdStartForecastSchedule(value["forecast_schedule"]);
|
|
1663
|
+
if (forecastSchedule.contract_revision !== contractRevision) {
|
|
1664
|
+
throw new Error("cold_start forecast_schedule contract_revision conflicts");
|
|
1665
|
+
}
|
|
1666
|
+
const namespace = value["policy_artifact_namespace"];
|
|
1667
|
+
if (namespace !== undefined && (typeof namespace !== "string" || namespace.length === 0)) {
|
|
1668
|
+
throw new Error("cold_start.policy_artifact_namespace must be non-empty when supplied");
|
|
1669
|
+
}
|
|
1670
|
+
return {
|
|
1671
|
+
contract_revision: contractRevision,
|
|
1672
|
+
contract_summary: contractSummary,
|
|
1673
|
+
no_anchor: noAnchor,
|
|
1674
|
+
live_observables: liveObservables,
|
|
1675
|
+
compiled_evidence_plan: compiledEvidencePlan,
|
|
1676
|
+
forecast_schedule: forecastSchedule,
|
|
1677
|
+
...(namespace === undefined ? {} : { policy_artifact_namespace: namespace }),
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
function readContractSummaryProjection(value) {
|
|
1681
|
+
if (!isRecord(value)) {
|
|
1682
|
+
throw new Error("cold_start.contract_summary must be an object");
|
|
1683
|
+
}
|
|
1684
|
+
const summary = readNonEmptyString(value["summary"]);
|
|
1685
|
+
if (summary === undefined) {
|
|
1686
|
+
throw new Error("cold_start.contract_summary.summary must be non-empty");
|
|
1687
|
+
}
|
|
1688
|
+
return {
|
|
1689
|
+
summary,
|
|
1690
|
+
source_contract_revision: readContentHashField(value, "source_contract_revision", "cold_start.contract_summary"),
|
|
1691
|
+
projection_hash: readContentHashField(value, "projection_hash", "cold_start.contract_summary"),
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
function readPolicyLiveObservables(value) {
|
|
1695
|
+
if (!Array.isArray(value)) {
|
|
1696
|
+
throw new Error("cold_start.live_observables must be an array");
|
|
1697
|
+
}
|
|
1698
|
+
return value.map((item, index) => {
|
|
1699
|
+
if (!isRecord(item)) {
|
|
1700
|
+
throw new Error(`cold_start.live_observables[${index}] must be an object`);
|
|
1701
|
+
}
|
|
1702
|
+
const id = readNonEmptyString(item["id"]);
|
|
1703
|
+
if (id === undefined) {
|
|
1704
|
+
throw new Error(`cold_start.live_observables[${index}].id must be non-empty`);
|
|
1705
|
+
}
|
|
1706
|
+
const source = item["source"];
|
|
1707
|
+
if (typeof source !== "string" ||
|
|
1708
|
+
!POLICY_LIVE_OBSERVABLE_SOURCES.has(source)) {
|
|
1709
|
+
throw new Error(`cold_start.live_observables[${index}].source is malformed`);
|
|
1710
|
+
}
|
|
1711
|
+
const description = readNonEmptyString(item["description"]);
|
|
1712
|
+
if (description === undefined) {
|
|
1713
|
+
throw new Error(`cold_start.live_observables[${index}].description must be non-empty`);
|
|
1714
|
+
}
|
|
1715
|
+
return {
|
|
1716
|
+
id,
|
|
1717
|
+
source: source,
|
|
1718
|
+
description,
|
|
1719
|
+
};
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
function readCompiledEvidencePlan(value, path = "cold_start.compiled_evidence_plan") {
|
|
1723
|
+
if (!isRecord(value)) {
|
|
1724
|
+
throw new Error(`${path} must be an object`);
|
|
1725
|
+
}
|
|
1726
|
+
const sources = readCompiledEvidenceSources(value["sources"], path);
|
|
1727
|
+
const plan = {
|
|
1728
|
+
responsibility_id: readNonEmptyString(value["responsibility_id"]) ?? "",
|
|
1729
|
+
contract_revision: readUnknownContentHash(value["contract_revision"]),
|
|
1730
|
+
policy_artifact_namespace: readNonEmptyString(value["policy_artifact_namespace"]) ?? "",
|
|
1731
|
+
policy_artifact_revision: readNonEmptyString(value["policy_artifact_revision"]) ?? "",
|
|
1732
|
+
plan_revision: readNonEmptyString(value["plan_revision"]) ?? "",
|
|
1733
|
+
as_of: readNonEmptyString(value["as_of"]) ?? "",
|
|
1734
|
+
evidence_order: readEvidenceReceiptOrder(value["evidence_order"], `${path}.evidence_order`),
|
|
1735
|
+
sources,
|
|
1736
|
+
};
|
|
1737
|
+
const errors = (0, evidence_plan_1.validateCompiledEvidencePlan)(plan);
|
|
1738
|
+
if (errors.length > 0) {
|
|
1739
|
+
throw new Error(`${path} is malformed: ${errors.join("; ")}`);
|
|
1740
|
+
}
|
|
1741
|
+
return plan;
|
|
1742
|
+
}
|
|
1743
|
+
function readCompiledEvidenceSources(value, path) {
|
|
1744
|
+
if (!Array.isArray(value)) {
|
|
1745
|
+
throw new Error(`${path}.sources must be an array`);
|
|
1746
|
+
}
|
|
1747
|
+
return value.map((item, index) => {
|
|
1748
|
+
if (!isRecord(item)) {
|
|
1749
|
+
throw new Error(`${path}.sources[${index}] must be an object`);
|
|
1750
|
+
}
|
|
1751
|
+
const id = readNonEmptyString(item["id"]) ?? "";
|
|
1752
|
+
const kind = item["kind"];
|
|
1753
|
+
const required = item["required"];
|
|
1754
|
+
const receiptOrder = item["receipt_order"];
|
|
1755
|
+
if (kind !== "adapter" && kind !== "forecast" && kind !== "dependency") {
|
|
1756
|
+
throw new Error(`${path}.sources[${index}].kind is malformed`);
|
|
1757
|
+
}
|
|
1758
|
+
if (typeof required !== "boolean") {
|
|
1759
|
+
throw new Error(`${path}.sources[${index}].required must be boolean`);
|
|
1760
|
+
}
|
|
1761
|
+
if (receiptOrder !== undefined &&
|
|
1762
|
+
receiptOrder !== "unordered" &&
|
|
1763
|
+
receiptOrder !== "declared") {
|
|
1764
|
+
throw new Error(`${path}.sources[${index}].receipt_order is malformed`);
|
|
1765
|
+
}
|
|
1766
|
+
return {
|
|
1767
|
+
id,
|
|
1768
|
+
kind,
|
|
1769
|
+
required,
|
|
1770
|
+
...(receiptOrder === "unordered" || receiptOrder === "declared"
|
|
1771
|
+
? { receipt_order: receiptOrder }
|
|
1772
|
+
: {}),
|
|
1773
|
+
};
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
function readEvidenceReceiptOrder(value, path) {
|
|
1777
|
+
if (value !== "unordered" && value !== "declared") {
|
|
1778
|
+
throw new Error(`${path} is malformed`);
|
|
1779
|
+
}
|
|
1780
|
+
return value;
|
|
1781
|
+
}
|
|
1782
|
+
function readColdStartForecastSchedule(value) {
|
|
1783
|
+
try {
|
|
1784
|
+
return readForecastSchedule(value);
|
|
1785
|
+
}
|
|
1786
|
+
catch (error) {
|
|
1787
|
+
throw new Error(error instanceof Error
|
|
1788
|
+
? error.message.replace("registry.forecast_schedule", "cold_start.forecast_schedule")
|
|
1789
|
+
: "cold_start.forecast_schedule is malformed");
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
function readContentHashField(value, field, path) {
|
|
1793
|
+
const item = value[field];
|
|
1794
|
+
if (!isContentHash(item)) {
|
|
1795
|
+
throw new Error(`${path}.${field} must be a sha256 content address`);
|
|
1796
|
+
}
|
|
1797
|
+
return item;
|
|
1798
|
+
}
|
|
1799
|
+
function readUnknownContentHash(value) {
|
|
1800
|
+
return isContentHash(value) ? value : "";
|
|
1801
|
+
}
|
|
1802
|
+
function isContentHash(value) {
|
|
1803
|
+
return typeof value === "string" && CONTENT_HASH_PATTERN.test(value);
|
|
1804
|
+
}
|
|
1805
|
+
function isReplayableInstant(value) {
|
|
1806
|
+
return Number.isFinite(Date.parse(value));
|
|
1807
|
+
}
|
|
1808
|
+
function readNonEmptyString(value) {
|
|
1809
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
1810
|
+
}
|
|
1811
|
+
function isNonNegativeSafeInteger(value) {
|
|
1812
|
+
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
1813
|
+
}
|
|
1814
|
+
function earliestInstant(values) {
|
|
1815
|
+
const parsed = values.map((value) => ({
|
|
1816
|
+
value,
|
|
1817
|
+
ms: Date.parse(value),
|
|
1818
|
+
}));
|
|
1819
|
+
if (parsed.some((item) => !Number.isFinite(item.ms))) {
|
|
1820
|
+
return undefined;
|
|
1821
|
+
}
|
|
1822
|
+
return parsed.sort((left, right) => left.ms - right.ms)[0]?.value;
|
|
1823
|
+
}
|
|
1824
|
+
function readForecastSchedule(schedule) {
|
|
1825
|
+
if (!isRecord(schedule)) {
|
|
1826
|
+
throw new Error("registry.forecast_schedule is required for runtime ingest");
|
|
1827
|
+
}
|
|
1828
|
+
const responsibilityId = schedule["responsibility_id"];
|
|
1829
|
+
const contractRevision = schedule["contract_revision"];
|
|
1830
|
+
const memoKey = schedule["memo_key"];
|
|
1831
|
+
const evidenceInputIds = schedule["evidence_input_ids"];
|
|
1832
|
+
const nextEvidenceRecheck = schedule["next_evidence_recheck"];
|
|
1833
|
+
const nextPlanRecheck = schedule["next_plan_recheck"];
|
|
1834
|
+
if (typeof responsibilityId !== "string" ||
|
|
1835
|
+
responsibilityId.length === 0 ||
|
|
1836
|
+
!isContentHash(contractRevision) ||
|
|
1837
|
+
typeof memoKey !== "string" ||
|
|
1838
|
+
memoKey.length === 0 ||
|
|
1839
|
+
!Array.isArray(evidenceInputIds) ||
|
|
1840
|
+
!evidenceInputIds.every(isContentHash) ||
|
|
1841
|
+
typeof nextEvidenceRecheck !== "string" ||
|
|
1842
|
+
typeof nextPlanRecheck !== "string" ||
|
|
1843
|
+
earliestInstant([nextEvidenceRecheck, nextPlanRecheck]) === undefined) {
|
|
1844
|
+
throw new Error("registry.forecast_schedule must be replayable for runtime ingest");
|
|
1845
|
+
}
|
|
1846
|
+
return {
|
|
1847
|
+
responsibility_id: responsibilityId,
|
|
1848
|
+
contract_revision: contractRevision,
|
|
1849
|
+
memo_key: memoKey,
|
|
1850
|
+
evidence_input_ids: evidenceInputIds,
|
|
1851
|
+
next_evidence_recheck: nextEvidenceRecheck,
|
|
1852
|
+
next_plan_recheck: nextPlanRecheck,
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
function isRecord(value) {
|
|
1856
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1857
|
+
return false;
|
|
1858
|
+
}
|
|
1859
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1860
|
+
return prototype === Object.prototype || prototype === null;
|
|
1861
|
+
}
|