ai-ctrl-workflow-logic 0.1.7 → 0.1.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-ctrl-workflow-logic",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "private": false,
5
5
  "description": "WorkCtrlFlow-backed AI Ctrl Workflow adapter logic",
6
6
  "type": "module",
@@ -11,12 +11,14 @@
11
11
  "./filesystem": "./src/filesystem.ts"
12
12
  },
13
13
  "dependencies": {
14
- "ai-ctrl-workflow-contract": "0.1.4",
15
- "ai-workflow-contract": "0.1.7",
16
- "ai-workflow-logic": "0.1.8",
14
+ "ai-ctrl-workflow-contract": "0.1.5",
15
+ "ai-workflow-contract": "0.1.8",
16
+ "ai-workflow-logic": "0.1.9",
17
17
  "flow-step-space-logic": "0.1.1",
18
18
  "instant-ctrl-flow-contract": "0.1.2",
19
19
  "instant-ctrl-flow-logic": "0.1.5",
20
+ "task-manager-contract": "0.1.3",
21
+ "task-manager-logic": "0.1.6",
20
22
  "work-ctrl-flow-contract": "0.1.3",
21
23
  "work-ctrl-flow-logic": "0.1.4"
22
24
  },
@@ -0,0 +1,264 @@
1
+ import {
2
+ createAIOrganizationTaskProfile,
3
+ normalizeHolonTaskSnapshotReceipt,
4
+ normalizeHolonTaskTarget,
5
+ type FrozenHolonTaskTarget,
6
+ type HolonTaskSnapshotAdoptionReceipt,
7
+ type HolonTaskSnapshotReceipt,
8
+ } from 'ai-workflow-contract';
9
+ import { assertFrozenHolonTaskTarget } from 'ai-workflow-logic/run-freeze';
10
+ import {
11
+ createHolonTaskSpace,
12
+ prepareHolonTaskSnapshot,
13
+ type CreateHolonTaskSpaceConfig,
14
+ type CreateHolonTaskSpaceRuntime,
15
+ type HolonOrganizationSnapshotReadPort,
16
+ } from 'ai-workflow-logic';
17
+ import type {
18
+ TaskClaimToken,
19
+ TaskCommandReceipt,
20
+ TaskRecord,
21
+ TaskSettlementReceipt,
22
+ TaskSpaceSnapshot,
23
+ } from 'task-manager-contract';
24
+ import {
25
+ cancelTask,
26
+ replanTask,
27
+ type TaskManagerRuntime,
28
+ type TaskProcessorResult,
29
+ } from 'task-manager-logic';
30
+
31
+ export interface AICtrlHolonTaskRuntime {
32
+ readonly taskManager: TaskManagerRuntime;
33
+ readonly organizationSnapshots: HolonOrganizationSnapshotReadPort;
34
+ }
35
+
36
+ export interface OpenAICtrlHolonTaskInput {
37
+ readonly frozenTarget: FrozenHolonTaskTarget;
38
+ readonly commandId: string;
39
+ readonly taskSpaceId: string;
40
+ readonly taskId: string;
41
+ readonly taskName: string;
42
+ readonly createdAt: string;
43
+ }
44
+
45
+ export interface OpenAICtrlHolonTaskResult {
46
+ readonly snapshot: TaskSpaceSnapshot;
47
+ readonly receipt: TaskCommandReceipt;
48
+ readonly snapshotReceipt: HolonTaskSnapshotReceipt;
49
+ readonly created: boolean;
50
+ readonly replayed: boolean;
51
+ }
52
+
53
+ export async function openAICtrlHolonTask(
54
+ runtime: AICtrlHolonTaskRuntime,
55
+ input: OpenAICtrlHolonTaskInput,
56
+ config: CreateHolonTaskSpaceConfig,
57
+ ): Promise<OpenAICtrlHolonTaskResult> {
58
+ const proof = assertFrozenHolonTaskTarget(input.frozenTarget);
59
+ const existing = await runtime.taskManager.owner.readSnapshot(input.taskSpaceId);
60
+ if (existing) {
61
+ const task = exactOrganizationTask(existing, input.taskId);
62
+ const target = normalizeHolonTaskTarget(task.profile.facts.target);
63
+ if (JSON.stringify(target) !== JSON.stringify(proof.target)) {
64
+ throw new Error('AI_CTRL_HOLON_TASK_TARGET_CONFLICT: existing TaskSpace is bound to another frozen target');
65
+ }
66
+ const receipt = await runtime.taskManager.owner.readReceiptByCommand(input.taskSpaceId, input.commandId);
67
+ if (!receipt) throw new Error('AI_CTRL_HOLON_TASK_OPEN_RECEIPT_MISSING: existing TaskSpace has no matching create receipt');
68
+ return Object.freeze({
69
+ snapshot: existing,
70
+ receipt,
71
+ snapshotReceipt: normalizeHolonTaskSnapshotReceipt(task.profile.facts.snapshotReceipt),
72
+ created: false,
73
+ replayed: true,
74
+ });
75
+ }
76
+ const created = await createHolonTaskSpace(runtime as CreateHolonTaskSpaceRuntime, {
77
+ commandId: input.commandId,
78
+ taskSpaceId: input.taskSpaceId,
79
+ taskId: input.taskId,
80
+ taskName: input.taskName,
81
+ target: proof.target,
82
+ createdAt: input.createdAt,
83
+ }, config);
84
+ return Object.freeze({ ...created, created: true });
85
+ }
86
+
87
+ export type ObserveAICtrlHolonTaskResult =
88
+ | Readonly<{
89
+ readonly kind: 'waiting';
90
+ readonly taskSpaceId: string;
91
+ readonly taskId: string;
92
+ readonly expectedSettlementCommandId: string;
93
+ readonly observedRevision: number;
94
+ }>
95
+ | Readonly<{
96
+ readonly kind: 'settled';
97
+ readonly receipt: TaskSettlementReceipt;
98
+ readonly task: TaskRecord;
99
+ }>;
100
+
101
+ export async function observeAICtrlHolonTask(
102
+ runtime: Pick<AICtrlHolonTaskRuntime, 'taskManager'>,
103
+ input: Readonly<{
104
+ readonly taskSpaceId: string;
105
+ readonly taskId: string;
106
+ readonly settlementCommandId: string;
107
+ }>,
108
+ _config: Readonly<Record<string, never>>,
109
+ ): Promise<ObserveAICtrlHolonTaskResult> {
110
+ const snapshot = await runtime.taskManager.owner.readSnapshot(input.taskSpaceId);
111
+ if (!snapshot) throw new Error(`AI_CTRL_HOLON_TASK_SPACE_MISSING: ${input.taskSpaceId}`);
112
+ const task = exactOrganizationTask(snapshot, input.taskId);
113
+ const receipt = await runtime.taskManager.owner.readReceiptByCommand(input.taskSpaceId, input.settlementCommandId);
114
+ if (!receipt) return Object.freeze({
115
+ kind: 'waiting',
116
+ taskSpaceId: input.taskSpaceId,
117
+ taskId: input.taskId,
118
+ expectedSettlementCommandId: input.settlementCommandId,
119
+ observedRevision: snapshot.revision,
120
+ });
121
+ if (receipt.kind !== 'task-settlement-receipt' || receipt.taskId !== input.taskId
122
+ || !['Succeeded', 'Failed', 'Cancelled'].includes(task.status)
123
+ || receipt.status !== task.status) {
124
+ throw new Error('AI_CTRL_HOLON_TASK_SETTLEMENT_RECEIPT_MISMATCH: receipt does not bind the terminal task projection');
125
+ }
126
+ return Object.freeze({ kind: 'settled', receipt, task });
127
+ }
128
+
129
+ export interface ReplanAICtrlHolonTaskInput {
130
+ readonly frozenSuccessorTarget: FrozenHolonTaskTarget;
131
+ readonly taskSpaceId: string;
132
+ readonly previousTaskId: string;
133
+ readonly successorTaskId: string;
134
+ readonly successorTaskName: string;
135
+ readonly cancelCommandId: string;
136
+ readonly replanCommandId: string;
137
+ readonly planId: string;
138
+ readonly replannedAt: string;
139
+ readonly activeClaim?: TaskClaimToken;
140
+ }
141
+
142
+ export interface ReplanAICtrlHolonTaskResult extends TaskProcessorResult {
143
+ readonly adoptionReceipt: HolonTaskSnapshotAdoptionReceipt;
144
+ readonly successorSnapshotReceipt: HolonTaskSnapshotReceipt;
145
+ }
146
+
147
+ export async function replanAICtrlHolonTask(
148
+ runtime: AICtrlHolonTaskRuntime,
149
+ input: ReplanAICtrlHolonTaskInput,
150
+ config: CreateHolonTaskSpaceConfig,
151
+ ): Promise<ReplanAICtrlHolonTaskResult> {
152
+ const successorProof = assertFrozenHolonTaskTarget(input.frozenSuccessorTarget);
153
+ const initial = await requiredSnapshot(runtime.taskManager, input.taskSpaceId);
154
+ const previous = exactOrganizationTask(initial, input.previousTaskId);
155
+ const previousTarget = normalizeHolonTaskTarget(previous.profile.facts.target);
156
+ const previousSnapshotReceipt = normalizeHolonTaskSnapshotReceipt(previous.profile.facts.snapshotReceipt);
157
+ if (previousTarget.executionBinding.ref !== successorProof.target.executionBinding.ref
158
+ || previousTarget.executionBinding.digest !== successorProof.target.executionBinding.digest) {
159
+ throw new Error('AI_CTRL_HOLON_TASK_REPLAN_SUCCESSOR_INSTANCE_REQUIRED: execution binding changed');
160
+ }
161
+ if (previousTarget.invocation.workflowKind !== successorProof.target.invocation.workflowKind
162
+ || previousTarget.invocation.workflowRef !== successorProof.target.invocation.workflowRef
163
+ || previousTarget.invocation.nodeId !== successorProof.target.invocation.nodeId) {
164
+ throw new Error('AI_CTRL_HOLON_TASK_REPLAN_SUCCESSOR_INSTANCE_REQUIRED: workflow executable identity changed');
165
+ }
166
+
167
+ // Read and validate the successor authority before mutating the current task.
168
+ // The following cancel + replan commands are independently idempotent, so a
169
+ // retry can converge after an interruption between their commits.
170
+ const prepared = await prepareHolonTaskSnapshot(runtime, {
171
+ taskSpaceId: input.taskSpaceId,
172
+ target: successorProof.target,
173
+ });
174
+
175
+ let current = initial;
176
+ if (!['Succeeded', 'Failed', 'Cancelled'].includes(previous.status)) {
177
+ if (previous.activeClaim && (!input.activeClaim || JSON.stringify(previous.activeClaim) !== JSON.stringify(input.activeClaim))) {
178
+ throw new Error('AI_CTRL_HOLON_TASK_REPLAN_CLAIM_REQUIRED: active task requires its exact claim capability');
179
+ }
180
+ const cancelled = await cancelTask(runtime.taskManager, {
181
+ kind: 'task.cancel',
182
+ commandId: input.cancelCommandId,
183
+ taskSpaceId: input.taskSpaceId,
184
+ expectedRevision: current.revision,
185
+ taskId: previous.taskId,
186
+ cancelledAt: input.replannedAt,
187
+ reason: 'organization-snapshot-replan',
188
+ ...(input.activeClaim === undefined ? {} : { claim: input.activeClaim }),
189
+ }, config);
190
+ current = cancelled.snapshot;
191
+ }
192
+
193
+ const adoptionReceipt: HolonTaskSnapshotAdoptionReceipt = Object.freeze({
194
+ kind: 'holon-task-snapshot-adoption-receipt',
195
+ schemaVersion: 'ai-workflow.holon-task-snapshot-adoption-receipt/v1',
196
+ taskSpaceId: input.taskSpaceId,
197
+ previousTaskId: input.previousTaskId,
198
+ successorTaskId: input.successorTaskId,
199
+ previousIssuerReceiptId: previousSnapshotReceipt.issuerReceiptId,
200
+ successorIssuerReceiptId: prepared.snapshotReceipt.issuerReceiptId,
201
+ executionBindingDigest: previousTarget.executionBinding.digest,
202
+ adoptedAt: input.replannedAt,
203
+ });
204
+ const priorAdoptions = Array.isArray(previous.profile.facts.snapshotAdoptions)
205
+ ? previous.profile.facts.snapshotAdoptions
206
+ : [];
207
+ const successorProfile = createAIOrganizationTaskProfile(
208
+ prepared.target,
209
+ prepared.snapshotReceipt,
210
+ [...priorAdoptions, adoptionReceipt],
211
+ );
212
+ const successorDefinition = Object.freeze({
213
+ kind: 'task' as const,
214
+ taskId: input.successorTaskId,
215
+ name: input.successorTaskName,
216
+ order: current.definition.tasks.length,
217
+ profile: successorProfile,
218
+ inputArtifacts: prepared.artifactRefs,
219
+ });
220
+ const rootCount = current.definition.relations.filter((relation) => relation.kind === 'parent-child' && relation.parentTaskId === null).length;
221
+ const replanned = await replanTask(runtime.taskManager, {
222
+ kind: 'task.replan',
223
+ commandId: input.replanCommandId,
224
+ taskSpaceId: input.taskSpaceId,
225
+ expectedRevision: current.revision,
226
+ replannedAt: input.replannedAt,
227
+ plan: {
228
+ kind: 'task-plan',
229
+ planId: input.planId,
230
+ tasks: [...current.definition.tasks, successorDefinition],
231
+ relations: [
232
+ ...current.definition.relations,
233
+ {
234
+ kind: 'parent-child',
235
+ relationId: `root:${input.successorTaskId}`,
236
+ parentTaskId: null,
237
+ childTaskId: input.successorTaskId,
238
+ order: rootCount,
239
+ },
240
+ ],
241
+ reason: 'organization-snapshot-replan',
242
+ },
243
+ artifactBodies: prepared.artifactBodies,
244
+ }, config);
245
+ return Object.freeze({
246
+ ...replanned,
247
+ adoptionReceipt,
248
+ successorSnapshotReceipt: prepared.snapshotReceipt,
249
+ });
250
+ }
251
+
252
+ function exactOrganizationTask(snapshot: TaskSpaceSnapshot, taskId: string): TaskRecord {
253
+ const task = snapshot.tasks.find((candidate) => candidate.taskId === taskId);
254
+ if (!task || task.profile.profileKind !== 'depa.ai.organization-task' || task.profile.schemaVersion !== 1) {
255
+ throw new Error(`AI_CTRL_HOLON_TASK_PROFILE_MISMATCH: ${taskId}`);
256
+ }
257
+ return task;
258
+ }
259
+
260
+ async function requiredSnapshot(runtime: TaskManagerRuntime, taskSpaceId: string): Promise<TaskSpaceSnapshot> {
261
+ const snapshot = await runtime.owner.readSnapshot(taskSpaceId);
262
+ if (!snapshot) throw new Error(`AI_CTRL_HOLON_TASK_SPACE_MISSING: ${taskSpaceId}`);
263
+ return snapshot;
264
+ }
package/src/index.ts CHANGED
@@ -330,3 +330,5 @@ export async function getAICtrlWorkflowRunRecord(
330
330
  const snapshot = await controller.store.load(treeId);
331
331
  return snapshot ? projectAICtrlWorkflowRunRecord(snapshot, await controller.getOutcome(treeId)) : undefined;
332
332
  }
333
+
334
+ export * from './holon-task.js';