ai-workflow-logic 0.2.0 → 0.2.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/src/run-freeze.ts CHANGED
@@ -7,6 +7,8 @@ import type {
7
7
  AIWorkflowRunResourceFreezeReceipt,
8
8
  MaterialBindingProjection,
9
9
  MaterialPortProjection,
10
+ CompiledAIAgentCodeExecution,
11
+ AIAgentCodeExecutionIdentity,
10
12
  } from 'ai-workflow-contract';
11
13
  import {
12
14
  normalizeHolonTaskTarget,
@@ -19,18 +21,24 @@ import {
19
21
  composeLayeredResourceRegistry,
20
22
  loadResourceTree,
21
23
  sha256Digest,
24
+ digestCanonical,
22
25
  type EffectiveResourceRegistry,
23
26
  type ResourceContentIdentity,
24
27
  type ResourceDependencyEdge,
28
+ type Sha256Digest,
25
29
  } from 'halfcode-compiler.xnl/resource-core';
26
30
  import { projectAIWorkflowAgentResources } from './agent-resources.js';
27
31
  import { sourceRegistryForAIWorkflowAgentResourceProjection } from './agent-resource-brand.js';
32
+ import { authorityForAIAgentCodeExecution } from './agent-code-brand.js';
33
+ import { assertAIAgentExecutionResources, assertAIAgentFreezeRegistry, type AIAgentExecutionResourceObservation } from './agent-execution-resources.js';
28
34
  import {
29
35
  createResolvedAIWorkflowContentIdentityIndex,
30
36
  resolveAIWorkflowResourceTree,
31
37
  } from './resource-resolution.js';
32
38
 
33
39
  export interface FreezeAIWorkflowRunResourcesInput {
40
+ readonly executionResources?: AIAgentExecutionResourceObservation;
41
+ readonly codeExecutions?: readonly CompiledAIAgentCodeExecution[];
34
42
  readonly registry: EffectiveResourceRegistry;
35
43
  readonly projection: AIWorkflowAgentResourceProjection;
36
44
  readonly task: AIWorkflowAgentTaskRef;
@@ -63,6 +71,7 @@ export class AIWorkflowRunResourceFreezeError extends Error {
63
71
 
64
72
  const authenticFreezeReceipts = new WeakSet<object>();
65
73
  const authenticTaskBindings = new WeakSet<object>();
74
+ const receiptByTaskBinding = new WeakMap<object, AIWorkflowRunResourceFreezeReceipt>();
66
75
  const authenticHolonTargetFreezeReceipts = new WeakSet<object>();
67
76
  const authenticFrozenHolonTargets = new WeakSet<object>();
68
77
 
@@ -185,6 +194,7 @@ export function assertFrozenHolonTaskTarget(value: FrozenHolonTaskTarget): Froze
185
194
  export function freezeAIWorkflowRunResources(
186
195
  input: FreezeAIWorkflowRunResourcesInput,
187
196
  ): AIWorkflowRunResourceFreezeReceipt {
197
+ assertAIAgentFreezeRegistry(input.registry);
188
198
  const diagnostics: AIWorkflowResourceProjectionDiagnostic[] = [];
189
199
  if (sourceRegistryForAIWorkflowAgentResourceProjection(input.projection) !== input.registry) {
190
200
  diagnostics.push(diagnostic(
@@ -194,6 +204,19 @@ export function freezeAIWorkflowRunResources(
194
204
  ));
195
205
  throwFreezeError(diagnostics);
196
206
  }
207
+ const executionResources = input.executionResources === undefined ? undefined
208
+ : assertAIAgentExecutionResources(input.executionResources, input.registry);
209
+ if (executionResources) {
210
+ if (input.contentIdentities.size !== executionResources.contentIdentities.size
211
+ || [...executionResources.contentIdentities].some(([id, identity]) =>
212
+ digestCanonical(input.contentIdentities.get(id) ?? null) !== digestCanonical(identity))) {
213
+ throw new AIWorkflowRunResourceFreezeError(Object.freeze([
214
+ diagnostic('AI_RUN_EXECUTION_CONTENT_IDENTITY_MISMATCH', 'run-freeze:identities', 'Execution observation requires its exact authored content identity facts.'),
215
+ ]));
216
+ }
217
+ input = { ...input, registry: executionResources.registry, projection: executionResources.projection,
218
+ contentIdentities: executionResources.contentIdentities };
219
+ }
197
220
  const task = normalizeTask(input.task, diagnostics);
198
221
  if (!task) throwFreezeError(diagnostics);
199
222
  const workflowId = resourceIdFromRef(task.workflowRef)!;
@@ -230,19 +253,34 @@ export function freezeAIWorkflowRunResources(
230
253
  agentDefinitionRef: task.agentDefinitionRef,
231
254
  });
232
255
  const bindingResourceIds = Object.freeze(orderedBindings.map((binding) => binding.resource.resourceId));
233
- const semanticFingerprint = semanticResourceFingerprint(
256
+ const resourceFingerprint = semanticResourceFingerprint(
234
257
  frozenTask,
235
258
  agent,
236
259
  orderedBindings,
237
260
  dependencySnapshot.closure,
238
261
  dependencySnapshot.edges,
239
262
  );
263
+ const codeExecutions = selectedCodeExecutions(input, dependencySnapshot.closure.map(item => item.resourceId));
264
+ const resourceResolutions = executionResources && Object.freeze(dependencySnapshot.closure
265
+ .map(item => {
266
+ const resolution = executionResources.resolutionIdentities.get(item.resourceId);
267
+ if (!resolution) throw new Error(`AI_RUN_RESOURCE_RESOLUTION_MISSING: ${item.resourceId}`);
268
+ return resolution;
269
+ }).sort((a, b) => a.resourceId < b.resourceId ? -1 : a.resourceId > b.resourceId ? 1 : 0));
270
+ const codeFingerprint = codeExecutions.length
271
+ ? sha256Digest(JSON.stringify({ schemaVersion: 'ai-workflow.code-bound-semantic/v1', resourceFingerprint, codeExecutions }))
272
+ : resourceFingerprint;
273
+ const semanticFingerprint = resourceResolutions
274
+ ? sha256Digest(JSON.stringify({ schemaVersion: 'ai-workflow.reader-bound-semantic/v1', resourceFingerprint: codeFingerprint, resourceResolutions }))
275
+ : codeFingerprint;
240
276
  const receipt = Object.freeze({
241
277
  schemaVersion: 'ai-workflow.run-resource-freeze/v1',
242
278
  task: frozenTask,
243
279
  bindingResourceIds,
244
280
  dependencySnapshot,
245
281
  semanticFingerprint,
282
+ ...(codeExecutions.length ? { codeExecutions } : {}),
283
+ ...(resourceResolutions ? { resourceResolutions } : {}),
246
284
  });
247
285
  authenticFreezeReceipts.add(receipt);
248
286
  return receipt;
@@ -263,11 +301,52 @@ export function projectFrozenAIAgentTaskBinding(
263
301
  semanticFingerprint: receipt.semanticFingerprint,
264
302
  snapshotRevision: receipt.dependencySnapshot.snapshotRevision,
265
303
  closureResourceIds: Object.freeze(receipt.dependencySnapshot.closure.map((item) => item.resourceId)),
304
+ ...(receipt.codeExecutions ? { codeExecutions: receipt.codeExecutions } : {}),
305
+ ...(receipt.resourceResolutions ? { resourceResolutions: receipt.resourceResolutions } : {}),
266
306
  });
267
307
  authenticTaskBindings.add(binding);
308
+ receiptByTaskBinding.set(binding, receipt);
268
309
  return binding;
269
310
  }
270
311
 
312
+ /** Retrieves the old Agent identity from the authentic freeze, not caller feedback. */
313
+ export function frozenAIAgentTaskAgentContentDigest(binding: FrozenAIAgentTaskBinding): Sha256Digest {
314
+ assertFrozenAIAgentTaskBinding(binding);
315
+ const receipt = receiptByTaskBinding.get(binding)!;
316
+ const agentId = resourceIdFromRef(receipt.task.agentDefinitionRef)!;
317
+ const identity = receipt.dependencySnapshot.closure.find(({ resourceId }) => resourceId === agentId);
318
+ if (!identity) throw new AIWorkflowRunResourceFreezeError(Object.freeze([
319
+ diagnostic('AI_RUN_AGENT_IDENTITY_MISSING', binding.task.agentDefinitionRef, 'Frozen Agent identity is absent.'),
320
+ ]));
321
+ if (!/^sha256:[a-f0-9]{64}$/.test(identity.contentDigest)) throw new Error('Invalid frozen Agent content digest');
322
+ return identity.contentDigest as Sha256Digest;
323
+ }
324
+
325
+ function selectedCodeExecutions(input: FreezeAIWorkflowRunResourcesInput, closureIds: readonly string[]): readonly AIAgentCodeExecutionIdentity[] {
326
+ const selected = new Set(closureIds);
327
+ const identities = new Map<string, AIAgentCodeExecutionIdentity>();
328
+ for (const execution of input.codeExecutions ?? []) {
329
+ const authority = authorityForAIAgentCodeExecution(execution);
330
+ if (!authority || !selected.has(authority.identity.resourceId)
331
+ || input.registry.byId.get(authority.identity.resourceId)?.resource !== authority.projection.resource
332
+ || !(input.projection.codeResources ?? []).includes(authority.projection)
333
+ || identities.has(authority.identity.resourceId)) {
334
+ throw new AIWorkflowRunResourceFreezeError(Object.freeze([
335
+ diagnostic('AI_RUN_CODE_EXECUTION_UNTRUSTED', 'run-freeze:code', 'Code execution must be authentic, selected and belong to the exact registry projection.'),
336
+ ]));
337
+ }
338
+ identities.set(authority.identity.resourceId, authority.identity);
339
+ }
340
+ for (const code of input.projection.codeResources ?? []) {
341
+ if (code.mode === 'native' && selected.has(code.resource.resourceId) && !identities.has(code.resource.resourceId)) {
342
+ throw new AIWorkflowRunResourceFreezeError(Object.freeze([
343
+ diagnostic('AI_RUN_CODE_EXECUTION_MISSING', `resource://${code.resource.resourceId}`, 'Native code must be compiled or restored before task freeze.'),
344
+ ]));
345
+ }
346
+ }
347
+ return Object.freeze([...identities.values()].sort((a, b) => a.resourceId < b.resourceId ? -1 : a.resourceId > b.resourceId ? 1 : 0));
348
+ }
349
+
271
350
  /** Runtime assertion; a structurally similar literal is not an admitted authority. */
272
351
  export function assertFrozenAIAgentTaskBinding(value: FrozenAIAgentTaskBinding): FrozenAIAgentTaskBinding {
273
352
  if (!authenticTaskBindings.has(value as object)) {