ai-workflow-logic 0.1.9 → 0.1.11
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 +3 -3
- package/src/agent-definition-selection.ts +755 -0
- package/src/agent-invocation.ts +2 -2
- package/src/agent-resources.ts +134 -29
- package/src/agent-state.ts +5 -5
- package/src/checkpoint-store.ts +3 -1
- package/src/index.ts +15 -0
- package/src/run-checkpoint.ts +225 -3
- package/src/run-freeze.ts +20 -10
package/src/agent-invocation.ts
CHANGED
|
@@ -121,8 +121,8 @@ function reserveName(ai: AIWorkflowProfileDurableState, receipt: AIAgentInvocati
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function resolveSelector(ai: AIWorkflowProfileDurableState, selector: AIAgentSelector, config: AIAgentEffectConfig): AIAgentInstanceReference {
|
|
124
|
-
const instanceId = '
|
|
125
|
-
if (instanceId === undefined) fail('AI_AGENT_INSTANCE_NAME_NOT_FOUND', 'selector.
|
|
124
|
+
const instanceId = 'byId' in selector ? selector.byId : ai.instanceIdByName[selector.byName];
|
|
125
|
+
if (instanceId === undefined) fail('AI_AGENT_INSTANCE_NAME_NOT_FOUND', 'selector.byName', 'Named Agent instance does not exist.');
|
|
126
126
|
const instance = ai.instancesById[instanceId];
|
|
127
127
|
if (!instance) fail('AI_AGENT_INSTANCE_ID_NOT_FOUND', 'selector', 'Agent instance does not exist.');
|
|
128
128
|
if (instance.instanceName !== undefined && ai.instanceIdByName[instance.instanceName] !== instance.instanceId) fail('AI_AGENT_INSTANCE_INDEX_CONFLICT', 'checkpoint.profile.ai', 'Agent instance indexes are not reciprocal.');
|
package/src/agent-resources.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AIAgentDefinitionProjection,
|
|
3
|
+
AIAgentMessagePrefixItemProjection,
|
|
3
4
|
AIAgentMessageProjection,
|
|
4
5
|
AIAgentMessageRole,
|
|
5
6
|
AIWorkflowEffectPolicyProjection,
|
|
@@ -27,6 +28,8 @@ import { AIWorkflowResourceProjectionError } from './errors.js';
|
|
|
27
28
|
|
|
28
29
|
const AGENT_SUBDOMAINS = Object.freeze([
|
|
29
30
|
'Messages',
|
|
31
|
+
'MessagePrefix',
|
|
32
|
+
'ContextPipeline',
|
|
30
33
|
'InputSchemaRef',
|
|
31
34
|
'OutputSchemaRef',
|
|
32
35
|
'ToolRefs',
|
|
@@ -144,12 +147,44 @@ function projectAgentDefinition(
|
|
|
144
147
|
diagnostics: AIWorkflowResourceProjectionDiagnostic[],
|
|
145
148
|
): AIAgentDefinitionProjection | undefined {
|
|
146
149
|
validateAllowedSubdomains(resource, AGENT_SUBDOMAINS, diagnostics);
|
|
147
|
-
const messagesNode =
|
|
150
|
+
const messagesNode = exactOptionalSubdomain(resource, 'Messages', diagnostics);
|
|
151
|
+
const messagePrefixNode = exactOptionalSubdomain(resource, 'MessagePrefix', diagnostics);
|
|
152
|
+
if (messagesNode && messagePrefixNode) {
|
|
153
|
+
diagnostics.push(diagnostic(
|
|
154
|
+
'AI_AGENT_MESSAGE_PREFIX_CONFLICT',
|
|
155
|
+
resource.documentUri,
|
|
156
|
+
'AIAgentDefinition must declare exactly one of legacy Messages or MessagePrefix.',
|
|
157
|
+
));
|
|
158
|
+
} else if (!messagesNode && !messagePrefixNode) {
|
|
159
|
+
diagnostics.push(diagnostic(
|
|
160
|
+
'AI_AGENT_MESSAGE_PREFIX_MISSING',
|
|
161
|
+
resource.documentUri,
|
|
162
|
+
'AIAgentDefinition must declare legacy Messages or MessagePrefix.',
|
|
163
|
+
));
|
|
164
|
+
}
|
|
148
165
|
const toolRefsNode = requiredSubdomain(resource, 'ToolRefs', diagnostics);
|
|
149
166
|
const materialPortRefsNode = requiredSubdomain(resource, 'MaterialPortRefs', diagnostics);
|
|
167
|
+
const projectedMessagePrefix = messagePrefixNode
|
|
168
|
+
? projectMessagePrefix(resource, messagePrefixNode, resources, edges, diagnostics)
|
|
169
|
+
: undefined;
|
|
150
170
|
const messages = messagesNode
|
|
151
171
|
? projectMessages(resource, messagesNode, resources, edges, diagnostics)
|
|
152
|
-
:
|
|
172
|
+
: projectedMessagePrefix
|
|
173
|
+
? projectedMessagePrefix
|
|
174
|
+
.filter((item): item is Extract<AIAgentMessagePrefixItemProjection, { type: 'message' }> => item.type === 'message')
|
|
175
|
+
.map(({ type: _type, ...message }) => Object.freeze(message))
|
|
176
|
+
: [];
|
|
177
|
+
const messagePrefix = projectedMessagePrefix
|
|
178
|
+
?? messages.map((message) => Object.freeze({ ...message, type: 'message' as const }));
|
|
179
|
+
const contextPipeline = projectOptionalTypedSubdomain(
|
|
180
|
+
resource,
|
|
181
|
+
'ContextPipeline',
|
|
182
|
+
resources,
|
|
183
|
+
'agent-context-pipeline',
|
|
184
|
+
edges,
|
|
185
|
+
diagnostics,
|
|
186
|
+
'AgentContextPipeline',
|
|
187
|
+
);
|
|
153
188
|
const inputSchema = projectOptionalSchemaSubdomain(resource, 'InputSchemaRef', resources, 'agent-input-schema', edges, diagnostics);
|
|
154
189
|
const outputSchema = projectOptionalSchemaSubdomain(resource, 'OutputSchemaRef', resources, 'agent-output-schema', edges, diagnostics);
|
|
155
190
|
const tools = toolRefsNode
|
|
@@ -160,12 +195,14 @@ function projectAgentDefinition(
|
|
|
160
195
|
? projectTypedRefList(resource, materialPortRefsNode, 'MaterialPortRef', resources, 'agent-material-port', edges, diagnostics, 'MaterialPort')
|
|
161
196
|
: [];
|
|
162
197
|
|
|
163
|
-
if (!messagesNode || !toolRefsNode || !materialPortRefsNode) return undefined;
|
|
198
|
+
if ((!messagesNode && !messagePrefixNode) || (messagesNode && messagePrefixNode) || !toolRefsNode || !materialPortRefsNode) return undefined;
|
|
164
199
|
return Object.freeze({
|
|
165
200
|
kind: 'AIAgentDefinition',
|
|
166
201
|
fqn: resource.fqn ?? resource.resourceId,
|
|
167
202
|
resource,
|
|
203
|
+
messagePrefix: Object.freeze(messagePrefix),
|
|
168
204
|
messages: Object.freeze(messages),
|
|
205
|
+
...(contextPipeline === undefined ? {} : { contextPipeline }),
|
|
169
206
|
...(inputSchema === undefined ? {} : { inputSchema }),
|
|
170
207
|
...(outputSchema === undefined ? {} : { outputSchema }),
|
|
171
208
|
tools: Object.freeze(tools),
|
|
@@ -174,6 +211,67 @@ function projectAgentDefinition(
|
|
|
174
211
|
});
|
|
175
212
|
}
|
|
176
213
|
|
|
214
|
+
function exactOptionalSubdomain(
|
|
215
|
+
resource: ResourceRecord,
|
|
216
|
+
name: string,
|
|
217
|
+
diagnostics: AIWorkflowResourceProjectionDiagnostic[],
|
|
218
|
+
): ResourceNode | undefined {
|
|
219
|
+
const value = resource.node.subdomains[name];
|
|
220
|
+
if (!value) return undefined;
|
|
221
|
+
if (value.tag !== name) {
|
|
222
|
+
diagnostics.push(diagnostic(
|
|
223
|
+
'AI_RESOURCE_SUBDOMAIN_TAG_MISMATCH',
|
|
224
|
+
`${resource.documentUri}#${name}`,
|
|
225
|
+
`Subdomain ${name} must contain a ${name} node, got '${value.tag}'.`,
|
|
226
|
+
));
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
return value;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function projectMessagePrefix(
|
|
233
|
+
resource: ResourceRecord,
|
|
234
|
+
prefixNode: ResourceNode,
|
|
235
|
+
resources: ReadonlyMap<string, ResourceRecord>,
|
|
236
|
+
edges: ResourceDependencyEdge[],
|
|
237
|
+
diagnostics: AIWorkflowResourceProjectionDiagnostic[],
|
|
238
|
+
): AIAgentMessagePrefixItemProjection[] {
|
|
239
|
+
const output: AIAgentMessagePrefixItemProjection[] = [];
|
|
240
|
+
const ids = new Set<string>();
|
|
241
|
+
prefixNode.body.forEach((value, index) => {
|
|
242
|
+
const location = `${resource.documentUri}#MessagePrefix:${index}`;
|
|
243
|
+
if (!isResourceNode(value) || (value.tag !== 'Message' && value.tag !== 'MessageSource')) {
|
|
244
|
+
diagnostics.push(diagnostic(
|
|
245
|
+
'AI_AGENT_MESSAGE_PREFIX_NODE_INVALID',
|
|
246
|
+
location,
|
|
247
|
+
'MessagePrefix may contain only Message or MessageSource nodes.',
|
|
248
|
+
));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const id = canonicalString(value.resourceId);
|
|
252
|
+
if (!id) {
|
|
253
|
+
diagnostics.push(diagnostic('AI_AGENT_MESSAGE_ID_MISSING', location, `${value.tag} must declare an explicit #id.`));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (ids.has(id)) {
|
|
257
|
+
diagnostics.push(diagnostic('AI_AGENT_MESSAGE_ID_DUPLICATE', location, `MessagePrefix id '${id}' is declared more than once.`));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
ids.add(id);
|
|
261
|
+
if (value.tag === 'Message') {
|
|
262
|
+
const message = projectMessageNode(resource, value, resources, edges, diagnostics, location);
|
|
263
|
+
if (message) output.push(Object.freeze({ ...message, type: 'message' }));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
validateAllowedNodeSubdomains(value, [], location, diagnostics);
|
|
267
|
+
const source = projectTypedValues(value.properties.kind, value.properties.ref, resources, location, diagnostics, 'AgentMessageSource');
|
|
268
|
+
if (!source) return;
|
|
269
|
+
edges.push(edge(resource.resourceId, source.resource.resourceId, 'agent-message-source'));
|
|
270
|
+
output.push(Object.freeze({ type: 'message-source', id, source }));
|
|
271
|
+
});
|
|
272
|
+
return output;
|
|
273
|
+
}
|
|
274
|
+
|
|
177
275
|
function projectMessages(
|
|
178
276
|
resource: ResourceRecord,
|
|
179
277
|
messagesNode: ResourceNode,
|
|
@@ -189,39 +287,45 @@ function projectMessages(
|
|
|
189
287
|
diagnostics.push(diagnostic('AI_AGENT_MESSAGE_NODE_INVALID', location, 'Messages may contain only Message nodes.'));
|
|
190
288
|
return;
|
|
191
289
|
}
|
|
192
|
-
validateAllowedNodeSubdomains(value, ['SchemaRef'], location, diagnostics);
|
|
193
290
|
const id = canonicalString(value.resourceId);
|
|
194
|
-
|
|
195
|
-
if (!id) {
|
|
196
|
-
diagnostics.push(diagnostic('AI_AGENT_MESSAGE_ID_MISSING', location, 'Message must declare an explicit #id.'));
|
|
197
|
-
} else if (ids.has(id)) {
|
|
291
|
+
if (id && ids.has(id)) {
|
|
198
292
|
diagnostics.push(diagnostic('AI_AGENT_MESSAGE_ID_DUPLICATE', location, `Message id '${id}' is declared more than once.`));
|
|
199
|
-
|
|
200
|
-
ids.add(id);
|
|
293
|
+
return;
|
|
201
294
|
}
|
|
202
|
-
if (
|
|
203
|
-
|
|
295
|
+
if (id) {
|
|
296
|
+
ids.add(id);
|
|
204
297
|
}
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
value.properties.promptRef,
|
|
208
|
-
resources,
|
|
209
|
-
location,
|
|
210
|
-
diagnostics,
|
|
211
|
-
);
|
|
212
|
-
if (prompt) edges.push(edge(resource.resourceId, prompt.resource.resourceId, 'agent-message-prompt'));
|
|
213
|
-
const schemaNode = value.subdomains.SchemaRef;
|
|
214
|
-
const schema = schemaNode?.tag === 'SchemaRef'
|
|
215
|
-
? projectMessageSchemaRefNode(schemaNode, resources, `${location}#SchemaRef`, diagnostics)
|
|
216
|
-
: undefined;
|
|
217
|
-
if (schemaNode && schemaNode.tag !== 'SchemaRef') diagnostics.push(diagnostic('AI_RESOURCE_SUBDOMAIN_TAG_MISMATCH', `${location}#SchemaRef`, `Subdomain SchemaRef must contain a SchemaRef node, got '${schemaNode.tag}'.`));
|
|
218
|
-
if (schema) edges.push(edge(resource.resourceId, schema.resource.resourceId, 'agent-message-schema'));
|
|
219
|
-
if (!id || !isMessageRole(role) || !prompt) return;
|
|
220
|
-
output.push(Object.freeze({ id, role, prompt, ...(schema === undefined ? {} : { schema }) }));
|
|
298
|
+
const message = projectMessageNode(resource, value, resources, edges, diagnostics, location);
|
|
299
|
+
if (message) output.push(message);
|
|
221
300
|
});
|
|
222
301
|
return output;
|
|
223
302
|
}
|
|
224
303
|
|
|
304
|
+
function projectMessageNode(
|
|
305
|
+
resource: ResourceRecord,
|
|
306
|
+
value: ResourceNode,
|
|
307
|
+
resources: ReadonlyMap<string, ResourceRecord>,
|
|
308
|
+
edges: ResourceDependencyEdge[],
|
|
309
|
+
diagnostics: AIWorkflowResourceProjectionDiagnostic[],
|
|
310
|
+
location: string,
|
|
311
|
+
): AIAgentMessageProjection | undefined {
|
|
312
|
+
validateAllowedNodeSubdomains(value, ['SchemaRef'], location, diagnostics);
|
|
313
|
+
const id = canonicalString(value.resourceId);
|
|
314
|
+
const role = value.properties.role;
|
|
315
|
+
if (!id) diagnostics.push(diagnostic('AI_AGENT_MESSAGE_ID_MISSING', location, 'Message must declare an explicit #id.'));
|
|
316
|
+
if (!isMessageRole(role)) diagnostics.push(diagnostic('AI_AGENT_MESSAGE_ROLE_INVALID', location, 'Message role must be system, developer, user, or assistant.'));
|
|
317
|
+
const prompt = projectTypedValues(value.properties.promptKind, value.properties.promptRef, resources, location, diagnostics);
|
|
318
|
+
if (prompt) edges.push(edge(resource.resourceId, prompt.resource.resourceId, 'agent-message-prompt'));
|
|
319
|
+
const schemaNode = value.subdomains.SchemaRef;
|
|
320
|
+
const schema = schemaNode?.tag === 'SchemaRef'
|
|
321
|
+
? projectMessageSchemaRefNode(schemaNode, resources, `${location}#SchemaRef`, diagnostics)
|
|
322
|
+
: undefined;
|
|
323
|
+
if (schemaNode && schemaNode.tag !== 'SchemaRef') diagnostics.push(diagnostic('AI_RESOURCE_SUBDOMAIN_TAG_MISMATCH', `${location}#SchemaRef`, `Subdomain SchemaRef must contain a SchemaRef node, got '${schemaNode.tag}'.`));
|
|
324
|
+
if (schema) edges.push(edge(resource.resourceId, schema.resource.resourceId, 'agent-message-schema'));
|
|
325
|
+
if (!id || !isMessageRole(role) || !prompt) return undefined;
|
|
326
|
+
return Object.freeze({ id, role, prompt, ...(schema === undefined ? {} : { schema }) });
|
|
327
|
+
}
|
|
328
|
+
|
|
225
329
|
function projectTypedRefList(
|
|
226
330
|
owner: ResourceRecord,
|
|
227
331
|
list: ResourceNode,
|
|
@@ -265,6 +369,7 @@ function projectOptionalTypedSubdomain(
|
|
|
265
369
|
relation: AIWorkflowAgentResourceRelation,
|
|
266
370
|
edges: ResourceDependencyEdge[],
|
|
267
371
|
diagnostics: AIWorkflowResourceProjectionDiagnostic[],
|
|
372
|
+
expectedKind?: string,
|
|
268
373
|
): AIWorkflowTypedResourceRef | undefined {
|
|
269
374
|
const node = owner.node.subdomains[name];
|
|
270
375
|
if (!node) return undefined;
|
|
@@ -272,7 +377,7 @@ function projectOptionalTypedSubdomain(
|
|
|
272
377
|
diagnostics.push(diagnostic('AI_RESOURCE_SUBDOMAIN_TAG_MISMATCH', `${owner.documentUri}#${name}`, `Subdomain ${name} must contain a ${name} node, got '${node.tag}'.`));
|
|
273
378
|
return undefined;
|
|
274
379
|
}
|
|
275
|
-
const typedRef = projectTypedRefNode(node, resources, `${owner.documentUri}#${name}`, diagnostics);
|
|
380
|
+
const typedRef = projectTypedRefNode(node, resources, `${owner.documentUri}#${name}`, diagnostics, expectedKind);
|
|
276
381
|
if (typedRef) edges.push(edge(owner.resourceId, typedRef.resource.resourceId, relation));
|
|
277
382
|
return typedRef;
|
|
278
383
|
}
|
package/src/agent-state.ts
CHANGED
|
@@ -115,13 +115,13 @@ function optionalStringArray(value: unknown, location: string): readonly string[
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
export function normalizeAIAgentSelector(value: unknown, location = 'selector'): AIAgentSelector {
|
|
118
|
-
const input = record(value, [], ['
|
|
119
|
-
const hasId = Object.prototype.hasOwnProperty.call(input, '
|
|
120
|
-
const hasName = Object.prototype.hasOwnProperty.call(input, '
|
|
118
|
+
const input = record(value, [], ['byId', 'byName'], location);
|
|
119
|
+
const hasId = Object.prototype.hasOwnProperty.call(input, 'byId');
|
|
120
|
+
const hasName = Object.prototype.hasOwnProperty.call(input, 'byName');
|
|
121
121
|
if (hasId === hasName) fail('AI_AGENT_SELECTOR_EXACTLY_ONE', location, 'Selector must contain exactly one supported key.');
|
|
122
122
|
return Object.freeze(hasId
|
|
123
|
-
? {
|
|
124
|
-
: {
|
|
123
|
+
? { byId: identity(input.byId, `${location}.byId`) }
|
|
124
|
+
: { byName: identity(input.byName, `${location}.byName`) });
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
export function normalizeAIAgentEffectConfig(value: unknown, location = 'config'): AIAgentEffectConfig {
|
package/src/checkpoint-store.ts
CHANGED
|
@@ -399,7 +399,9 @@ function validateAIDataProfileTransition(
|
|
|
399
399
|
throw new FlowRunCheckpointTransitionError(`AI Data patch operation ${operation.op} is unsupported.`);
|
|
400
400
|
}
|
|
401
401
|
for (const [field, expected] of Object.entries(operation.changes)) {
|
|
402
|
-
|
|
402
|
+
const actual = (candidate as Readonly<Record<string, unknown>>)[field];
|
|
403
|
+
if (field === 'subflow' && expected === null && actual === undefined) continue;
|
|
404
|
+
if (!sameClosedValue(actual, expected)) {
|
|
403
405
|
throw new FlowRunCheckpointTransitionError(`AI Data update-node ${nodeId} does not project declared ${field}.`);
|
|
404
406
|
}
|
|
405
407
|
}
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,21 @@ import { AIWorkflowFoundationError, AIWorkflowResourceProjectionError } from './
|
|
|
36
36
|
|
|
37
37
|
export { AIWorkflowFoundationError, AIWorkflowResourceProjectionError } from './errors.js';
|
|
38
38
|
export { projectAIWorkflowAgentResources } from './agent-resources.js';
|
|
39
|
+
export {
|
|
40
|
+
AIAgentDefinitionSelectionError,
|
|
41
|
+
admitAIAgentDefinitionSelection,
|
|
42
|
+
freezeSelectedAIAgentTaskBinding,
|
|
43
|
+
normalizeAIAgentTaskRequirement,
|
|
44
|
+
projectAIAgentDefinitionCandidates,
|
|
45
|
+
reconcileAuthoredAIAgentDefinition,
|
|
46
|
+
} from './agent-definition-selection.js';
|
|
47
|
+
export type {
|
|
48
|
+
AIAgentDefinitionSelectionErrorCode,
|
|
49
|
+
AdmitAIAgentDefinitionSelectionInput,
|
|
50
|
+
FreezeSelectedAIAgentTaskBindingInput,
|
|
51
|
+
ProjectAIAgentDefinitionCandidatesInput,
|
|
52
|
+
ReconcileAuthoredAIAgentDefinitionInput,
|
|
53
|
+
} from './agent-definition-selection.js';
|
|
39
54
|
export {
|
|
40
55
|
FlowRunContractValidationError,
|
|
41
56
|
normalizeAIWorkflowProfileDurableState,
|
package/src/run-checkpoint.ts
CHANGED
|
@@ -755,7 +755,10 @@ function normalizeAIDataRunGraph(value: unknown, runId: string, location: string
|
|
|
755
755
|
'id', 'tag', 'nodeType', 'generation', 'status', 'declarationIndex', 'inputs',
|
|
756
756
|
'outputs', 'config', 'dependsOn', 'dependents', 'reusePolicy',
|
|
757
757
|
],
|
|
758
|
-
[
|
|
758
|
+
[
|
|
759
|
+
'subflow', 'childInvocation', 'childFreezeReceipt', 'childTerminalReceipt',
|
|
760
|
+
'semanticFingerprint', 'result', 'invalidatedBy',
|
|
761
|
+
],
|
|
759
762
|
nodeLocation,
|
|
760
763
|
);
|
|
761
764
|
const storedId = canonicalIdentity(dataField(node, 'id', `${nodeLocation}.id`), `${nodeLocation}.id`);
|
|
@@ -778,6 +781,46 @@ function normalizeAIDataRunGraph(value: unknown, runId: string, location: string
|
|
|
778
781
|
normalizeAIDataInputs(dataField(node, 'inputs', `${nodeLocation}.inputs`), `${nodeLocation}.inputs`);
|
|
779
782
|
uniqueIdentityArray(dataField(node, 'outputs', `${nodeLocation}.outputs`), `${nodeLocation}.outputs`);
|
|
780
783
|
const config = plainRecord(dataField(node, 'config', `${nodeLocation}.config`), `${nodeLocation}.config`);
|
|
784
|
+
const tag = String(dataField(node, 'tag', `${nodeLocation}.tag`));
|
|
785
|
+
const hasSubflow = Object.prototype.hasOwnProperty.call(node, 'subflow');
|
|
786
|
+
if (tag === 'SubFlowNode' && !hasSubflow) {
|
|
787
|
+
invalid('AI_DATA_SUBFLOW_BINDING_MISSING', `${nodeLocation}.subflow`, 'SubFlowNode requires an exact durable child binding.');
|
|
788
|
+
}
|
|
789
|
+
if (tag !== 'SubFlowNode' && hasSubflow) {
|
|
790
|
+
invalid('AI_DATA_SUBFLOW_BINDING_INVALID', `${nodeLocation}.subflow`, 'Only SubFlowNode may carry a child binding.');
|
|
791
|
+
}
|
|
792
|
+
if (hasSubflow) normalizeAIDataSubflowBinding(dataField(node, 'subflow', `${nodeLocation}.subflow`), `${nodeLocation}.subflow`);
|
|
793
|
+
const hasChildInvocation = Object.prototype.hasOwnProperty.call(node, 'childInvocation');
|
|
794
|
+
const hasChildFreeze = Object.prototype.hasOwnProperty.call(node, 'childFreezeReceipt');
|
|
795
|
+
const hasChildTerminal = Object.prototype.hasOwnProperty.call(node, 'childTerminalReceipt');
|
|
796
|
+
if ((hasChildInvocation || hasChildFreeze || hasChildTerminal) && tag !== 'SubFlowNode') {
|
|
797
|
+
invalid('AI_DATA_CHILD_INVOCATION_NODE_INVALID', nodeLocation, 'Only SubFlowNode may carry child invocation facts.');
|
|
798
|
+
}
|
|
799
|
+
if ((hasChildFreeze || hasChildTerminal) && !hasChildInvocation) {
|
|
800
|
+
invalid('AI_DATA_CHILD_INVOCATION_IDENTITY_MISSING', nodeLocation, 'Child receipts require a reserved invocation identity.');
|
|
801
|
+
}
|
|
802
|
+
if (hasChildInvocation) {
|
|
803
|
+
const identity = normalizeAIDataChildInvocationIdentity(
|
|
804
|
+
dataField(node, 'childInvocation', `${nodeLocation}.childInvocation`),
|
|
805
|
+
graphRunId,
|
|
806
|
+
nodeId,
|
|
807
|
+
generation,
|
|
808
|
+
dataField(node, 'subflow', `${nodeLocation}.subflow`),
|
|
809
|
+
`${nodeLocation}.childInvocation`,
|
|
810
|
+
);
|
|
811
|
+
if (hasChildFreeze) normalizeAIDataChildFreezeReceipt(
|
|
812
|
+
dataField(node, 'childFreezeReceipt', `${nodeLocation}.childFreezeReceipt`),
|
|
813
|
+
identity,
|
|
814
|
+
`${nodeLocation}.childFreezeReceipt`,
|
|
815
|
+
);
|
|
816
|
+
if (hasChildTerminal) normalizeAIDataChildTerminalReceipt(
|
|
817
|
+
dataField(node, 'childTerminalReceipt', `${nodeLocation}.childTerminalReceipt`),
|
|
818
|
+
identity,
|
|
819
|
+
node,
|
|
820
|
+
String(status),
|
|
821
|
+
`${nodeLocation}.childTerminalReceipt`,
|
|
822
|
+
);
|
|
823
|
+
}
|
|
781
824
|
const dependsOn = uniqueIdentityArray(dataField(node, 'dependsOn', `${nodeLocation}.dependsOn`), `${nodeLocation}.dependsOn`);
|
|
782
825
|
const dependents = uniqueIdentityArray(dataField(node, 'dependents', `${nodeLocation}.dependents`), `${nodeLocation}.dependents`);
|
|
783
826
|
normalizeReusePolicy(
|
|
@@ -811,6 +854,17 @@ function normalizeAIDataRunGraph(value: unknown, runId: string, location: string
|
|
|
811
854
|
`${nodeLocation}.result`,
|
|
812
855
|
);
|
|
813
856
|
normalizeAIDataResultOutput(resultValue, node, String(status), bindingDefinition, `${nodeLocation}.result`);
|
|
857
|
+
if (hasChildTerminal) {
|
|
858
|
+
const childReceipt = plainRecord(
|
|
859
|
+
dataField(node, 'childTerminalReceipt', `${nodeLocation}.childTerminalReceipt`),
|
|
860
|
+
`${nodeLocation}.childTerminalReceipt`,
|
|
861
|
+
);
|
|
862
|
+
if (String(status) === 'Succeeded'
|
|
863
|
+
&& JSON.stringify(dataField(childReceipt, 'output', `${nodeLocation}.childTerminalReceipt.output`))
|
|
864
|
+
!== JSON.stringify(dataField(plainRecord(resultValue, `${nodeLocation}.result`), 'output', `${nodeLocation}.result.output`))) {
|
|
865
|
+
invalid('AI_DATA_CHILD_PARENT_OUTPUT_MISMATCH', `${nodeLocation}.result.output`, 'Parent result must equal the accepted child output.');
|
|
866
|
+
}
|
|
867
|
+
}
|
|
814
868
|
}
|
|
815
869
|
const invalidationFacts: string[] = [];
|
|
816
870
|
if (Object.prototype.hasOwnProperty.call(node, 'invalidatedBy')) {
|
|
@@ -988,12 +1042,176 @@ function normalizeInitialAIDataNodeProjection(
|
|
|
988
1042
|
? dataField(definitionNode, 'config', `${location}.bindingNode.config`)
|
|
989
1043
|
: {},
|
|
990
1044
|
dependsOn: eagerDefinitionDependencies(definition, definitionNode, nodeId, location),
|
|
1045
|
+
...(dataField(definitionNode, 'tag', `${location}.bindingNode.tag`) === 'SubFlowNode'
|
|
1046
|
+
? {
|
|
1047
|
+
subflow: {
|
|
1048
|
+
kind: 'declared',
|
|
1049
|
+
flowRef: dataField(definitionNode, 'flow', `${location}.bindingNode.flow`),
|
|
1050
|
+
},
|
|
1051
|
+
}
|
|
1052
|
+
: {}),
|
|
991
1053
|
};
|
|
992
1054
|
for (const field of ['tag', 'declarationIndex', 'inputs', 'outputs', 'config', 'dependsOn'] as const) {
|
|
993
1055
|
if (JSON.stringify(dataField(node, field, `${location}.${field}`)) !== JSON.stringify(projected[field])) {
|
|
994
1056
|
invalid('AI_DATA_NODE_PROJECTION_MISMATCH', `${location}.${field}`, `Initial run node ${field} differs from its binding projection.`);
|
|
995
1057
|
}
|
|
996
1058
|
}
|
|
1059
|
+
if (Object.prototype.hasOwnProperty.call(projected, 'subflow')
|
|
1060
|
+
&& JSON.stringify(dataField(node, 'subflow', `${location}.subflow`)) !== JSON.stringify(projected.subflow)) {
|
|
1061
|
+
invalid('AI_DATA_NODE_PROJECTION_MISMATCH', `${location}.subflow`, 'Initial run node subflow differs from its binding projection.');
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function normalizeAIDataSubflowBinding(value: unknown, location: string): void {
|
|
1066
|
+
const binding = plainRecord(value, location);
|
|
1067
|
+
const kind = dataField(binding, 'kind', `${location}.kind`);
|
|
1068
|
+
if (kind === 'declared') {
|
|
1069
|
+
exactRecord(value, ['kind', 'flowRef'], [], location);
|
|
1070
|
+
} else if (kind === 'autonomous') {
|
|
1071
|
+
const autonomous = exactRecord(
|
|
1072
|
+
value,
|
|
1073
|
+
['kind', 'schemaVersion', 'capabilityId', 'catalogDigest', 'definitionRef', 'flowRef', 'contractDigest'],
|
|
1074
|
+
[],
|
|
1075
|
+
location,
|
|
1076
|
+
);
|
|
1077
|
+
if (dataField(autonomous, 'schemaVersion', `${location}.schemaVersion`) !== 'depa.ai-data-subflow-binding/v1') {
|
|
1078
|
+
invalid('AI_DATA_SUBFLOW_BINDING_VERSION_INVALID', `${location}.schemaVersion`, 'Unsupported autonomous child binding version.');
|
|
1079
|
+
}
|
|
1080
|
+
const definitionRef = canonicalIdentity(dataField(autonomous, 'definitionRef', `${location}.definitionRef`), `${location}.definitionRef`);
|
|
1081
|
+
if (!definitionRef.startsWith('resource://')) {
|
|
1082
|
+
invalid('AI_DATA_SUBFLOW_DEFINITION_REF_INVALID', `${location}.definitionRef`, 'Child definition must use resource:// identity.');
|
|
1083
|
+
}
|
|
1084
|
+
for (const field of ['capabilityId', 'catalogDigest', 'contractDigest'] as const) {
|
|
1085
|
+
canonicalIdentity(dataField(autonomous, field, `${location}.${field}`), `${location}.${field}`);
|
|
1086
|
+
}
|
|
1087
|
+
} else {
|
|
1088
|
+
invalid('AI_DATA_SUBFLOW_BINDING_KIND_INVALID', `${location}.kind`, 'Child binding kind must be declared or autonomous.');
|
|
1089
|
+
}
|
|
1090
|
+
const flowRef = canonicalIdentity(dataField(binding, 'flowRef', `${location}.flowRef`), `${location}.flowRef`);
|
|
1091
|
+
if (!flowRef.startsWith('eager-data-flow://')) {
|
|
1092
|
+
invalid('AI_DATA_SUBFLOW_FLOW_REF_INVALID', `${location}.flowRef`, 'Child flow must use eager-data-flow:// identity.');
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
interface NormalizedAIDataChildInvocationIdentity {
|
|
1097
|
+
readonly invocationKey: string;
|
|
1098
|
+
readonly childInstanceId: string;
|
|
1099
|
+
readonly childRunId: string;
|
|
1100
|
+
readonly definitionRef: string;
|
|
1101
|
+
readonly flowRef: string;
|
|
1102
|
+
readonly contractDigest: string;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
function normalizeAIDataChildInvocationIdentity(
|
|
1106
|
+
value: unknown,
|
|
1107
|
+
parentRunId: string,
|
|
1108
|
+
parentNodeId: string,
|
|
1109
|
+
parentGeneration: number,
|
|
1110
|
+
subflowValue: unknown,
|
|
1111
|
+
location: string,
|
|
1112
|
+
): NormalizedAIDataChildInvocationIdentity {
|
|
1113
|
+
const identity = exactRecord(value, [
|
|
1114
|
+
'schemaVersion', 'invocationKey', 'parentInstanceId', 'parentRunId', 'parentNodeId', 'parentGeneration',
|
|
1115
|
+
'childInstanceId', 'childRunId', 'subflow',
|
|
1116
|
+
], [], location);
|
|
1117
|
+
if (dataField(identity, 'schemaVersion', `${location}.schemaVersion`) !== 'depa.ai-data-child-invocation/v1') {
|
|
1118
|
+
invalid('AI_DATA_CHILD_INVOCATION_SCHEMA_INVALID', `${location}.schemaVersion`, 'Unsupported child invocation schema.');
|
|
1119
|
+
}
|
|
1120
|
+
canonicalIdentity(dataField(identity, 'parentInstanceId', `${location}.parentInstanceId`), `${location}.parentInstanceId`);
|
|
1121
|
+
if (canonicalIdentity(dataField(identity, 'parentRunId', `${location}.parentRunId`), `${location}.parentRunId`) !== parentRunId
|
|
1122
|
+
|| canonicalIdentity(dataField(identity, 'parentNodeId', `${location}.parentNodeId`), `${location}.parentNodeId`) !== parentNodeId
|
|
1123
|
+
|| nonNegativeSafeInteger(dataField(identity, 'parentGeneration', `${location}.parentGeneration`), `${location}.parentGeneration`) !== parentGeneration) {
|
|
1124
|
+
invalid('AI_DATA_CHILD_INVOCATION_PARENT_MISMATCH', location, 'Child invocation identity must bind the owning parent node generation.');
|
|
1125
|
+
}
|
|
1126
|
+
const subflow = dataField(identity, 'subflow', `${location}.subflow`);
|
|
1127
|
+
normalizeAIDataSubflowBinding(subflow, `${location}.subflow`);
|
|
1128
|
+
if (JSON.stringify(subflow) !== JSON.stringify(subflowValue)) {
|
|
1129
|
+
invalid('AI_DATA_CHILD_INVOCATION_SUBFLOW_MISMATCH', `${location}.subflow`, 'Child invocation must bind the exact node subflow.');
|
|
1130
|
+
}
|
|
1131
|
+
const normalizedSubflow = plainRecord(subflow, `${location}.subflow`);
|
|
1132
|
+
if (dataField(normalizedSubflow, 'kind', `${location}.subflow.kind`) !== 'autonomous') {
|
|
1133
|
+
invalid('AI_DATA_CHILD_INVOCATION_SUBFLOW_INVALID', `${location}.subflow`, 'Durable child invocation requires an autonomous subflow binding.');
|
|
1134
|
+
}
|
|
1135
|
+
return {
|
|
1136
|
+
invocationKey: canonicalIdentity(dataField(identity, 'invocationKey', `${location}.invocationKey`), `${location}.invocationKey`),
|
|
1137
|
+
childInstanceId: canonicalIdentity(dataField(identity, 'childInstanceId', `${location}.childInstanceId`), `${location}.childInstanceId`),
|
|
1138
|
+
childRunId: canonicalIdentity(dataField(identity, 'childRunId', `${location}.childRunId`), `${location}.childRunId`),
|
|
1139
|
+
definitionRef: canonicalIdentity(dataField(normalizedSubflow, 'definitionRef', `${location}.subflow.definitionRef`), `${location}.subflow.definitionRef`),
|
|
1140
|
+
flowRef: canonicalIdentity(dataField(normalizedSubflow, 'flowRef', `${location}.subflow.flowRef`), `${location}.subflow.flowRef`),
|
|
1141
|
+
contractDigest: canonicalIdentity(dataField(normalizedSubflow, 'contractDigest', `${location}.subflow.contractDigest`), `${location}.subflow.contractDigest`),
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
function assertAIDataChildReceiptIdentity(
|
|
1146
|
+
value: Record<string, unknown>,
|
|
1147
|
+
identity: NormalizedAIDataChildInvocationIdentity,
|
|
1148
|
+
location: string,
|
|
1149
|
+
): void {
|
|
1150
|
+
for (const [field, expected] of Object.entries(identity)) {
|
|
1151
|
+
if (field === 'definitionRef' || field === 'flowRef' || field === 'contractDigest'
|
|
1152
|
+
|| field === 'invocationKey' || field === 'childInstanceId' || field === 'childRunId') {
|
|
1153
|
+
if (canonicalIdentity(dataField(value, field, `${location}.${field}`), `${location}.${field}`) !== expected) {
|
|
1154
|
+
invalid('AI_DATA_CHILD_RECEIPT_IDENTITY_MISMATCH', `${location}.${field}`, 'Child receipt does not match the reserved invocation.');
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function normalizeAIDataChildFreezeReceipt(
|
|
1161
|
+
value: unknown,
|
|
1162
|
+
identity: NormalizedAIDataChildInvocationIdentity,
|
|
1163
|
+
location: string,
|
|
1164
|
+
): void {
|
|
1165
|
+
const receipt = exactRecord(value, [
|
|
1166
|
+
'schemaVersion', 'invocationKey', 'childInstanceId', 'childRunId', 'definitionRef', 'flowRef',
|
|
1167
|
+
'contractDigest', 'definitionRevision', 'definitionDigest',
|
|
1168
|
+
], [], location);
|
|
1169
|
+
if (dataField(receipt, 'schemaVersion', `${location}.schemaVersion`) !== 'depa.ai-data-child-invocation/v1') {
|
|
1170
|
+
invalid('AI_DATA_CHILD_FREEZE_SCHEMA_INVALID', `${location}.schemaVersion`, 'Unsupported child freeze schema.');
|
|
1171
|
+
}
|
|
1172
|
+
assertAIDataChildReceiptIdentity(receipt, identity, location);
|
|
1173
|
+
canonicalIdentity(dataField(receipt, 'definitionRevision', `${location}.definitionRevision`), `${location}.definitionRevision`);
|
|
1174
|
+
canonicalIdentity(dataField(receipt, 'definitionDigest', `${location}.definitionDigest`), `${location}.definitionDigest`);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function normalizeAIDataChildTerminalReceipt(
|
|
1178
|
+
value: unknown,
|
|
1179
|
+
identity: NormalizedAIDataChildInvocationIdentity,
|
|
1180
|
+
node: Record<string, unknown>,
|
|
1181
|
+
nodeStatus: string,
|
|
1182
|
+
location: string,
|
|
1183
|
+
): void {
|
|
1184
|
+
const base = plainRecord(value, location);
|
|
1185
|
+
const terminalStatus = dataField(base, 'status', `${location}.status`);
|
|
1186
|
+
const receipt = exactRecord(value, [
|
|
1187
|
+
'schemaVersion', 'invocationKey', 'childInstanceId', 'childRunId', 'definitionRef', 'flowRef',
|
|
1188
|
+
'contractDigest', 'status', 'receiptDigest',
|
|
1189
|
+
...(terminalStatus === 'Succeeded' ? ['output'] : ['failureCode', 'failureMessage']),
|
|
1190
|
+
], [], location);
|
|
1191
|
+
if (dataField(receipt, 'schemaVersion', `${location}.schemaVersion`) !== 'depa.ai-data-child-invocation/v1') {
|
|
1192
|
+
invalid('AI_DATA_CHILD_TERMINAL_SCHEMA_INVALID', `${location}.schemaVersion`, 'Unsupported child terminal schema.');
|
|
1193
|
+
}
|
|
1194
|
+
if (terminalStatus !== 'Succeeded' && terminalStatus !== 'Failed') {
|
|
1195
|
+
invalid('AI_DATA_CHILD_TERMINAL_STATUS_INVALID', `${location}.status`, 'Child terminal receipt must be Succeeded or Failed.');
|
|
1196
|
+
}
|
|
1197
|
+
if (terminalStatus !== nodeStatus) {
|
|
1198
|
+
invalid('AI_DATA_CHILD_TERMINAL_PARENT_STATUS_MISMATCH', `${location}.status`, 'Child terminal status must equal its parent node status.');
|
|
1199
|
+
}
|
|
1200
|
+
assertAIDataChildReceiptIdentity(receipt, identity, location);
|
|
1201
|
+
canonicalIdentity(dataField(receipt, 'receiptDigest', `${location}.receiptDigest`), `${location}.receiptDigest`);
|
|
1202
|
+
if (terminalStatus === 'Succeeded') {
|
|
1203
|
+
const output = plainRecord(dataField(receipt, 'output', `${location}.output`), `${location}.output`);
|
|
1204
|
+
const expected = [...identityArray(
|
|
1205
|
+
dataField(node, 'outputs', `${location}.nodeOutputs`),
|
|
1206
|
+
`${location}.nodeOutputs`,
|
|
1207
|
+
)].sort(compareCodeUnits);
|
|
1208
|
+
if (JSON.stringify(stringDataKeys(output, `${location}.output`).sort(compareCodeUnits)) !== JSON.stringify(expected)) {
|
|
1209
|
+
invalid('AI_DATA_CHILD_TERMINAL_OUTPUT_MISMATCH', `${location}.output`, 'Child output keys must equal the parent SubFlowNode ports.');
|
|
1210
|
+
}
|
|
1211
|
+
} else {
|
|
1212
|
+
canonicalIdentity(dataField(receipt, 'failureCode', `${location}.failureCode`), `${location}.failureCode`);
|
|
1213
|
+
canonicalIdentity(dataField(receipt, 'failureMessage', `${location}.failureMessage`), `${location}.failureMessage`);
|
|
1214
|
+
}
|
|
997
1215
|
}
|
|
998
1216
|
|
|
999
1217
|
function eagerDefinitionDependencies(
|
|
@@ -1321,7 +1539,7 @@ function normalizeAIDataPatchNodeSpec(value: unknown, location: string): string
|
|
|
1321
1539
|
const spec = exactRecord(
|
|
1322
1540
|
value,
|
|
1323
1541
|
['id'],
|
|
1324
|
-
['tag', 'nodeType', 'declarationIndex', 'inputs', 'outputs', 'config', 'dependsOn', 'reusePolicy'],
|
|
1542
|
+
['tag', 'nodeType', 'declarationIndex', 'inputs', 'outputs', 'config', 'subflow', 'dependsOn', 'reusePolicy'],
|
|
1325
1543
|
location,
|
|
1326
1544
|
);
|
|
1327
1545
|
const nodeId = canonicalIdentity(dataField(spec, 'id', `${location}.id`), `${location}.id`);
|
|
@@ -1333,7 +1551,7 @@ function normalizeAIDataPatchNodeChanges(value: unknown, location: string): void
|
|
|
1333
1551
|
const changes = exactRecord(
|
|
1334
1552
|
value,
|
|
1335
1553
|
[],
|
|
1336
|
-
['tag', 'nodeType', 'inputs', 'outputs', 'config', 'dependsOn', 'reusePolicy'],
|
|
1554
|
+
['tag', 'nodeType', 'inputs', 'outputs', 'config', 'subflow', 'dependsOn', 'reusePolicy'],
|
|
1337
1555
|
location,
|
|
1338
1556
|
);
|
|
1339
1557
|
normalizeAIDataPatchNodeFields(changes, location);
|
|
@@ -1349,6 +1567,10 @@ function normalizeAIDataPatchNodeFields(record: Record<string, unknown>, locatio
|
|
|
1349
1567
|
if (Object.prototype.hasOwnProperty.call(record, field)) uniqueIdentityArray(dataField(record, field, `${location}.${field}`), `${location}.${field}`);
|
|
1350
1568
|
}
|
|
1351
1569
|
if (Object.prototype.hasOwnProperty.call(record, 'config')) plainRecord(dataField(record, 'config', `${location}.config`), `${location}.config`);
|
|
1570
|
+
if (Object.prototype.hasOwnProperty.call(record, 'subflow')) {
|
|
1571
|
+
const value = dataField(record, 'subflow', `${location}.subflow`);
|
|
1572
|
+
if (value !== null) normalizeAIDataSubflowBinding(value, `${location}.subflow`);
|
|
1573
|
+
}
|
|
1352
1574
|
if (Object.prototype.hasOwnProperty.call(record, 'reusePolicy')) {
|
|
1353
1575
|
const reuse = exactRecord(dataField(record, 'reusePolicy', `${location}.reusePolicy`), ['policy', 'source', 'nodeType'], [], `${location}.reusePolicy`);
|
|
1354
1576
|
canonicalIdentity(dataField(reuse, 'nodeType', `${location}.reusePolicy.nodeType`), `${location}.reusePolicy.nodeType`);
|
package/src/run-freeze.ts
CHANGED
|
@@ -329,16 +329,26 @@ function semanticResourceFingerprint(
|
|
|
329
329
|
},
|
|
330
330
|
agent: {
|
|
331
331
|
resourceId: agent.resource.resourceId,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
332
|
+
messagePrefix: agent.messagePrefix.map((item) => item.type === 'message'
|
|
333
|
+
? {
|
|
334
|
+
type: item.type,
|
|
335
|
+
id: item.id,
|
|
336
|
+
role: item.role,
|
|
337
|
+
prompt: [item.prompt.kind, item.prompt.resource.resourceId],
|
|
338
|
+
schema: item.schema ? {
|
|
339
|
+
kind: item.schema.kind,
|
|
340
|
+
resourceId: item.schema.resource.resourceId,
|
|
341
|
+
value: item.schema.schema,
|
|
342
|
+
} : null,
|
|
343
|
+
}
|
|
344
|
+
: {
|
|
345
|
+
type: item.type,
|
|
346
|
+
id: item.id,
|
|
347
|
+
source: [item.source.kind, item.source.resource.resourceId],
|
|
348
|
+
}),
|
|
349
|
+
contextPipeline: agent.contextPipeline
|
|
350
|
+
? [agent.contextPipeline.kind, agent.contextPipeline.resource.resourceId]
|
|
351
|
+
: null,
|
|
342
352
|
inputSchema: agent.inputSchema ? {
|
|
343
353
|
kind: agent.inputSchema.kind,
|
|
344
354
|
resourceId: agent.inputSchema.resource.resourceId,
|