@abelxiaoxing/cadence 1.1.1 → 1.2.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/README.md +8 -2
- package/agents/implementation-worker.md +17 -11
- package/config/routes.example.json +0 -2
- package/package.json +2 -2
- package/prompts/abel-design.md +20 -16
- package/prompts/abel-implement.md +4 -3
- package/skills/abel-workflow/SKILL.md +6 -3
- package/src/agent-registry.ts +0 -1
- package/src/candidate-patch.ts +250 -0
- package/src/child-session.ts +27 -24
- package/src/contracts.ts +1 -8
- package/src/control-contracts.ts +14 -48
- package/src/design-control.ts +224 -17
- package/src/design-journal.ts +69 -22
- package/src/index.ts +67 -20
- package/src/packet-runtime.ts +6 -6
- package/src/route-policy.ts +45 -17
- package/src/run-state.ts +4 -2
- package/src/run-store.ts +22 -6
- package/src/submit-tool.ts +149 -49
- package/src/worker-broker.ts +0 -1
- package/src/workflow-engine.ts +22 -171
- package/agents/contract-reviewer.md +0 -11
package/src/index.ts
CHANGED
|
@@ -210,6 +210,42 @@ const DESIGN_CONTROL_REQUEST_SCHEMA = {
|
|
|
210
210
|
description:
|
|
211
211
|
"One closed durable Design control operation. The operation body is strict and idempotent by operationId.",
|
|
212
212
|
oneOf: [
|
|
213
|
+
...(["requirement", "change"] as const).map((field) => ({
|
|
214
|
+
type: "object" as const,
|
|
215
|
+
properties: {
|
|
216
|
+
operation: { type: "string" as const, enum: ["start"] },
|
|
217
|
+
operationId: { type: "string" as const },
|
|
218
|
+
[field]: {
|
|
219
|
+
type: "string" as const,
|
|
220
|
+
description:
|
|
221
|
+
field === "requirement"
|
|
222
|
+
? "Raw Design requirement; code derives and stores only its stable hash."
|
|
223
|
+
: "Existing canonical change name to resume or revise.",
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
required: ["operation", "operationId", field],
|
|
227
|
+
additionalProperties: false,
|
|
228
|
+
})),
|
|
229
|
+
{
|
|
230
|
+
type: "object",
|
|
231
|
+
properties: {
|
|
232
|
+
operation: { type: "string", enum: ["status"] },
|
|
233
|
+
runId: { type: "string" },
|
|
234
|
+
},
|
|
235
|
+
required: ["operation", "runId"],
|
|
236
|
+
additionalProperties: false,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
type: "object",
|
|
240
|
+
properties: {
|
|
241
|
+
operation: { type: "string", enum: ["bind-change"] },
|
|
242
|
+
runId: { type: "string" },
|
|
243
|
+
operationId: { type: "string" },
|
|
244
|
+
change: { type: "string" },
|
|
245
|
+
},
|
|
246
|
+
required: ["operation", "runId", "operationId", "change"],
|
|
247
|
+
additionalProperties: false,
|
|
248
|
+
},
|
|
213
249
|
{
|
|
214
250
|
type: "object",
|
|
215
251
|
properties: {
|
|
@@ -218,7 +254,11 @@ const DESIGN_CONTROL_REQUEST_SCHEMA = {
|
|
|
218
254
|
operationId: { type: "string" },
|
|
219
255
|
decisionId: { type: "string" },
|
|
220
256
|
category: { type: "string", enum: ["behavior", "technical"] },
|
|
221
|
-
|
|
257
|
+
contract: {
|
|
258
|
+
type: "string",
|
|
259
|
+
description:
|
|
260
|
+
"Normalized decision contract; code hashes it and never persists the text.",
|
|
261
|
+
},
|
|
222
262
|
refs: { type: "array", items: { type: "string" } },
|
|
223
263
|
},
|
|
224
264
|
required: [
|
|
@@ -227,7 +267,7 @@ const DESIGN_CONTROL_REQUEST_SCHEMA = {
|
|
|
227
267
|
"operationId",
|
|
228
268
|
"decisionId",
|
|
229
269
|
"category",
|
|
230
|
-
"
|
|
270
|
+
"contract",
|
|
231
271
|
"refs",
|
|
232
272
|
],
|
|
233
273
|
additionalProperties: false,
|
|
@@ -238,10 +278,25 @@ const DESIGN_CONTROL_REQUEST_SCHEMA = {
|
|
|
238
278
|
operation: { type: "string", enum: ["approve-gate"] },
|
|
239
279
|
runId: { type: "string" },
|
|
240
280
|
operationId: { type: "string" },
|
|
241
|
-
gate: { type: "string", enum: ["gate-a"
|
|
242
|
-
|
|
281
|
+
gate: { type: "string", enum: ["gate-a"] },
|
|
282
|
+
contract: {
|
|
283
|
+
type: "string",
|
|
284
|
+
description:
|
|
285
|
+
"Complete approved WHAT contract; code hashes it and never persists the text.",
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
required: ["operation", "runId", "operationId", "gate", "contract"],
|
|
289
|
+
additionalProperties: false,
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
type: "object",
|
|
293
|
+
properties: {
|
|
294
|
+
operation: { type: "string", enum: ["approve-gate"] },
|
|
295
|
+
runId: { type: "string" },
|
|
296
|
+
operationId: { type: "string" },
|
|
297
|
+
gate: { type: "string", enum: ["gate-b"] },
|
|
243
298
|
},
|
|
244
|
-
required: ["operation", "runId", "operationId", "gate"
|
|
299
|
+
required: ["operation", "runId", "operationId", "gate"],
|
|
245
300
|
additionalProperties: false,
|
|
246
301
|
},
|
|
247
302
|
{
|
|
@@ -417,7 +472,7 @@ export interface PackageDeliverySourceOptions {
|
|
|
417
472
|
|
|
418
473
|
export interface PackageWorkflowDeliverySource extends WorkflowDeliverySource {
|
|
419
474
|
discoverLatest(input: {
|
|
420
|
-
stage: "abel-
|
|
475
|
+
stage: "abel-implement";
|
|
421
476
|
change: string;
|
|
422
477
|
}): Promise<WorkflowAvailableDelivery | undefined>;
|
|
423
478
|
}
|
|
@@ -1418,6 +1473,7 @@ export function openPackageWorkflowControlEngine(
|
|
|
1418
1473
|
const routeResolution = loadRoutePolicy({
|
|
1419
1474
|
cwd: consumerRoot,
|
|
1420
1475
|
home: homedir(),
|
|
1476
|
+
...(initialContext.model ? { parentModel: initialContext.model } : {}),
|
|
1421
1477
|
});
|
|
1422
1478
|
const stateRoot = resolveStateRoot({
|
|
1423
1479
|
consumerRoot,
|
|
@@ -1614,6 +1670,7 @@ export function openPackageWorkflowControlEngine(
|
|
|
1614
1670
|
const operationRouteResolution = loadRoutePolicy({
|
|
1615
1671
|
cwd: consumerRoot,
|
|
1616
1672
|
home: homedir(),
|
|
1673
|
+
...(context.model ? { parentModel: context.model } : {}),
|
|
1617
1674
|
});
|
|
1618
1675
|
engine.updateRoutePolicy(
|
|
1619
1676
|
operationRouteResolution.ok
|
|
@@ -1636,12 +1693,7 @@ export function openPackageWorkflowControlEngine(
|
|
|
1636
1693
|
operationRouteResolution,
|
|
1637
1694
|
engine.routePolicyStatus(),
|
|
1638
1695
|
);
|
|
1639
|
-
|
|
1640
|
-
validation.value.stage === "abel-design" &&
|
|
1641
|
-
typeof outcome.runId === "string"
|
|
1642
|
-
? { design: design.status(outcome.runId) }
|
|
1643
|
-
: {};
|
|
1644
|
-
return { ...outcome, ...designStatus, routePolicy };
|
|
1696
|
+
return { ...outcome, routePolicy };
|
|
1645
1697
|
});
|
|
1646
1698
|
},
|
|
1647
1699
|
executeDesign(request: unknown) {
|
|
@@ -1781,10 +1833,9 @@ export function registerWorkflowControl(
|
|
|
1781
1833
|
},
|
|
1782
1834
|
stage: {
|
|
1783
1835
|
type: "string",
|
|
1784
|
-
enum: ["abel-
|
|
1836
|
+
enum: ["abel-implement"],
|
|
1785
1837
|
},
|
|
1786
1838
|
change: { type: "string" },
|
|
1787
|
-
provisionalKey: { type: "string" },
|
|
1788
1839
|
operationId: { type: "string" },
|
|
1789
1840
|
deliveryRevision: { type: "integer", minimum: 1 },
|
|
1790
1841
|
receiptHash: { type: "string" },
|
|
@@ -1818,7 +1869,7 @@ export function registerWorkflowControl(
|
|
|
1818
1869
|
label: "Abel Control",
|
|
1819
1870
|
description:
|
|
1820
1871
|
kind === "command"
|
|
1821
|
-
? "Private stage-bound Abel workflow control. Accepts durable
|
|
1872
|
+
? "Private stage-bound Abel workflow control. Accepts durable Implement change commands."
|
|
1822
1873
|
: "Private stage-bound Abel packet control. Accepts bounded Design and Diagnose packet operations.",
|
|
1823
1874
|
executionMode: "parallel",
|
|
1824
1875
|
parameters:
|
|
@@ -2007,10 +2058,6 @@ export function registerWorkflowControl(
|
|
|
2007
2058
|
["completed", "discarded", "rejected"].includes(String(payload.state))
|
|
2008
2059
|
) {
|
|
2009
2060
|
await deactivateStage();
|
|
2010
|
-
} else if (validation.value.stage === "abel-design") {
|
|
2011
|
-
// Design starts/statuses through the command surface, then switches to
|
|
2012
|
-
// its bounded evidence and artifact packet surface for the next turn.
|
|
2013
|
-
registerDispatchTool("packet");
|
|
2014
2061
|
}
|
|
2015
2062
|
let display: ReturnType<typeof activity.finalizeWorkflow>;
|
|
2016
2063
|
if (tuiControl) {
|
|
@@ -2068,7 +2115,7 @@ export function registerWorkflowControl(
|
|
|
2068
2115
|
if (activePrompt === "abel-design" && prompt !== "abel-design") {
|
|
2069
2116
|
restoreDesignTools();
|
|
2070
2117
|
}
|
|
2071
|
-
registerDispatchTool(prompt === "abel-
|
|
2118
|
+
registerDispatchTool(prompt === "abel-implement" ? "command" : "packet");
|
|
2072
2119
|
activePrompt = prompt;
|
|
2073
2120
|
const sessionId = ctx.sessionManager?.getSessionId?.();
|
|
2074
2121
|
if (typeof sessionId === "string") {
|
package/src/packet-runtime.ts
CHANGED
|
@@ -20,7 +20,6 @@ import { runtimeForWorkerRoute } from "./parent-provider.ts";
|
|
|
20
20
|
import {
|
|
21
21
|
loadRoutePolicy,
|
|
22
22
|
type RoutePolicy,
|
|
23
|
-
unavailableRoutePolicy,
|
|
24
23
|
type WorkerRole,
|
|
25
24
|
} from "./route-policy.ts";
|
|
26
25
|
import { type BrokerActivityUpdate, RunWorkerBroker } from "./worker-broker.ts";
|
|
@@ -319,7 +318,7 @@ export class PacketRuntime {
|
|
|
319
318
|
if (!agent) {
|
|
320
319
|
throw new Error("packet agent definition is unavailable");
|
|
321
320
|
}
|
|
322
|
-
const broker = this.#brokerFor(context
|
|
321
|
+
const broker = this.#brokerFor(context);
|
|
323
322
|
const routed = await broker.run({
|
|
324
323
|
runId: `${packet.stage}:${packet.id}`,
|
|
325
324
|
operationId: packet.id,
|
|
@@ -405,8 +404,8 @@ export class PacketRuntime {
|
|
|
405
404
|
return routed.value.child;
|
|
406
405
|
}
|
|
407
406
|
|
|
408
|
-
#brokerFor(
|
|
409
|
-
const root = path.resolve(cwd);
|
|
407
|
+
#brokerFor(context: PacketContext): RunWorkerBroker {
|
|
408
|
+
const root = path.resolve(context.cwd);
|
|
410
409
|
const existing = this.#brokers.get(root);
|
|
411
410
|
let policy = this.#routePolicy;
|
|
412
411
|
if (policy && existing) return existing;
|
|
@@ -414,12 +413,13 @@ export class PacketRuntime {
|
|
|
414
413
|
const resolution = loadRoutePolicy({
|
|
415
414
|
cwd: root,
|
|
416
415
|
...(this.#routePolicyHome ? { home: this.#routePolicyHome } : {}),
|
|
416
|
+
...(context.model ? { parentModel: context.model } : {}),
|
|
417
417
|
});
|
|
418
|
-
if (!resolution.ok
|
|
418
|
+
if (!resolution.ok) {
|
|
419
419
|
const code = resolution.diagnostics[0]?.code ?? "policy-invalid";
|
|
420
420
|
throw new Error(`route-policy-unavailable:${code}`);
|
|
421
421
|
}
|
|
422
|
-
policy = resolution.
|
|
422
|
+
policy = resolution.policy;
|
|
423
423
|
}
|
|
424
424
|
const serializedPolicy = JSON.stringify(policy);
|
|
425
425
|
if (existing && this.#brokerPolicies.get(root) === serializedPolicy) {
|
package/src/route-policy.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { lstatSync, readFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { ROLES } from "./contracts.ts";
|
|
@@ -21,6 +21,11 @@ export interface RouteCapabilities {
|
|
|
21
21
|
maxTokens: number;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export interface ParentModelCapabilities {
|
|
25
|
+
contextWindow: number;
|
|
26
|
+
maxTokens: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
interface RouteBase {
|
|
25
30
|
id: string;
|
|
26
31
|
kind: "inherited" | "custom";
|
|
@@ -50,10 +55,9 @@ export interface RoutePolicy {
|
|
|
50
55
|
|
|
51
56
|
export type RoutePolicySource =
|
|
52
57
|
| { kind: "project" | "user"; path: string }
|
|
53
|
-
| { kind: "
|
|
58
|
+
| { kind: "default" };
|
|
54
59
|
|
|
55
60
|
export type RoutePolicyDiagnosticCode =
|
|
56
|
-
| "policy-missing"
|
|
57
61
|
| "policy-unreadable"
|
|
58
62
|
| "policy-invalid-json"
|
|
59
63
|
| "policy-invalid"
|
|
@@ -77,7 +81,7 @@ export type ParsedRoutePolicy =
|
|
|
77
81
|
export type RoutePolicyResolution =
|
|
78
82
|
| {
|
|
79
83
|
ok: true;
|
|
80
|
-
source:
|
|
84
|
+
source: RoutePolicySource;
|
|
81
85
|
policy: RoutePolicy;
|
|
82
86
|
}
|
|
83
87
|
| {
|
|
@@ -87,8 +91,8 @@ export type RoutePolicyResolution =
|
|
|
87
91
|
};
|
|
88
92
|
|
|
89
93
|
/**
|
|
90
|
-
* Internal fail-closed policy used while
|
|
91
|
-
*
|
|
94
|
+
* Internal fail-closed policy used while a selected explicit policy is
|
|
95
|
+
* invalid. It deliberately contains no inherited-parent route:
|
|
92
96
|
* the broker therefore reports endpoint-unavailable without making a Worker
|
|
93
97
|
* request, while the local durable control plane remains available.
|
|
94
98
|
*
|
|
@@ -351,8 +355,16 @@ export function parseRoutePolicy(value: unknown): ParsedRoutePolicy {
|
|
|
351
355
|
};
|
|
352
356
|
}
|
|
353
357
|
|
|
354
|
-
/**
|
|
355
|
-
export function parentRoutePolicy(
|
|
358
|
+
/** Default parent-model route used when no explicit policy file exists. */
|
|
359
|
+
export function parentRoutePolicy(
|
|
360
|
+
parentModel?: ParentModelCapabilities,
|
|
361
|
+
): RoutePolicy {
|
|
362
|
+
const contextWindow = positiveInteger(parentModel?.contextWindow)
|
|
363
|
+
? parentModel.contextWindow
|
|
364
|
+
: 1;
|
|
365
|
+
const maxTokens = positiveInteger(parentModel?.maxTokens)
|
|
366
|
+
? Math.min(parentModel.maxTokens, contextWindow)
|
|
367
|
+
: 1;
|
|
356
368
|
const parsed = parseRoutePolicy({
|
|
357
369
|
version: ROUTE_POLICY_VERSION,
|
|
358
370
|
routes: {
|
|
@@ -361,8 +373,8 @@ export function parentRoutePolicy(): RoutePolicy {
|
|
|
361
373
|
capabilities: {
|
|
362
374
|
roles: [...ROLES],
|
|
363
375
|
dialects: [...ROUTE_DIALECTS],
|
|
364
|
-
contextWindow
|
|
365
|
-
maxTokens
|
|
376
|
+
contextWindow,
|
|
377
|
+
maxTokens,
|
|
366
378
|
},
|
|
367
379
|
},
|
|
368
380
|
},
|
|
@@ -372,25 +384,41 @@ export function parentRoutePolicy(): RoutePolicy {
|
|
|
372
384
|
return parsed.policy;
|
|
373
385
|
}
|
|
374
386
|
|
|
375
|
-
function selectedSource(
|
|
387
|
+
function selectedSource(
|
|
388
|
+
cwd: string,
|
|
389
|
+
home: string,
|
|
390
|
+
): { kind: "project" | "user"; path: string } | { kind: "none" } {
|
|
391
|
+
const mayExist = (candidate: string): boolean => {
|
|
392
|
+
try {
|
|
393
|
+
return lstatSync(candidate, { throwIfNoEntry: false }) !== undefined;
|
|
394
|
+
} catch {
|
|
395
|
+
// Permission and path-observation failures are explicit unreadable
|
|
396
|
+
// policy candidates, not proof that the policy is absent.
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
376
400
|
const projectPath = path.join(cwd, ".pi", "cadence", "routes.json");
|
|
377
|
-
if (
|
|
401
|
+
if (mayExist(projectPath)) return { kind: "project", path: projectPath };
|
|
378
402
|
const userPath = path.join(home, ".pi", "agent", "cadence", "routes.json");
|
|
379
|
-
if (
|
|
403
|
+
if (mayExist(userPath)) return { kind: "user", path: userPath };
|
|
380
404
|
return { kind: "none" };
|
|
381
405
|
}
|
|
382
406
|
|
|
383
407
|
export function loadRoutePolicy(
|
|
384
|
-
options: {
|
|
408
|
+
options: {
|
|
409
|
+
cwd?: string;
|
|
410
|
+
home?: string;
|
|
411
|
+
parentModel?: ParentModelCapabilities;
|
|
412
|
+
} = {},
|
|
385
413
|
): RoutePolicyResolution {
|
|
386
414
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
387
415
|
const home = path.resolve(options.home ?? homedir());
|
|
388
416
|
const source = selectedSource(cwd, home);
|
|
389
417
|
if (source.kind === "none") {
|
|
390
418
|
return {
|
|
391
|
-
ok:
|
|
392
|
-
source,
|
|
393
|
-
|
|
419
|
+
ok: true,
|
|
420
|
+
source: { kind: "default" },
|
|
421
|
+
policy: parentRoutePolicy(options.parentModel),
|
|
394
422
|
};
|
|
395
423
|
}
|
|
396
424
|
let bytes: string;
|
package/src/run-state.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
export const RUN_STAGES = ["abel-design", "abel-implement"] as const;
|
|
4
|
+
export type RunStage = (typeof RUN_STAGES)[number];
|
|
3
5
|
|
|
4
6
|
export const RUN_STATES = [
|
|
5
7
|
"created",
|
|
@@ -57,7 +59,7 @@ export interface DeliveryBindingProjection {
|
|
|
57
59
|
export interface RunProjection {
|
|
58
60
|
runId: string;
|
|
59
61
|
rootHash: string;
|
|
60
|
-
stage:
|
|
62
|
+
stage: RunStage;
|
|
61
63
|
change?: string;
|
|
62
64
|
provisionalKey?: string;
|
|
63
65
|
state: RunState;
|
package/src/run-store.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { chmodSync, closeSync, lstatSync, openSync } from "node:fs";
|
|
3
3
|
import { DatabaseSync } from "node:sqlite";
|
|
4
|
-
import type { ControlStage } from "./control-contracts.ts";
|
|
5
4
|
import {
|
|
6
5
|
createRunEvent,
|
|
7
6
|
type DeliveryGate,
|
|
8
7
|
type RunEvent,
|
|
9
8
|
type RunProjection,
|
|
9
|
+
type RunStage,
|
|
10
10
|
type RunState,
|
|
11
11
|
reduceRunEvents,
|
|
12
12
|
ZERO_EVENT_HASH,
|
|
@@ -37,6 +37,7 @@ interface EventRow {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
interface OperationRow {
|
|
40
|
+
kind: string;
|
|
40
41
|
state: string;
|
|
41
42
|
outcome_json: string | null;
|
|
42
43
|
lease_token: string | null;
|
|
@@ -49,7 +50,7 @@ export interface RunStoreOptions {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export interface StartRunInput {
|
|
52
|
-
stage:
|
|
53
|
+
stage: RunStage;
|
|
53
54
|
change?: string;
|
|
54
55
|
provisionalKey?: string;
|
|
55
56
|
operationId: string;
|
|
@@ -338,7 +339,7 @@ export class RunStore {
|
|
|
338
339
|
#operation(runId: string, operationId: string): OperationRow | undefined {
|
|
339
340
|
return this.#database
|
|
340
341
|
.prepare(
|
|
341
|
-
`SELECT state, outcome_json, lease_token, lease_expires_at
|
|
342
|
+
`SELECT kind, state, outcome_json, lease_token, lease_expires_at
|
|
342
343
|
FROM operations WHERE run_id = ? AND operation_id = ?`,
|
|
343
344
|
)
|
|
344
345
|
.get(runId, operationId) as OperationRow | undefined;
|
|
@@ -347,8 +348,10 @@ export class RunStore {
|
|
|
347
348
|
#operationOutcome(
|
|
348
349
|
runId: string,
|
|
349
350
|
operationId: string,
|
|
351
|
+
kind: string,
|
|
350
352
|
): RunProjection | undefined {
|
|
351
353
|
const row = this.#operation(runId, operationId);
|
|
354
|
+
if (row && row.kind !== kind) throw new Error("operation-id-conflict");
|
|
352
355
|
if (row?.state !== "committed" || row.outcome_json === null)
|
|
353
356
|
return undefined;
|
|
354
357
|
return parseProjection(row.outcome_json);
|
|
@@ -465,6 +468,7 @@ export class RunStore {
|
|
|
465
468
|
const replay = this.#operationOutcome(
|
|
466
469
|
existing.run_id,
|
|
467
470
|
input.operationId,
|
|
471
|
+
"start",
|
|
468
472
|
);
|
|
469
473
|
if (replay) return replay;
|
|
470
474
|
const projection = parseProjection(existing.projection_json);
|
|
@@ -539,7 +543,11 @@ export class RunStore {
|
|
|
539
543
|
requireIdentifier(input.operationId, "operation-id");
|
|
540
544
|
if (!CHANGE_NAME.test(input.change)) throw new Error("invalid-change");
|
|
541
545
|
return this.#transaction(() => {
|
|
542
|
-
const replay = this.#operationOutcome(
|
|
546
|
+
const replay = this.#operationOutcome(
|
|
547
|
+
input.runId,
|
|
548
|
+
input.operationId,
|
|
549
|
+
"change-bound",
|
|
550
|
+
);
|
|
543
551
|
if (replay) return replay;
|
|
544
552
|
const current = this.status(input.runId);
|
|
545
553
|
if (current.stage !== "abel-design" || !current.provisionalKey) {
|
|
@@ -597,7 +605,11 @@ export class RunStore {
|
|
|
597
605
|
if (input.lease.runId !== input.runId) throw new Error("lease-fenced");
|
|
598
606
|
this.#assertLease(input.lease);
|
|
599
607
|
}
|
|
600
|
-
const replay = this.#operationOutcome(
|
|
608
|
+
const replay = this.#operationOutcome(
|
|
609
|
+
input.runId,
|
|
610
|
+
input.operationId,
|
|
611
|
+
"delivery-bound",
|
|
612
|
+
);
|
|
601
613
|
if (replay) return replay;
|
|
602
614
|
const existing = this.#database
|
|
603
615
|
.prepare(
|
|
@@ -911,7 +923,11 @@ export class RunStore {
|
|
|
911
923
|
if (input.lease.runId !== input.runId) throw new Error("lease-fenced");
|
|
912
924
|
this.#assertLease(input.lease);
|
|
913
925
|
}
|
|
914
|
-
const replay = this.#operationOutcome(
|
|
926
|
+
const replay = this.#operationOutcome(
|
|
927
|
+
input.runId,
|
|
928
|
+
input.operationId,
|
|
929
|
+
"state-transitioned",
|
|
930
|
+
);
|
|
915
931
|
if (replay) return replay;
|
|
916
932
|
const current = this.status(input.runId);
|
|
917
933
|
const projection = this.#appendEvent(input.runId, "state-transitioned", {
|