ai-workflow-logic 0.2.0 → 0.2.2
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 +11 -9
- package/src/agent-code-brand.ts +14 -0
- package/src/agent-code-execution.ts +98 -0
- package/src/agent-code-projection.ts +98 -0
- package/src/agent-definition-selection.ts +256 -17
- package/src/agent-execution-resources.ts +160 -0
- package/src/agent-invocation.ts +62 -2
- package/src/agent-resources.ts +3 -0
- package/src/agent-state.ts +3 -2
- package/src/browser.ts +1 -1
- package/src/checkpoint-filesystem.ts +23 -10
- package/src/checkpoint-profile.ts +3 -0
- package/src/checkpoint-sidecar.ts +77 -16
- package/src/checkpoint-store.ts +58 -8
- package/src/core.ts +544 -0
- package/src/definitions.ts +30 -0
- package/src/delegation.ts +387 -0
- package/src/index.ts +7 -563
- package/src/resource-resolution.ts +38 -14
- package/src/run-checkpoint.ts +30 -6
- package/src/run-freeze.ts +80 -1
|
@@ -12,6 +12,9 @@ import type {
|
|
|
12
12
|
AIWorkflowDefinitionResourceKind,
|
|
13
13
|
FrozenAIAgentTaskBinding,
|
|
14
14
|
SelectedAIAgentDefinitionAdmission,
|
|
15
|
+
CompiledAIAgentCodeExecution,
|
|
16
|
+
ReviseExistingAIAgentDefinitionAdmission,
|
|
17
|
+
ReviseExistingAIAgentDefinitionDecision,
|
|
15
18
|
} from 'ai-workflow-contract';
|
|
16
19
|
import { AI_AGENT_DEFINITION_SELECTION_SCHEMA_VERSION } from 'ai-workflow-contract';
|
|
17
20
|
import {
|
|
@@ -32,8 +35,10 @@ import {
|
|
|
32
35
|
type Sha256Digest,
|
|
33
36
|
} from 'halfcode-compiler.xnl/resource-core';
|
|
34
37
|
import { sourceRegistryForAIWorkflowAgentResourceProjection } from './agent-resource-brand.js';
|
|
38
|
+
import { assertAIAgentExecutionResources, type AIAgentExecutionResourceObservation } from './agent-execution-resources.js';
|
|
35
39
|
import {
|
|
36
40
|
freezeAIWorkflowRunResources,
|
|
41
|
+
frozenAIAgentTaskAgentContentDigest,
|
|
37
42
|
projectFrozenAIAgentTaskBinding,
|
|
38
43
|
} from './run-freeze.js';
|
|
39
44
|
|
|
@@ -41,6 +46,7 @@ export type AIAgentDefinitionSelectionErrorCode =
|
|
|
41
46
|
| 'AI_AGENT_REQUIREMENT_INVALID'
|
|
42
47
|
| 'AI_AGENT_CANDIDATE_PROJECTION_UNTRUSTED'
|
|
43
48
|
| 'AI_AGENT_CANDIDATE_CONTENT_IDENTITY_MISSING'
|
|
49
|
+
| 'AI_AGENT_SELECTION_OWNER_MISMATCH'
|
|
44
50
|
| 'AI_AGENT_AUTHORING_RECEIPT_INVALID'
|
|
45
51
|
| 'AI_AGENT_AUTHORING_RECEIPT_STALE'
|
|
46
52
|
| 'AI_AGENT_AUTHORING_RECEIPT_RESOURCE_MISMATCH'
|
|
@@ -57,6 +63,9 @@ export class AIAgentDefinitionSelectionError extends Error {
|
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
export interface ProjectAIAgentDefinitionCandidatesInput {
|
|
66
|
+
readonly executionResources?: AIAgentExecutionResourceObservation;
|
|
67
|
+
/** Reuse one owner for all observations of the same resource host. */
|
|
68
|
+
readonly selectionOwner?: AIAgentDefinitionSelectionOwner;
|
|
60
69
|
readonly registry: EffectiveResourceRegistry;
|
|
61
70
|
readonly projection: AIWorkflowAgentResourceProjection;
|
|
62
71
|
readonly layers: readonly ResourceLayerContentIdentityInput[];
|
|
@@ -65,18 +74,24 @@ export interface ProjectAIAgentDefinitionCandidatesInput {
|
|
|
65
74
|
export interface ReconcileAuthoredAIAgentDefinitionInput
|
|
66
75
|
extends ProjectAIAgentDefinitionCandidatesInput {
|
|
67
76
|
readonly candidateSet: AIAgentDefinitionCandidateSet;
|
|
68
|
-
readonly authoringAdmission: AuthorNewAIAgentDefinitionAdmission;
|
|
77
|
+
readonly authoringAdmission: AuthorNewAIAgentDefinitionAdmission | ReviseExistingAIAgentDefinitionAdmission;
|
|
69
78
|
readonly receipt: ResourceAuthoringReceipt;
|
|
70
79
|
readonly receiptAuthority: Pick<ResourceAuthoringTransactionPort, 'loadReceipt'>;
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
export interface AdmitAIAgentDefinitionSelectionInput {
|
|
83
|
+
readonly selectionOwner?: AIAgentDefinitionSelectionOwner;
|
|
74
84
|
readonly requirement: AIAgentTaskRequirementProjection;
|
|
75
85
|
readonly candidateSet: AIAgentDefinitionCandidateSet;
|
|
76
86
|
readonly decision: AIAgentDefinitionSelectionDecision;
|
|
87
|
+
/** Required for revise-existing; an authentic immutable proof from the previous execution. */
|
|
88
|
+
readonly previousExecution?: FrozenAIAgentTaskBinding;
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
export interface FreezeSelectedAIAgentTaskBindingInput {
|
|
92
|
+
readonly executionResources?: AIAgentExecutionResourceObservation;
|
|
93
|
+
readonly selectionOwner?: AIAgentDefinitionSelectionOwner;
|
|
94
|
+
readonly codeExecutions?: readonly CompiledAIAgentCodeExecution[];
|
|
80
95
|
readonly registry: EffectiveResourceRegistry;
|
|
81
96
|
readonly projection: AIWorkflowAgentResourceProjection;
|
|
82
97
|
readonly contentIdentities: ReadonlyMap<string, ResourceContentIdentity>;
|
|
@@ -87,6 +102,9 @@ export interface FreezeSelectedAIAgentTaskBindingInput {
|
|
|
87
102
|
}
|
|
88
103
|
|
|
89
104
|
interface CandidateSetAuthority {
|
|
105
|
+
readonly executionResources?: AIAgentExecutionResourceObservation;
|
|
106
|
+
readonly selectionOwner?: AIAgentDefinitionSelectionOwner;
|
|
107
|
+
readonly generation: number;
|
|
90
108
|
readonly registry: EffectiveResourceRegistry;
|
|
91
109
|
readonly projection: AIWorkflowAgentResourceProjection;
|
|
92
110
|
readonly layers: readonly ResourceLayerContentIdentityInput[];
|
|
@@ -97,13 +115,70 @@ interface AuthoringAdmissionAuthority {
|
|
|
97
115
|
readonly candidateSet: AIAgentDefinitionCandidateSet;
|
|
98
116
|
readonly requirement: AIAgentTaskRequirementProjection;
|
|
99
117
|
readonly proposalDigest: Sha256Digest;
|
|
118
|
+
readonly before?: {
|
|
119
|
+
readonly authorityDigest: Sha256Digest;
|
|
120
|
+
readonly effectiveOrigin: ResourceOrigin;
|
|
121
|
+
readonly authorityResourceIds: readonly string[];
|
|
122
|
+
};
|
|
100
123
|
}
|
|
101
124
|
|
|
102
125
|
const authenticRequirements = new WeakSet<object>();
|
|
103
126
|
const candidateSetAuthorities = new WeakMap<object, CandidateSetAuthority>();
|
|
104
127
|
const selectedAdmissionAuthorities = new WeakMap<object, CandidateSetAuthority>();
|
|
105
128
|
const authoringAdmissionAuthorities = new WeakMap<object, AuthoringAdmissionAuthority>();
|
|
106
|
-
const
|
|
129
|
+
declare const selectionOwnerBrand: unique symbol;
|
|
130
|
+
|
|
131
|
+
/** Process-local state dependency. It is not a resource identity or a serializable receipt. */
|
|
132
|
+
export interface AIAgentDefinitionSelectionOwner {
|
|
133
|
+
readonly [selectionOwnerBrand]: true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
interface SelectionOwnerState {
|
|
137
|
+
readonly pending: Map<`resource://${string}`, object>;
|
|
138
|
+
readonly receiptAuthority: Pick<ResourceAuthoringTransactionPort, 'loadReceipt'>;
|
|
139
|
+
generation: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface CreateAIAgentDefinitionSelectionOwnerInput {
|
|
143
|
+
readonly receiptAuthority: Pick<ResourceAuthoringTransactionPort, 'loadReceipt'>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const selectionOwnerStates = new WeakMap<AIAgentDefinitionSelectionOwner, SelectionOwnerState>();
|
|
147
|
+
// This association prevents removing/changing the explicit owner on the same observation.
|
|
148
|
+
// Pending state lives with the stable owner, never with this per-observation guard.
|
|
149
|
+
const registryOwners = new WeakMap<EffectiveResourceRegistry, {
|
|
150
|
+
readonly owner: AIAgentDefinitionSelectionOwner;
|
|
151
|
+
readonly generation: number;
|
|
152
|
+
}>();
|
|
153
|
+
|
|
154
|
+
export function createAIAgentDefinitionSelectionOwner(
|
|
155
|
+
input: CreateAIAgentDefinitionSelectionOwnerInput,
|
|
156
|
+
): AIAgentDefinitionSelectionOwner {
|
|
157
|
+
const owner = Object.freeze({}) as AIAgentDefinitionSelectionOwner;
|
|
158
|
+
selectionOwnerStates.set(owner, { pending: new Map(), generation: 0, receiptAuthority: input.receiptAuthority });
|
|
159
|
+
return owner;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function ownerMatches(
|
|
163
|
+
authority: CandidateSetAuthority,
|
|
164
|
+
owner: AIAgentDefinitionSelectionOwner | undefined,
|
|
165
|
+
): boolean {
|
|
166
|
+
return authority.selectionOwner === owner
|
|
167
|
+
&& registryOwners.get(authority.registry)?.owner === owner
|
|
168
|
+
&& (owner === undefined || selectionOwnerStates.has(owner));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function isSuperseded(authority: CandidateSetAuthority): boolean {
|
|
172
|
+
if (!authority.selectionOwner) return false;
|
|
173
|
+
const generation = selectionOwnerStates.get(authority.selectionOwner)!.generation;
|
|
174
|
+
return authority.generation !== generation
|
|
175
|
+
|| registryOwners.get(authority.registry)?.generation !== generation;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function pendingFor(authority: CandidateSetAuthority): SelectionOwnerState['pending'] | undefined {
|
|
179
|
+
return authority.selectionOwner === undefined
|
|
180
|
+
? undefined : selectionOwnerStates.get(authority.selectionOwner)?.pending;
|
|
181
|
+
}
|
|
107
182
|
|
|
108
183
|
export function normalizeAIAgentTaskRequirement(
|
|
109
184
|
value: AIAgentTaskRequirement,
|
|
@@ -160,6 +235,18 @@ export function projectAIAgentDefinitionCandidates(
|
|
|
160
235
|
input: ProjectAIAgentDefinitionCandidatesInput,
|
|
161
236
|
): AIAgentDefinitionCandidateSet {
|
|
162
237
|
assertProjectionAuthority(input);
|
|
238
|
+
const ownerState = input.selectionOwner === undefined ? undefined : selectionOwnerStates.get(input.selectionOwner);
|
|
239
|
+
const registeredOwner = registryOwners.get(input.registry);
|
|
240
|
+
if ((input.selectionOwner !== undefined && !ownerState)
|
|
241
|
+
|| (registeredOwner && registeredOwner.owner !== input.selectionOwner)) {
|
|
242
|
+
throw new AIAgentDefinitionSelectionError(
|
|
243
|
+
'AI_AGENT_SELECTION_OWNER_MISMATCH',
|
|
244
|
+
'Candidate projection requires the authentic selection owner already bound to this registry observation.',
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
if (input.selectionOwner && !registeredOwner) {
|
|
248
|
+
registryOwners.set(input.registry, { owner: input.selectionOwner, generation: ownerState!.generation });
|
|
249
|
+
}
|
|
163
250
|
const contentIdentities = resolveEffectiveResourceContentIdentities({
|
|
164
251
|
registry: input.registry,
|
|
165
252
|
layers: input.layers,
|
|
@@ -168,7 +255,7 @@ export function projectAIAgentDefinitionCandidates(
|
|
|
168
255
|
registry: input.registry,
|
|
169
256
|
layers: input.layers,
|
|
170
257
|
}), 'registry revision');
|
|
171
|
-
const candidates = input.projection.agentDefinitions
|
|
258
|
+
const candidates = (input.executionResources?.projection ?? input.projection).agentDefinitions
|
|
172
259
|
.map((agent): AIAgentDefinitionCandidate => {
|
|
173
260
|
const identity = contentIdentities.get(agent.resource.resourceId);
|
|
174
261
|
if (!identity) {
|
|
@@ -211,6 +298,9 @@ export function projectAIAgentDefinitionCandidates(
|
|
|
211
298
|
candidateSetDigest: candidateSetDigest(registryRevision, candidates),
|
|
212
299
|
}) satisfies AIAgentDefinitionCandidateSet;
|
|
213
300
|
candidateSetAuthorities.set(candidateSet, Object.freeze({
|
|
301
|
+
executionResources: input.executionResources,
|
|
302
|
+
selectionOwner: input.selectionOwner,
|
|
303
|
+
generation: registryOwners.get(input.registry)?.generation ?? 0,
|
|
214
304
|
registry: input.registry,
|
|
215
305
|
projection: input.projection,
|
|
216
306
|
layers: input.layers,
|
|
@@ -223,21 +313,42 @@ export async function reconcileAuthoredAIAgentDefinition(
|
|
|
223
313
|
): Promise<AIAgentDefinitionCandidateSet> {
|
|
224
314
|
const authoringAuthority = authoringAdmissionAuthorities.get(input.authoringAdmission as object);
|
|
225
315
|
const authority = candidateSetAuthorities.get(input.candidateSet);
|
|
316
|
+
if (!authority || !input.selectionOwner || !ownerMatches(authority, input.selectionOwner)
|
|
317
|
+
|| authoringAuthority?.candidateSetAuthority.selectionOwner !== input.selectionOwner) {
|
|
318
|
+
throw new AIAgentDefinitionSelectionError(
|
|
319
|
+
'AI_AGENT_SELECTION_OWNER_MISMATCH',
|
|
320
|
+
'Authoring reconciliation requires the same explicit owner as its pending admission and current observation.',
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
const ownerState = selectionOwnerStates.get(input.selectionOwner)!;
|
|
324
|
+
if (input.receiptAuthority !== ownerState.receiptAuthority) {
|
|
325
|
+
throw new AIAgentDefinitionSelectionError(
|
|
326
|
+
'AI_AGENT_SELECTION_OWNER_MISMATCH',
|
|
327
|
+
'Authoring reconciliation requires the receipt authority explicitly bound to this selection owner.',
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if (isSuperseded(authority)) {
|
|
331
|
+
throw new AIAgentDefinitionSelectionError(
|
|
332
|
+
'AI_AGENT_AUTHORING_RECEIPT_STALE',
|
|
333
|
+
'Authoring reconciliation cannot restore a superseded registry observation.',
|
|
334
|
+
);
|
|
335
|
+
}
|
|
226
336
|
if (!authority
|
|
227
337
|
|| authority.registry !== input.registry
|
|
228
338
|
|| authority.projection !== input.projection
|
|
339
|
+
|| authority.executionResources !== input.executionResources
|
|
229
340
|
|| authority.layers !== input.layers
|
|
230
341
|
|| !authoringAuthority
|
|
231
342
|
|| candidateSetAuthorities.get(authoringAuthority.candidateSet)
|
|
232
343
|
!== authoringAuthority.candidateSetAuthority
|
|
233
344
|
|| authoringAuthority.requirement.requirementDigest
|
|
234
345
|
!== input.authoringAdmission.requirementDigest
|
|
235
|
-
||
|
|
346
|
+
|| ownerState.pending.get(input.authoringAdmission.agentDefinitionRef)
|
|
236
347
|
!== input.authoringAdmission
|
|
237
348
|
|| authoringAuthority.proposalDigest !== digest(input.authoringAdmission.proposal)) {
|
|
238
349
|
throw new AIAgentDefinitionSelectionError(
|
|
239
350
|
'AI_AGENT_CANDIDATE_PROJECTION_UNTRUSTED',
|
|
240
|
-
'Authoring reconciliation requires the exact
|
|
351
|
+
'Authoring reconciliation requires the exact pending admission and candidate projection for this registry observation.',
|
|
241
352
|
);
|
|
242
353
|
}
|
|
243
354
|
validateReceiptShapeAndDigest(input.receipt);
|
|
@@ -249,6 +360,13 @@ export async function reconcileAuthoredAIAgentDefinition(
|
|
|
249
360
|
);
|
|
250
361
|
}
|
|
251
362
|
validateReceiptShapeAndDigest(durableReceipt);
|
|
363
|
+
if (isSuperseded(authority)
|
|
364
|
+
|| ownerState.pending.get(input.authoringAdmission.agentDefinitionRef) !== input.authoringAdmission) {
|
|
365
|
+
throw new AIAgentDefinitionSelectionError(
|
|
366
|
+
'AI_AGENT_AUTHORING_RECEIPT_STALE',
|
|
367
|
+
'The pending admission or registry observation was superseded while loading the durable receipt.',
|
|
368
|
+
);
|
|
369
|
+
}
|
|
252
370
|
const proposal = input.authoringAdmission.proposal;
|
|
253
371
|
const currentRevision = asDigest(deriveResourceAuthoringRegistryRevision({
|
|
254
372
|
registry: input.registry,
|
|
@@ -286,7 +404,7 @@ export async function reconcileAuthoredAIAgentDefinition(
|
|
|
286
404
|
|| input.receipt.writerSpecVersion !== proposal.writerSpecVersion
|
|
287
405
|
|| input.receipt.sourceShape !== proposal.sourceShape
|
|
288
406
|
|| input.receipt.documentUri !== proposal.documentUri
|
|
289
|
-
|| input.receipt.authorityDigestBefore !== null
|
|
407
|
+
|| input.receipt.authorityDigestBefore !== (authoringAuthority.before?.authorityDigest ?? null)
|
|
290
408
|
|| input.receipt.authorityDigestAfter !== sha256Digest(proposal.authorityText)
|
|
291
409
|
|| input.receipt.registryRevisionBefore !== proposal.expected.registryRevision
|
|
292
410
|
|| input.receipt.kind !== 'AIAgentDefinition'
|
|
@@ -299,6 +417,8 @@ export async function reconcileAuthoredAIAgentDefinition(
|
|
|
299
417
|
|| identity.contentDigest !== input.receipt.contentDigest
|
|
300
418
|
|| !effectiveOrigin
|
|
301
419
|
|| !sameReceiptOrigin(effectiveOrigin, input.receipt.effectiveOrigin)
|
|
420
|
+
|| (authoringAuthority.before !== undefined
|
|
421
|
+
&& canonicalJson(authorityResourceIds) !== canonicalJson(authoringAuthority.before.authorityResourceIds))
|
|
302
422
|
|| canonicalJson(authorityResourceIds) !== canonicalJson([...input.receipt.authorityResourceIds].sort(compareCodeUnits))
|
|
303
423
|
|| !candidate
|
|
304
424
|
|| candidate.contentDigest !== input.receipt.contentDigest) {
|
|
@@ -312,6 +432,15 @@ export async function reconcileAuthoredAIAgentDefinition(
|
|
|
312
432
|
planDigest: asDigest(input.receipt.planDigest, 'receipt plan digest'),
|
|
313
433
|
transactionId: input.receipt.transactionId,
|
|
314
434
|
receiptDigest: asDigest(input.receipt.receiptDigest, 'receipt digest'),
|
|
435
|
+
...(input.authoringAdmission.status === 'revision-request' ? {
|
|
436
|
+
revision: Object.freeze({
|
|
437
|
+
candidateRef: input.authoringAdmission.candidate.agentDefinitionRef,
|
|
438
|
+
candidateDigest: input.authoringAdmission.candidate.candidateDigest,
|
|
439
|
+
contentDigest: input.authoringAdmission.candidate.contentDigest,
|
|
440
|
+
registryRevision: input.authoringAdmission.candidate.registryRevision,
|
|
441
|
+
feedback: input.authoringAdmission.feedback,
|
|
442
|
+
}),
|
|
443
|
+
} : {}),
|
|
315
444
|
}) satisfies AIAgentDefinitionAuthoringEvidence;
|
|
316
445
|
const candidates = Object.freeze(input.candidateSet.candidates.map((value) => {
|
|
317
446
|
if (value !== candidate) return value;
|
|
@@ -330,8 +459,10 @@ export async function reconcileAuthoredAIAgentDefinition(
|
|
|
330
459
|
candidates,
|
|
331
460
|
candidateSetDigest: candidateSetDigest(input.candidateSet.registryRevision, candidates),
|
|
332
461
|
}) satisfies AIAgentDefinitionCandidateSet;
|
|
333
|
-
|
|
334
|
-
|
|
462
|
+
if (authoringAuthority.before) ownerState.generation += 1;
|
|
463
|
+
registryOwners.set(input.registry, { owner: input.selectionOwner, generation: ownerState.generation });
|
|
464
|
+
candidateSetAuthorities.set(reconciled, Object.freeze({ ...authority, generation: ownerState.generation }));
|
|
465
|
+
ownerState.pending.delete(input.authoringAdmission.agentDefinitionRef);
|
|
335
466
|
return reconciled;
|
|
336
467
|
}
|
|
337
468
|
|
|
@@ -346,7 +477,11 @@ export function admitAIAgentDefinitionSelection(
|
|
|
346
477
|
return rejection('requirement-stale', 'The decision is not bound to the current normalized task requirement.');
|
|
347
478
|
}
|
|
348
479
|
const candidateSetAuthority = candidateSetAuthorities.get(input.candidateSet);
|
|
480
|
+
if (candidateSetAuthority && !ownerMatches(candidateSetAuthority, input.selectionOwner)) {
|
|
481
|
+
return rejection('selection-owner-mismatch', 'Selection requires the same explicit owner as its candidate observation.');
|
|
482
|
+
}
|
|
349
483
|
if (!candidateSetAuthority
|
|
484
|
+
|| isSuperseded(candidateSetAuthority)
|
|
350
485
|
|| input.decision.candidateSetDigest !== input.candidateSet.candidateSetDigest) {
|
|
351
486
|
return rejection('candidate-set-stale', 'The decision is not bound to the current authentic candidate set.');
|
|
352
487
|
}
|
|
@@ -355,11 +490,10 @@ export function admitAIAgentDefinitionSelection(
|
|
|
355
490
|
const candidate = input.candidateSet.candidates.find(({ agentDefinitionRef }) =>
|
|
356
491
|
agentDefinitionRef === decision.candidateRef);
|
|
357
492
|
if (!candidate) return rejection('candidate-missing', 'The selected candidate is absent from the current set.');
|
|
358
|
-
if (
|
|
359
|
-
&& candidate.authoringEvidence === undefined) {
|
|
493
|
+
if (pendingFor(candidateSetAuthority)?.has(candidate.agentDefinitionRef)) {
|
|
360
494
|
return rejection(
|
|
361
495
|
'authoring-in-progress',
|
|
362
|
-
'An in-flight
|
|
496
|
+
'An in-flight authoring transition must reconcile its durable Halfcode receipt before this definition is selectable.',
|
|
363
497
|
);
|
|
364
498
|
}
|
|
365
499
|
if (candidate.candidateDigest !== decision.candidateDigest) {
|
|
@@ -379,6 +513,47 @@ export function admitAIAgentDefinitionSelection(
|
|
|
379
513
|
selectedAdmissionAuthorities.set(admission, candidateSetAuthority);
|
|
380
514
|
return admission;
|
|
381
515
|
}
|
|
516
|
+
if (!input.selectionOwner) {
|
|
517
|
+
return rejection('selection-owner-required', 'Authoring requires an explicit stable selection owner shared across registry refreshes.');
|
|
518
|
+
}
|
|
519
|
+
const pending = pendingFor(candidateSetAuthority)!;
|
|
520
|
+
if (input.decision.mode === 'revise-existing') {
|
|
521
|
+
const decision = input.decision;
|
|
522
|
+
const candidate = input.candidateSet.candidates.find(({ agentDefinitionRef }) => agentDefinitionRef === decision.candidateRef);
|
|
523
|
+
if (!candidate) return rejection('candidate-missing', 'The revision candidate is absent from the current set.');
|
|
524
|
+
if (candidate.candidateDigest !== decision.candidateDigest) {
|
|
525
|
+
return rejection('candidate-digest-mismatch', 'The revision candidate digest is stale or forged.');
|
|
526
|
+
}
|
|
527
|
+
if (!matchesRevisionFeedback(input, decision, candidate)) {
|
|
528
|
+
return rejection('revision-feedback-invalid', 'Revision feedback must match the exact candidate, requirement, observation and authentic prior execution.');
|
|
529
|
+
}
|
|
530
|
+
const before = revisionAuthority(candidateSetAuthority, candidate);
|
|
531
|
+
if (!before || !isValidAuthoringProposal(decision.proposal, decision.candidateRef, input.candidateSet, before.authorityDigest)
|
|
532
|
+
|| decision.proposal.documentUri !== before.effectiveOrigin.documentUri) {
|
|
533
|
+
return rejection('authoring-proposal-invalid', 'Revision requires an exact native update/present proposal against the effective authority digest and document.');
|
|
534
|
+
}
|
|
535
|
+
if (pending.has(candidate.agentDefinitionRef)) {
|
|
536
|
+
return rejection('authoring-in-progress', 'An authoring request for this exact Agent definition is already in flight.');
|
|
537
|
+
}
|
|
538
|
+
const proposal = cloneAuthoringProposal(decision.proposal);
|
|
539
|
+
const admission = Object.freeze({
|
|
540
|
+
schemaVersion: AI_AGENT_DEFINITION_SELECTION_SCHEMA_VERSION,
|
|
541
|
+
status: 'revision-request' as const,
|
|
542
|
+
requirementDigest: input.requirement.requirementDigest,
|
|
543
|
+
candidateSetDigest: input.candidateSet.candidateSetDigest,
|
|
544
|
+
agentDefinitionRef: candidate.agentDefinitionRef,
|
|
545
|
+
candidate,
|
|
546
|
+
feedback: Object.freeze({ ...decision.feedback }),
|
|
547
|
+
proposal,
|
|
548
|
+
reason: decision.reason,
|
|
549
|
+
}) satisfies ReviseExistingAIAgentDefinitionAdmission;
|
|
550
|
+
authoringAdmissionAuthorities.set(admission, Object.freeze({
|
|
551
|
+
candidateSetAuthority, candidateSet: input.candidateSet, requirement: input.requirement,
|
|
552
|
+
proposalDigest: digest(proposal), before,
|
|
553
|
+
}));
|
|
554
|
+
pending.set(candidate.agentDefinitionRef, admission);
|
|
555
|
+
return admission;
|
|
556
|
+
}
|
|
382
557
|
if (!isValidAuthoringProposal(input.decision.proposal, input.decision.agentDefinitionRef, input.candidateSet)) {
|
|
383
558
|
return rejection('authoring-proposal-invalid', 'The author-new decision is not one exact Halfcode create proposal for the current registry.');
|
|
384
559
|
}
|
|
@@ -392,7 +567,7 @@ export function admitAIAgentDefinitionSelection(
|
|
|
392
567
|
proposal,
|
|
393
568
|
reason: stringValue(input.decision.reason, 'decision.reason'),
|
|
394
569
|
}) satisfies AuthorNewAIAgentDefinitionAdmission;
|
|
395
|
-
if (
|
|
570
|
+
if (pending.has(admission.agentDefinitionRef)) {
|
|
396
571
|
return rejection('authoring-in-progress', 'An authoring request for this exact Agent definition is already in flight.');
|
|
397
572
|
}
|
|
398
573
|
authoringAdmissionAuthorities.set(admission, Object.freeze({
|
|
@@ -401,7 +576,7 @@ export function admitAIAgentDefinitionSelection(
|
|
|
401
576
|
requirement: input.requirement,
|
|
402
577
|
proposalDigest: digest(proposal),
|
|
403
578
|
}));
|
|
404
|
-
|
|
579
|
+
pending.set(admission.agentDefinitionRef, admission);
|
|
405
580
|
return admission;
|
|
406
581
|
}
|
|
407
582
|
|
|
@@ -410,8 +585,12 @@ export function freezeSelectedAIAgentTaskBinding(
|
|
|
410
585
|
): FrozenAIAgentTaskBinding {
|
|
411
586
|
const authority = selectedAdmissionAuthorities.get(input.admission as object);
|
|
412
587
|
if (!authority
|
|
588
|
+
|| !ownerMatches(authority, input.selectionOwner)
|
|
589
|
+
|| isSuperseded(authority)
|
|
590
|
+
|| pendingFor(authority)?.has(input.admission.candidate.agentDefinitionRef)
|
|
413
591
|
|| authority.registry !== input.registry
|
|
414
592
|
|| authority.projection !== input.projection
|
|
593
|
+
|| authority.executionResources !== input.executionResources
|
|
415
594
|
|| input.contentIdentities.get(resourceId(input.admission.candidate.agentDefinitionRef))?.contentDigest
|
|
416
595
|
!== input.admission.candidate.contentDigest) {
|
|
417
596
|
throw new AIAgentDefinitionSelectionError(
|
|
@@ -420,6 +599,8 @@ export function freezeSelectedAIAgentTaskBinding(
|
|
|
420
599
|
);
|
|
421
600
|
}
|
|
422
601
|
return projectFrozenAIAgentTaskBinding(freezeAIWorkflowRunResources({
|
|
602
|
+
executionResources: input.executionResources,
|
|
603
|
+
codeExecutions: input.codeExecutions,
|
|
423
604
|
registry: input.registry,
|
|
424
605
|
projection: input.projection,
|
|
425
606
|
task: {
|
|
@@ -465,6 +646,20 @@ function isValidDecision(value: AIAgentDefinitionSelectionDecision): boolean {
|
|
|
465
646
|
exactRef(decision.agentDefinitionRef, 'decision.agentDefinitionRef');
|
|
466
647
|
return true;
|
|
467
648
|
}
|
|
649
|
+
if (decision.mode === 'revise-existing') {
|
|
650
|
+
exactKeys(decision, ['schemaVersion', 'mode', 'requirementDigest', 'candidateSetDigest',
|
|
651
|
+
'candidateRef', 'candidateDigest', 'feedback', 'proposal', 'reason'], 'decision');
|
|
652
|
+
exactRef(decision.candidateRef, 'decision.candidateRef');
|
|
653
|
+
const feedback = closedRecord(decision.feedback, 'decision.feedback');
|
|
654
|
+
exactKeys(feedback, ['observationRef', 'attemptRef', 'verificationRef', 'requirementDigest',
|
|
655
|
+
'candidateSetDigest', 'candidateDigest', 'previousExecutionFingerprint'], 'decision.feedback');
|
|
656
|
+
for (const key of ['observationRef', 'attemptRef', 'verificationRef'] as const) {
|
|
657
|
+
nonEmptyString(feedback[key], `decision.feedback.${key}`);
|
|
658
|
+
}
|
|
659
|
+
return isDigest(decision.candidateDigest)
|
|
660
|
+
&& ['requirementDigest', 'candidateSetDigest', 'candidateDigest', 'previousExecutionFingerprint']
|
|
661
|
+
.every((key) => isDigest(feedback[key]));
|
|
662
|
+
}
|
|
468
663
|
return false;
|
|
469
664
|
} catch {
|
|
470
665
|
return false;
|
|
@@ -472,6 +667,7 @@ function isValidDecision(value: AIAgentDefinitionSelectionDecision): boolean {
|
|
|
472
667
|
}
|
|
473
668
|
|
|
474
669
|
function assertProjectionAuthority(input: ProjectAIAgentDefinitionCandidatesInput): void {
|
|
670
|
+
if (input.executionResources) assertAIAgentExecutionResources(input.executionResources, input.registry, input.layers);
|
|
475
671
|
if (sourceRegistryForAIWorkflowAgentResourceProjection(input.projection) !== input.registry) {
|
|
476
672
|
throw new AIAgentDefinitionSelectionError(
|
|
477
673
|
'AI_AGENT_CANDIDATE_PROJECTION_UNTRUSTED',
|
|
@@ -494,10 +690,49 @@ function isCompatible(
|
|
|
494
690
|
&& containsAll(candidate.messageSourceRefs, requirement.requiredMessageSourceRefs);
|
|
495
691
|
}
|
|
496
692
|
|
|
693
|
+
function matchesRevisionFeedback(
|
|
694
|
+
input: AdmitAIAgentDefinitionSelectionInput,
|
|
695
|
+
decision: ReviseExistingAIAgentDefinitionDecision,
|
|
696
|
+
candidate: AIAgentDefinitionCandidate,
|
|
697
|
+
): boolean {
|
|
698
|
+
try {
|
|
699
|
+
const execution = input.previousExecution;
|
|
700
|
+
return execution !== undefined
|
|
701
|
+
&& frozenAIAgentTaskAgentContentDigest(execution) === candidate.contentDigest
|
|
702
|
+
&& execution.task.agentDefinitionRef === candidate.agentDefinitionRef
|
|
703
|
+
&& execution.semanticFingerprint === decision.feedback.previousExecutionFingerprint
|
|
704
|
+
&& decision.feedback.requirementDigest === input.requirement.requirementDigest
|
|
705
|
+
&& decision.feedback.candidateSetDigest === input.candidateSet.candidateSetDigest
|
|
706
|
+
&& decision.feedback.candidateDigest === candidate.candidateDigest;
|
|
707
|
+
} catch {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function revisionAuthority(
|
|
713
|
+
authority: CandidateSetAuthority,
|
|
714
|
+
candidate: AIAgentDefinitionCandidate,
|
|
715
|
+
): AuthoringAdmissionAuthority['before'] {
|
|
716
|
+
const id = resourceId(candidate.agentDefinitionRef);
|
|
717
|
+
const entry = authority.registry.byId.get(id);
|
|
718
|
+
const identity = resolveEffectiveResourceContentIdentities(authority).get(id);
|
|
719
|
+
if (!entry?.resource || entry.resource.kind !== 'AIAgentDefinition'
|
|
720
|
+
|| entry.resource.sourceShape !== 'single-file' || !entry.effectiveOrigin
|
|
721
|
+
|| !identity || identity.contentDigest !== candidate.contentDigest) return undefined;
|
|
722
|
+
return Object.freeze({
|
|
723
|
+
authorityDigest: asDigest(identity.authorityDigest, 'previous authority digest'),
|
|
724
|
+
effectiveOrigin: Object.freeze({ ...entry.effectiveOrigin }),
|
|
725
|
+
authorityResourceIds: Object.freeze([...authority.registry.byId.values()]
|
|
726
|
+
.filter((value) => value.resource && sameOrigin(value.effectiveOrigin, entry.effectiveOrigin))
|
|
727
|
+
.map(({ resourceId }) => resourceId).sort(compareCodeUnits)),
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
497
731
|
function isValidAuthoringProposal(
|
|
498
732
|
value: ResourceAuthoringProposal,
|
|
499
733
|
definitionRef: `resource://${string}`,
|
|
500
734
|
candidateSet: AIAgentDefinitionCandidateSet,
|
|
735
|
+
authorityDigestBefore?: Sha256Digest,
|
|
501
736
|
): boolean {
|
|
502
737
|
try {
|
|
503
738
|
const proposal = closedRecord(value, 'proposal');
|
|
@@ -515,15 +750,19 @@ function isValidAuthoringProposal(
|
|
|
515
750
|
'expected',
|
|
516
751
|
], 'proposal');
|
|
517
752
|
const expected = closedRecord(proposal.expected, 'proposal.expected');
|
|
518
|
-
exactKeys(expected,
|
|
753
|
+
exactKeys(expected, authorityDigestBefore === undefined
|
|
754
|
+
? ['state', 'registryRevision'] : ['state', 'registryRevision', 'authorityDigest'], 'proposal.expected');
|
|
519
755
|
return proposal.schemaVersion === RESOURCE_AUTHORING_SCHEMA_VERSION
|
|
520
|
-
&& proposal.operation === 'create'
|
|
756
|
+
&& proposal.operation === (authorityDigestBefore === undefined ? 'create' : 'update')
|
|
521
757
|
&& proposal.kind === 'AIAgentDefinition'
|
|
522
758
|
&& proposal.sourceShape === 'single-file'
|
|
523
|
-
&& expected.state === 'absent'
|
|
759
|
+
&& expected.state === (authorityDigestBefore === undefined ? 'absent' : 'present')
|
|
760
|
+
&& (authorityDigestBefore === undefined || (expected.authorityDigest === authorityDigestBefore
|
|
761
|
+
&& sha256Digest(stringValue(proposal.authorityText, 'proposal.authorityText')) !== authorityDigestBefore))
|
|
524
762
|
&& expected.registryRevision === candidateSet.registryRevision
|
|
525
763
|
&& definitionRef === resourceRef(nonEmptyString(proposal.resourceId, 'proposal.resourceId'))
|
|
526
|
-
&&
|
|
764
|
+
&& (authorityDigestBefore !== undefined
|
|
765
|
+
|| !candidateSet.candidates.some(({ agentDefinitionRef }) => agentDefinitionRef === definitionRef))
|
|
527
766
|
&& nonEmptyString(proposal.catalogId, 'proposal.catalogId').length > 0
|
|
528
767
|
&& proposal.envelopeVersion === RESOURCE_ENVELOPE_VERSION
|
|
529
768
|
&& Number.isSafeInteger(proposal.writerSpecVersion)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEPA_AI_RESOURCE_KIND_READER_REGISTRATIONS,
|
|
3
|
+
type AIAgentCodeResourceProjection,
|
|
4
|
+
type AIAgentResolvedResourceIdentity,
|
|
5
|
+
type AIWorkflowAgentResourceProjection,
|
|
6
|
+
} from 'ai-workflow-contract';
|
|
7
|
+
import {
|
|
8
|
+
composeLayeredResourceRegistry,
|
|
9
|
+
digestCanonical,
|
|
10
|
+
resolveEffectiveResourceContentIdentities,
|
|
11
|
+
type EffectiveResourceRegistry,
|
|
12
|
+
type ResourceContentIdentity,
|
|
13
|
+
type ResourceLayerContentIdentityInput,
|
|
14
|
+
type ResourceRecord,
|
|
15
|
+
type ResolvedResourceRecord,
|
|
16
|
+
type ResourceTombstone,
|
|
17
|
+
} from 'halfcode-compiler.xnl/resource-core';
|
|
18
|
+
import { projectAIWorkflowAgentResources } from './agent-resources.js';
|
|
19
|
+
import { resolveAIWorkflowResourceTree, type AIWorkflowResourceResolutionContext } from './resource-resolution.js';
|
|
20
|
+
|
|
21
|
+
export interface ResolveAIAgentExecutionResourcesRuntime {
|
|
22
|
+
readonly resolutionContexts: ReadonlyMap<string, AIWorkflowResourceResolutionContext>;
|
|
23
|
+
}
|
|
24
|
+
export interface ResolveAIAgentExecutionResourcesInput {
|
|
25
|
+
readonly registry: EffectiveResourceRegistry;
|
|
26
|
+
readonly layers: readonly ResourceLayerContentIdentityInput[];
|
|
27
|
+
}
|
|
28
|
+
declare const executionResourceBrand: unique symbol;
|
|
29
|
+
/** Process-local derived observation; rebuild from authored bytes and exact readers on recovery. */
|
|
30
|
+
export interface AIAgentExecutionResourceObservation {
|
|
31
|
+
readonly [executionResourceBrand]: true;
|
|
32
|
+
readonly registry: EffectiveResourceRegistry;
|
|
33
|
+
readonly projection: AIWorkflowAgentResourceProjection;
|
|
34
|
+
readonly contentIdentities: ReadonlyMap<string, ResourceContentIdentity>;
|
|
35
|
+
readonly resolutionIdentities: ReadonlyMap<string, AIAgentResolvedResourceIdentity>;
|
|
36
|
+
/** Exact native effective records. Custom readerValue semantics remain owned by that Kind. */
|
|
37
|
+
readonly resolvedResources: ReadonlyMap<string, ResolvedResourceRecord>;
|
|
38
|
+
}
|
|
39
|
+
interface ExecutionAuthority extends ResolveAIAgentExecutionResourcesInput {
|
|
40
|
+
readonly layerBindings: readonly ResourceLayerContentIdentityInput[];
|
|
41
|
+
}
|
|
42
|
+
const authorities = new WeakMap<AIAgentExecutionResourceObservation, ExecutionAuthority>();
|
|
43
|
+
const codeResolutions = new WeakMap<AIAgentCodeResourceProjection, AIAgentResolvedResourceIdentity>();
|
|
44
|
+
const executionRegistries = new WeakSet<EffectiveResourceRegistry>();
|
|
45
|
+
const executionCodeResources = new WeakSet<ResourceRecord>();
|
|
46
|
+
const nodeReaderSubjects = new Set(DEPA_AI_RESOURCE_KIND_READER_REGISTRATIONS.map(reader => reader.subjectFqn));
|
|
47
|
+
|
|
48
|
+
export function resolveAIAgentExecutionResources(
|
|
49
|
+
runtime: ResolveAIAgentExecutionResourcesRuntime,
|
|
50
|
+
input: ResolveAIAgentExecutionResourcesInput,
|
|
51
|
+
): AIAgentExecutionResourceObservation {
|
|
52
|
+
// Native owner validates registry provenance, exact authored trees, layer order and origins.
|
|
53
|
+
const contentIdentities = resolveEffectiveResourceContentIdentities(input);
|
|
54
|
+
if (runtime.resolutionContexts.size !== input.layers.length
|
|
55
|
+
|| input.layers.some(layer => !runtime.resolutionContexts.get(layer.id))) {
|
|
56
|
+
fail('Reader context keys must exactly match the authored layer ids.');
|
|
57
|
+
}
|
|
58
|
+
// Tombstones are facts of the authentic composer result. Reuse the native composer
|
|
59
|
+
// to replay them, including tombstones that were subsequently shadowed by a layer.
|
|
60
|
+
const tombstones = new Map<string, ResourceTombstone[]>();
|
|
61
|
+
for (const entry of input.registry.byId.values()) for (const tombstone of entry.tombstones) {
|
|
62
|
+
const values = tombstones.get(tombstone.layerId) ?? [];
|
|
63
|
+
values.push({ resourceId: tombstone.resourceId,
|
|
64
|
+
...(tombstone.expectedKind === undefined ? {} : { expectedKind: tombstone.expectedKind }),
|
|
65
|
+
...(tombstone.reason === undefined ? {} : { reason: tombstone.reason }) });
|
|
66
|
+
tombstones.set(tombstone.layerId, values);
|
|
67
|
+
}
|
|
68
|
+
const resolvedLayers = input.layers.map(layer => ({
|
|
69
|
+
id: layer.id,
|
|
70
|
+
tree: resolveAIWorkflowResourceTree(layer.tree, runtime.resolutionContexts.get(layer.id)!),
|
|
71
|
+
tombstones: tombstones.get(layer.id) ?? [],
|
|
72
|
+
}));
|
|
73
|
+
const registry = composeLayeredResourceRegistry({ layers: resolvedLayers });
|
|
74
|
+
if (digestCanonical(topology(registry)) !== digestCanonical(topology(input.registry))) {
|
|
75
|
+
fail('Resolved composition must retain the exact authored layer, origin, shadow and tombstone topology.');
|
|
76
|
+
}
|
|
77
|
+
const resolutions: Array<readonly [string, AIAgentResolvedResourceIdentity]> = [];
|
|
78
|
+
const resolvedResources: Array<readonly [string, ResolvedResourceRecord]> = [];
|
|
79
|
+
for (const entry of registry.byId.values()) {
|
|
80
|
+
if (!entry.resource) continue;
|
|
81
|
+
const layer = resolvedLayers.find(value => value.id === entry.effectiveLayerId);
|
|
82
|
+
const record = layer?.tree.registry.byKind.get(entry.kind)?.find(value => value.resourceId === entry.resourceId);
|
|
83
|
+
if (!record || !layer) fail(`Missing native resolution for '${entry.resourceId}'.`);
|
|
84
|
+
resolvedResources.push([entry.resourceId, record]);
|
|
85
|
+
// This projector's explicit input protocol is resolved PortableSpec/node. It does
|
|
86
|
+
// not execute arbitrary Kind reader outputs with different business semantics.
|
|
87
|
+
if (nodeReaderSubjects.has(record.subjectFqn)
|
|
88
|
+
&& digestCanonical(record.readerValue) !== digestCanonical(record.resolvedSpec)) {
|
|
89
|
+
fail(`Kind '${record.kind}' requires a reader whose value preserves the resolved node input protocol.`);
|
|
90
|
+
}
|
|
91
|
+
resolutions.push([entry.resourceId, Object.freeze({
|
|
92
|
+
resourceId: record.resourceId, readerId: record.readerId, readerSpecVersion: record.readerSpecVersion,
|
|
93
|
+
effectiveContentDigest: record.effectiveContentDigest, readerProfileId: layer.tree.readerProfileId,
|
|
94
|
+
resolutionReceiptDigest: digestCanonical(record.resolution),
|
|
95
|
+
})]);
|
|
96
|
+
}
|
|
97
|
+
const resolutionIdentities = immutableMap(resolutions.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
|
|
98
|
+
const projection = projectAIWorkflowAgentResources(registry);
|
|
99
|
+
for (const code of projection.codeResources ?? []) {
|
|
100
|
+
codeResolutions.set(code, resolutionIdentities.get(code.resource.resourceId)!);
|
|
101
|
+
executionCodeResources.add(code.resource);
|
|
102
|
+
}
|
|
103
|
+
const observation = Object.freeze({ registry, projection, contentIdentities, resolutionIdentities,
|
|
104
|
+
resolvedResources: immutableMap(resolvedResources.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)),
|
|
105
|
+
}) as AIAgentExecutionResourceObservation;
|
|
106
|
+
authorities.set(observation, Object.freeze({ registry: input.registry, layers: input.layers,
|
|
107
|
+
layerBindings: Object.freeze(input.layers.map(layer => Object.freeze({ id: layer.id, tree: layer.tree }))) }));
|
|
108
|
+
executionRegistries.add(registry);
|
|
109
|
+
return observation;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Internal admission guard; never exported as a public branding operation. */
|
|
113
|
+
export function assertAIAgentExecutionResources(
|
|
114
|
+
observation: AIAgentExecutionResourceObservation,
|
|
115
|
+
registry: EffectiveResourceRegistry,
|
|
116
|
+
layers?: readonly ResourceLayerContentIdentityInput[],
|
|
117
|
+
): AIAgentExecutionResourceObservation {
|
|
118
|
+
const authority = authorities.get(observation);
|
|
119
|
+
if (!authority || authority.registry !== registry || (layers !== undefined && authority.layers !== layers)
|
|
120
|
+
|| authority.layers.length !== authority.layerBindings.length
|
|
121
|
+
|| authority.layerBindings.some((layer, index) => authority.layers[index]?.id !== layer.id || authority.layers[index]?.tree !== layer.tree)) {
|
|
122
|
+
fail('Execution resources require the exact authentic observation for this authored registry and layers.');
|
|
123
|
+
}
|
|
124
|
+
return observation;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function resolutionForAIAgentCodeProjection(projection: AIAgentCodeResourceProjection): AIAgentResolvedResourceIdentity | undefined {
|
|
128
|
+
const resolution = codeResolutions.get(projection);
|
|
129
|
+
if (!resolution && executionCodeResources.has(projection.resource)) {
|
|
130
|
+
fail('Code compilation and restore require the exact execution observation projection.');
|
|
131
|
+
}
|
|
132
|
+
return resolution;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Prevents discarding reader identities by passing a bridge registry to the old freeze path. */
|
|
136
|
+
export function assertAIAgentFreezeRegistry(registry: EffectiveResourceRegistry): void {
|
|
137
|
+
if (executionRegistries.has(registry)) fail('Freeze requires the original authored registry and its executionResources observation.');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function topology(registry: EffectiveResourceRegistry): unknown {
|
|
141
|
+
return { layers: registry.layers, entries: [...registry.byId.values()].map(entry => ({
|
|
142
|
+
resourceId: entry.resourceId, kind: entry.kind, present: entry.resource !== undefined,
|
|
143
|
+
origin: entry.effectiveOrigin ?? null, shadowed: entry.shadowed,
|
|
144
|
+
tombstone: entry.tombstone ?? null, tombstones: entry.tombstones,
|
|
145
|
+
})) };
|
|
146
|
+
}
|
|
147
|
+
function immutableMap<K, V>(entries: readonly (readonly [K, V])[]): ReadonlyMap<K, V> {
|
|
148
|
+
const values = new Map(entries);
|
|
149
|
+
const view: ReadonlyMap<K, V> = Object.freeze({
|
|
150
|
+
get size() { return values.size; },
|
|
151
|
+
get: (key: K) => values.get(key), has: (key: K) => values.has(key),
|
|
152
|
+
keys: () => values.keys(), values: () => values.values(), entries: () => values.entries(),
|
|
153
|
+
[Symbol.iterator]: () => values[Symbol.iterator](),
|
|
154
|
+
forEach: (callback: (value: V, key: K, map: ReadonlyMap<K, V>) => void, thisArg?: unknown) => {
|
|
155
|
+
values.forEach((value, key) => callback.call(thisArg, value, key, view));
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
return view;
|
|
159
|
+
}
|
|
160
|
+
function fail(message: string): never { throw new Error(`AI_AGENT_EXECUTION_RESOURCES_UNTRUSTED: ${message}`); }
|