ai-workflow-logic 0.1.11 → 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.
@@ -0,0 +1,160 @@
1
+ import {
2
+ DEPA_AI_RESOURCE_KIND_READER_REGISTRATIONS,
3
+ type AIAgentCodeResourceProjection,
4
+ type AIAgentResolvedResourceIdentity,
5
+ type AIWorkflowAgentResourceProjection,
6
+ } from 'ai-workflow-contract';
7
+ import {
8
+ composeLayeredResourceRegistry,
9
+ digestCanonical,
10
+ resolveEffectiveResourceContentIdentities,
11
+ type EffectiveResourceRegistry,
12
+ type ResourceContentIdentity,
13
+ type ResourceLayerContentIdentityInput,
14
+ type ResourceRecord,
15
+ type ResolvedResourceRecord,
16
+ type ResourceTombstone,
17
+ } from 'halfcode-compiler.xnl/resource-core';
18
+ import { projectAIWorkflowAgentResources } from './agent-resources.js';
19
+ import { resolveAIWorkflowResourceTree, type AIWorkflowResourceResolutionContext } from './resource-resolution.js';
20
+
21
+ export interface ResolveAIAgentExecutionResourcesRuntime {
22
+ readonly resolutionContexts: ReadonlyMap<string, AIWorkflowResourceResolutionContext>;
23
+ }
24
+ export interface ResolveAIAgentExecutionResourcesInput {
25
+ readonly registry: EffectiveResourceRegistry;
26
+ readonly layers: readonly ResourceLayerContentIdentityInput[];
27
+ }
28
+ declare const executionResourceBrand: unique symbol;
29
+ /** Process-local derived observation; rebuild from authored bytes and exact readers on recovery. */
30
+ export interface AIAgentExecutionResourceObservation {
31
+ readonly [executionResourceBrand]: true;
32
+ readonly registry: EffectiveResourceRegistry;
33
+ readonly projection: AIWorkflowAgentResourceProjection;
34
+ readonly contentIdentities: ReadonlyMap<string, ResourceContentIdentity>;
35
+ readonly resolutionIdentities: ReadonlyMap<string, AIAgentResolvedResourceIdentity>;
36
+ /** Exact native effective records. Custom readerValue semantics remain owned by that Kind. */
37
+ readonly resolvedResources: ReadonlyMap<string, ResolvedResourceRecord>;
38
+ }
39
+ interface ExecutionAuthority extends ResolveAIAgentExecutionResourcesInput {
40
+ readonly layerBindings: readonly ResourceLayerContentIdentityInput[];
41
+ }
42
+ const authorities = new WeakMap<AIAgentExecutionResourceObservation, ExecutionAuthority>();
43
+ const codeResolutions = new WeakMap<AIAgentCodeResourceProjection, AIAgentResolvedResourceIdentity>();
44
+ const executionRegistries = new WeakSet<EffectiveResourceRegistry>();
45
+ const executionCodeResources = new WeakSet<ResourceRecord>();
46
+ const nodeReaderSubjects = new Set(DEPA_AI_RESOURCE_KIND_READER_REGISTRATIONS.map(reader => reader.subjectFqn));
47
+
48
+ export function resolveAIAgentExecutionResources(
49
+ runtime: ResolveAIAgentExecutionResourcesRuntime,
50
+ input: ResolveAIAgentExecutionResourcesInput,
51
+ ): AIAgentExecutionResourceObservation {
52
+ // Native owner validates registry provenance, exact authored trees, layer order and origins.
53
+ const contentIdentities = resolveEffectiveResourceContentIdentities(input);
54
+ if (runtime.resolutionContexts.size !== input.layers.length
55
+ || input.layers.some(layer => !runtime.resolutionContexts.get(layer.id))) {
56
+ fail('Reader context keys must exactly match the authored layer ids.');
57
+ }
58
+ // Tombstones are facts of the authentic composer result. Reuse the native composer
59
+ // to replay them, including tombstones that were subsequently shadowed by a layer.
60
+ const tombstones = new Map<string, ResourceTombstone[]>();
61
+ for (const entry of input.registry.byId.values()) for (const tombstone of entry.tombstones) {
62
+ const values = tombstones.get(tombstone.layerId) ?? [];
63
+ values.push({ resourceId: tombstone.resourceId,
64
+ ...(tombstone.expectedKind === undefined ? {} : { expectedKind: tombstone.expectedKind }),
65
+ ...(tombstone.reason === undefined ? {} : { reason: tombstone.reason }) });
66
+ tombstones.set(tombstone.layerId, values);
67
+ }
68
+ const resolvedLayers = input.layers.map(layer => ({
69
+ id: layer.id,
70
+ tree: resolveAIWorkflowResourceTree(layer.tree, runtime.resolutionContexts.get(layer.id)!),
71
+ tombstones: tombstones.get(layer.id) ?? [],
72
+ }));
73
+ const registry = composeLayeredResourceRegistry({ layers: resolvedLayers });
74
+ if (digestCanonical(topology(registry)) !== digestCanonical(topology(input.registry))) {
75
+ fail('Resolved composition must retain the exact authored layer, origin, shadow and tombstone topology.');
76
+ }
77
+ const resolutions: Array<readonly [string, AIAgentResolvedResourceIdentity]> = [];
78
+ const resolvedResources: Array<readonly [string, ResolvedResourceRecord]> = [];
79
+ for (const entry of registry.byId.values()) {
80
+ if (!entry.resource) continue;
81
+ const layer = resolvedLayers.find(value => value.id === entry.effectiveLayerId);
82
+ const record = layer?.tree.registry.byKind.get(entry.kind)?.find(value => value.resourceId === entry.resourceId);
83
+ if (!record || !layer) fail(`Missing native resolution for '${entry.resourceId}'.`);
84
+ resolvedResources.push([entry.resourceId, record]);
85
+ // This projector's explicit input protocol is resolved PortableSpec/node. It does
86
+ // not execute arbitrary Kind reader outputs with different business semantics.
87
+ if (nodeReaderSubjects.has(record.subjectFqn)
88
+ && digestCanonical(record.readerValue) !== digestCanonical(record.resolvedSpec)) {
89
+ fail(`Kind '${record.kind}' requires a reader whose value preserves the resolved node input protocol.`);
90
+ }
91
+ resolutions.push([entry.resourceId, Object.freeze({
92
+ resourceId: record.resourceId, readerId: record.readerId, readerSpecVersion: record.readerSpecVersion,
93
+ effectiveContentDigest: record.effectiveContentDigest, readerProfileId: layer.tree.readerProfileId,
94
+ resolutionReceiptDigest: digestCanonical(record.resolution),
95
+ })]);
96
+ }
97
+ const resolutionIdentities = immutableMap(resolutions.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
98
+ const projection = projectAIWorkflowAgentResources(registry);
99
+ for (const code of projection.codeResources ?? []) {
100
+ codeResolutions.set(code, resolutionIdentities.get(code.resource.resourceId)!);
101
+ executionCodeResources.add(code.resource);
102
+ }
103
+ const observation = Object.freeze({ registry, projection, contentIdentities, resolutionIdentities,
104
+ resolvedResources: immutableMap(resolvedResources.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)),
105
+ }) as AIAgentExecutionResourceObservation;
106
+ authorities.set(observation, Object.freeze({ registry: input.registry, layers: input.layers,
107
+ layerBindings: Object.freeze(input.layers.map(layer => Object.freeze({ id: layer.id, tree: layer.tree }))) }));
108
+ executionRegistries.add(registry);
109
+ return observation;
110
+ }
111
+
112
+ /** Internal admission guard; never exported as a public branding operation. */
113
+ export function assertAIAgentExecutionResources(
114
+ observation: AIAgentExecutionResourceObservation,
115
+ registry: EffectiveResourceRegistry,
116
+ layers?: readonly ResourceLayerContentIdentityInput[],
117
+ ): AIAgentExecutionResourceObservation {
118
+ const authority = authorities.get(observation);
119
+ if (!authority || authority.registry !== registry || (layers !== undefined && authority.layers !== layers)
120
+ || authority.layers.length !== authority.layerBindings.length
121
+ || authority.layerBindings.some((layer, index) => authority.layers[index]?.id !== layer.id || authority.layers[index]?.tree !== layer.tree)) {
122
+ fail('Execution resources require the exact authentic observation for this authored registry and layers.');
123
+ }
124
+ return observation;
125
+ }
126
+
127
+ export function resolutionForAIAgentCodeProjection(projection: AIAgentCodeResourceProjection): AIAgentResolvedResourceIdentity | undefined {
128
+ const resolution = codeResolutions.get(projection);
129
+ if (!resolution && executionCodeResources.has(projection.resource)) {
130
+ fail('Code compilation and restore require the exact execution observation projection.');
131
+ }
132
+ return resolution;
133
+ }
134
+
135
+ /** Prevents discarding reader identities by passing a bridge registry to the old freeze path. */
136
+ export function assertAIAgentFreezeRegistry(registry: EffectiveResourceRegistry): void {
137
+ if (executionRegistries.has(registry)) fail('Freeze requires the original authored registry and its executionResources observation.');
138
+ }
139
+
140
+ function topology(registry: EffectiveResourceRegistry): unknown {
141
+ return { layers: registry.layers, entries: [...registry.byId.values()].map(entry => ({
142
+ resourceId: entry.resourceId, kind: entry.kind, present: entry.resource !== undefined,
143
+ origin: entry.effectiveOrigin ?? null, shadowed: entry.shadowed,
144
+ tombstone: entry.tombstone ?? null, tombstones: entry.tombstones,
145
+ })) };
146
+ }
147
+ function immutableMap<K, V>(entries: readonly (readonly [K, V])[]): ReadonlyMap<K, V> {
148
+ const values = new Map(entries);
149
+ const view: ReadonlyMap<K, V> = Object.freeze({
150
+ get size() { return values.size; },
151
+ get: (key: K) => values.get(key), has: (key: K) => values.has(key),
152
+ keys: () => values.keys(), values: () => values.values(), entries: () => values.entries(),
153
+ [Symbol.iterator]: () => values[Symbol.iterator](),
154
+ forEach: (callback: (value: V, key: K, map: ReadonlyMap<K, V>) => void, thisArg?: unknown) => {
155
+ values.forEach((value, key) => callback.call(thisArg, value, key, view));
156
+ },
157
+ });
158
+ return view;
159
+ }
160
+ function fail(message: string): never { throw new Error(`AI_AGENT_EXECUTION_RESOURCES_UNTRUSTED: ${message}`); }
@@ -25,6 +25,7 @@ import type {
25
25
  } from 'halfcode-compiler.xnl/resource-core';
