ai-ctrl-workflow-logic 0.1.4 → 0.1.5

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.4",
3
+ "version": "0.1.5",
4
4
  "private": false,
5
5
  "description": "WorkCtrlFlow-backed AI Ctrl Workflow adapter logic",
6
6
  "type": "module",
@@ -11,14 +11,14 @@
11
11
  "./filesystem": "./src/filesystem.ts"
12
12
  },
13
13
  "dependencies": {
14
- "ai-ctrl-workflow-contract": "0.1.2",
15
- "ai-workflow-contract": "0.1.5",
16
- "ai-workflow-logic": "0.1.5",
17
- "flow-step-space-logic": "0.1.0",
18
- "instant-ctrl-flow-contract": "0.1.1",
19
- "instant-ctrl-flow-logic": "0.1.3",
20
- "work-ctrl-flow-contract": "0.1.2",
21
- "work-ctrl-flow-logic": "0.1.2"
14
+ "ai-ctrl-workflow-contract": "0.1.3",
15
+ "ai-workflow-contract": "0.1.6",
16
+ "ai-workflow-logic": "0.1.6",
17
+ "flow-step-space-logic": "0.1.1",
18
+ "instant-ctrl-flow-contract": "0.1.2",
19
+ "instant-ctrl-flow-logic": "0.1.4",
20
+ "work-ctrl-flow-contract": "0.1.3",
21
+ "work-ctrl-flow-logic": "0.1.3"
22
22
  },
