ai-workflow-contract 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-workflow-contract",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "description": "Shared AI workflow contracts over WorkCtrlFlow and EagerDataFlow substrates",
6
6
  "type": "module",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "eager-data-flow-contract": "0.1.4",
13
- "halfcode-compiler.xnl": "0.3.0",
13
+ "halfcode-compiler.xnl": "0.3.1",
14
14
  "task-manager-contract": "0.1.3",
15
15
  "work-ctrl-flow-contract": "0.1.3"
16
16
  },
@@ -0,0 +1,46 @@
1
+ import type { CodeBinding, FrozenCodeClosure } from 'halfcode-compiler.xnl/application-assembly';
2
+ import type { ResourceRecord, Sha256Digest } from 'halfcode-compiler.xnl/resource-core';
3
+ import type { FlowClosedObject } from 'work-ctrl-flow-contract';
4
+ import type { AIWorkflowTypedResourceRef } from './index.js';
5
+
6
+ export type AIAgentCodeResourceKind = 'AgentContextPipeline' | 'AgentMessageSource';
7
+ /** Closed identity derived from the actual native resolver output. */
8
+ export interface AIAgentResolvedResourceIdentity {
9
+ readonly resourceId: string;
10
+ readonly readerId: string;
11
+ readonly readerSpecVersion: number;
12
+ readonly effectiveContentDigest: Sha256Digest;
13
+ readonly readerProfileId: string;
14
+ readonly resolutionReceiptDigest: Sha256Digest;
15
+ }
16
+ interface AIAgentCodeProjectionBase {
17
+ readonly kind: AIAgentCodeResourceKind;
18
+ readonly resource: ResourceRecord;
19
+ readonly config: FlowClosedObject;
20
+ readonly assets: Readonly<Record<string, 'text' | 'json'>>;
21
+ readonly dependencies: readonly AIWorkflowTypedResourceRef[];
22
+ }
23
+ export type AIAgentCodeResourceProjection = AIAgentCodeProjectionBase & (
24
+ | { readonly mode: 'native'; readonly binding: CodeBinding }
25
+ | { readonly mode: 'legacy'; readonly content: string }
26
+ );
27
+ export interface FrozenAIAgentCodeArtifact {
28
+ readonly resourceResolution?: AIAgentResolvedResourceIdentity;
29
+ readonly schemaVersion: 'ai-workflow.agent-code-artifact/v1';
30
+ readonly resourceId: string;
31
+ readonly resourceDigest: Sha256Digest;
32
+ readonly config: FlowClosedObject;
33
+ readonly closure: FrozenCodeClosure;
34
+ }
35
+ export interface AIAgentCodeExecutionIdentity {
36
+ readonly resourceId: string;
37
+ readonly resourceDigest: Sha256Digest;
38
+ readonly artifactDigest: Sha256Digest;
39
+ readonly closureDigest: string;
40
+ }
41
+ /** Runtime handle. Persist artifact/identity, never the callable. */
42
+ export interface CompiledAIAgentCodeExecution {
43
+ readonly artifact: FrozenAIAgentCodeArtifact;
44
+ readonly artifactDigest: Sha256Digest;
45
+ readonly execute: (runtime: unknown, input: unknown) => unknown;
46
+ }
package/src/index.ts CHANGED
@@ -24,6 +24,7 @@ import type {
24
24
 
25
25
  export * from './holon-task.js';
26
26
  export * from './resource-kind-contracts.js';
27
+ export * from './agent-code.js';
27
28
 
28
29
  export {
29
30
  FLOW_INSTANCE_SCHEMA_VERSION,
@@ -260,6 +261,7 @@ export interface MaterialBindingProjection {
260
261
  }
261
262
 
262
263
  export interface AIWorkflowAgentResourceProjection {
264
+ readonly codeResources?: readonly import('./agent-code.js').AIAgentCodeResourceProjection[];
263
265
  readonly agentDefinitions: readonly AIAgentDefinitionProjection[];
264
266
  readonly materialPorts: readonly MaterialPortProjection[];
265
267
  readonly materialBindings: readonly MaterialBindingProjection[];
@@ -267,6 +269,8 @@ export interface AIWorkflowAgentResourceProjection {
267
269
  }
268
270
 
269
271
  export interface AIWorkflowRunResourceFreezeReceipt {
272
+ readonly resourceResolutions?: readonly import('./agent-code.js').AIAgentResolvedResourceIdentity[];
273
+ readonly codeExecutions?: readonly import('./agent-code.js').AIAgentCodeExecutionIdentity[];
270
274
  readonly schemaVersion: 'ai-workflow.run-resource-freeze/v1';
271
275
  readonly task: AIWorkflowAgentTaskRef;
272
276
  readonly bindingResourceIds: readonly string[];
@@ -276,6 +280,8 @@ export interface AIWorkflowRunResourceFreezeReceipt {
276
280
 
277
281
  /** In-memory proof projected only from an admitted run-resource freeze receipt. */
278
282
  export interface FrozenAIAgentTaskBinding {
283
+ readonly resourceResolutions?: readonly import('./agent-code.js').AIAgentResolvedResourceIdentity[];
284
+ readonly codeExecutions?: readonly import('./agent-code.js').AIAgentCodeExecutionIdentity[];
279
285
  readonly schemaVersion: 'ai-workflow.frozen-agent-task-binding/v1';
280
286
  readonly task: AIWorkflowAgentTaskRef;
281
287
  readonly semanticFingerprint: Sha256Digest;
@@ -323,6 +329,13 @@ export interface AIAgentDefinitionAuthoringEvidence {
323
329
  readonly planDigest: Sha256Digest;
324
330
  readonly transactionId: string;
325
331
  readonly receiptDigest: Sha256Digest;
332
+ readonly revision?: {
333
+ readonly candidateRef: `resource://${string}`;
334
+ readonly candidateDigest: Sha256Digest;
335
+ readonly contentDigest: Sha256Digest;
336
+ readonly registryRevision: Sha256Digest;
337
+ readonly feedback: AIAgentDefinitionRevisionFeedback;
338
+ };
326
339
  }
327
340
 
328
341
  export interface AIAgentDefinitionCandidate {
@@ -363,9 +376,30 @@ export interface AuthorNewAIAgentDefinitionDecision
363
376
  readonly proposal: ResourceAuthoringProposal;
364
377
  }
365
378
 
379
+ /** Upstream causal references, never diagnostic or publication authority. */
380
+ export interface AIAgentDefinitionRevisionFeedback {
381
+ readonly observationRef: string;
382
+ readonly attemptRef: string;
383
+ readonly verificationRef: string;
384
+ readonly requirementDigest: Sha256Digest;
385
+ readonly candidateSetDigest: Sha256Digest;
386
+ readonly candidateDigest: Sha256Digest;
387
+ readonly previousExecutionFingerprint: Sha256Digest;
388
+ }
389
+
390
+ export interface ReviseExistingAIAgentDefinitionDecision
391
+ extends AIAgentDefinitionSelectionDecisionBase {
392
+ readonly mode: 'revise-existing';
393
+ readonly candidateRef: `resource://${string}`;
394
+ readonly candidateDigest: Sha256Digest;
395
+ readonly feedback: AIAgentDefinitionRevisionFeedback;
396
+ readonly proposal: ResourceAuthoringProposal;
397
+ }
398
+
366
399
  export type AIAgentDefinitionSelectionDecision =
367
400
  | SelectExistingAIAgentDefinitionDecision
368
- | AuthorNewAIAgentDefinitionDecision;
401
+ | AuthorNewAIAgentDefinitionDecision
402
+ | ReviseExistingAIAgentDefinitionDecision;
369
403
 
370
404
  export type AIAgentDefinitionSelectionRejectionCode =
371
405
  | 'decision-invalid'
@@ -375,6 +409,9 @@ export type AIAgentDefinitionSelectionRejectionCode =
375
409
  | 'candidate-digest-mismatch'
376
410
  | 'candidate-incompatible'
377
411
  | 'authoring-in-progress'
412
+ | 'selection-owner-required'
413
+ | 'selection-owner-mismatch'
414
+ | 'revision-feedback-invalid'
378
415
  | 'authoring-proposal-invalid';
379
416
 
380
417
  export interface SelectedAIAgentDefinitionAdmission {
@@ -396,6 +433,18 @@ export interface AuthorNewAIAgentDefinitionAdmission {
396
433
  readonly reason: string;
397
434
  }
398
435
 
436
+ export interface ReviseExistingAIAgentDefinitionAdmission {
437
+ readonly schemaVersion: typeof AI_AGENT_DEFINITION_SELECTION_SCHEMA_VERSION;
438
+ readonly status: 'revision-request';
439
+ readonly requirementDigest: Sha256Digest;
440
+ readonly candidateSetDigest: Sha256Digest;
441
+ readonly agentDefinitionRef: `resource://${string}`;
442
+ readonly candidate: AIAgentDefinitionCandidate;
443
+ readonly feedback: AIAgentDefinitionRevisionFeedback;
444
+ readonly proposal: ResourceAuthoringProposal;
445
+ readonly reason: string;
446
+ }
447
+
399
448
  export interface RejectedAIAgentDefinitionAdmission {
400
449
  readonly schemaVersion: typeof AI_AGENT_DEFINITION_SELECTION_SCHEMA_VERSION;
401
450
  readonly status: 'rejected';
@@ -406,6 +455,7 @@ export interface RejectedAIAgentDefinitionAdmission {
406
455
  export type AIAgentDefinitionSelectionAdmission =
407
456
  | SelectedAIAgentDefinitionAdmission
408
457
  | AuthorNewAIAgentDefinitionAdmission
458
+ | ReviseExistingAIAgentDefinitionAdmission
409
459
  | RejectedAIAgentDefinitionAdmission;
410
460
 
411
461
  export type AIWorkflowNodeType =
@@ -6,6 +6,7 @@ import {
6
6
  type KindSpecRevision,
7
7
  type KindSubjectOwner,
8
8
  type PortableSpec,
9
+ type SpecResolutionDefinition,
9
10
  } from 'halfcode-compiler.xnl/resource-core';
10
11
  import {
11
12
  createKindSpecRevision,
@@ -148,6 +149,91 @@ function authoredSpecSchema(kind: DepaAIResourceKind): JsonSchema {
148
149
  });
149
150
  }
150
151
 
152
+ /** Native code recipes have a separate writer contract; legacy Content stays spec 1. */
153
+ function nativeCodeSpecSchema(): JsonSchema {
154
+ const string = Object.freeze({ type: 'string', minLength: 1, pattern: '^\\S(?:[\\s\\S]*\\S)?$' });
155
+ const emptyObject = Object.freeze({ type: 'object', properties: Object.freeze({}), additionalProperties: false });
156
+ const emptyBody = Object.freeze({ type: 'array', maxItems: 0 });
157
+ const node = (tag: string, properties: JsonSchema, body: JsonSchema = emptyBody): JsonSchema => Object.freeze({
158
+ type: 'object',
159
+ required: Object.freeze(['tag', 'metadata', 'properties', 'body', 'subdomains']),
160
+ properties: Object.freeze({
161
+ tag: Object.freeze({ const: tag }),
162
+ resourceId: string,
163
+ metadata: emptyObject,
164
+ properties,
165
+ body,
166
+ subdomains: emptyObject,
167
+ }),
168
+ additionalProperties: false,
169
+ });
170
+ const closedProperties = (properties: Readonly<Record<string, JsonSchema>>, required = Object.keys(properties)): JsonSchema => Object.freeze({
171
+ type: 'object', required: Object.freeze(required), properties: Object.freeze(properties), additionalProperties: false,
172
+ });
173
+ return Object.freeze({
174
+ type: 'object',
175
+ required: Object.freeze(['properties', 'body', 'subdomains']),
176
+ properties: Object.freeze({
177
+ properties: closedProperties({ lifecycle: string, description: Object.freeze({ type: 'string' }) }, []),
178
+ body: emptyBody,
179
+ subdomains: closedProperties({
180
+ CodeBinding: node('CodeBinding', closedProperties({ packageName: string, module: string, exportName: string })),
181
+ Config: node('Config', closedProperties({ value: Object.freeze({ type: 'object' }) })),
182
+ Assets: node('Assets', emptyObject, Object.freeze({
183
+ type: 'array',
184
+ items: node('Asset', closedProperties({ path: string, format: Object.freeze({ enum: Object.freeze(['text', 'json']) }) })),
185
+ })),
186
+ DependencyRefs: node('DependencyRefs', emptyObject, Object.freeze({
187
+ type: 'array',
188
+ items: node('DependencyRef', closedProperties({
189
+ kind: string,
190
+ ref: Object.freeze({ type: 'string', pattern: '^resource://\\S+$' }),
191
+ })),
192
+ })),
193
+ }, ['CodeBinding']),
194
+ }),
195
+ additionalProperties: false,
196
+ });
197
+ }
198
+
199
+ function codeRecipeSpecSchema(kind: DepaAIResourceKind): JsonSchema {
200
+ const legacy = authoredSpecSchema(kind) as Readonly<Record<string, unknown>>;
201
+ return Object.freeze({
202
+ oneOf: Object.freeze([
203
+ nativeCodeSpecSchema(),
204
+ Object.freeze({
205
+ ...legacy,
206
+ properties: Object.freeze({
207
+ ...(legacy.properties as Readonly<Record<string, unknown>>),
208
+ subdomains: Object.freeze({
209
+ type: 'object', required: Object.freeze(['Content']),
210
+ properties: Object.freeze({ CodeBinding: false }),
211
+ }),
212
+ }),
213
+ }),
214
+ ]),
215
+ });
216
+ }
217
+
218
+ function nativeCodeSemanticContract(kind: DepaAIResourceKind): KindSemanticContract {
219
+ const authority = 'ai-workflow-contract/depa-ai-resource-kinds/v2';
220
+ return Object.freeze({
221
+ semanticValidatorFingerprint: digestCanonical(Object.freeze({
222
+ authority, kind, role: 'closed-native-code-recipe-validation',
223
+ fields: Object.freeze(['CodeBinding', 'Config', 'Assets', 'DependencyRefs']),
224
+ config: 'closed-json-object', assets: 'source-root-relative-text-or-json',
225
+ legacy: 'exclusive-Content-requires-explicit-compatibility-resolver',
226
+ })),
227
+ referenceProjectionFingerprint: digestCanonical(Object.freeze({
228
+ authority, kind, role: 'typed-resource-reference-projection', relations: Object.freeze(['code-dependency']),
229
+ })),
230
+ compilerInputFingerprint: digestCanonical(Object.freeze({
231
+ authority, kind, role: 'native-code-binding-and-frozen-closure-projection',
232
+ fields: Object.freeze(['packageName', 'module', 'exportName', 'config', 'assets', 'dependencyRefs']),
233
+ })),
234
+ });
235
+ }
236
+
151
237
  function semanticContract(kind: DepaAIResourceKind): KindSemanticContract {
152
238
  return Object.freeze({
153
239
  semanticValidatorFingerprint: digestCanonical(Object.freeze({
@@ -175,26 +261,26 @@ function readerRegistration(
175
261
  kind: DepaAIResourceKind,
176
262
  revision: KindSpecRevision,
177
263
  ): KindReaderRegistration<PortableSpec> {
178
- const readerId = `ai-workflow-contract.${kind}.reader.v1`;
264
+ const readerId = `ai-workflow-contract.${kind}.reader.v${revision.specVersion}`;
179
265
  return Object.freeze({
180
266
  readerId,
181
267
  subjectFqn: revision.subjectFqn,
182
268
  readerSpecVersion: revision.specVersion,
183
269
  contractFingerprint: revision.contractFingerprint,
184
270
  readerImplementationFingerprint: digestCanonical(Object.freeze({
185
- authority: 'ai-workflow-contract/depa-ai-resource-kinds/v1',
271
+ authority: `ai-workflow-contract/depa-ai-resource-kinds/v${revision.specVersion}`,
186
272
  readerId,
187
273
  kind,
188
274
  contractFingerprint: revision.contractFingerprint,
189
275
  operations: KIND_SEMANTIC_OPERATIONS[kind],
190
276
  implementation: 'identity-read-of-halfcode-validated-portable-spec',
191
277
  })),
192
- compatibilityPolicy: 'exact',
278
+ compatibilityPolicy: revision.specVersion === 1 ? 'exact' : 'backward-transitive',
193
279
  read: (spec: PortableSpec): PortableSpec => spec,
194
280
  });
195
281
  }
196
282
 
197
- function renderKindDefinition(owner: KindSubjectOwner, revision: KindSpecRevision): string {
283
+ function renderKindDefinition(owner: KindSubjectOwner, revisions: readonly KindSpecRevision[]): string {
198
284
  return [
199
285
  `<KindDefinition #${owner.subjectFqn} envelopeVersion="${DEPA_AI_RESOURCE_ENVELOPE_VERSION}" specVersion=1 {`,
200
286
  ' lifecycle = "Stable"',
@@ -205,16 +291,18 @@ function renderKindDefinition(owner: KindSubjectOwner, revision: KindSpecRevisio
205
291
  ` description = "Canonical depa-flows ${owner.kind} resource contract."`,
206
292
  '} (',
207
293
  ' <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
- ' }>',
294
+ ...revisions.flatMap((revision) => [
295
+ ` <SpecRevision #v${revision.specVersion} {`,
296
+ ` specVersion = ${revision.specVersion}`,
297
+ ` schemaRef = "vfs://./spec-v${revision.specVersion}.schema.json"`,
298
+ ` schemaFingerprint = "${revision.schemaFingerprint}"`,
299
+ ` contractFingerprint = "${revision.contractFingerprint}"`,
300
+ ` semanticValidatorFingerprint = "${revision.semanticContract.semanticValidatorFingerprint}"`,
301
+ ` referenceProjectionFingerprint = "${revision.semanticContract.referenceProjectionFingerprint}"`,
302
+ ` compilerInputFingerprint = "${revision.semanticContract.compilerInputFingerprint}"`,
303
+ ` stability = "${revision.stability}"`,
304
+ ' }>',
305
+ ]),
218
306
  ' ]>',
219
307
  ')>',
220
308
  '',
@@ -246,16 +334,36 @@ function createContract(kind: DepaAIResourceKind): DepaAIResourceKindContract {
246
334
  owner,
247
335
  revision,
248
336
  reader: readerRegistration(kind, revision),
249
- kindDefinitionSource: renderKindDefinition(owner, revision),
337
+ kindDefinitionSource: renderKindDefinition(owner, [revision]),
250
338
  });
251
339
  }
252
340
 
253
- export const DEPA_AI_RESOURCE_KIND_CONTRACTS: readonly DepaAIResourceKindContract[] = Object.freeze(
254
- DEPA_AI_RESOURCE_KINDS.map(createContract),
255
- );
341
+ const LEGACY_KIND_CONTRACTS = DEPA_AI_RESOURCE_KINDS.map(createContract);
342
+
343
+ export const DEPA_AI_RESOURCE_KIND_CONTRACTS: readonly DepaAIResourceKindContract[] = Object.freeze([
344
+ ...LEGACY_KIND_CONTRACTS,
345
+ ...LEGACY_KIND_CONTRACTS.filter(({ kind }) => kind === 'AgentMessageSource' || kind === 'AgentContextPipeline').map((legacy) => {
346
+ const { kind } = legacy;
347
+ const revision = createKindSpecRevision({
348
+ kind,
349
+ subjectFqn: legacy.subjectFqn,
350
+ specVersion: 2,
351
+ specSchema: codeRecipeSpecSchema(kind),
352
+ semanticContract: nativeCodeSemanticContract(kind),
353
+ sourceContractFingerprint: legacy.owner.sourceContract.sourceContractFingerprint,
354
+ stability: 'stable',
355
+ });
356
+ return Object.freeze({
357
+ ...legacy,
358
+ revision,
359
+ reader: readerRegistration(kind, revision),
360
+ kindDefinitionSource: renderKindDefinition(legacy.owner, [legacy.revision, revision]),
361
+ });
362
+ }),
363
+ ]);
256
364
 
257
365
  export const DEPA_AI_RESOURCE_KIND_OWNERS: readonly KindSubjectOwner[] = Object.freeze(
258
- DEPA_AI_RESOURCE_KIND_CONTRACTS.map((contract) => contract.owner),
366
+ LEGACY_KIND_CONTRACTS.map((contract) => contract.owner),
259
367
  );
260
368
 
261
369
  export const DEPA_AI_RESOURCE_KIND_SPEC_REVISIONS: readonly KindSpecRevision[] = Object.freeze(
@@ -266,16 +374,40 @@ export const DEPA_AI_RESOURCE_KIND_READER_REGISTRATIONS: readonly KindReaderRegi
266
374
  DEPA_AI_RESOURCE_KIND_CONTRACTS.map((contract) => contract.reader),
267
375
  );
268
376
 
269
- const CONTRACTS_BY_KIND: ReadonlyMap<DepaAIResourceKind, DepaAIResourceKindContract> = new Map(
270
- DEPA_AI_RESOURCE_KIND_CONTRACTS.map((contract) => [contract.kind, contract]),
377
+ const CONTRACTS_BY_KIND: ReadonlyMap<string, DepaAIResourceKindContract> = new Map(
378
+ DEPA_AI_RESOURCE_KIND_CONTRACTS.map((contract) => [`${contract.kind}@${contract.revision.specVersion}`, contract]),
271
379
  );
272
380
 
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}'.`);
381
+ export function depaAIResourceKindContract(kind: DepaAIResourceKind, specVersion: number = DEPA_AI_RESOURCE_SPEC_VERSION): DepaAIResourceKindContract {
382
+ const contract = CONTRACTS_BY_KIND.get(`${kind}@${specVersion}`);
383
+ if (!contract) throw new Error(`Unknown depa AI resource Kind '${kind}' spec version '${specVersion}'.`);
276
384
  return contract;
277
385
  }
278
386
 
387
+ export const DEPA_AI_RESOURCE_KIND_SPEC_RESOLUTIONS: readonly SpecResolutionDefinition[] = Object.freeze(
388
+ DEPA_AI_RESOURCE_KIND_CONTRACTS.filter((contract) => contract.revision.specVersion === 2).map((contract) => {
389
+ const writer = depaAIResourceKindContract(contract.kind, 1);
390
+ const identity = Object.freeze({
391
+ id: `ai-workflow-contract.${contract.kind}.v1-to-v2`,
392
+ subjectFqn: contract.subjectFqn,
393
+ ownerPackageId: contract.owner.ownerPackageId,
394
+ ownerPackageFingerprint: contract.owner.ownerPackageFingerprint,
395
+ writerSpecVersion: 1,
396
+ readerSpecVersion: 2,
397
+ writerContractFingerprint: writer.revision.contractFingerprint,
398
+ readerContractFingerprint: contract.revision.contractFingerprint,
399
+ });
400
+ return Object.freeze({
401
+ ...identity,
402
+ resolutionFingerprint: digestCanonical(Object.freeze({
403
+ ...identity,
404
+ implementation: 'identity-Content-preservation-requires-explicit-legacy-code-resolver',
405
+ })),
406
+ resolve: (spec: PortableSpec): PortableSpec => spec,
407
+ });
408
+ }),
409
+ );
410
+
279
411
  function compareCodeUnits(left: string, right: string): number {
280
412
  return left < right ? -1 : left > right ? 1 : 0;
281
413
  }