26
26
  import { markAIWorkflowAgentResourceProjection } from './agent-resource-brand.js';
27
27
  import { AIWorkflowResourceProjectionError } from './errors.js';
28
+ import { projectAIAgentCodeResources } from './agent-code-projection.js';
28
29
 
29
30
  const AGENT_SUBDOMAINS = Object.freeze([
30
31
  'Messages',
@@ -43,6 +44,7 @@ export function projectAIWorkflowAgentResources(
43
44
  const diagnostics: AIWorkflowResourceProjectionDiagnostic[] = [];
44
45
  const resources = buildResourceIndex(registry, diagnostics);
45
46
  const edges: ResourceDependencyEdge[] = [];
47
+ const codeResources = projectAIAgentCodeResources(resources, edges);
46
48
 
47
49
  const materialPorts = recordsForKind(registry, 'MaterialPort')
48
50
  .map((resource) => projectMaterialPort(resource, resources, edges, diagnostics))
@@ -60,6 +62,7 @@ export function projectAIWorkflowAgentResources(
60
62
 
61
63
  if (diagnostics.length > 0) throwProjectionError(diagnostics);
62
64
  return markAIWorkflowAgentResourceProjection(Object.freeze({
65
+ codeResources,
63
66
  agentDefinitions: Object.freeze(agentDefinitions),
64
67
  materialPorts: Object.freeze(materialPorts),
65
68
  materialBindings: Object.freeze(materialBindings),
package/src/browser.ts CHANGED
@@ -1 +1 @@
1
- export * from './index.js';
1
+ export * from './core.js';