@victframework/runtime 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-governance.d.ts +285 -0
- package/dist/agent-governance.js +438 -0
- package/dist/agent-governance.js.map +1 -0
- package/dist/agent-registry.d.ts +103 -0
- package/dist/agent-registry.js +758 -0
- package/dist/agent-registry.js.map +1 -0
- package/dist/agent-types.d.ts +433 -0
- package/dist/agent-types.js +480 -0
- package/dist/agent-types.js.map +1 -0
- package/dist/authority.d.ts +83 -0
- package/dist/authority.js +170 -0
- package/dist/authority.js.map +1 -0
- package/dist/boundary-conformance.d.ts +51 -0
- package/dist/boundary-conformance.js +364 -0
- package/dist/boundary-conformance.js.map +1 -0
- package/dist/control-conformance.d.ts +68 -0
- package/dist/control-conformance.js +1131 -0
- package/dist/control-conformance.js.map +1 -0
- package/dist/control-in-memory.d.ts +257 -0
- package/dist/control-in-memory.js +886 -0
- package/dist/control-in-memory.js.map +1 -0
- package/dist/control-types.d.ts +1017 -0
- package/dist/control-types.js +779 -0
- package/dist/control-types.js.map +1 -0
- package/dist/durable-run.d.ts +73 -0
- package/dist/durable-run.js +262 -0
- package/dist/durable-run.js.map +1 -0
- package/dist/effect-policy.d.ts +22 -0
- package/dist/effect-policy.js +45 -0
- package/dist/effect-policy.js.map +1 -0
- package/dist/errors.d.ts +36 -0
- package/dist/errors.js +71 -0
- package/dist/errors.js.map +1 -0
- package/dist/in-memory-stores.d.ts +16 -0
- package/dist/in-memory-stores.js +522 -0
- package/dist/in-memory-stores.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/operator-config.d.ts +141 -0
- package/dist/operator-config.js +289 -0
- package/dist/operator-config.js.map +1 -0
- package/dist/orchestration-activation.d.ts +116 -0
- package/dist/orchestration-activation.js +223 -0
- package/dist/orchestration-activation.js.map +1 -0
- package/dist/orchestration-canary-conformance.d.ts +10 -0
- package/dist/orchestration-canary-conformance.js +726 -0
- package/dist/orchestration-canary-conformance.js.map +1 -0
- package/dist/orchestration-commands.d.ts +47 -0
- package/dist/orchestration-commands.js +538 -0
- package/dist/orchestration-commands.js.map +1 -0
- package/dist/orchestration-conformance.d.ts +59 -0
- package/dist/orchestration-conformance.js +594 -0
- package/dist/orchestration-conformance.js.map +1 -0
- package/dist/orchestration-driver-types.d.ts +163 -0
- package/dist/orchestration-driver-types.js +21 -0
- package/dist/orchestration-driver-types.js.map +1 -0
- package/dist/orchestration-driver.d.ts +48 -0
- package/dist/orchestration-driver.js +1030 -0
- package/dist/orchestration-driver.js.map +1 -0
- package/dist/orchestration-in-memory.d.ts +18 -0
- package/dist/orchestration-in-memory.js +1313 -0
- package/dist/orchestration-in-memory.js.map +1 -0
- package/dist/orchestration-join-conformance.d.ts +20 -0
- package/dist/orchestration-join-conformance.js +542 -0
- package/dist/orchestration-join-conformance.js.map +1 -0
- package/dist/orchestration-plan.d.ts +52 -0
- package/dist/orchestration-plan.js +265 -0
- package/dist/orchestration-plan.js.map +1 -0
- package/dist/orchestration-race-conformance.d.ts +35 -0
- package/dist/orchestration-race-conformance.js +1265 -0
- package/dist/orchestration-race-conformance.js.map +1 -0
- package/dist/orchestration-remediation-conformance.d.ts +54 -0
- package/dist/orchestration-remediation-conformance.js +632 -0
- package/dist/orchestration-remediation-conformance.js.map +1 -0
- package/dist/orchestration-store-types.d.ts +568 -0
- package/dist/orchestration-store-types.js +2 -0
- package/dist/orchestration-store-types.js.map +1 -0
- package/dist/pack-conformance.d.ts +59 -0
- package/dist/pack-conformance.js +222 -0
- package/dist/pack-conformance.js.map +1 -0
- package/dist/pack-install.d.ts +44 -0
- package/dist/pack-install.js +126 -0
- package/dist/pack-install.js.map +1 -0
- package/dist/registry.d.ts +107 -0
- package/dist/registry.js +481 -0
- package/dist/registry.js.map +1 -0
- package/dist/runtime.d.ts +158 -0
- package/dist/runtime.js +1341 -0
- package/dist/runtime.js.map +1 -0
- package/dist/serialization.d.ts +26 -0
- package/dist/serialization.js +162 -0
- package/dist/serialization.js.map +1 -0
- package/dist/store-conformance.d.ts +45 -0
- package/dist/store-conformance.js +876 -0
- package/dist/store-conformance.js.map +1 -0
- package/dist/store-errors.d.ts +49 -0
- package/dist/store-errors.js +48 -0
- package/dist/store-errors.js.map +1 -0
- package/dist/store-types.d.ts +331 -0
- package/dist/store-types.js +14 -0
- package/dist/store-types.js.map +1 -0
- package/dist/store-validation.d.ts +63 -0
- package/dist/store-validation.js +164 -0
- package/dist/store-validation.js.map +1 -0
- package/dist/stream-hub.d.ts +151 -0
- package/dist/stream-hub.js +402 -0
- package/dist/stream-hub.js.map +1 -0
- package/dist/testing.d.ts +25 -0
- package/dist/testing.js +19 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +221 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
import { neutralJsonContract } from '@victframework/sdk';
|
|
2
|
+
import { SAFE_ISSUE_CODES } from '@victframework/contracts';
|
|
3
|
+
/**
|
|
4
|
+
* Adapter-neutral Stage 03 adversarial canary suite (final audit-readiness
|
|
5
|
+
* closure). A single ALPHANUMERIC canary — the class that survives any
|
|
6
|
+
* character-filtering "sanitizer" — is injected independently into every
|
|
7
|
+
* untrusted position of a hostile contract parser's issues (code, path,
|
|
8
|
+
* message, expected/received, extra nested properties, payload-derived key
|
|
9
|
+
* names) and into three additional sources: valid signal payloads,
|
|
10
|
+
* cancellation metadata, and external-ledger thrown errors (with nested
|
|
11
|
+
* causes). The canary must be absent from EVERY observable or persistable
|
|
12
|
+
* surface: the `onEvent` callback, the stored event ledger, the stored run
|
|
13
|
+
* record, the public failure error, `RunResult.trace`, wait and receipt
|
|
14
|
+
* records, and cancellation/operator records. Rejections must still be
|
|
15
|
+
* USEFUL: a `contract.rejected` fact with a framework-controlled issue
|
|
16
|
+
* code, an ordinal path, and a framework-generated message.
|
|
17
|
+
*/
|
|
18
|
+
/** Alphanumeric canary: deliberately defeats character-filter sanitizers. */
|
|
19
|
+
const CANARY = 'CANARYSecret123';
|
|
20
|
+
/** Run one graph to completion while capturing the onEvent callback stream. */
|
|
21
|
+
async function runCaptured(runtime, input) {
|
|
22
|
+
const callbackEvents = [];
|
|
23
|
+
const result = (await runtime.run(input, {
|
|
24
|
+
onEvent: (event) => callbackEvents.push(event),
|
|
25
|
+
}));
|
|
26
|
+
result.callbackEvents = callbackEvents;
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Assert the canary is absent from every observable and persistable
|
|
31
|
+
* surface of a failed run, and that the rejection is still structurally
|
|
32
|
+
* useful (framework-controlled code, ordinal path, generated message).
|
|
33
|
+
*/
|
|
34
|
+
async function assertRejectionIsSafe(expect, orchestration, runtime, captured, expectedStage) {
|
|
35
|
+
expect(captured.status).toBe('failed');
|
|
36
|
+
const surfaces = [
|
|
37
|
+
{ label: 'onEvent callback events', value: captured.callbackEvents },
|
|
38
|
+
{ label: 'RunResult.trace', value: captured.trace },
|
|
39
|
+
{ label: 'public failure error', value: captured.error },
|
|
40
|
+
{
|
|
41
|
+
label: 'stored event ledger',
|
|
42
|
+
value: await orchestration.listOrchestrationEvents(captured.runId),
|
|
43
|
+
},
|
|
44
|
+
{ label: 'stored run record', value: await runtime.getRun(captured.runId) },
|
|
45
|
+
{
|
|
46
|
+
label: 'signal receipts',
|
|
47
|
+
value: await orchestration.listSignalReceipts(captured.runId),
|
|
48
|
+
},
|
|
49
|
+
{ label: 'wait records', value: await orchestration.listWaits(captured.runId) },
|
|
50
|
+
];
|
|
51
|
+
for (const { label, value } of surfaces) {
|
|
52
|
+
const text = JSON.stringify(value) ?? '';
|
|
53
|
+
expect(text.includes(CANARY)).toBe(false);
|
|
54
|
+
if (text.includes(CANARY)) {
|
|
55
|
+
throw new Error(`canary leaked into ${label}: ${text.slice(0, 200)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// The rejection remains diagnosable WITHOUT untrusted content.
|
|
59
|
+
const rejected = [...captured.callbackEvents, ...captured.trace].find((event) => event.type === 'contract.rejected');
|
|
60
|
+
expect(rejected).toBeDefined();
|
|
61
|
+
if (!rejected) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (expectedStage !== undefined) {
|
|
65
|
+
expect(rejected.stage).toBe(expectedStage);
|
|
66
|
+
}
|
|
67
|
+
expect(rejected.issues.length).toBeGreaterThan(0);
|
|
68
|
+
for (const [index, issue] of rejected.issues.entries()) {
|
|
69
|
+
expect(SAFE_ISSUE_CODES.includes(issue.code) ||
|
|
70
|
+
issue.code === 'untrusted_issue').toBe(true);
|
|
71
|
+
expect(issue.path).toBe(`issues[${index}]`);
|
|
72
|
+
// Framework-GENERATED message: quotes the ordinal path, never the raw
|
|
73
|
+
// parser content (allowlisted codes read "Expected … at 'issues[i]'",
|
|
74
|
+
// the fallback reads "Validation failed (untrusted_issue) …").
|
|
75
|
+
expect(issue.message.includes(`'issues[${index}]'`)).toBe(true);
|
|
76
|
+
expect(JSON.stringify(issue).includes(CANARY)).toBe(false);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** A hostile contract whose parse() rejects with the canary in ONE position. */
|
|
80
|
+
function hostileContract(id, position) {
|
|
81
|
+
return {
|
|
82
|
+
id,
|
|
83
|
+
revision: '1',
|
|
84
|
+
expected: 'never',
|
|
85
|
+
parse: (_input) => {
|
|
86
|
+
const issue = {
|
|
87
|
+
code: 'invalid_type',
|
|
88
|
+
path: '(root)',
|
|
89
|
+
message: 'rejected',
|
|
90
|
+
};
|
|
91
|
+
switch (position) {
|
|
92
|
+
case 'code':
|
|
93
|
+
issue.code = CANARY;
|
|
94
|
+
break;
|
|
95
|
+
case 'path':
|
|
96
|
+
issue.path = CANARY;
|
|
97
|
+
break;
|
|
98
|
+
case 'message':
|
|
99
|
+
issue.message = `rejected because of ${CANARY}`;
|
|
100
|
+
break;
|
|
101
|
+
case 'fields':
|
|
102
|
+
issue.expected = CANARY;
|
|
103
|
+
issue.received = CANARY;
|
|
104
|
+
issue.safeMessage = CANARY;
|
|
105
|
+
break;
|
|
106
|
+
case 'extra':
|
|
107
|
+
issue.nested = { deeper: [CANARY] };
|
|
108
|
+
issue.toStringOverride = CANARY;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
return { ok: false, issues: [issue] };
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export function runOrchestrationCanarySuite(runner, expect, factory) {
|
|
116
|
+
const t = runner.test;
|
|
117
|
+
const label = (name) => `[${factory.name}] ${name}`;
|
|
118
|
+
for (const position of ['code', 'path', 'message', 'fields', 'extra']) {
|
|
119
|
+
t(label(`join contract canary in issue ${position} never reaches any surface`), async () => {
|
|
120
|
+
const fixture = await factory.create();
|
|
121
|
+
try {
|
|
122
|
+
const { runtime, orchestration } = fixture;
|
|
123
|
+
let downstreamCalls = 0;
|
|
124
|
+
runtime
|
|
125
|
+
.registerCapability({
|
|
126
|
+
id: 'c.first',
|
|
127
|
+
revision: '1',
|
|
128
|
+
effect: 'pure',
|
|
129
|
+
input: neutralJsonContract,
|
|
130
|
+
output: neutralJsonContract,
|
|
131
|
+
invoke: () => 's',
|
|
132
|
+
})
|
|
133
|
+
.registerCapability({
|
|
134
|
+
id: 'c.b1',
|
|
135
|
+
revision: '1',
|
|
136
|
+
effect: 'pure',
|
|
137
|
+
input: neutralJsonContract,
|
|
138
|
+
output: neutralJsonContract,
|
|
139
|
+
invoke: () => 'ALPHA',
|
|
140
|
+
})
|
|
141
|
+
.registerCapability({
|
|
142
|
+
id: 'c.b2',
|
|
143
|
+
revision: '1',
|
|
144
|
+
effect: 'pure',
|
|
145
|
+
input: neutralJsonContract,
|
|
146
|
+
output: neutralJsonContract,
|
|
147
|
+
invoke: () => 'BETA',
|
|
148
|
+
})
|
|
149
|
+
.registerCapability({
|
|
150
|
+
id: 'c.after',
|
|
151
|
+
revision: '1',
|
|
152
|
+
effect: 'pure',
|
|
153
|
+
input: neutralJsonContract,
|
|
154
|
+
output: neutralJsonContract,
|
|
155
|
+
invoke: () => {
|
|
156
|
+
downstreamCalls += 1;
|
|
157
|
+
return 'after';
|
|
158
|
+
},
|
|
159
|
+
})
|
|
160
|
+
.registerContract(hostileContract(`c.hostile-${position}`, position));
|
|
161
|
+
const activated = await runtime.activate({
|
|
162
|
+
id: `canary-join-${position}`,
|
|
163
|
+
entry: 's',
|
|
164
|
+
nodes: [
|
|
165
|
+
{ id: 's', capability: 'c.first' },
|
|
166
|
+
{ id: 'f', kind: 'fork', join: 'j' },
|
|
167
|
+
{ id: 'x1', capability: 'c.b1' },
|
|
168
|
+
{ id: 'x2', capability: 'c.b2' },
|
|
169
|
+
{ id: 'j', kind: 'join', fork: 'f', output: `c.hostile-${position}` },
|
|
170
|
+
{ id: 'z', capability: 'c.after' },
|
|
171
|
+
],
|
|
172
|
+
edges: [
|
|
173
|
+
{ from: 's', to: 'f' },
|
|
174
|
+
{ from: 'f', to: 'x1', kind: 'branch', key: 'a' },
|
|
175
|
+
{ from: 'f', to: 'x2', kind: 'branch', key: 'b' },
|
|
176
|
+
{ from: 'x1', to: 'j' },
|
|
177
|
+
{ from: 'x2', to: 'j' },
|
|
178
|
+
{ from: 'j', to: 'z' },
|
|
179
|
+
],
|
|
180
|
+
});
|
|
181
|
+
expect(activated.ok).toBe(true);
|
|
182
|
+
const captured = (await runCaptured(runtime, 'seed'));
|
|
183
|
+
await assertRejectionIsSafe(expect, orchestration, runtime, captured, 'output');
|
|
184
|
+
expect(downstreamCalls).toBe(0);
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
await fixture.dispose();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
t(label('a payload-derived canary key cannot leak through parser-derived issue fields'), async () => {
|
|
192
|
+
const fixture = await factory.create();
|
|
193
|
+
try {
|
|
194
|
+
const { runtime, orchestration } = fixture;
|
|
195
|
+
let downstreamCalls = 0;
|
|
196
|
+
runtime
|
|
197
|
+
.registerCapability({
|
|
198
|
+
id: 'p.first',
|
|
199
|
+
revision: '1',
|
|
200
|
+
effect: 'pure',
|
|
201
|
+
input: neutralJsonContract,
|
|
202
|
+
output: neutralJsonContract,
|
|
203
|
+
invoke: () => 's',
|
|
204
|
+
})
|
|
205
|
+
// Branch outputs carry the canary as a DYNAMIC OBJECT KEY: the
|
|
206
|
+
// canonical join payload itself contains the secret key name.
|
|
207
|
+
.registerCapability({
|
|
208
|
+
id: 'p.b',
|
|
209
|
+
revision: '1',
|
|
210
|
+
effect: 'pure',
|
|
211
|
+
input: neutralJsonContract,
|
|
212
|
+
output: neutralJsonContract,
|
|
213
|
+
invoke: () => ({ [CANARY]: 'value' }),
|
|
214
|
+
})
|
|
215
|
+
.registerCapability({
|
|
216
|
+
id: 'p.after',
|
|
217
|
+
revision: '1',
|
|
218
|
+
effect: 'pure',
|
|
219
|
+
input: neutralJsonContract,
|
|
220
|
+
output: neutralJsonContract,
|
|
221
|
+
invoke: () => {
|
|
222
|
+
downstreamCalls += 1;
|
|
223
|
+
return 'after';
|
|
224
|
+
},
|
|
225
|
+
})
|
|
226
|
+
// A hostile parser that derives its diagnostics from the payload:
|
|
227
|
+
// the secret key name flows into code, path, and message.
|
|
228
|
+
.registerContract({
|
|
229
|
+
id: 'p.derive',
|
|
230
|
+
revision: '1',
|
|
231
|
+
expected: 'never',
|
|
232
|
+
parse: (input) => {
|
|
233
|
+
const keys = Object.keys(input).join(',');
|
|
234
|
+
return {
|
|
235
|
+
ok: false,
|
|
236
|
+
issues: [{ code: keys, path: `$.${keys}`, message: `keys: ${keys}` }],
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
const activated = await runtime.activate({
|
|
241
|
+
id: 'canary-derive',
|
|
242
|
+
entry: 's',
|
|
243
|
+
nodes: [
|
|
244
|
+
{ id: 's', capability: 'p.first' },
|
|
245
|
+
{ id: 'f', kind: 'fork', join: 'j' },
|
|
246
|
+
{ id: 'x1', capability: 'p.b' },
|
|
247
|
+
{ id: 'x2', capability: 'p.b' },
|
|
248
|
+
{ id: 'j', kind: 'join', fork: 'f', output: 'p.derive' },
|
|
249
|
+
{ id: 'z', capability: 'p.after' },
|
|
250
|
+
],
|
|
251
|
+
edges: [
|
|
252
|
+
{ from: 's', to: 'f' },
|
|
253
|
+
{ from: 'f', to: 'x1', kind: 'branch', key: 'a' },
|
|
254
|
+
{ from: 'f', to: 'x2', kind: 'branch', key: 'b' },
|
|
255
|
+
{ from: 'x1', to: 'j' },
|
|
256
|
+
{ from: 'x2', to: 'j' },
|
|
257
|
+
{ from: 'j', to: 'z' },
|
|
258
|
+
],
|
|
259
|
+
});
|
|
260
|
+
expect(activated.ok).toBe(true);
|
|
261
|
+
const captured = (await runCaptured(runtime, 'seed'));
|
|
262
|
+
await assertRejectionIsSafe(expect, orchestration, runtime, captured, 'output');
|
|
263
|
+
expect(downstreamCalls).toBe(0);
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
await fixture.dispose();
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
t(label('an input-contract canary never reaches any surface (input stage)'), async () => {
|
|
270
|
+
const fixture = await factory.create();
|
|
271
|
+
try {
|
|
272
|
+
const { runtime, orchestration } = fixture;
|
|
273
|
+
let invoked = 0;
|
|
274
|
+
runtime
|
|
275
|
+
.registerCapability({
|
|
276
|
+
id: 'i.guarded',
|
|
277
|
+
revision: '1',
|
|
278
|
+
effect: 'pure',
|
|
279
|
+
input: neutralJsonContract,
|
|
280
|
+
output: neutralJsonContract,
|
|
281
|
+
invoke: () => {
|
|
282
|
+
invoked += 1;
|
|
283
|
+
return 'ran';
|
|
284
|
+
},
|
|
285
|
+
})
|
|
286
|
+
.registerContract(hostileContract('i.hostile-input', 'message'));
|
|
287
|
+
const activated = await runtime.activate({
|
|
288
|
+
id: 'canary-input',
|
|
289
|
+
entry: 'n',
|
|
290
|
+
// A declared timeout forces the durable orchestration engine, so
|
|
291
|
+
// the driver's input boundary (not the sequential engine) is what
|
|
292
|
+
// rejects and records the failure.
|
|
293
|
+
nodes: [
|
|
294
|
+
{
|
|
295
|
+
id: 'n',
|
|
296
|
+
capability: 'i.guarded',
|
|
297
|
+
input: 'i.hostile-input',
|
|
298
|
+
timeoutMs: 30_000,
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
edges: [],
|
|
302
|
+
});
|
|
303
|
+
expect(activated.ok).toBe(true);
|
|
304
|
+
const captured = (await runCaptured(runtime, 'seed'));
|
|
305
|
+
await assertRejectionIsSafe(expect, orchestration, runtime, captured, 'input');
|
|
306
|
+
expect(invoked).toBe(0);
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
await fixture.dispose();
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
t(label('a signal-payload canary stays inside the private continuation boundary'), async () => {
|
|
313
|
+
const fixture = await factory.create();
|
|
314
|
+
try {
|
|
315
|
+
const { runtime, orchestration } = fixture;
|
|
316
|
+
runtime
|
|
317
|
+
.registerCapability({
|
|
318
|
+
id: 's.first',
|
|
319
|
+
revision: '1',
|
|
320
|
+
effect: 'pure',
|
|
321
|
+
input: neutralJsonContract,
|
|
322
|
+
output: neutralJsonContract,
|
|
323
|
+
invoke: () => 'go',
|
|
324
|
+
})
|
|
325
|
+
.registerCapability({
|
|
326
|
+
id: 's.echo',
|
|
327
|
+
revision: '1',
|
|
328
|
+
effect: 'pure',
|
|
329
|
+
input: neutralJsonContract,
|
|
330
|
+
output: neutralJsonContract,
|
|
331
|
+
invoke: (input) => `echo:${String(input)}`,
|
|
332
|
+
})
|
|
333
|
+
.registerContract({
|
|
334
|
+
id: 's.string',
|
|
335
|
+
revision: '1',
|
|
336
|
+
expected: 'a string',
|
|
337
|
+
parse: (input) => typeof input === 'string'
|
|
338
|
+
? { ok: true, value: input, issues: [] }
|
|
339
|
+
: {
|
|
340
|
+
ok: false,
|
|
341
|
+
issues: [{ code: 'invalid_type', path: '(root)', message: 'string' }],
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
const activated = await runtime.activate({
|
|
345
|
+
id: 'canary-signal',
|
|
346
|
+
entry: 'a',
|
|
347
|
+
nodes: [
|
|
348
|
+
{ id: 'a', capability: 's.first' },
|
|
349
|
+
{ id: 'w', kind: 'wait', wait: { kind: 'signal', name: 'go', contract: 's.string' } },
|
|
350
|
+
{ id: 'b', capability: 's.echo', output: 's.string' },
|
|
351
|
+
],
|
|
352
|
+
edges: [
|
|
353
|
+
{ from: 'a', to: 'w' },
|
|
354
|
+
{ from: 'w', to: 'b' },
|
|
355
|
+
],
|
|
356
|
+
});
|
|
357
|
+
expect(activated.ok).toBe(true);
|
|
358
|
+
const callbackEvents = [];
|
|
359
|
+
const parked = (await runtime.run('seed', {
|
|
360
|
+
onEvent: (event) => callbackEvents.push(event),
|
|
361
|
+
}));
|
|
362
|
+
expect(parked.status).toBe('waiting');
|
|
363
|
+
// A VALID secret-bearing signal payload: usable only inside the
|
|
364
|
+
// private continuation boundary (it becomes the branch completion
|
|
365
|
+
// value / checkpoint of the resumed work).
|
|
366
|
+
const signaled = await runtime.signal({
|
|
367
|
+
runId: parked.runId,
|
|
368
|
+
waitId: parked.waits?.[0]?.waitId,
|
|
369
|
+
signalId: 'sig-1',
|
|
370
|
+
signalName: 'go',
|
|
371
|
+
payload: `${CANARY}-payload`,
|
|
372
|
+
});
|
|
373
|
+
expect(signaled.ok).toBe(true);
|
|
374
|
+
const final = await runtime.resumeRun(parked.runId);
|
|
375
|
+
// Returned output is the intentional public result contract; the
|
|
376
|
+
// application transformed the payload, so the raw payload value
|
|
377
|
+
// itself must still be absent from every stored surface.
|
|
378
|
+
expect(final.status).toBe('completed');
|
|
379
|
+
expect(final.output).toBe(`echo:${CANARY}-payload`);
|
|
380
|
+
const surfaces = [
|
|
381
|
+
{ label: 'onEvent callback events', value: callbackEvents },
|
|
382
|
+
{ label: 'RunResult.trace', value: final.trace },
|
|
383
|
+
{
|
|
384
|
+
label: 'stored event ledger',
|
|
385
|
+
value: await orchestration.listOrchestrationEvents(parked.runId),
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
label: 'default run record',
|
|
389
|
+
value: await runtime.getRun(parked.runId),
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
label: 'signal receipts',
|
|
393
|
+
value: await orchestration.listSignalReceipts(parked.runId),
|
|
394
|
+
},
|
|
395
|
+
{ label: 'wait records', value: await orchestration.listWaits(parked.runId) },
|
|
396
|
+
];
|
|
397
|
+
for (const { label: name, value } of surfaces) {
|
|
398
|
+
const text = JSON.stringify(value) ?? '';
|
|
399
|
+
if (text.includes(`${CANARY}-payload`)) {
|
|
400
|
+
throw new Error(`canary leaked into ${name}: ${text.slice(0, 200)}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
finally {
|
|
405
|
+
await fixture.dispose();
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
t(label('a hostile signal contract rejects without echoing the payload or issue content'), async () => {
|
|
409
|
+
const fixture = await factory.create();
|
|
410
|
+
try {
|
|
411
|
+
const { runtime, orchestration } = fixture;
|
|
412
|
+
runtime
|
|
413
|
+
.registerCapability({
|
|
414
|
+
id: 'v.first',
|
|
415
|
+
revision: '1',
|
|
416
|
+
effect: 'pure',
|
|
417
|
+
input: neutralJsonContract,
|
|
418
|
+
output: neutralJsonContract,
|
|
419
|
+
invoke: () => 'go',
|
|
420
|
+
})
|
|
421
|
+
.registerCapability({
|
|
422
|
+
id: 'v.after',
|
|
423
|
+
revision: '1',
|
|
424
|
+
effect: 'pure',
|
|
425
|
+
input: neutralJsonContract,
|
|
426
|
+
output: neutralJsonContract,
|
|
427
|
+
invoke: () => 'after',
|
|
428
|
+
})
|
|
429
|
+
// Hostile wait-payload contract: canary in every issue position
|
|
430
|
+
// plus a full payload echo.
|
|
431
|
+
.registerContract({
|
|
432
|
+
id: 'v.hostile',
|
|
433
|
+
revision: '1',
|
|
434
|
+
expected: 'never',
|
|
435
|
+
parse: (input) => ({
|
|
436
|
+
ok: false,
|
|
437
|
+
issues: [
|
|
438
|
+
{
|
|
439
|
+
code: CANARY,
|
|
440
|
+
path: `${CANARY}:${JSON.stringify(input)}`,
|
|
441
|
+
message: `${CANARY}:${JSON.stringify(input)}`,
|
|
442
|
+
expected: CANARY,
|
|
443
|
+
received: CANARY,
|
|
444
|
+
nested: { echo: CANARY },
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
}),
|
|
448
|
+
});
|
|
449
|
+
const activated = await runtime.activate({
|
|
450
|
+
id: 'canary-signal-reject',
|
|
451
|
+
entry: 'a',
|
|
452
|
+
nodes: [
|
|
453
|
+
{ id: 'a', capability: 'v.first' },
|
|
454
|
+
{ id: 'w', kind: 'wait', wait: { kind: 'signal', name: 'go', contract: 'v.hostile' } },
|
|
455
|
+
{ id: 'b', capability: 'v.after' },
|
|
456
|
+
],
|
|
457
|
+
edges: [
|
|
458
|
+
{ from: 'a', to: 'w' },
|
|
459
|
+
{ from: 'w', to: 'b' },
|
|
460
|
+
],
|
|
461
|
+
});
|
|
462
|
+
expect(activated.ok).toBe(true);
|
|
463
|
+
const parked = (await runtime.run('seed'));
|
|
464
|
+
expect(parked.status).toBe('waiting');
|
|
465
|
+
const rejected = await runtime.signal({
|
|
466
|
+
runId: parked.runId,
|
|
467
|
+
waitId: parked.waits?.[0]?.waitId,
|
|
468
|
+
signalId: 'sig-1',
|
|
469
|
+
signalName: 'go',
|
|
470
|
+
payload: `${CANARY}-value`,
|
|
471
|
+
});
|
|
472
|
+
expect(rejected.ok).toBe(false);
|
|
473
|
+
if (!rejected.ok) {
|
|
474
|
+
expect(JSON.stringify(rejected).includes(CANARY)).toBe(false);
|
|
475
|
+
}
|
|
476
|
+
// The wait stays open; nothing about the rejected payload persists.
|
|
477
|
+
expect((await orchestration.listWaits(parked.runId)).length).toBe(1);
|
|
478
|
+
expect((await orchestration.listSignalReceipts(parked.runId)).length).toBe(0);
|
|
479
|
+
const ledger = JSON.stringify(await orchestration.listOrchestrationEvents(parked.runId));
|
|
480
|
+
expect(ledger.includes(CANARY)).toBe(false);
|
|
481
|
+
const record = JSON.stringify(await orchestration.getOrchestrationRun(parked.runId));
|
|
482
|
+
expect(record.includes(CANARY)).toBe(false);
|
|
483
|
+
}
|
|
484
|
+
finally {
|
|
485
|
+
await fixture.dispose();
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
t(label('cancellation metadata uses only the closed safe vocabulary'), async () => {
|
|
489
|
+
const fixture = await factory.create();
|
|
490
|
+
try {
|
|
491
|
+
const { runtime, orchestration } = fixture;
|
|
492
|
+
runtime
|
|
493
|
+
.registerCapability({
|
|
494
|
+
id: 'm.first',
|
|
495
|
+
revision: '1',
|
|
496
|
+
effect: 'pure',
|
|
497
|
+
input: neutralJsonContract,
|
|
498
|
+
output: neutralJsonContract,
|
|
499
|
+
invoke: () => 'go',
|
|
500
|
+
})
|
|
501
|
+
.registerCapability({
|
|
502
|
+
id: 'm.after',
|
|
503
|
+
revision: '1',
|
|
504
|
+
effect: 'pure',
|
|
505
|
+
input: neutralJsonContract,
|
|
506
|
+
output: neutralJsonContract,
|
|
507
|
+
invoke: () => 'after',
|
|
508
|
+
});
|
|
509
|
+
const activated = await runtime.activate({
|
|
510
|
+
id: 'canary-cancel-meta',
|
|
511
|
+
entry: 'a',
|
|
512
|
+
nodes: [
|
|
513
|
+
{ id: 'a', capability: 'm.first' },
|
|
514
|
+
{ id: 'w', kind: 'wait', wait: { kind: 'signal', name: 'go' } },
|
|
515
|
+
{ id: 'b', capability: 'm.after' },
|
|
516
|
+
],
|
|
517
|
+
edges: [
|
|
518
|
+
{ from: 'a', to: 'w' },
|
|
519
|
+
{ from: 'w', to: 'b' },
|
|
520
|
+
],
|
|
521
|
+
});
|
|
522
|
+
expect(activated.ok).toBe(true);
|
|
523
|
+
const parked = (await runtime.run('seed'));
|
|
524
|
+
expect(parked.status).toBe('waiting');
|
|
525
|
+
// An invalid reason code fails safely WITHOUT echoing the value.
|
|
526
|
+
const invalid = (await runtime.cancel({
|
|
527
|
+
runId: parked.runId,
|
|
528
|
+
requestId: 'req-invalid',
|
|
529
|
+
reasonCode: CANARY,
|
|
530
|
+
}));
|
|
531
|
+
expect(invalid.ok).toBe(false);
|
|
532
|
+
expect(JSON.stringify(invalid).includes(CANARY)).toBe(false);
|
|
533
|
+
expect(invalid.code).toBe('VICT_ORCH_INVALID_REASON');
|
|
534
|
+
// The run is untouched by the invalid request.
|
|
535
|
+
expect((await orchestration.getOrchestrationRun(parked.runId))?.status).toBe('waiting');
|
|
536
|
+
// A valid request persists ONLY the closed vocabulary plus the
|
|
537
|
+
// caller-owned requestId (an identifier in the caller's namespace,
|
|
538
|
+
// documented as explicitly safe — never arbitrary diagnostic text).
|
|
539
|
+
const accepted = await runtime.cancel({
|
|
540
|
+
runId: parked.runId,
|
|
541
|
+
requestId: 'req-1',
|
|
542
|
+
reasonCode: 'operator_request',
|
|
543
|
+
});
|
|
544
|
+
expect(accepted.ok).toBe(true);
|
|
545
|
+
const record = (await orchestration.getOrchestrationRun(parked.runId));
|
|
546
|
+
expect(record.status).toBe('cancelled');
|
|
547
|
+
// Persisted cancellation facts: the closed-vocabulary reason code
|
|
548
|
+
// plus the caller-owned requestId identifier. No free-text field
|
|
549
|
+
// exists to carry arbitrary content.
|
|
550
|
+
expect(record.cancellation?.reasonCode).toBe('operator_request');
|
|
551
|
+
expect(Object.keys(record.cancellation ?? {}).sort()).toEqual(['reasonCode', 'requestId'].sort());
|
|
552
|
+
const ledger = await orchestration.listOrchestrationEvents(parked.runId);
|
|
553
|
+
for (const event of ledger) {
|
|
554
|
+
const reason = event.reasonCode;
|
|
555
|
+
if (reason !== undefined) {
|
|
556
|
+
expect(['operator_request', 'shutdown', 'policy', 'superseded']).toContain(reason);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
finally {
|
|
561
|
+
await fixture.dispose();
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
t(label('an external-ledger thrown error with a nested cause never leaks'), async () => {
|
|
565
|
+
const fixture = await factory.create();
|
|
566
|
+
try {
|
|
567
|
+
const { runtime, orchestration } = fixture;
|
|
568
|
+
const externalLedger = [];
|
|
569
|
+
runtime.registerCapability({
|
|
570
|
+
id: 'e.keyedWrite',
|
|
571
|
+
revision: '1',
|
|
572
|
+
effect: 'write',
|
|
573
|
+
input: neutralJsonContract,
|
|
574
|
+
output: neutralJsonContract,
|
|
575
|
+
idempotency: 'keyed',
|
|
576
|
+
invoke: (input, context) => {
|
|
577
|
+
// Disposable external-effect ledger: records the mutation,
|
|
578
|
+
// then the reconciliation boundary throws with a nested cause.
|
|
579
|
+
externalLedger.push(`${String(input)}:${context.idempotencyKey ?? ''}`);
|
|
580
|
+
throw new Error(`outer ${CANARY}`, { cause: new Error(`inner ${CANARY}`) });
|
|
581
|
+
},
|
|
582
|
+
});
|
|
583
|
+
const activated = await runtime.activate({
|
|
584
|
+
id: 'canary-ledger',
|
|
585
|
+
entry: 'w',
|
|
586
|
+
nodes: [{ id: 'w', capability: 'e.keyedWrite', timeoutMs: 30_000 }],
|
|
587
|
+
edges: [],
|
|
588
|
+
});
|
|
589
|
+
expect(activated.ok).toBe(true);
|
|
590
|
+
const captured = (await runCaptured(runtime, 'seed'));
|
|
591
|
+
expect(captured.status).toBe('failed');
|
|
592
|
+
const surfaces = [
|
|
593
|
+
{ label: 'onEvent callback events', value: captured.callbackEvents },
|
|
594
|
+
{ label: 'RunResult.trace', value: captured.trace },
|
|
595
|
+
{ label: 'public failure error', value: captured.error },
|
|
596
|
+
{
|
|
597
|
+
label: 'stored event ledger',
|
|
598
|
+
value: await orchestration.listOrchestrationEvents(captured.runId),
|
|
599
|
+
},
|
|
600
|
+
{ label: 'stored run record', value: await runtime.getRun(captured.runId) },
|
|
601
|
+
{ label: 'wait records', value: await orchestration.listWaits(captured.runId) },
|
|
602
|
+
];
|
|
603
|
+
for (const { label: name, value } of surfaces) {
|
|
604
|
+
const text = JSON.stringify(value) ?? '';
|
|
605
|
+
if (text.includes(CANARY)) {
|
|
606
|
+
throw new Error(`canary leaked into ${name}: ${text.slice(0, 200)}`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
// The stable framework class is all that is observable about the
|
|
610
|
+
// thrown error: no message, no error name chain, no cause text.
|
|
611
|
+
const failedEvent = captured.callbackEvents.find((event) => event.type === 'node.failed');
|
|
612
|
+
expect(failedEvent?.error?.code).toBe('VICT_RUNTIME_CAPABILITY_THREW');
|
|
613
|
+
expect((failedEvent?.error?.message ?? '').includes('threw')).toBe(true);
|
|
614
|
+
// Recovery/operator diagnostics after the failure carry no secret.
|
|
615
|
+
const recovery = await runtime.recoverOrchestration();
|
|
616
|
+
expect(JSON.stringify(recovery).includes(CANARY)).toBe(false);
|
|
617
|
+
}
|
|
618
|
+
finally {
|
|
619
|
+
await fixture.dispose();
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
t(label('operator confirmation rejection echoes nothing from the hostile contract'), async () => {
|
|
623
|
+
const fixture = await factory.create();
|
|
624
|
+
try {
|
|
625
|
+
const { runtime, orchestration } = fixture;
|
|
626
|
+
runtime
|
|
627
|
+
.registerCapability({
|
|
628
|
+
id: 'o.blocked',
|
|
629
|
+
revision: '1',
|
|
630
|
+
effect: 'irreversible',
|
|
631
|
+
input: neutralJsonContract,
|
|
632
|
+
output: neutralJsonContract,
|
|
633
|
+
invoke: () => 'never',
|
|
634
|
+
})
|
|
635
|
+
.registerCapability({
|
|
636
|
+
id: 'o.after',
|
|
637
|
+
revision: '1',
|
|
638
|
+
effect: 'pure',
|
|
639
|
+
input: neutralJsonContract,
|
|
640
|
+
output: neutralJsonContract,
|
|
641
|
+
invoke: () => 'x',
|
|
642
|
+
})
|
|
643
|
+
.registerContract({
|
|
644
|
+
id: 'o.hostile-output',
|
|
645
|
+
revision: '1',
|
|
646
|
+
expected: 'never',
|
|
647
|
+
parse: (input) => ({
|
|
648
|
+
ok: false,
|
|
649
|
+
issues: [
|
|
650
|
+
{
|
|
651
|
+
code: CANARY,
|
|
652
|
+
path: `${CANARY}:${JSON.stringify(input)}`,
|
|
653
|
+
message: `${CANARY}:${JSON.stringify(input)}`,
|
|
654
|
+
expected: CANARY,
|
|
655
|
+
received: CANARY,
|
|
656
|
+
nested: { echo: CANARY },
|
|
657
|
+
},
|
|
658
|
+
],
|
|
659
|
+
}),
|
|
660
|
+
});
|
|
661
|
+
const activated = await runtime.activate({
|
|
662
|
+
id: 'canary-operator',
|
|
663
|
+
entry: 'b',
|
|
664
|
+
nodes: [
|
|
665
|
+
{
|
|
666
|
+
id: 'b',
|
|
667
|
+
capability: 'o.blocked',
|
|
668
|
+
output: 'o.hostile-output',
|
|
669
|
+
// Declared timeout: forces the durable orchestration engine,
|
|
670
|
+
// so the blocked-run operator surface is the one under probe.
|
|
671
|
+
timeoutMs: 30_000,
|
|
672
|
+
},
|
|
673
|
+
{ id: 'z', capability: 'o.after' },
|
|
674
|
+
],
|
|
675
|
+
edges: [{ from: 'b', to: 'z' }],
|
|
676
|
+
});
|
|
677
|
+
expect(activated.ok).toBe(true);
|
|
678
|
+
const blocked = (await runtime.run('seed'));
|
|
679
|
+
expect(blocked.status).toBe('blocked');
|
|
680
|
+
const operator = await fixture.createOperatorRuntime();
|
|
681
|
+
operator.registerCapability({
|
|
682
|
+
id: 'o.blocked',
|
|
683
|
+
revision: '1',
|
|
684
|
+
effect: 'irreversible',
|
|
685
|
+
input: neutralJsonContract,
|
|
686
|
+
output: neutralJsonContract,
|
|
687
|
+
invoke: () => 'never',
|
|
688
|
+
});
|
|
689
|
+
operator.registerContract({
|
|
690
|
+
id: 'o.hostile-output',
|
|
691
|
+
revision: '1',
|
|
692
|
+
expected: 'never',
|
|
693
|
+
parse: (input) => ({
|
|
694
|
+
ok: false,
|
|
695
|
+
issues: [
|
|
696
|
+
{
|
|
697
|
+
code: CANARY,
|
|
698
|
+
path: `${CANARY}:${JSON.stringify(input)}`,
|
|
699
|
+
message: `${CANARY}:${JSON.stringify(input)}`,
|
|
700
|
+
expected: CANARY,
|
|
701
|
+
received: CANARY,
|
|
702
|
+
nested: { echo: CANARY },
|
|
703
|
+
},
|
|
704
|
+
],
|
|
705
|
+
}),
|
|
706
|
+
});
|
|
707
|
+
const confirmed = (await operator.resolveBlocked({
|
|
708
|
+
runId: blocked.runId,
|
|
709
|
+
resolutionId: 'res-1',
|
|
710
|
+
action: 'confirm_applied',
|
|
711
|
+
reasonCode: 'operator_request',
|
|
712
|
+
output: `${CANARY}-confirmed`,
|
|
713
|
+
}));
|
|
714
|
+
expect(confirmed.ok).toBe(false);
|
|
715
|
+
expect(JSON.stringify(confirmed).includes(CANARY)).toBe(false);
|
|
716
|
+
// The run remains blocked; no resolution fact carries the secret.
|
|
717
|
+
expect((await orchestration.getOrchestrationRun(blocked.runId))?.status).toBe('blocked');
|
|
718
|
+
const ledger = JSON.stringify(await orchestration.listOrchestrationEvents(blocked.runId));
|
|
719
|
+
expect(ledger.includes(CANARY)).toBe(false);
|
|
720
|
+
}
|
|
721
|
+
finally {
|
|
722
|
+
await fixture.dispose();
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
//# sourceMappingURL=orchestration-canary-conformance.js.map
|