ai-workflow-contract 0.1.8 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-workflow-contract",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "description": "Shared AI workflow contracts over WorkCtrlFlow and EagerDataFlow substrates",
6
6
  "type": "module",
@@ -9,8 +9,8 @@
9
9
  ".": "./src/index.ts"
10
10
  },
11
11
  "dependencies": {
12
- "eager-data-flow-contract": "0.1.3",
13
- "halfcode-compiler.xnl": "0.2.2",
12
+ "eager-data-flow-contract": "0.1.4",
13
+ "halfcode-compiler.xnl": "0.3.0",
14
14
  "task-manager-contract": "0.1.3",
15
15
  "work-ctrl-flow-contract": "0.1.3"
16
16
  },
package/src/index.ts CHANGED
@@ -4,10 +4,14 @@ import type {
4
4
  ResourceDependencySnapshot,
5
5
  ResourceRecord,
6
6
  ResourceRegistry,
7
- ResourceTree,
7
+ AuthoredResourceTree,
8
+ ResolvedResourceTree,
8
9
  ResourceValue,
9
10
  Sha256Digest,
10
11
  } from 'halfcode-compiler.xnl/resource-core';
12
+ import type {
13
+ ResourceAuthoringProposal,
14
+ } from 'halfcode-compiler.xnl/authoring-runtime';
11
15
  import type {
12
16
  FlowBundleSpec,
13
17
  FlowClosedObject,
@@ -19,6 +23,7 @@ import type {
19
23
  } from 'work-ctrl-flow-contract';
20
24
 
21
25
  export * from './holon-task.js';
26
+ export * from './resource-kind-contracts.js';
22
27
 
23
28
  export {
24
29
  FLOW_INSTANCE_SCHEMA_VERSION,
@@ -145,7 +150,8 @@ export interface ProjectAIWorkflowAppBundlesOptions {
145
150
  }
146
151
 
147
152
  export interface LoadedAIWorkflowResourcePackage {
148
- readonly tree: ResourceTree;
153
+ readonly authoredTree: AuthoredResourceTree;
154
+ readonly tree: ResolvedResourceTree;
149
155
  readonly appBundles: readonly AIWorkflowAppBundleProjection[];
150
156
  }
151
157
 
@@ -156,6 +162,8 @@ export type MaterialCardinality = 'one' | 'many';
156
162
  export type AIWorkflowAgentResourceRelation =
157
163
  | 'agent-message-prompt'
158
164
  | 'agent-message-schema'
165
+ | 'agent-message-source'
166
+ | 'agent-context-pipeline'
159
167
  | 'agent-input-schema'
160
168
  | 'agent-output-schema'
161
169
  | 'agent-tool'
@@ -196,6 +204,20 @@ export interface AIAgentMessageProjection {
196
204
  readonly schema?: AIWorkflowMessageSchemaProjection;
197
205
  }
198
206
 
207
+ export interface AIAgentStaticMessagePrefixProjection extends AIAgentMessageProjection {
208
+ readonly type: 'message';
209
+ }
210
+
211
+ export interface AIAgentMessageSourcePrefixProjection {
212
+ readonly type: 'message-source';
213
+ readonly id: string;
214
+ readonly source: AIWorkflowTypedResourceRef;
215
+ }
216
+
217
+ export type AIAgentMessagePrefixItemProjection =
218
+ | AIAgentStaticMessagePrefixProjection
219
+ | AIAgentMessageSourcePrefixProjection;
220
+
199
221
  export interface MaterialPortProjection {
200
222
  readonly kind: 'MaterialPort';
201
223
  readonly fqn: string;
@@ -210,7 +232,10 @@ export interface AIAgentDefinitionProjection {
210
232
  readonly kind: 'AIAgentDefinition';
211
233
  readonly fqn: string;
212
234
  readonly resource: ResourceRecord;
235
+ readonly messagePrefix: readonly AIAgentMessagePrefixItemProjection[];
236
+ /** Static compatibility view for legacy consumers during MessagePrefix adoption. */
213
237
  readonly messages: readonly AIAgentMessageProjection[];
238
+ readonly contextPipeline?: AIWorkflowTypedResourceRef;
214
239
  readonly inputSchema?: AIWorkflowMessageSchemaProjection;
215
240
  readonly outputSchema?: AIWorkflowMessageSchemaProjection;
216
241
  readonly tools: readonly AIWorkflowTypedResourceRef[];
@@ -258,6 +283,131 @@ export interface FrozenAIAgentTaskBinding {
258
283
  readonly closureResourceIds: readonly string[];
259
284
  }
260
285
 
286
+ export const AI_AGENT_DEFINITION_SELECTION_SCHEMA_VERSION =
287
+ 'ai-workflow.agent-definition-selection/v1' as const;
288
+
289
+ export interface AIAgentTaskRequirement {
290
+ readonly schemaVersion: typeof AI_AGENT_DEFINITION_SELECTION_SCHEMA_VERSION;
291
+ readonly requirementId: string;
292
+ /** Audit/model evidence only. It is never a code-level routing input. */
293
+ readonly objective: string;
294
+ readonly inputSchemaRef?: `resource://${string}`;
295
+ readonly outputSchemaRef?: `resource://${string}`;
296
+ readonly requiredToolRefs: readonly `resource://${string}`[];
297
+ readonly requiredEffectPolicyRef?: `resource://${string}`;
298
+ readonly requiredToolMode?: AIWorkflowEffectToolMode;
299
+ readonly requiredMaterialPortRefs: readonly `resource://${string}`[];
300
+ readonly requiredContextPipelineRef?: `resource://${string}`;
301
+ readonly requiredMessageSourceRefs: readonly `resource://${string}`[];
302
+ }
303
+
304
+ export interface AIAgentTaskRequirementProjection {
305
+ readonly schemaVersion: typeof AI_AGENT_DEFINITION_SELECTION_SCHEMA_VERSION;
306
+ readonly requirement: AIAgentTaskRequirement;
307
+ readonly requirementDigest: Sha256Digest;
308
+ }
309
+
310
+ export interface AIAgentDefinitionCandidateContract {
311
+ readonly inputSchemaRef?: `resource://${string}`;
312
+ readonly outputSchemaRef?: `resource://${string}`;
313
+ readonly toolRefs: readonly `resource://${string}`[];
314
+ readonly effectPolicyRef?: `resource://${string}`;
315
+ readonly effectToolMode?: AIWorkflowEffectToolMode;
316
+ readonly materialPortRefs: readonly `resource://${string}`[];
317
+ readonly contextPipelineRef?: `resource://${string}`;
318
+ readonly messageSourceRefs: readonly `resource://${string}`[];
319
+ }
320
+
321
+ export interface AIAgentDefinitionAuthoringEvidence {
322
+ readonly schemaVersion: 'ai-workflow.agent-definition-authoring-evidence/v1';
323
+ readonly planDigest: Sha256Digest;
324
+ readonly transactionId: string;
325
+ readonly receiptDigest: Sha256Digest;
326
+ }
327
+
328
+ export interface AIAgentDefinitionCandidate {
329
+ readonly schemaVersion: typeof AI_AGENT_DEFINITION_SELECTION_SCHEMA_VERSION;
330
+ readonly agentDefinitionRef: `resource://${string}`;
331
+ readonly contentDigest: Sha256Digest;
332
+ readonly registryRevision: Sha256Digest;
333
+ readonly contract: AIAgentDefinitionCandidateContract;
334
+ readonly candidateDigest: Sha256Digest;
335
+ readonly authoringEvidence?: AIAgentDefinitionAuthoringEvidence;
336
+ }
337
+
338
+ export interface AIAgentDefinitionCandidateSet {
339
+ readonly schemaVersion: typeof AI_AGENT_DEFINITION_SELECTION_SCHEMA_VERSION;
340
+ readonly registryRevision: Sha256Digest;
341
+ readonly candidates: readonly AIAgentDefinitionCandidate[];
342
+ readonly candidateSetDigest: Sha256Digest;
343
+ }
344
+
345
+ interface AIAgentDefinitionSelectionDecisionBase {
346
+ readonly schemaVersion: typeof AI_AGENT_DEFINITION_SELECTION_SCHEMA_VERSION;
347
+ readonly requirementDigest: Sha256Digest;
348
+ readonly candidateSetDigest: Sha256Digest;
349
+ readonly reason: string;
350
+ }
351
+
352
+ export interface SelectExistingAIAgentDefinitionDecision
353
+ extends AIAgentDefinitionSelectionDecisionBase {
354
+ readonly mode: 'select-existing';
355
+ readonly candidateRef: `resource://${string}`;
356
+ readonly candidateDigest: Sha256Digest;
357
+ }
358
+
359
+ export interface AuthorNewAIAgentDefinitionDecision
360
+ extends AIAgentDefinitionSelectionDecisionBase {
361
+ readonly mode: 'author-new';
362
+ readonly agentDefinitionRef: `resource://${string}`;
363
+ readonly proposal: ResourceAuthoringProposal;
364
+ }
365
+
366
+ export type AIAgentDefinitionSelectionDecision =
367
+ | SelectExistingAIAgentDefinitionDecision
368
+ | AuthorNewAIAgentDefinitionDecision;
369
+
370
+ export type AIAgentDefinitionSelectionRejectionCode =
371
+ | 'decision-invalid'
372
+ | 'requirement-stale'
373
+ | 'candidate-set-stale'
374
+ | 'candidate-missing'
375
+ | 'candidate-digest-mismatch'
376
+ | 'candidate-incompatible'
377
+ | 'authoring-in-progress'
378
+ | 'authoring-proposal-invalid';
379
+
380
+ export interface SelectedAIAgentDefinitionAdmission {
381
+ readonly schemaVersion: typeof AI_AGENT_DEFINITION_SELECTION_SCHEMA_VERSION;
382
+ readonly status: 'selected';
383
+ readonly requirementDigest: Sha256Digest;
384
+ readonly candidateSetDigest: Sha256Digest;
385
+ readonly candidate: AIAgentDefinitionCandidate;
386
+ readonly reason: string;
387
+ }
388
+
389
+ export interface AuthorNewAIAgentDefinitionAdmission {
390
+ readonly schemaVersion: typeof AI_AGENT_DEFINITION_SELECTION_SCHEMA_VERSION;
391
+ readonly status: 'authoring-request';
392
+ readonly requirementDigest: Sha256Digest;
393
+ readonly candidateSetDigest: Sha256Digest;
394
+ readonly agentDefinitionRef: `resource://${string}`;
395
+ readonly proposal: ResourceAuthoringProposal;
396
+ readonly reason: string;
397
+ }
398
+
399
+ export interface RejectedAIAgentDefinitionAdmission {
400
+ readonly schemaVersion: typeof AI_AGENT_DEFINITION_SELECTION_SCHEMA_VERSION;
401
+ readonly status: 'rejected';
402
+ readonly code: AIAgentDefinitionSelectionRejectionCode;
403
+ readonly message: string;
404
+ }
405
+
406
+ export type AIAgentDefinitionSelectionAdmission =
407
+ | SelectedAIAgentDefinitionAdmission
408
+ | AuthorNewAIAgentDefinitionAdmission
409
+ | RejectedAIAgentDefinitionAdmission;
410
+
261
411
  export type AIWorkflowNodeType =
262
412
  | 'agent'
263
413
  | 'data'
@@ -410,8 +560,8 @@ export interface AIWorkflowExecutionReceipt {
410
560
  }
411
561
 
412
562
  export type AIAgentSelector =
413
- | { readonly byInstanceId: string; readonly byInstanceName?: never }
414
- | { readonly byInstanceName: string; readonly byInstanceId?: never };
563
+ | { readonly byId: string; readonly byName?: never }
564
+ | { readonly byName: string; readonly byId?: never };
415
565
 
416
566
  export interface AIAgentInvocation<Input extends FlowClosedValue = FlowClosedValue> {
417
567
  readonly kind: 'ai.agent';
@@ -0,0 +1,281 @@
1
+ import {
2
+ digestCanonical,
3
+ type JsonSchema,
4
+ type KindReaderRegistration,
5
+ type KindSemanticContract,
6
+ type KindSpecRevision,
7
+ type KindSubjectOwner,
8
+ type PortableSpec,
9
+ } from 'halfcode-compiler.xnl/resource-core';
10
+ import {
11
+ createKindSpecRevision,
12
+ createKindSubjectOwner,
13
+ } from 'halfcode-compiler.xnl/kind-definition';
14
+
15
+ export const DEPA_AI_RESOURCE_KINDS = Object.freeze([
16
+ 'AIAgentDefinition',
17
+ 'AgentMessageSource',
18
+ 'AgentContextPipeline',
19
+ 'Prompt',
20
+ 'Tool',
21
+ 'MessageSchema',
22
+ 'EffectPolicy',
23
+ 'MaterialPort',
24
+ 'MaterialBinding',
25
+ 'RequestMaterial',
26
+ 'ContextMaterial',
27
+ 'AIWorkflowAppBundle',
28
+ 'AICtrlWorkflow',
29
+ 'AIDataWorkflow',
30
+ ] as const);
31
+
32
+ export type DepaAIResourceKind = (typeof DEPA_AI_RESOURCE_KINDS)[number];
33
+
34
+ export const DEPA_AI_RESOURCE_ENVELOPE_VERSION = 'halfcode.resource-envelope/v1' as const;
35
+ export const DEPA_AI_RESOURCE_SPEC_VERSION = 1 as const;
36
+ export const DEPA_AI_RESOURCE_KIND_OWNER_PACKAGE_ID = 'ai-workflow-contract' as const;
37
+ export const DEPA_AI_RESOURCE_SEMANTIC_API_VERSION = 'depa.flows/v1' as const;
38
+
39
+ const subjectFqnFor = (kind: DepaAIResourceKind): `Depa.Flows.KindDefinition.${DepaAIResourceKind}` =>
40
+ `Depa.Flows.KindDefinition.${kind}`;
41
+
42
+ export const DEPA_AI_RESOURCE_KIND_OWNER_PACKAGE_FINGERPRINT = digestCanonical(Object.freeze({
43
+ authority: 'ai-workflow-contract/depa-ai-resource-kinds/v1',
44
+ ownerPackageId: DEPA_AI_RESOURCE_KIND_OWNER_PACKAGE_ID,
45
+ subjects: Object.freeze(DEPA_AI_RESOURCE_KINDS.map(subjectFqnFor).sort(compareCodeUnits)),
46
+ semanticContractVersion: DEPA_AI_RESOURCE_SEMANTIC_API_VERSION,
47
+ }));
48
+
49
+ interface KindSchemaShape {
50
+ readonly requiredProperties?: readonly string[];
51
+ readonly requiredSubdomains?: readonly string[];
52
+ }
53
+
54
+ const KIND_SCHEMA_SHAPES: Readonly<Record<DepaAIResourceKind, KindSchemaShape>> = Object.freeze({
55
+ AIAgentDefinition: Object.freeze({ requiredSubdomains: Object.freeze(['ToolRefs', 'MaterialPortRefs']) }),
56
+ AgentMessageSource: Object.freeze({ requiredSubdomains: Object.freeze(['Content']) }),
57
+ AgentContextPipeline: Object.freeze({ requiredSubdomains: Object.freeze(['Content']) }),
58
+ Prompt: Object.freeze({ requiredProperties: Object.freeze(['template']) }),
59
+ Tool: Object.freeze({}),
60
+ MessageSchema: Object.freeze({ requiredProperties: Object.freeze(['schema']) }),
61
+ EffectPolicy: Object.freeze({ requiredProperties: Object.freeze(['toolMode']) }),
62
+ MaterialPort: Object.freeze({ requiredProperties: Object.freeze(['materialKind', 'required', 'cardinality']) }),
63
+ MaterialBinding: Object.freeze({ requiredSubdomains: Object.freeze(['AgentTaskRef', 'PortRef', 'MaterialRef']) }),
64
+ RequestMaterial: Object.freeze({ requiredProperties: Object.freeze(['value']) }),
65
+ ContextMaterial: Object.freeze({ requiredProperties: Object.freeze(['value']) }),
66
+ AIWorkflowAppBundle: Object.freeze({ requiredSubdomains: Object.freeze(['WorkflowBindings']) }),
67
+ AICtrlWorkflow: Object.freeze({}),
68
+ AIDataWorkflow: Object.freeze({}),
69
+ });
70
+
71
+ const KIND_SEMANTIC_OPERATIONS: Readonly<Record<DepaAIResourceKind, readonly string[]>> = Object.freeze({
72
+ AIAgentDefinition: Object.freeze(['projectAIWorkflowAgentResources', 'freezeAIAgentTaskResources']),
73
+ AgentMessageSource: Object.freeze(['projectAIWorkflowAgentResources', 'compileAgentMessageSource']),
74
+ AgentContextPipeline: Object.freeze(['projectAIWorkflowAgentResources', 'compileAgentContextPipeline']),
75
+ Prompt: Object.freeze(['projectAIWorkflowAgentResources', 'compileAgentMessagePrefix']),
76
+ Tool: Object.freeze(['projectAIWorkflowAgentResources', 'freezeAIAgentTaskResources']),
77
+ MessageSchema: Object.freeze(['projectAIWorkflowAgentResources', 'validateExecutableMessageSchema']),
78
+ EffectPolicy: Object.freeze(['projectAIWorkflowAgentResources', 'validateExecutableEffectPolicy']),
79
+ MaterialPort: Object.freeze(['projectAIWorkflowAgentResources', 'validateMaterialPortBindings']),
80
+ MaterialBinding: Object.freeze(['projectAIWorkflowAgentResources', 'freezeAIAgentTaskResources']),
81
+ RequestMaterial: Object.freeze(['projectAIWorkflowAgentResources', 'projectMaterialValue']),
82
+ ContextMaterial: Object.freeze(['projectAIWorkflowAgentResources', 'projectMaterialValue']),
83
+ AIWorkflowAppBundle: Object.freeze(['projectAIWorkflowAppBundles']),
84
+ AICtrlWorkflow: Object.freeze(['loadAICtrlWorkflowSources', 'projectAIWorkflowAppBundles']),
85
+ AIDataWorkflow: Object.freeze(['loadAIDataWorkflowSources', 'projectAIWorkflowAppBundles']),
86
+ });
87
+
88
+ const KIND_REFERENCE_ROLES: Readonly<Record<DepaAIResourceKind, readonly string[]>> = Object.freeze({
89
+ AIAgentDefinition: Object.freeze([
90
+ 'message-prompt',
91
+ 'message-schema',
92
+ 'message-source',
93
+ 'context-pipeline',
94
+ 'input-schema',
95
+ 'output-schema',
96
+ 'tool',
97
+ 'effect-policy',
98
+ 'material-port',
99
+ ]),
100
+ AgentMessageSource: Object.freeze([]),
101
+ AgentContextPipeline: Object.freeze([]),
102
+ Prompt: Object.freeze([]),
103
+ Tool: Object.freeze([]),
104
+ MessageSchema: Object.freeze([]),
105
+ EffectPolicy: Object.freeze([]),
106
+ MaterialPort: Object.freeze(['schema']),
107
+ MaterialBinding: Object.freeze(['agent-task', 'material-port', 'material-value']),
108
+ RequestMaterial: Object.freeze([]),
109
+ ContextMaterial: Object.freeze([]),
110
+ AIWorkflowAppBundle: Object.freeze(['workflow-binding']),
111
+ AICtrlWorkflow: Object.freeze([]),
112
+ AIDataWorkflow: Object.freeze([]),
113
+ });
114
+
115
+ export interface DepaAIResourceKindContract {
116
+ readonly kind: DepaAIResourceKind;
117
+ readonly subjectFqn: `Depa.Flows.KindDefinition.${DepaAIResourceKind}`;
118
+ readonly owner: KindSubjectOwner;
119
+ readonly revision: KindSpecRevision;
120
+ readonly reader: KindReaderRegistration<PortableSpec>;
121
+ readonly kindDefinitionSource: string;
122
+ }
123
+
124
+ function authoredSpecSchema(kind: DepaAIResourceKind): JsonSchema {
125
+ const shape = KIND_SCHEMA_SHAPES[kind];
126
+ const propertiesSchema = Object.freeze({
127
+ type: 'object',
128
+ ...(shape.requiredProperties && shape.requiredProperties.length > 0
129
+ ? { required: Object.freeze([...shape.requiredProperties]) }
130
+ : {}),
131
+ });
132
+ const subdomainsSchema = Object.freeze({
133
+ type: 'object',
134
+ ...(shape.requiredSubdomains && shape.requiredSubdomains.length > 0
135
+ ? { required: Object.freeze([...shape.requiredSubdomains]) }
136
+ : {}),
137
+ });
138
+ return Object.freeze({
139
+ type: 'object',
140
+ required: Object.freeze(['properties', 'body', 'subdomains']),
141
+ properties: Object.freeze({
142
+ properties: propertiesSchema,
143
+ body: Object.freeze({ type: 'array' }),
144
+ subdomains: subdomainsSchema,
145
+ text: Object.freeze({ type: 'string' }),
146
+ }),
147
+ additionalProperties: false,
148
+ });
149
+ }
150
+
151
+ function semanticContract(kind: DepaAIResourceKind): KindSemanticContract {
152
+ return Object.freeze({
153
+ semanticValidatorFingerprint: digestCanonical(Object.freeze({
154
+ authority: 'ai-workflow-contract/depa-ai-resource-kinds/v1',
155
+ kind,
156
+ role: 'closed-domain-semantic-validation',
157
+ operations: KIND_SEMANTIC_OPERATIONS[kind],
158
+ })),
159
+ referenceProjectionFingerprint: digestCanonical(Object.freeze({
160
+ authority: 'ai-workflow-contract/depa-ai-resource-kinds/v1',
161
+ kind,
162
+ role: 'typed-resource-reference-projection',
163
+ relations: KIND_REFERENCE_ROLES[kind],
164
+ })),
165
+ compilerInputFingerprint: digestCanonical(Object.freeze({
166
+ authority: 'ai-workflow-contract/depa-ai-resource-kinds/v1',
167
+ kind,
168
+ role: 'authored-spec-envelope-projection',
169
+ fields: Object.freeze(['properties', 'body', 'subdomains', 'text']),
170
+ })),
171
+ });
172
+ }
173
+
174
+ function readerRegistration(
175
+ kind: DepaAIResourceKind,
176
+ revision: KindSpecRevision,
177
+ ): KindReaderRegistration<PortableSpec> {
178
+ const readerId = `ai-workflow-contract.${kind}.reader.v1`;
179
+ return Object.freeze({
180
+ readerId,
181
+ subjectFqn: revision.subjectFqn,
182
+ readerSpecVersion: revision.specVersion,
183
+ contractFingerprint: revision.contractFingerprint,
184
+ readerImplementationFingerprint: digestCanonical(Object.freeze({
185
+ authority: 'ai-workflow-contract/depa-ai-resource-kinds/v1',
186
+ readerId,
187
+ kind,
188
+ contractFingerprint: revision.contractFingerprint,
189
+ operations: KIND_SEMANTIC_OPERATIONS[kind],
190
+ implementation: 'identity-read-of-halfcode-validated-portable-spec',
191
+ })),
192
+ compatibilityPolicy: 'exact',
193
+ read: (spec: PortableSpec): PortableSpec => spec,
194
+ });
195
+ }
196
+
197
+ function renderKindDefinition(owner: KindSubjectOwner, revision: KindSpecRevision): string {
198
+ return [
199
+ `<KindDefinition #${owner.subjectFqn} envelopeVersion="${DEPA_AI_RESOURCE_ENVELOPE_VERSION}" specVersion=1 {`,
200
+ ' lifecycle = "Stable"',
201
+ ` resourceKind = "${owner.kind}"`,
202
+ ` subjectFqn = "${owner.subjectFqn}"`,
203
+ ' sourceShapes = ["single-file"]',
204
+ ` documentCardinality = "${owner.sourceContract.documentCardinality}"`,
205
+ ` description = "Canonical depa-flows ${owner.kind} resource contract."`,
206
+ '} (',
207
+ ' <SpecRevisions [',
208
+ ` <SpecRevision #v${revision.specVersion} {`,
209
+ ` specVersion = ${revision.specVersion}`,
210
+ ' schemaRef = "vfs://./spec-v1.schema.json"',
211
+ ` schemaFingerprint = "${revision.schemaFingerprint}"`,
212
+ ` contractFingerprint = "${revision.contractFingerprint}"`,
213
+ ` semanticValidatorFingerprint = "${revision.semanticContract.semanticValidatorFingerprint}"`,
214
+ ` referenceProjectionFingerprint = "${revision.semanticContract.referenceProjectionFingerprint}"`,
215
+ ` compilerInputFingerprint = "${revision.semanticContract.compilerInputFingerprint}"`,
216
+ ` stability = "${revision.stability}"`,
217
+ ' }>',
218
+ ' ]>',
219
+ ')>',
220
+ '',
221
+ ].join('\n');
222
+ }
223
+
224
+ function createContract(kind: DepaAIResourceKind): DepaAIResourceKindContract {
225
+ const subjectFqn = subjectFqnFor(kind);
226
+ const owner = createKindSubjectOwner({
227
+ kind,
228
+ subjectFqn,
229
+ ownerPackageId: DEPA_AI_RESOURCE_KIND_OWNER_PACKAGE_ID,
230
+ ownerPackageFingerprint: DEPA_AI_RESOURCE_KIND_OWNER_PACKAGE_FINGERPRINT,
231
+ sourceShapes: Object.freeze(['single-file']),
232
+ documentCardinality: 'one',
233
+ });
234
+ const revision = createKindSpecRevision({
235
+ kind,
236
+ subjectFqn,
237
+ specVersion: DEPA_AI_RESOURCE_SPEC_VERSION,
238
+ specSchema: authoredSpecSchema(kind),
239
+ semanticContract: semanticContract(kind),
240
+ sourceContractFingerprint: owner.sourceContract.sourceContractFingerprint,
241
+ stability: 'stable',
242
+ });
243
+ return Object.freeze({
244
+ kind,
245
+ subjectFqn,
246
+ owner,
247
+ revision,
248
+ reader: readerRegistration(kind, revision),
249
+ kindDefinitionSource: renderKindDefinition(owner, revision),
250
+ });
251
+ }
252
+
253
+ export const DEPA_AI_RESOURCE_KIND_CONTRACTS: readonly DepaAIResourceKindContract[] = Object.freeze(
254
+ DEPA_AI_RESOURCE_KINDS.map(createContract),
255
+ );
256
+
257
+ export const DEPA_AI_RESOURCE_KIND_OWNERS: readonly KindSubjectOwner[] = Object.freeze(
258
+ DEPA_AI_RESOURCE_KIND_CONTRACTS.map((contract) => contract.owner),
259
+ );
260
+
261
+ export const DEPA_AI_RESOURCE_KIND_SPEC_REVISIONS: readonly KindSpecRevision[] = Object.freeze(
262
+ DEPA_AI_RESOURCE_KIND_CONTRACTS.map((contract) => contract.revision),
263
+ );
264
+
265
+ export const DEPA_AI_RESOURCE_KIND_READER_REGISTRATIONS: readonly KindReaderRegistration<PortableSpec>[] = Object.freeze(
266
+ DEPA_AI_RESOURCE_KIND_CONTRACTS.map((contract) => contract.reader),
267
+ );
268
+
269
+ const CONTRACTS_BY_KIND: ReadonlyMap<DepaAIResourceKind, DepaAIResourceKindContract> = new Map(
270
+ DEPA_AI_RESOURCE_KIND_CONTRACTS.map((contract) => [contract.kind, contract]),
271
+ );
272
+
273
+ export function depaAIResourceKindContract(kind: DepaAIResourceKind): DepaAIResourceKindContract {
274
+ const contract = CONTRACTS_BY_KIND.get(kind);
275
+ if (!contract) throw new Error(`Unknown depa AI resource Kind '${kind}'.`);
276
+ return contract;
277
+ }
278
+
279
+ function compareCodeUnits(left: string, right: string): number {
280
+ return left < right ? -1 : left > right ? 1 : 0;
281
+ }