ai-workflow-logic 0.1.11 → 0.2.0
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 +6 -5
- package/src/agent-definition-selection.ts +18 -7
- package/src/filesystem.ts +22 -2
- package/src/resource-resolution.ts +121 -0
- package/src/run-freeze.ts +10 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-workflow-logic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "AI workflow domain projections and pure runtime helpers with an explicit Halfcode filesystem adapter",
|
|
6
6
|
"type": "module",
|
|
@@ -13,15 +13,16 @@
|
|
|
13
13
|
"./run-freeze": "./src/run-freeze.ts"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"ai-workflow-contract": "0.
|
|
16
|
+
"ai-workflow-contract": "0.2.0",
|
|
17
|
+
"ajv": "8.20.0",
|
|
17
18
|
"flow-step-space-contract": "0.1.1",
|
|
18
19
|
"flow-step-space-logic": "0.1.1",
|
|
19
|
-
"halfcode-compiler.xnl": "0.
|
|
20
|
-
"holarchy-core-contract": "0.
|
|
20
|
+
"halfcode-compiler.xnl": "0.3.0",
|
|
21
|
+
"holarchy-core-contract": "0.2.0",
|
|
21
22
|
"task-manager-contract": "0.1.3",
|
|
22
23
|
"task-manager-logic": "0.1.6",
|
|
23
24
|
"work-ctrl-flow-contract": "0.1.3",
|
|
24
|
-
"work-ctrl-flow-logic": "0.1.
|
|
25
|
+
"work-ctrl-flow-logic": "0.1.5"
|
|
25
26
|
},
|
|
26
27
|
"files": [
|
|
27
28
|
"src"
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type ResourceAuthoringTransactionPort,
|
|
23
23
|
} from 'halfcode-compiler.xnl/authoring-runtime';
|
|
24
24
|
import {
|
|
25
|
+
RESOURCE_ENVELOPE_VERSION,
|
|
25
26
|
resolveEffectiveResourceContentIdentities,
|
|
26
27
|
sha256Digest,
|
|
27
28
|
type EffectiveResourceRegistry,
|
|
@@ -281,14 +282,16 @@ export async function reconcileAuthoredAIAgentDefinition(
|
|
|
281
282
|
|| input.receipt.catalogId !== proposal.catalogId
|
|
282
283
|
|| input.receipt.resourceId !== proposal.resourceId
|
|
283
284
|
|| input.receipt.kind !== proposal.kind
|
|
284
|
-
|| input.receipt.
|
|
285
|
+
|| input.receipt.envelopeVersion !== proposal.envelopeVersion
|
|
286
|
+
|| input.receipt.writerSpecVersion !== proposal.writerSpecVersion
|
|
285
287
|
|| input.receipt.sourceShape !== proposal.sourceShape
|
|
286
288
|
|| input.receipt.documentUri !== proposal.documentUri
|
|
287
289
|
|| input.receipt.authorityDigestBefore !== null
|
|
288
290
|
|| input.receipt.authorityDigestAfter !== sha256Digest(proposal.authorityText)
|
|
289
291
|
|| input.receipt.registryRevisionBefore !== proposal.expected.registryRevision
|
|
290
292
|
|| input.receipt.kind !== 'AIAgentDefinition'
|
|
291
|
-
|| resource.metadata.
|
|
293
|
+
|| resource.metadata.envelopeVersion !== input.receipt.envelopeVersion
|
|
294
|
+
|| resource.metadata.specVersion !== input.receipt.writerSpecVersion
|
|
292
295
|
|| resource.sourceShape !== input.receipt.sourceShape
|
|
293
296
|
|| resource.documentUri !== input.receipt.documentUri
|
|
294
297
|
|| !identity
|
|
@@ -504,7 +507,8 @@ function isValidAuthoringProposal(
|
|
|
504
507
|
'catalogId',
|
|
505
508
|
'resourceId',
|
|
506
509
|
'kind',
|
|
507
|
-
'
|
|
510
|
+
'envelopeVersion',
|
|
511
|
+
'writerSpecVersion',
|
|
508
512
|
'sourceShape',
|
|
509
513
|
'documentUri',
|
|
510
514
|
'authorityText',
|
|
@@ -521,7 +525,9 @@ function isValidAuthoringProposal(
|
|
|
521
525
|
&& definitionRef === resourceRef(nonEmptyString(proposal.resourceId, 'proposal.resourceId'))
|
|
522
526
|
&& !candidateSet.candidates.some(({ agentDefinitionRef }) => agentDefinitionRef === definitionRef)
|
|
523
527
|
&& nonEmptyString(proposal.catalogId, 'proposal.catalogId').length > 0
|
|
524
|
-
&&
|
|
528
|
+
&& proposal.envelopeVersion === RESOURCE_ENVELOPE_VERSION
|
|
529
|
+
&& Number.isSafeInteger(proposal.writerSpecVersion)
|
|
530
|
+
&& Number(proposal.writerSpecVersion) > 0
|
|
525
531
|
&& /^vfs:\/\/@\/.+\.xnl$/u.test(nonEmptyString(proposal.documentUri, 'proposal.documentUri'))
|
|
526
532
|
&& stringValue(proposal.authorityText, 'proposal.authorityText').length > 0;
|
|
527
533
|
} catch {
|
|
@@ -536,7 +542,8 @@ function cloneAuthoringProposal(value: ResourceAuthoringProposal): ResourceAutho
|
|
|
536
542
|
catalogId: value.catalogId,
|
|
537
543
|
resourceId: value.resourceId,
|
|
538
544
|
kind: value.kind,
|
|
539
|
-
|
|
545
|
+
envelopeVersion: value.envelopeVersion,
|
|
546
|
+
writerSpecVersion: value.writerSpecVersion,
|
|
540
547
|
sourceShape: value.sourceShape,
|
|
541
548
|
documentUri: value.documentUri,
|
|
542
549
|
authorityText: value.authorityText,
|
|
@@ -555,7 +562,8 @@ function validateReceiptShapeAndDigest(receiptValue: ResourceAuthoringReceipt):
|
|
|
555
562
|
'catalogId',
|
|
556
563
|
'resourceId',
|
|
557
564
|
'kind',
|
|
558
|
-
'
|
|
565
|
+
'envelopeVersion',
|
|
566
|
+
'writerSpecVersion',
|
|
559
567
|
'sourceShape',
|
|
560
568
|
'documentUri',
|
|
561
569
|
'authorityDigestBefore',
|
|
@@ -572,6 +580,9 @@ function validateReceiptShapeAndDigest(receiptValue: ResourceAuthoringReceipt):
|
|
|
572
580
|
if (receipt.schemaVersion !== RESOURCE_AUTHORING_SCHEMA_VERSION
|
|
573
581
|
|| (receipt.operation !== 'create' && receipt.operation !== 'update')
|
|
574
582
|
|| receipt.sourceShape !== 'single-file'
|
|
583
|
+
|| receipt.envelopeVersion !== RESOURCE_ENVELOPE_VERSION
|
|
584
|
+
|| !Number.isSafeInteger(receipt.writerSpecVersion)
|
|
585
|
+
|| Number(receipt.writerSpecVersion) <= 0
|
|
575
586
|
|| (receipt.authorityDigestBefore !== null && !isDigest(receipt.authorityDigestBefore))
|
|
576
587
|
|| !isDigest(receipt.planDigest)
|
|
577
588
|
|| !isDigest(receipt.authorityDigestAfter)
|
|
@@ -587,7 +598,7 @@ function validateReceiptShapeAndDigest(receiptValue: ResourceAuthoringReceipt):
|
|
|
587
598
|
|| (origin.logicalPath !== undefined && typeof origin.logicalPath !== 'string')) {
|
|
588
599
|
throw new Error('invalid receipt fields');
|
|
589
600
|
}
|
|
590
|
-
for (const field of ['transactionId', 'catalogId', 'resourceId', 'kind', '
|
|
601
|
+
for (const field of ['transactionId', 'catalogId', 'resourceId', 'kind', 'envelopeVersion', 'documentUri'] as const) {
|
|
591
602
|
nonEmptyString(receipt[field], `receipt.${field}`);
|
|
592
603
|
}
|
|
593
604
|
for (const field of ['layerId', 'packageId', 'documentUri'] as const) {
|
package/src/filesystem.ts
CHANGED
|
@@ -4,12 +4,32 @@ import {
|
|
|
4
4
|
} from 'halfcode-compiler.xnl/resource-core';
|
|
5
5
|
import type { LoadedAIWorkflowResourcePackage } from 'ai-workflow-contract';
|
|
6
6
|
import { projectAIWorkflowAppBundles } from './index.js';
|
|
7
|
+
import {
|
|
8
|
+
resolveAIWorkflowResourceTree,
|
|
9
|
+
type AIWorkflowResourceResolutionContext,
|
|
10
|
+
} from './resource-resolution.js';
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
AI_WORKFLOW_RESOURCE_SCHEMA_VALIDATOR,
|
|
14
|
+
DEPA_AI_RESOURCE_READER_PROFILE_ID,
|
|
15
|
+
createAIWorkflowResourceResolutionContext,
|
|
16
|
+
createResolvedAIWorkflowContentIdentityIndex,
|
|
17
|
+
resolveAIWorkflowResourceTree,
|
|
18
|
+
} from './resource-resolution.js';
|
|
19
|
+
export type { AIWorkflowResourceResolutionContext } from './resource-resolution.js';
|
|
20
|
+
|
|
21
|
+
export interface LoadAIWorkflowResourcePackageOptions extends LoadResourceTreeOptions {
|
|
22
|
+
readonly resolutionContext?: AIWorkflowResourceResolutionContext;
|
|
23
|
+
}
|
|
7
24
|
|
|
8
25
|
export async function loadAIWorkflowResourcePackage(
|
|
9
|
-
options:
|
|
26
|
+
options: LoadAIWorkflowResourcePackageOptions,
|
|
10
27
|
): Promise<LoadedAIWorkflowResourcePackage> {
|
|
11
|
-
const
|
|
28
|
+
const { resolutionContext, ...loadOptions } = options;
|
|
29
|
+
const authoredTree = await loadResourceTree(loadOptions);
|
|
30
|
+
const tree = resolveAIWorkflowResourceTree(authoredTree, resolutionContext);
|
|
12
31
|
return Object.freeze({
|
|
32
|
+
authoredTree,
|
|
13
33
|
tree,
|
|
14
34
|
appBundles: projectAIWorkflowAppBundles(tree.registry, {
|
|
15
35
|
packageResourceId: tree.manifest.resourceId,
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import Ajv, { type ValidateFunction } from 'ajv';
|
|
2
|
+
import {
|
|
3
|
+
DEPA_AI_RESOURCE_KIND_OWNERS,
|
|
4
|
+
DEPA_AI_RESOURCE_KIND_READER_REGISTRATIONS,
|
|
5
|
+
DEPA_AI_RESOURCE_KIND_SPEC_REVISIONS,
|
|
6
|
+
} from 'ai-workflow-contract';
|
|
7
|
+
import type {
|
|
8
|
+
AuthoredResourceTree,
|
|
9
|
+
JsonSchemaValidator,
|
|
10
|
+
KindSpecRevision,
|
|
11
|
+
PortableSpec,
|
|
12
|
+
ResolvedResourceTree,
|
|
13
|
+
ResourceContentIdentity,
|
|
14
|
+
} from 'halfcode-compiler.xnl/resource-core';
|
|
15
|
+
import { createResourceContentIdentity } from 'halfcode-compiler.xnl/resource-core';
|
|
16
|
+
import {
|
|
17
|
+
CORE_KIND_READER_REGISTRATIONS,
|
|
18
|
+
CORE_KIND_SPEC_REVISIONS,
|
|
19
|
+
CORE_KIND_SUBJECT_OWNERS,
|
|
20
|
+
KindContractRegistry,
|
|
21
|
+
KindReaderRegistry,
|
|
22
|
+
SpecResolutionRegistry,
|
|
23
|
+
createReaderProfile,
|
|
24
|
+
resolveResourceTree,
|
|
25
|
+
type ReaderProfile,
|
|
26
|
+
} from 'halfcode-compiler.xnl/kind-definition';
|
|
27
|
+
|
|
28
|
+
export const DEPA_AI_RESOURCE_READER_PROFILE_ID = 'depa.flows.ai-resources.v1' as const;
|
|
29
|
+
|
|
30
|
+
export interface AIWorkflowResourceResolutionContext {
|
|
31
|
+
readonly contracts: KindContractRegistry;
|
|
32
|
+
readonly readers: KindReaderRegistry;
|
|
33
|
+
readonly readerProfile: ReaderProfile;
|
|
34
|
+
readonly resolutions: SpecResolutionRegistry;
|
|
35
|
+
readonly validator: JsonSchemaValidator;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ajv = new Ajv({ allErrors: true, strict: false, validateFormats: false });
|
|
39
|
+
const compiledSchemas = new Map<string, ValidateFunction>();
|
|
40
|
+
|
|
41
|
+
export const AI_WORKFLOW_RESOURCE_SCHEMA_VALIDATOR: JsonSchemaValidator = Object.freeze({
|
|
42
|
+
validate(revision: KindSpecRevision, value: PortableSpec): readonly string[] {
|
|
43
|
+
let validate = compiledSchemas.get(revision.contractFingerprint);
|
|
44
|
+
if (!validate) {
|
|
45
|
+
validate = ajv.compile(revision.specSchema);
|
|
46
|
+
compiledSchemas.set(revision.contractFingerprint, validate);
|
|
47
|
+
}
|
|
48
|
+
return validate(value)
|
|
49
|
+
? Object.freeze([])
|
|
50
|
+
: Object.freeze((validate.errors ?? []).map((error) =>
|
|
51
|
+
`${error.instancePath || '/'} ${error.message ?? 'invalid'}`));
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export function createAIWorkflowResourceResolutionContext(
|
|
56
|
+
validator: JsonSchemaValidator = AI_WORKFLOW_RESOURCE_SCHEMA_VALIDATOR,
|
|
57
|
+
): AIWorkflowResourceResolutionContext {
|
|
58
|
+
const contracts = new KindContractRegistry();
|
|
59
|
+
for (const owner of [...CORE_KIND_SUBJECT_OWNERS, ...DEPA_AI_RESOURCE_KIND_OWNERS]) {
|
|
60
|
+
contracts.registerOwner(owner);
|
|
61
|
+
}
|
|
62
|
+
for (const revision of [...CORE_KIND_SPEC_REVISIONS, ...DEPA_AI_RESOURCE_KIND_SPEC_REVISIONS]) {
|
|
63
|
+
contracts.registerRevision(revision);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const readerRegistrations = [
|
|
67
|
+
...CORE_KIND_READER_REGISTRATIONS,
|
|
68
|
+
...DEPA_AI_RESOURCE_KIND_READER_REGISTRATIONS,
|
|
69
|
+
];
|
|
70
|
+
const readers = new KindReaderRegistry();
|
|
71
|
+
for (const reader of readerRegistrations) readers.register(reader);
|
|
72
|
+
const readerProfile = createReaderProfile(
|
|
73
|
+
DEPA_AI_RESOURCE_READER_PROFILE_ID,
|
|
74
|
+
readerRegistrations.map((reader) => ({
|
|
75
|
+
readerId: reader.readerId,
|
|
76
|
+
subjectFqn: reader.subjectFqn,
|
|
77
|
+
readerSpecVersion: reader.readerSpecVersion,
|
|
78
|
+
contractFingerprint: reader.contractFingerprint,
|
|
79
|
+
readerImplementationFingerprint: reader.readerImplementationFingerprint,
|
|
80
|
+
})),
|
|
81
|
+
);
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
contracts,
|
|
84
|
+
readers,
|
|
85
|
+
readerProfile,
|
|
86
|
+
resolutions: new SpecResolutionRegistry(contracts),
|
|
87
|
+
validator,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function resolveAIWorkflowResourceTree(
|
|
92
|
+
tree: AuthoredResourceTree,
|
|
93
|
+
context: AIWorkflowResourceResolutionContext = createAIWorkflowResourceResolutionContext(),
|
|
94
|
+
): ResolvedResourceTree {
|
|
95
|
+
return resolveResourceTree({ tree, ...context });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Projects Halfcode's exact resolved content identity into the public dependency
|
|
100
|
+
* snapshot shape. The digest is still constructed by Halfcode; depa-flows only
|
|
101
|
+
* chooses the resolved receipt identity as the authority input.
|
|
102
|
+
*/
|
|
103
|
+
export function createResolvedAIWorkflowContentIdentityIndex(
|
|
104
|
+
tree: ResolvedResourceTree,
|
|
105
|
+
): ReadonlyMap<string, ResourceContentIdentity> {
|
|
106
|
+
const entries: Array<readonly [string, ResourceContentIdentity]> = [];
|
|
107
|
+
for (const records of tree.registry.byKind.values()) {
|
|
108
|
+
for (const record of records) {
|
|
109
|
+
entries.push([record.resourceId, createResourceContentIdentity({
|
|
110
|
+
resourceId: record.resourceId,
|
|
111
|
+
authorityDigest: record.effectiveContentDigest,
|
|
112
|
+
})]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
entries.sort(([left], [right]) => compareCodeUnits(left, right));
|
|
116
|
+
return new Map(entries);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function compareCodeUnits(left: string, right: string): number {
|
|
120
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
121
|
+
}
|
package/src/run-freeze.ts
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
buildResourceDependencySnapshot,
|
|
19
19
|
composeLayeredResourceRegistry,
|
|
20
20
|
loadResourceTree,
|
|
21
|
-
resolveEffectiveResourceContentIdentities,
|
|
22
21
|
sha256Digest,
|
|
23
22
|
type EffectiveResourceRegistry,
|
|
24
23
|
type ResourceContentIdentity,
|
|
@@ -26,6 +25,10 @@ import {
|
|
|
26
25
|
} from 'halfcode-compiler.xnl/resource-core';
|
|
27
26
|
import { projectAIWorkflowAgentResources } from './agent-resources.js';
|
|
28
27
|
import { sourceRegistryForAIWorkflowAgentResourceProjection } from './agent-resource-brand.js';
|
|
28
|
+
import {
|
|
29
|
+
createResolvedAIWorkflowContentIdentityIndex,
|
|
30
|
+
resolveAIWorkflowResourceTree,
|
|
31
|
+
} from './resource-resolution.js';
|
|
29
32
|
|
|
30
33
|
export interface FreezeAIWorkflowRunResourcesInput {
|
|
31
34
|
readonly registry: EffectiveResourceRegistry;
|
|
@@ -39,14 +42,15 @@ export async function freezeAIWorkflowRunResourcesFromDirectory(
|
|
|
39
42
|
rootDir: string,
|
|
40
43
|
task: AIWorkflowAgentTaskRef,
|
|
41
44
|
): Promise<AIWorkflowRunResourceFreezeReceipt> {
|
|
42
|
-
const
|
|
45
|
+
const authoredTree = await loadResourceTree({ rootDir });
|
|
46
|
+
const tree = resolveAIWorkflowResourceTree(authoredTree);
|
|
43
47
|
const layers = [{ id: 'workspace', tree }] as const;
|
|
44
48
|
const registry = composeLayeredResourceRegistry({ layers });
|
|
45
49
|
return freezeAIWorkflowRunResources({
|
|
46
50
|
registry,
|
|
47
51
|
projection: projectAIWorkflowAgentResources(registry),
|
|
48
52
|
task,
|
|
49
|
-
contentIdentities:
|
|
53
|
+
contentIdentities: createResolvedAIWorkflowContentIdentityIndex(tree),
|
|
50
54
|
});
|
|
51
55
|
}
|
|
52
56
|
|
|
@@ -72,13 +76,14 @@ export async function freezeAIWorkflowHolonTaskTargetFromDirectory(
|
|
|
72
76
|
rootDir: string,
|
|
73
77
|
target: HolonTaskTarget,
|
|
74
78
|
): Promise<AIWorkflowHolonTaskTargetFreezeReceipt> {
|
|
75
|
-
const
|
|
79
|
+
const authoredTree = await loadResourceTree({ rootDir });
|
|
80
|
+
const tree = resolveAIWorkflowResourceTree(authoredTree);
|
|
76
81
|
const layers = [{ id: 'workspace', tree }] as const;
|
|
77
82
|
const registry = composeLayeredResourceRegistry({ layers });
|
|
78
83
|
return freezeAIWorkflowHolonTaskTarget({
|
|
79
84
|
registry,
|
|
80
85
|
target,
|
|
81
|
-
contentIdentities:
|
|
86
|
+
contentIdentities: createResolvedAIWorkflowContentIdentityIndex(tree),
|
|
82
87
|
});
|
|
83
88
|
}
|
|
84
89
|
|