23
23
  "files": [
24
24
  "src"
package/src/filesystem.ts CHANGED
@@ -7,9 +7,25 @@ import type {
7
7
  FlowClosedValue,
8
8
  AIWorkflowProfileDurableState,
9
9
  } from 'ai-workflow-contract';
10
- import { FilesystemFlowRunCheckpointStore } from 'ai-workflow-logic/checkpoint-filesystem';
10
+ import {
11
+ createFilesystemFlowRunCheckpointRuntime,
12
+ createFilesystemFlowRunCheckpointStore,
13
+ type FilesystemFlowRunCheckpointStore,
14
+ } from 'ai-workflow-logic/checkpoint-filesystem';
15
+ import {
16
+ createInitialRunStepExtensions,
17
+ normalizeFlowClosedValue,
18
+ type FlowRunProfileAuthorityPort,
19
+ type FlowRunProfileAuthorityRequest,
20
+ type FrozenFlowRunProfileAuthority,
21
+ } from 'ai-workflow-logic';
11
22
  import { createFilesystemFlowCodeResolver, type FlowModuleImporter } from 'instant-ctrl-flow-logic/filesystem';
12
- import { assembleDefinitionStepProfileDirectory } from 'flow-step-space-logic/filesystem';
23
+ import type { FlowNodeSpec } from 'instant-ctrl-flow-contract';
24
+ import {
25
+ assembleDefinitionStepProfileDirectory,
26
+ createFilesystemDefinitionStepProfileRuntime,
27
+ } from 'flow-step-space-logic/filesystem';
28
+ import type { DefinitionStepAssemblyRuntime, DefinitionStepForest } from 'flow-step-space-logic';
13
29
  import {
14
30
  loadMaterializedFlowInstance,
15
31
  materializeFlowInstance,
@@ -34,15 +50,20 @@ export interface MaterializedAICtrlWorkflowControllerOptions
34
50
  readonly durableAI: AIWorkflowProfileDurableState;
35
51
  readonly projectCheckpointOutput?: (snapshot: WorkCtrlFlowSnapshot) => FlowClosedValue;
36
52
  readonly importModule?: FlowModuleImporter;
53
+ readonly extensionCodecs?: DefinitionStepAssemblyRuntime['extensionCodecs'];
37
54
  readonly agentEffects?: AIAgentHostEffectPort;
38
55
  readonly agentTaskProofs?: Readonly<Record<string, FrozenAIAgentTaskBinding>>;
39
56
  }
40
57
 
41
58
  export interface MaterializedAICtrlWorkflowController extends AICtrlWorkflowController {
42
59
  readonly checkpointStore: FilesystemFlowRunCheckpointStore;
60
+ readonly profileAuthority: FlowRunProfileAuthorityPort;
61
+ readonly extensionCodecs: DefinitionStepAssemblyRuntime['extensionCodecs'];
43
62
  readonly instanceId: string;
44
63
  }
45
64
 
65
+ const emptyExtensionCodecs = Object.freeze({ resolve: () => undefined });
66
+
46
67
  /** Frozen AICtrl definition plus canonical filesystem checkpoint owner. */
47
68
  export function createMaterializedAICtrlWorkflowControllerFromDirectory(
48
69
  options: MaterializedAICtrlWorkflowControllerOptions,
@@ -50,10 +71,11 @@ export function createMaterializedAICtrlWorkflowControllerFromDirectory(
50
71
  if (options.agentEffects && (!options.agentTaskProofs || Object.keys(options.agentTaskProofs).length === 0)) {
51
72
  throw new Error('AI_AGENT_TASK_PROOF_REQUIRED: materialized AI Ctrl Agent effects require run-freeze task proofs.');
52
73
  }
74
+ const extensionCodecs = options.extensionCodecs ?? emptyExtensionCodecs;
53
75
  const instancesDir = path.join(options.storeRoot, 'instances');
54
76
  const instanceDir = path.join(instancesDir, options.instanceId);
55
77
  const validateDefinition = (definitionDir: string) => {
56
- loadAICtrlBinding(definitionDir);
78
+ loadAICtrlBinding(definitionDir, extensionCodecs);
57
79
  };
58
80
  const instance = fs.existsSync(instanceDir)
59
81
  ? loadMaterializedFlowInstance(instanceDir, options.instanceId, validateDefinition)
@@ -69,8 +91,25 @@ export function createMaterializedAICtrlWorkflowControllerFromDirectory(
69
91
  if (options.bundleDir || options.source) throw new Error('bundleDir and source identity must be provided together.');
70
92
  throw new Error('bundleDir and source identity are required for first admission.');
71
93
  })();
72
- const binding = loadAICtrlBinding(instance.definitionDir);
73
- const checkpointStore = new FilesystemFlowRunCheckpointStore({ storeRoot: options.storeRoot });
94
+ const binding = loadAICtrlBinding(instance.definitionDir, extensionCodecs);
95
+ const profileAuthority = createMaterializedAICtrlWorkflowProfileAuthority({
96
+ storeRoot: options.storeRoot,
97
+ extensionCodecs,
98
+ });
99
+ const frozenProfile = profileAuthority.load({
100
+ instanceId: options.instanceId,
101
+ definition: instance.descriptor.definition,
102
+ profileKind: 'AICtrlWorkflow',
103
+ });
104
+ const checkpointStore = createFilesystemFlowRunCheckpointStore(
105
+ createFilesystemFlowRunCheckpointRuntime({
106
+ storeRoot: options.storeRoot,
107
+ extensionCodecs,
108
+ profileAuthority,
109
+ }),
110
+ {},
111
+ {},
112
+ );
74
113
  const store = createAICtrlWorkflowCheckpointStore(
75
114
  { checkpointStore },
76
115
  {
@@ -78,6 +117,7 @@ export function createMaterializedAICtrlWorkflowControllerFromDirectory(
78
117
  definition: instance.descriptor.definition,
79
118
  config: options.checkpointConfig,
80
119
  ai: options.durableAI,
120
+ initialStepExtensions: createInitialRunStepExtensions(frozenProfile),
81
121
  ...(options.projectCheckpointOutput ? { projectOutput: options.projectCheckpointOutput } : {}),
82
122
  },
83
123
  );
@@ -90,6 +130,7 @@ export function createMaterializedAICtrlWorkflowControllerFromDirectory(
90
130
  durableAI: _durableAI,
91
131
  projectCheckpointOutput: _projectCheckpointOutput,
92
132
  importModule: _importModule,
133
+ extensionCodecs: _extensionCodecs,
93
134
  agentEffects: _agentEffects,
94
135
  agentTaskProofs: _agentTaskProofs,
95
136
  ...controllerOptions
@@ -125,11 +166,23 @@ export function createMaterializedAICtrlWorkflowControllerFromDirectory(
125
166
  return controller.start(treeId, startOptions);
126
167
  },
127
168
  };
128
- return Object.assign(materializedController, { checkpointStore, instanceId: options.instanceId });
169
+ return Object.assign(materializedController, {
170
+ checkpointStore,
171
+ profileAuthority,
172
+ extensionCodecs,
173
+ instanceId: options.instanceId,
174
+ });
129
175
  }
130
176
 
131
- function loadAICtrlBinding(definitionDir: string) {
132
- const assembled = assembleDefinitionStepProfileDirectory(definitionDir, 'AICtrlWorkflow');
177
+ function loadAICtrlBinding(
178
+ definitionDir: string,
179
+ extensionCodecs: DefinitionStepAssemblyRuntime['extensionCodecs'],
180
+ ) {
181
+ const assembled = assembleDefinitionStepProfileDirectory(
182
+ createFilesystemDefinitionStepProfileRuntime({ rootDir: definitionDir, extensionCodecs }),
183
+ {},
184
+ { profileRoot: 'AICtrlWorkflow' },
185
+ );
133
186
  if (assembled.diagnostics.length > 0) {
134
187
  throw new Error(`AICtrlWorkflow StepSpace is invalid: ${assembled.diagnostics.map((item) => item.code).join(', ')}`);
135
188
  }
@@ -143,3 +196,60 @@ function loadAICtrlBinding(definitionDir: string) {
143
196
  ? { ...loaded.binding, definition: { ...loaded.binding.definition, definitionStepForest: assembled.forest } }
144
197
  : loaded.binding;
145
198
  }
199
+
200
+ function profileAuthorityFromBinding(
201
+ binding: ReturnType<typeof loadAICtrlBinding>,
202
+ ): FrozenFlowRunProfileAuthority {
203
+ const forest = binding.definition.definitionStepForest as DefinitionStepForest | undefined;
204
+ const definitionStepIds: string[] = [];
205
+ const visit = (node: FlowNodeSpec): void => {
206
+ if (node.id) definitionStepIds.push(node.id);
207
+ node.children.forEach(visit);
208
+ Object.values(node.sections).forEach(visit);
209
+ };
210
+ binding.definition.statements.forEach(visit);
211
+ return Object.freeze({
212
+ profileKind: 'AICtrlWorkflow' as const,
213
+ definitionStepIds: Object.freeze([...definitionStepIds]),
214
+ extensionsByStepId: Object.freeze(Object.fromEntries((forest?.stepOrder ?? []).flatMap((stepId) => {
215
+ const extensions = Object.fromEntries(Object.entries(forest!.steps[stepId].extensions).map(([kind, extension]) => [kind, Object.freeze({
216
+ ...(extension.schemaRef === undefined ? {} : { schemaRef: extension.schemaRef }),
217
+ initialValue: extension.value as import('ai-workflow-contract').FlowClosedValue,
218
+ })]));
219
+ return Object.keys(extensions).length === 0 ? [] : [[stepId, Object.freeze(extensions)]];
220
+ }))),
221
+ });
222
+ }
223
+
224
+ export function createMaterializedAICtrlWorkflowProfileAuthority(
225
+ runtime: Pick<MaterializedAICtrlWorkflowControllerOptions, 'storeRoot' | 'extensionCodecs'>,
226
+ ): FlowRunProfileAuthorityPort {
227
+ const extensionCodecs = runtime.extensionCodecs ?? emptyExtensionCodecs;
228
+ return Object.freeze({
229
+ load(request: FlowRunProfileAuthorityRequest) {
230
+ if (request.profileKind !== 'AICtrlWorkflow') {
231
+ throw new Error(`AI Ctrl profile authority cannot load ${request.profileKind}.`);
232
+ }
233
+ const instanceDir = path.join(runtime.storeRoot, 'instances', request.instanceId);
234
+ const instance = loadMaterializedFlowInstance(instanceDir, request.instanceId, (definitionDir) => {
235
+ loadAICtrlBinding(definitionDir, extensionCodecs);
236
+ });
237
+ if (canonicalClosed(normalizeFlowClosedValue(instance.descriptor.definition, 'profileAuthority.instanceDefinition'))
238
+ !== canonicalClosed(normalizeFlowClosedValue(request.definition, 'profileAuthority.requestDefinition'))) {
239
+ throw new Error('AI Ctrl profile authority definition identity differs from the checkpoint.');
240
+ }
241
+ return profileAuthorityFromBinding(loadAICtrlBinding(instance.definitionDir, extensionCodecs));
242
+ },
243
+ });
244
+ }
245
+
246
+ const compareCodeUnits = (left: string, right: string): number => left < right ? -1 : left > right ? 1 : 0;
247
+
248
+ function canonicalClosed(value: import('ai-workflow-contract').FlowClosedValue): string {
249
+ if (value === null || typeof value !== 'object') return JSON.stringify(value);
250
+ if (Array.isArray(value)) return `[${value.map(canonicalClosed).join(',')}]`;
251
+ const record = value as import('ai-workflow-contract').FlowClosedObject;
252
+ return `{${Object.keys(record).sort(compareCodeUnits).map((key) => (
253
+ `${JSON.stringify(key)}:${canonicalClosed(record[key])}`
254
+ )).join(',')}}`;
255
+ }
package/src/index.ts CHANGED
@@ -20,6 +20,7 @@ import type {
20
20
  FlowDefinitionArtifactIdentity,
21
21
  FlowRunCheckpointKey,
22
22
  FlowRunCheckpointRuntime,
23
+ FlowRunStepExtensions,
23
24
  } from 'ai-workflow-contract';
24
25
  import {
25
26
  commitFlowRunCheckpoint,
@@ -52,6 +53,8 @@ export interface AICtrlWorkflowCheckpointStoreOptions {
52
53
  readonly definition: FlowDefinitionArtifactIdentity;
53
54
  readonly config: FlowClosedValue;
54
55
  readonly ai: AIWorkflowProfileDurableState;
56
+ /** Frozen definition-derived initial facts; later saves preserve the canonical owner value. */
57
+ readonly initialStepExtensions?: FlowRunStepExtensions;
55
58
  readonly projectOutput?: (snapshot: WorkCtrlFlowSnapshot) => FlowClosedValue;
56
59
  }
57
60
 
@@ -106,6 +109,11 @@ export function createAICtrlWorkflowCheckpointStore(
106
109
  output: options.projectOutput?.(snapshot) ?? current?.output ?? null,
107
110
  controllerSidecars: ctrlControllerSidecars(durableSnapshot) as unknown as FlowClosedObject,
108
111
  nodeSidecars: ctrlNodeSidecars(durableSnapshot) as unknown as FlowClosedObject,
112
+ ...(current?.stepExtensions !== undefined
113
+ ? { stepExtensions: current.stepExtensions }
114
+ : options.initialStepExtensions === undefined
115
+ ? {}
116
+ : { stepExtensions: options.initialStepExtensions }),
109
117
  profile: {
110
118
  kind: 'AICtrlWorkflow',
111
119
  snapshot: durableSnapshot as unknown as FlowClosedObject,