holarchy-eidolon-adapter 0.1.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
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "holarchy-eidolon-adapter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Eidolon ResourcePackage adapter for frozen Holarchy organization snapshots and execution bindings.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./HolonExecutionBinding": "./src/HolonExecutionBinding.ts",
|
|
11
|
+
"./HolonExecutionBindingProjection": "./src/HolonExecutionBindingProjection.ts"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"src"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
18
|
+
"test": "bun test"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"ai-workflow-contract": "0.1.7",
|
|
22
|
+
"halfcode-compiler.xnl": "0.2.2",
|
|
23
|
+
"holarchy-core-contract": "0.1.1"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"typescript": "^5.4.0"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
export const HOLON_EXECUTION_BINDING_KIND = "HolonExecutionBinding" as const
|
|
2
|
+
export const HOLON_EXECUTION_BINDING_KIND_DEFINITION_FQN =
|
|
3
|
+
"Eidolon.AI.KindDefinition.HolonExecutionBinding" as const
|
|
4
|
+
export const HOLON_EXECUTION_BINDING_API_VERSION = "eidolon.ai/v1" as const
|
|
5
|
+
export const HOLON_EXECUTION_BINDING_KIND_DEFINITION_VERSION = "1.0.0" as const
|
|
6
|
+
|
|
7
|
+
export const HOLON_EXECUTION_BINDING_KIND_DEFINITION_SOURCE =
|
|
8
|
+
`<KindDefinition #Eidolon.AI.KindDefinition.HolonExecutionBinding apiVersion="halfcode.resources/v1" version="1.0.0" {
|
|
9
|
+
lifecycle = "Stable"
|
|
10
|
+
resourceKind = "HolonExecutionBinding"
|
|
11
|
+
sourceShapes = ["single-file"]
|
|
12
|
+
currentApiVersion = "eidolon.ai/v1"
|
|
13
|
+
supportedApiVersions = ["eidolon.ai/v1"]
|
|
14
|
+
description = "Maps one frozen Holon Member or Role identity to an exact Eidolon execution adapter closure."
|
|
15
|
+
}>
|
|
16
|
+
` as const
|
|
17
|
+
|
|
18
|
+
export const HOLON_EXECUTION_BINDING_KIND_DEFINITION_BYTES: Readonly<Uint8Array> =
|
|
19
|
+
new TextEncoder().encode(HOLON_EXECUTION_BINDING_KIND_DEFINITION_SOURCE)
|
|
20
|
+
|
|
21
|
+
export const HOLON_MEMBER_EXECUTION_ADAPTER_KINDS = Object.freeze([
|
|
22
|
+
"ai-agent",
|
|
23
|
+
"human-endpoint",
|
|
24
|
+
"service",
|
|
25
|
+
"hybrid",
|
|
26
|
+
] as const)
|
|
27
|
+
|
|
28
|
+
export type HolonExecutionResourceRef = `resource://${string}`
|
|
29
|
+
|
|
30
|
+
export type HolonExecutionTarget =
|
|
31
|
+
| Readonly<{ readonly kind: "member"; readonly memberRef: string }>
|
|
32
|
+
| Readonly<{ readonly kind: "role"; readonly roleRef: string }>
|
|
33
|
+
|
|
34
|
+
export type HolonMemberExecutionAdapter =
|
|
35
|
+
| Readonly<{
|
|
36
|
+
readonly kind: "ai-agent"
|
|
37
|
+
readonly agentDefinitionRef: HolonExecutionResourceRef
|
|
38
|
+
readonly runtimeProfileRef: HolonExecutionResourceRef
|
|
39
|
+
}>
|
|
40
|
+
| Readonly<{
|
|
41
|
+
readonly kind: "human-endpoint"
|
|
42
|
+
readonly humanEndpointRef: HolonExecutionResourceRef
|
|
43
|
+
readonly inboxProfileRef: HolonExecutionResourceRef
|
|
44
|
+
}>
|
|
45
|
+
| Readonly<{
|
|
46
|
+
readonly kind: "service"
|
|
47
|
+
readonly serviceAdapterRef: HolonExecutionResourceRef
|
|
48
|
+
readonly runtimeProfileRef: HolonExecutionResourceRef
|
|
49
|
+
}>
|
|
50
|
+
| Readonly<{
|
|
51
|
+
readonly kind: "hybrid"
|
|
52
|
+
readonly policyRef: HolonExecutionResourceRef
|
|
53
|
+
readonly candidateBindingRefs: readonly [HolonExecutionResourceRef, ...HolonExecutionResourceRef[]]
|
|
54
|
+
}>
|
|
55
|
+
|
|
56
|
+
export type HolonExecutionRuntimeIntent =
|
|
57
|
+
| Readonly<{ readonly mode: "shared-member-runtime" }>
|
|
58
|
+
| Readonly<{ readonly mode: "isolated-task-runtime" }>
|
|
59
|
+
|
|
60
|
+
export interface HolonExecutionPolicyV1 {
|
|
61
|
+
readonly version: "1"
|
|
62
|
+
readonly runtime: HolonExecutionRuntimeIntent
|
|
63
|
+
readonly taskProfileRef: HolonExecutionResourceRef
|
|
64
|
+
readonly capabilityRefs: readonly HolonExecutionResourceRef[]
|
|
65
|
+
readonly toolRefs: readonly HolonExecutionResourceRef[]
|
|
66
|
+
readonly materialRefs: readonly HolonExecutionResourceRef[]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface HolonExecutionBinding {
|
|
70
|
+
readonly apiVersion: typeof HOLON_EXECUTION_BINDING_API_VERSION
|
|
71
|
+
readonly kind: typeof HOLON_EXECUTION_BINDING_KIND
|
|
72
|
+
readonly bindingRef: HolonExecutionResourceRef
|
|
73
|
+
readonly snapshotRef: HolonExecutionResourceRef
|
|
74
|
+
readonly target: HolonExecutionTarget
|
|
75
|
+
readonly adapter: HolonMemberExecutionAdapter
|
|
76
|
+
readonly policy: HolonExecutionPolicyV1
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface HolonExecutionInvocation {
|
|
80
|
+
readonly apiVersion: typeof HOLON_EXECUTION_BINDING_API_VERSION
|
|
81
|
+
readonly kind: "HolonExecutionInvocation"
|
|
82
|
+
readonly taskSpaceRef: string
|
|
83
|
+
readonly taskRef: string
|
|
84
|
+
readonly claimRef: string
|
|
85
|
+
readonly invocationRef: string
|
|
86
|
+
readonly targetBindingRef: HolonExecutionResourceRef
|
|
87
|
+
readonly input: ClosedValue
|
|
88
|
+
readonly materialRefs: readonly HolonExecutionResourceRef[]
|
|
89
|
+
readonly resultContractRef: HolonExecutionResourceRef
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface ClosedArray extends ReadonlyArray<ClosedValue> {}
|
|
93
|
+
interface ClosedRecord { readonly [key: string]: ClosedValue }
|
|
94
|
+
export type ClosedValue = null | string | number | boolean | ClosedArray | ClosedRecord
|
|
95
|
+
|
|
96
|
+
export class HolonExecutionBindingContractError extends Error {
|
|
97
|
+
constructor(readonly path: string, message: string) {
|
|
98
|
+
super(`${path}: ${message}`)
|
|
99
|
+
this.name = "HolonExecutionBindingContractError"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const invalid = (path: string, message: string): never => {
|
|
104
|
+
throw new HolonExecutionBindingContractError(path, message)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const compareUtf16CodeUnits = (left: string, right: string): number =>
|
|
108
|
+
left < right ? -1 : left > right ? 1 : 0
|
|
109
|
+
|
|
110
|
+
function closedObject(value: unknown, path: string): Readonly<Record<string, unknown>> {
|
|
111
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
112
|
+
return invalid(path, "Expected a plain object")
|
|
113
|
+
}
|
|
114
|
+
const prototype = Object.getPrototypeOf(value)
|
|
115
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
116
|
+
return invalid(path, "Expected a plain object")
|
|
117
|
+
}
|
|
118
|
+
const descriptors = Object.getOwnPropertyDescriptors(value)
|
|
119
|
+
const keys = Reflect.ownKeys(descriptors)
|
|
120
|
+
if (keys.some((key) => typeof key !== "string")) {
|
|
121
|
+
return invalid(path, "Symbol properties are not allowed")
|
|
122
|
+
}
|
|
123
|
+
const result: Record<string, unknown> = Object.create(null)
|
|
124
|
+
for (const key of (keys as string[]).sort(compareUtf16CodeUnits)) {
|
|
125
|
+
const descriptor = descriptors[key]
|
|
126
|
+
if (!descriptor || !("value" in descriptor) || descriptor.enumerable !== true) {
|
|
127
|
+
return invalid(`${path}.${key}`, "Expected one enumerable own-data property")
|
|
128
|
+
}
|
|
129
|
+
result[key] = descriptor.value
|
|
130
|
+
}
|
|
131
|
+
return result
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function exactKeys(
|
|
135
|
+
value: Readonly<Record<string, unknown>>,
|
|
136
|
+
expected: readonly string[],
|
|
137
|
+
path: string,
|
|
138
|
+
): void {
|
|
139
|
+
const expectedSet = new Set(expected)
|
|
140
|
+
const extra = Object.keys(value).find((key) => !expectedSet.has(key))
|
|
141
|
+
if (extra !== undefined) invalid(`${path}.${extra}`, "Unsupported field")
|
|
142
|
+
const missing = expected.find((key) => !Object.prototype.hasOwnProperty.call(value, key))
|
|
143
|
+
if (missing !== undefined) invalid(`${path}.${missing}`, "Required field is missing")
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function exactString(value: unknown, path: string): string {
|
|
147
|
+
if (typeof value !== "string" || value.length === 0 || value !== value.trim()) {
|
|
148
|
+
return invalid(path, "Expected an exact non-empty string")
|
|
149
|
+
}
|
|
150
|
+
return value
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function exactLiteral<T extends string>(value: unknown, expected: T, path: string): T {
|
|
154
|
+
if (value !== expected) invalid(path, `Expected '${expected}'`)
|
|
155
|
+
return expected
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function resourceRef(value: unknown, path: string): HolonExecutionResourceRef {
|
|
159
|
+
const exact = exactString(value, path)
|
|
160
|
+
const prefix = "resource://"
|
|
161
|
+
if (!exact.startsWith(prefix)) invalid(path, "Expected an exact resource:// reference")
|
|
162
|
+
const identity = exact.slice(prefix.length)
|
|
163
|
+
if (!identity || identity.includes("://")) invalid(path, "Expected one exact Resource identity")
|
|
164
|
+
return exact as HolonExecutionResourceRef
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function denseArray(value: unknown, path: string): readonly unknown[] {
|
|
168
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
|
|
169
|
+
return invalid(path, "Expected a plain dense array")
|
|
170
|
+
}
|
|
171
|
+
const descriptors = Object.getOwnPropertyDescriptors(value)
|
|
172
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
173
|
+
const descriptor = descriptors[String(index)]
|
|
174
|
+
if (!descriptor || !("value" in descriptor) || descriptor.enumerable !== true) {
|
|
175
|
+
return invalid(`${path}[${index}]`, "Expected a dense enumerable own-data entry")
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
179
|
+
if (key === "length") continue
|
|
180
|
+
if (typeof key !== "string" || !/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= value.length) {
|
|
181
|
+
return invalid(path, "Symbol and extra array properties are not allowed")
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return value
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function resourceRefs(
|
|
188
|
+
value: unknown,
|
|
189
|
+
path: string,
|
|
190
|
+
options: { readonly nonEmpty?: boolean; readonly sort?: boolean } = {},
|
|
191
|
+
): readonly HolonExecutionResourceRef[] {
|
|
192
|
+
const refs = denseArray(value, path).map((entry, index) => resourceRef(entry, `${path}[${index}]`))
|
|
193
|
+
if (options.nonEmpty && refs.length === 0) invalid(path, "Expected at least one Resource reference")
|
|
194
|
+
if (new Set(refs).size !== refs.length) invalid(path, "Resource reference list contains a duplicate")
|
|
195
|
+
if (options.sort) refs.sort(compareUtf16CodeUnits)
|
|
196
|
+
return Object.freeze(refs)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function target(value: unknown): HolonExecutionTarget {
|
|
200
|
+
const record = closedObject(value, "$.target")
|
|
201
|
+
const kind = exactString(record.kind, "$.target.kind")
|
|
202
|
+
if (kind === "member") {
|
|
203
|
+
exactKeys(record, ["kind", "memberRef"], "$.target")
|
|
204
|
+
return Object.freeze({ kind, memberRef: exactString(record.memberRef, "$.target.memberRef") })
|
|
205
|
+
}
|
|
206
|
+
if (kind === "role") {
|
|
207
|
+
exactKeys(record, ["kind", "roleRef"], "$.target")
|
|
208
|
+
return Object.freeze({ kind, roleRef: exactString(record.roleRef, "$.target.roleRef") })
|
|
209
|
+
}
|
|
210
|
+
return invalid("$.target.kind", "Expected 'member' or 'role'")
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function adapter(value: unknown): HolonMemberExecutionAdapter {
|
|
214
|
+
const record = closedObject(value, "$.adapter")
|
|
215
|
+
const kind = exactString(record.kind, "$.adapter.kind")
|
|
216
|
+
if (kind === "ai-agent") {
|
|
217
|
+
exactKeys(record, ["kind", "agentDefinitionRef", "runtimeProfileRef"], "$.adapter")
|
|
218
|
+
return Object.freeze({
|
|
219
|
+
kind,
|
|
220
|
+
agentDefinitionRef: resourceRef(record.agentDefinitionRef, "$.adapter.agentDefinitionRef"),
|
|
221
|
+
runtimeProfileRef: resourceRef(record.runtimeProfileRef, "$.adapter.runtimeProfileRef"),
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
if (kind === "human-endpoint") {
|
|
225
|
+
exactKeys(record, ["kind", "humanEndpointRef", "inboxProfileRef"], "$.adapter")
|
|
226
|
+
return Object.freeze({
|
|
227
|
+
kind,
|
|
228
|
+
humanEndpointRef: resourceRef(record.humanEndpointRef, "$.adapter.humanEndpointRef"),
|
|
229
|
+
inboxProfileRef: resourceRef(record.inboxProfileRef, "$.adapter.inboxProfileRef"),
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
if (kind === "service") {
|
|
233
|
+
exactKeys(record, ["kind", "serviceAdapterRef", "runtimeProfileRef"], "$.adapter")
|
|
234
|
+
return Object.freeze({
|
|
235
|
+
kind,
|
|
236
|
+
serviceAdapterRef: resourceRef(record.serviceAdapterRef, "$.adapter.serviceAdapterRef"),
|
|
237
|
+
runtimeProfileRef: resourceRef(record.runtimeProfileRef, "$.adapter.runtimeProfileRef"),
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
if (kind === "hybrid") {
|
|
241
|
+
exactKeys(record, ["kind", "policyRef", "candidateBindingRefs"], "$.adapter")
|
|
242
|
+
const refs = resourceRefs(record.candidateBindingRefs, "$.adapter.candidateBindingRefs", { nonEmpty: true })
|
|
243
|
+
return Object.freeze({
|
|
244
|
+
kind,
|
|
245
|
+
policyRef: resourceRef(record.policyRef, "$.adapter.policyRef"),
|
|
246
|
+
candidateBindingRefs: refs as readonly [HolonExecutionResourceRef, ...HolonExecutionResourceRef[]],
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
return invalid("$.adapter.kind", "Expected one closed execution adapter kind")
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function policy(value: unknown): HolonExecutionPolicyV1 {
|
|
253
|
+
const record = closedObject(value, "$.policy")
|
|
254
|
+
exactKeys(record, [
|
|
255
|
+
"version",
|
|
256
|
+
"runtime",
|
|
257
|
+
"taskProfileRef",
|
|
258
|
+
"capabilityRefs",
|
|
259
|
+
"toolRefs",
|
|
260
|
+
"materialRefs",
|
|
261
|
+
], "$.policy")
|
|
262
|
+
exactLiteral(record.version, "1", "$.policy.version")
|
|
263
|
+
const runtime = closedObject(record.runtime, "$.policy.runtime")
|
|
264
|
+
exactKeys(runtime, ["mode"], "$.policy.runtime")
|
|
265
|
+
const mode = exactString(runtime.mode, "$.policy.runtime.mode")
|
|
266
|
+
if (mode !== "shared-member-runtime" && mode !== "isolated-task-runtime") {
|
|
267
|
+
invalid("$.policy.runtime.mode", "Expected one closed runtime intent")
|
|
268
|
+
}
|
|
269
|
+
const normalizedMode = mode as HolonExecutionRuntimeIntent["mode"]
|
|
270
|
+
return Object.freeze({
|
|
271
|
+
version: "1",
|
|
272
|
+
runtime: Object.freeze({ mode: normalizedMode }),
|
|
273
|
+
taskProfileRef: resourceRef(record.taskProfileRef, "$.policy.taskProfileRef"),
|
|
274
|
+
capabilityRefs: resourceRefs(record.capabilityRefs, "$.policy.capabilityRefs", { sort: true }),
|
|
275
|
+
toolRefs: resourceRefs(record.toolRefs, "$.policy.toolRefs", { sort: true }),
|
|
276
|
+
materialRefs: resourceRefs(record.materialRefs, "$.policy.materialRefs", { sort: true }),
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function normalizeHolonExecutionBinding(value: unknown): HolonExecutionBinding {
|
|
281
|
+
const record = closedObject(value, "$")
|
|
282
|
+
exactKeys(record, [
|
|
283
|
+
"apiVersion",
|
|
284
|
+
"kind",
|
|
285
|
+
"bindingRef",
|
|
286
|
+
"snapshotRef",
|
|
287
|
+
"target",
|
|
288
|
+
"adapter",
|
|
289
|
+
"policy",
|
|
290
|
+
], "$")
|
|
291
|
+
return Object.freeze({
|
|
292
|
+
apiVersion: exactLiteral(record.apiVersion, HOLON_EXECUTION_BINDING_API_VERSION, "$.apiVersion"),
|
|
293
|
+
kind: exactLiteral(record.kind, HOLON_EXECUTION_BINDING_KIND, "$.kind"),
|
|
294
|
+
bindingRef: resourceRef(record.bindingRef, "$.bindingRef"),
|
|
295
|
+
snapshotRef: resourceRef(record.snapshotRef, "$.snapshotRef"),
|
|
296
|
+
target: target(record.target),
|
|
297
|
+
adapter: adapter(record.adapter),
|
|
298
|
+
policy: policy(record.policy),
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function sameBytes(left: Uint8Array, right: Uint8Array): boolean {
|
|
303
|
+
if (left.byteLength !== right.byteLength) return false
|
|
304
|
+
for (let index = 0; index < left.byteLength; index += 1) {
|
|
305
|
+
if (left[index] !== right[index]) return false
|
|
306
|
+
}
|
|
307
|
+
return true
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function canonicalHolonExecutionBindingBytes(value: unknown): Uint8Array {
|
|
311
|
+
return new TextEncoder().encode(JSON.stringify(normalizeHolonExecutionBinding(value)))
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function parseHolonExecutionBindingBytes(bytes: Uint8Array): HolonExecutionBinding {
|
|
315
|
+
let decoded: string
|
|
316
|
+
try {
|
|
317
|
+
decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes)
|
|
318
|
+
} catch {
|
|
319
|
+
return invalid("$bytes", "Binding bytes are not valid UTF-8")
|
|
320
|
+
}
|
|
321
|
+
let value: unknown
|
|
322
|
+
try {
|
|
323
|
+
value = JSON.parse(decoded)
|
|
324
|
+
} catch {
|
|
325
|
+
return invalid("$bytes", "Binding bytes are not valid JSON")
|
|
326
|
+
}
|
|
327
|
+
const binding = normalizeHolonExecutionBinding(value)
|
|
328
|
+
if (!sameBytes(bytes, canonicalHolonExecutionBindingBytes(binding))) {
|
|
329
|
+
return invalid("$bytes", "Binding bytes are not canonical")
|
|
330
|
+
}
|
|
331
|
+
return binding
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function validateHolonExecutionBindingGraph(
|
|
335
|
+
values: readonly HolonExecutionBinding[],
|
|
336
|
+
): readonly HolonExecutionBinding[] {
|
|
337
|
+
const bindings = values.map(normalizeHolonExecutionBinding)
|
|
338
|
+
const byRef = new Map<HolonExecutionResourceRef, HolonExecutionBinding>()
|
|
339
|
+
for (const binding of bindings) {
|
|
340
|
+
if (byRef.has(binding.bindingRef)) {
|
|
341
|
+
invalid("$.bindings", `Duplicate binding identity '${binding.bindingRef}'`)
|
|
342
|
+
}
|
|
343
|
+
byRef.set(binding.bindingRef, binding)
|
|
344
|
+
}
|
|
345
|
+
const visiting = new Set<HolonExecutionResourceRef>()
|
|
346
|
+
const visited = new Set<HolonExecutionResourceRef>()
|
|
347
|
+
const visit = (bindingRef: HolonExecutionResourceRef): void => {
|
|
348
|
+
if (visited.has(bindingRef)) return
|
|
349
|
+
if (visiting.has(bindingRef)) invalid("$.bindings", `Hybrid binding cycle includes '${bindingRef}'`)
|
|
350
|
+
const binding = byRef.get(bindingRef)
|
|
351
|
+
?? invalid("$.bindings", `Hybrid candidate '${bindingRef}' is unresolved`)
|
|
352
|
+
visiting.add(bindingRef)
|
|
353
|
+
if (binding.adapter.kind === "hybrid") {
|
|
354
|
+
for (const candidateRef of binding.adapter.candidateBindingRefs) visit(candidateRef)
|
|
355
|
+
}
|
|
356
|
+
visiting.delete(bindingRef)
|
|
357
|
+
visited.add(bindingRef)
|
|
358
|
+
}
|
|
359
|
+
for (const binding of bindings) visit(binding.bindingRef)
|
|
360
|
+
return Object.freeze(bindings)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function cloneClosedValue(value: unknown, path: string, active = new Set<object>()): ClosedValue {
|
|
364
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value
|
|
365
|
+
if (typeof value === "number") {
|
|
366
|
+
if (!Number.isFinite(value)) invalid(path, "Expected a finite number")
|
|
367
|
+
return value
|
|
368
|
+
}
|
|
369
|
+
if (typeof value !== "object") return invalid(path, "Expected JSON-compatible plain data")
|
|
370
|
+
if (active.has(value)) return invalid(path, "Cycles are not allowed")
|
|
371
|
+
active.add(value)
|
|
372
|
+
try {
|
|
373
|
+
if (Array.isArray(value)) {
|
|
374
|
+
return Object.freeze(denseArray(value, path).map((entry, index) =>
|
|
375
|
+
cloneClosedValue(entry, `${path}[${index}]`, active)))
|
|
376
|
+
}
|
|
377
|
+
const record = closedObject(value, path)
|
|
378
|
+
const output: Record<string, ClosedValue> = Object.create(null)
|
|
379
|
+
for (const key of Object.keys(record).sort(compareUtf16CodeUnits)) {
|
|
380
|
+
output[key] = cloneClosedValue(record[key], `${path}.${key}`, active)
|
|
381
|
+
}
|
|
382
|
+
return Object.freeze(output)
|
|
383
|
+
} finally {
|
|
384
|
+
active.delete(value)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function normalizeHolonExecutionInvocation(value: unknown): HolonExecutionInvocation {
|
|
389
|
+
const record = closedObject(value, "$")
|
|
390
|
+
exactKeys(record, [
|
|
391
|
+
"apiVersion",
|
|
392
|
+
"kind",
|
|
393
|
+
"taskSpaceRef",
|
|
394
|
+
"taskRef",
|
|
395
|
+
"claimRef",
|
|
396
|
+
"invocationRef",
|
|
397
|
+
"targetBindingRef",
|
|
398
|
+
"input",
|
|
399
|
+
"materialRefs",
|
|
400
|
+
"resultContractRef",
|
|
401
|
+
], "$")
|
|
402
|
+
return Object.freeze({
|
|
403
|
+
apiVersion: exactLiteral(record.apiVersion, HOLON_EXECUTION_BINDING_API_VERSION, "$.apiVersion"),
|
|
404
|
+
kind: exactLiteral(record.kind, "HolonExecutionInvocation", "$.kind"),
|
|
405
|
+
taskSpaceRef: exactString(record.taskSpaceRef, "$.taskSpaceRef"),
|
|
406
|
+
taskRef: exactString(record.taskRef, "$.taskRef"),
|
|
407
|
+
claimRef: exactString(record.claimRef, "$.claimRef"),
|
|
408
|
+
invocationRef: exactString(record.invocationRef, "$.invocationRef"),
|
|
409
|
+
targetBindingRef: resourceRef(record.targetBindingRef, "$.targetBindingRef"),
|
|
410
|
+
input: cloneClosedValue(record.input, "$.input"),
|
|
411
|
+
materialRefs: resourceRefs(record.materialRefs, "$.materialRefs", { sort: true }),
|
|
412
|
+
resultContractRef: resourceRef(record.resultContractRef, "$.resultContractRef"),
|
|
413
|
+
})
|
|
414
|
+
}
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HOLON_EXECUTION_BINDING_KIND,
|
|
3
|
+
HOLON_EXECUTION_BINDING_KIND_DEFINITION_BYTES,
|
|
4
|
+
HOLON_EXECUTION_BINDING_KIND_DEFINITION_FQN,
|
|
5
|
+
parseHolonExecutionBindingBytes,
|
|
6
|
+
validateHolonExecutionBindingGraph,
|
|
7
|
+
type HolonExecutionBinding,
|
|
8
|
+
type HolonExecutionResourceRef,
|
|
9
|
+
} from "./HolonExecutionBinding"
|
|
10
|
+
import {
|
|
11
|
+
HOLON_EFFECTIVE_SNAPSHOT_KIND,
|
|
12
|
+
HOLON_EFFECTIVE_SNAPSHOT_KIND_DEFINITION_BYTES,
|
|
13
|
+
HOLON_EFFECTIVE_SNAPSHOT_KIND_DEFINITION_FQN,
|
|
14
|
+
parseHolonEffectiveSnapshotBytes,
|
|
15
|
+
parseHolonEffectiveSnapshotIssuanceReceiptBytes,
|
|
16
|
+
type HolonEffectiveSnapshot,
|
|
17
|
+
type HolonEffectiveSnapshotIssuanceReceipt,
|
|
18
|
+
} from "holarchy-core-contract"
|
|
19
|
+
import {
|
|
20
|
+
buildResourceDependencySnapshot,
|
|
21
|
+
sha256Digest,
|
|
22
|
+
type EffectiveResourceRegistry,
|
|
23
|
+
type ResourceContentIdentity,
|
|
24
|
+
type ResourceRecord,
|
|
25
|
+
} from "halfcode-compiler.xnl/resource-core"
|
|
26
|
+
import type { AIWorkflowAgentResourceProjection } from "ai-workflow-contract"
|
|
27
|
+
|
|
28
|
+
import { HolarchyEidolonAdapterError as EidolonResourceRegistryError } from "./HolarchyEidolonAdapterError"
|
|
29
|
+
|
|
30
|
+
export interface EidolonHolonExecutionBindingProjection {
|
|
31
|
+
readonly schemaVersion: "eidolon.holon-execution-binding-projection/v1"
|
|
32
|
+
readonly resource: ResourceRecord
|
|
33
|
+
readonly binding: HolonExecutionBinding
|
|
34
|
+
readonly snapshotResource: ResourceRecord
|
|
35
|
+
readonly snapshot: HolonEffectiveSnapshot
|
|
36
|
+
readonly receipt: HolonEffectiveSnapshotIssuanceReceipt
|
|
37
|
+
readonly bindingBytesDigest: `sha256:${string}`
|
|
38
|
+
readonly receiptBytesDigest: `sha256:${string}`
|
|
39
|
+
readonly closureResourceIds: readonly string[]
|
|
40
|
+
readonly registryRevision: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface EidolonHolonExecutionAgentProof {
|
|
44
|
+
readonly agentDefinitionRef: HolonExecutionResourceRef
|
|
45
|
+
readonly agentContentDigest: `sha256:${string}`
|
|
46
|
+
readonly closureResourceIds: readonly string[]
|
|
47
|
+
readonly snapshotRevision: `sha256:${string}`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface EidolonHolonExecutionBindingFreezeReceipt {
|
|
51
|
+
readonly schemaVersion: "eidolon.holon-execution-binding-freeze/v1"
|
|
52
|
+
readonly bindingRef: HolonExecutionResourceRef
|
|
53
|
+
readonly snapshotRef: HolonExecutionResourceRef
|
|
54
|
+
readonly snapshotTreeDigest: `sha256:${string}`
|
|
55
|
+
readonly snapshotReceiptDigest: `sha256:${string}`
|
|
56
|
+
readonly bindingBytesDigest: `sha256:${string}`
|
|
57
|
+
readonly registryRevision: string
|
|
58
|
+
readonly closure: readonly Readonly<{
|
|
59
|
+
readonly resourceId: string
|
|
60
|
+
readonly kind: string
|
|
61
|
+
readonly authorityDigest: `sha256:${string}`
|
|
62
|
+
readonly contentDigest: `sha256:${string}`
|
|
63
|
+
}>[]
|
|
64
|
+
readonly agentProofs: readonly EidolonHolonExecutionAgentProof[]
|
|
65
|
+
readonly semanticFingerprint: `sha256:${string}`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface HolonExecutionBindingProjectionInput {
|
|
69
|
+
readonly registry: EffectiveResourceRegistry
|
|
70
|
+
readonly contentIdentities: ReadonlyMap<string, ResourceContentIdentity>
|
|
71
|
+
readonly kindDefinitionAuthorityDigests: ReadonlyMap<string, `sha256:${string}`>
|
|
72
|
+
readonly registryRevision: string
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface HolonExecutionBindingFreezeInput {
|
|
76
|
+
readonly registry: EffectiveResourceRegistry
|
|
77
|
+
readonly contentIdentities: ReadonlyMap<string, ResourceContentIdentity>
|
|
78
|
+
readonly registryRevision: string
|
|
79
|
+
readonly projection: EidolonHolonExecutionBindingProjection
|
|
80
|
+
readonly agentResources: AIWorkflowAgentResourceProjection
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const authenticProjections = new WeakSet<object>()
|
|
84
|
+
const authenticFreezeReceipts = new WeakSet<object>()
|
|
85
|
+
|
|
86
|
+
export async function projectHolonExecutionBindings(
|
|
87
|
+
input: HolonExecutionBindingProjectionInput,
|
|
88
|
+
): Promise<readonly EidolonHolonExecutionBindingProjection[]> {
|
|
89
|
+
const resources = [...(input.registry.byKind.get(HOLON_EXECUTION_BINDING_KIND) ?? [])]
|
|
90
|
+
.sort((left, right) => compareUtf16(left.resourceId, right.resourceId))
|
|
91
|
+
if (resources.length === 0) return Object.freeze([])
|
|
92
|
+
assertCanonicalKindDefinition(input, {
|
|
93
|
+
resourceKind: HOLON_EXECUTION_BINDING_KIND,
|
|
94
|
+
fqn: HOLON_EXECUTION_BINDING_KIND_DEFINITION_FQN,
|
|
95
|
+
bytes: HOLON_EXECUTION_BINDING_KIND_DEFINITION_BYTES,
|
|
96
|
+
code: "EIDOLON_HOLON_BINDING_KIND_DEFINITION_MISMATCH",
|
|
97
|
+
})
|
|
98
|
+
assertCanonicalKindDefinition(input, {
|
|
99
|
+
resourceKind: HOLON_EFFECTIVE_SNAPSHOT_KIND,
|
|
100
|
+
fqn: HOLON_EFFECTIVE_SNAPSHOT_KIND_DEFINITION_FQN,
|
|
101
|
+
bytes: HOLON_EFFECTIVE_SNAPSHOT_KIND_DEFINITION_BYTES,
|
|
102
|
+
code: "EIDOLON_HOLON_SNAPSHOT_KIND_DEFINITION_MISMATCH",
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const decoded = resources.map((resource) => {
|
|
106
|
+
const bindingBytes = canonicalBase64Property(resource, "bindingBytesBase64")
|
|
107
|
+
const binding = parseHolonExecutionBindingBytes(bindingBytes)
|
|
108
|
+
if (binding.bindingRef !== resourceRef(resource.resourceId)) {
|
|
109
|
+
throw new EidolonResourceRegistryError(
|
|
110
|
+
"EIDOLON_HOLON_BINDING_IDENTITY_MISMATCH",
|
|
111
|
+
`HolonExecutionBinding '${resource.resourceId}' bytes select '${binding.bindingRef}'.`,
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
return Object.freeze({ resource, binding, bindingBytes })
|
|
115
|
+
})
|
|
116
|
+
const bindingGraph = validateHolonExecutionBindingGraph(decoded.map(({ binding }) => binding))
|
|
117
|
+
const decodedByRef = new Map(decoded.map((entry) => [entry.binding.bindingRef, entry]))
|
|
118
|
+
|
|
119
|
+
const projections: EidolonHolonExecutionBindingProjection[] = []
|
|
120
|
+
for (const binding of bindingGraph) {
|
|
121
|
+
const decodedBinding = decodedByRef.get(binding.bindingRef)
|
|
122
|
+
if (!decodedBinding) throw new EidolonResourceRegistryError(
|
|
123
|
+
"EIDOLON_HOLON_BINDING_GRAPH_INTERNAL",
|
|
124
|
+
`HolonExecutionBinding '${binding.bindingRef}' disappeared during projection.`,
|
|
125
|
+
)
|
|
126
|
+
const snapshotId = resourceId(binding.snapshotRef)
|
|
127
|
+
const snapshotResource = exactResource(input.registry, snapshotId)
|
|
128
|
+
if (snapshotResource.kind !== HOLON_EFFECTIVE_SNAPSHOT_KIND) {
|
|
129
|
+
throw new EidolonResourceRegistryError(
|
|
130
|
+
"EIDOLON_HOLON_SNAPSHOT_KIND_MISMATCH",
|
|
131
|
+
`Resource '${snapshotId}' must be a canonical ${HOLON_EFFECTIVE_SNAPSHOT_KIND} Resource.`,
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
const snapshotBytes = canonicalBase64Property(snapshotResource, "snapshotBytesBase64")
|
|
135
|
+
const snapshot = await parseHolonEffectiveSnapshotBytes(snapshotBytes)
|
|
136
|
+
const receiptBytes = canonicalBase64Property(snapshotResource, "issuanceReceiptBytesBase64")
|
|
137
|
+
const receipt = parseHolonEffectiveSnapshotIssuanceReceiptBytes(receiptBytes, snapshot)
|
|
138
|
+
assertTargetExists(binding, snapshot)
|
|
139
|
+
|
|
140
|
+
const closureResourceIds = resolveBindingClosureResourceIds(binding, decodedByRef, input.registry)
|
|
141
|
+
for (const resourceId of closureResourceIds) requiredIdentity(input.contentIdentities, resourceId)
|
|
142
|
+
requiredIdentity(input.contentIdentities, snapshotResource.resourceId)
|
|
143
|
+
|
|
144
|
+
const projection = Object.freeze({
|
|
145
|
+
schemaVersion: "eidolon.holon-execution-binding-projection/v1",
|
|
146
|
+
resource: decodedBinding.resource,
|
|
147
|
+
binding,
|
|
148
|
+
snapshotResource,
|
|
149
|
+
snapshot,
|
|
150
|
+
receipt,
|
|
151
|
+
bindingBytesDigest: sha256Digest(decodedBinding.bindingBytes),
|
|
152
|
+
receiptBytesDigest: sha256Digest(receiptBytes),
|
|
153
|
+
closureResourceIds,
|
|
154
|
+
registryRevision: input.registryRevision,
|
|
155
|
+
})
|
|
156
|
+
authenticProjections.add(projection)
|
|
157
|
+
projections.push(projection)
|
|
158
|
+
}
|
|
159
|
+
return Object.freeze(projections)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function freezeHolonExecutionBinding(
|
|
163
|
+
input: HolonExecutionBindingFreezeInput,
|
|
164
|
+
): EidolonHolonExecutionBindingFreezeReceipt {
|
|
165
|
+
if (!authenticProjections.has(input.projection as object)) {
|
|
166
|
+
throw new EidolonResourceRegistryError(
|
|
167
|
+
"EIDOLON_HOLON_BINDING_PROJECTION_UNTRUSTED",
|
|
168
|
+
"Binding freeze requires the exact projection produced by the shared registry.",
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
const closure = Object.freeze(input.projection.closureResourceIds.map((resourceId) => {
|
|
172
|
+
const resource = exactResource(input.registry, resourceId)
|
|
173
|
+
const identity = requiredIdentity(input.contentIdentities, resourceId)
|
|
174
|
+
return Object.freeze({
|
|
175
|
+
resourceId,
|
|
176
|
+
kind: resource.kind,
|
|
177
|
+
authorityDigest: exactSha256(identity.authorityDigest, `${resourceId}.authorityDigest`),
|
|
178
|
+
contentDigest: exactSha256(identity.contentDigest, `${resourceId}.contentDigest`),
|
|
179
|
+
})
|
|
180
|
+
}))
|
|
181
|
+
const agentProofs = Object.freeze(agentRefs(input.projection.binding, new Map(
|
|
182
|
+
(input.registry.byKind.get(HOLON_EXECUTION_BINDING_KIND) ?? []).map((resource) => {
|
|
183
|
+
const projected = resourceRef(resource.resourceId)
|
|
184
|
+
return [projected, projected === input.projection.binding.bindingRef
|
|
185
|
+
? input.projection.binding
|
|
186
|
+
: parseHolonExecutionBindingBytes(canonicalBase64Property(resource, "bindingBytesBase64"))]
|
|
187
|
+
}),
|
|
188
|
+
)).map((agentDefinitionRef) => {
|
|
189
|
+
const agentId = resourceId(agentDefinitionRef)
|
|
190
|
+
const agent = input.agentResources.agentDefinitions.find(
|
|
191
|
+
(candidate) => candidate.resource.resourceId === agentId,
|
|
192
|
+
)
|
|
193
|
+
if (!agent) throw new EidolonResourceRegistryError(
|
|
194
|
+
"EIDOLON_HOLON_BINDING_AGENT_PROJECTION_MISSING",
|
|
195
|
+
`AIAgentDefinition '${agentDefinitionRef}' has no authentic shared-registry projection.`,
|
|
196
|
+
)
|
|
197
|
+
const roots = agentClosureResourceIds(agent)
|
|
198
|
+
const dependencySnapshot = buildResourceDependencySnapshot({
|
|
199
|
+
registry: input.registry,
|
|
200
|
+
roots,
|
|
201
|
+
edges: input.agentResources.dependencyEdges,
|
|
202
|
+
contentIdentities: input.contentIdentities,
|
|
203
|
+
})
|
|
204
|
+
return Object.freeze({
|
|
205
|
+
agentDefinitionRef,
|
|
206
|
+
agentContentDigest: requiredIdentity(input.contentIdentities, agentId).contentDigest,
|
|
207
|
+
closureResourceIds: Object.freeze(dependencySnapshot.closure.map(({ resourceId }) => resourceId)),
|
|
208
|
+
snapshotRevision: dependencySnapshot.snapshotRevision,
|
|
209
|
+
})
|
|
210
|
+
}))
|
|
211
|
+
const semanticFingerprint = sha256Digest(JSON.stringify({
|
|
212
|
+
schemaVersion: "eidolon.holon-execution-binding-freeze-semantic/v1",
|
|
213
|
+
bindingRef: input.projection.binding.bindingRef,
|
|
214
|
+
snapshotRef: input.projection.binding.snapshotRef,
|
|
215
|
+
snapshotTreeDigest: input.projection.snapshot.treeDigest,
|
|
216
|
+
snapshotReceiptDigest: input.projection.receiptBytesDigest,
|
|
217
|
+
bindingBytesDigest: input.projection.bindingBytesDigest,
|
|
218
|
+
registryRevision: input.registryRevision,
|
|
219
|
+
closure,
|
|
220
|
+
agentProofs,
|
|
221
|
+
}))
|
|
222
|
+
const receipt = Object.freeze({
|
|
223
|
+
schemaVersion: "eidolon.holon-execution-binding-freeze/v1" as const,
|
|
224
|
+
bindingRef: input.projection.binding.bindingRef,
|
|
225
|
+
snapshotRef: input.projection.binding.snapshotRef,
|
|
226
|
+
snapshotTreeDigest: input.projection.snapshot.treeDigest,
|
|
227
|
+
snapshotReceiptDigest: input.projection.receiptBytesDigest,
|
|
228
|
+
bindingBytesDigest: input.projection.bindingBytesDigest,
|
|
229
|
+
registryRevision: input.registryRevision,
|
|
230
|
+
closure,
|
|
231
|
+
agentProofs,
|
|
232
|
+
semanticFingerprint,
|
|
233
|
+
})
|
|
234
|
+
authenticFreezeReceipts.add(receipt)
|
|
235
|
+
return receipt
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function assertHolonExecutionBindingFreezeReceipt(
|
|
239
|
+
value: EidolonHolonExecutionBindingFreezeReceipt,
|
|
240
|
+
): EidolonHolonExecutionBindingFreezeReceipt {
|
|
241
|
+
if (!authenticFreezeReceipts.has(value as object)) {
|
|
242
|
+
throw new EidolonResourceRegistryError(
|
|
243
|
+
"EIDOLON_HOLON_BINDING_FREEZE_RECEIPT_UNTRUSTED",
|
|
244
|
+
"Holon binding dispatch requires the authentic receipt produced by the shared registry freeze boundary.",
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
return value
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function agentRefs(
|
|
251
|
+
root: HolonExecutionBinding,
|
|
252
|
+
byRef: ReadonlyMap<HolonExecutionResourceRef, HolonExecutionBinding>,
|
|
253
|
+
): readonly HolonExecutionResourceRef[] {
|
|
254
|
+
const refs = new Set<HolonExecutionResourceRef>()
|
|
255
|
+
const visited = new Set<HolonExecutionResourceRef>()
|
|
256
|
+
const visit = (binding: HolonExecutionBinding): void => {
|
|
257
|
+
if (visited.has(binding.bindingRef)) return
|
|
258
|
+
visited.add(binding.bindingRef)
|
|
259
|
+
if (binding.adapter.kind === "ai-agent") refs.add(binding.adapter.agentDefinitionRef)
|
|
260
|
+
if (binding.adapter.kind === "hybrid") {
|
|
261
|
+
for (const candidateRef of binding.adapter.candidateBindingRefs) {
|
|
262
|
+
const candidate = byRef.get(candidateRef)
|
|
263
|
+
if (!candidate) throw new EidolonResourceRegistryError(
|
|
264
|
+
"EIDOLON_HOLON_BINDING_CANDIDATE_MISSING",
|
|
265
|
+
`Hybrid candidate '${candidateRef}' disappeared before freeze.`,
|
|
266
|
+
)
|
|
267
|
+
visit(candidate)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
visit(root)
|
|
272
|
+
return Object.freeze([...refs].sort(compareUtf16))
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function agentClosureResourceIds(
|
|
276
|
+
agent: AIWorkflowAgentResourceProjection["agentDefinitions"][number],
|
|
277
|
+
): readonly string[] {
|
|
278
|
+
const ids = new Set<string>([agent.resource.resourceId])
|
|
279
|
+
for (const message of agent.messages) {
|
|
280
|
+
ids.add(message.prompt.resource.resourceId)
|
|
281
|
+
if (message.schema) ids.add(message.schema.resource.resourceId)
|
|
282
|
+
}
|
|
283
|
+
if (agent.inputSchema) ids.add(agent.inputSchema.resource.resourceId)
|
|
284
|
+
if (agent.outputSchema) ids.add(agent.outputSchema.resource.resourceId)
|
|
285
|
+
if (agent.effectPolicy) ids.add(agent.effectPolicy.resource.resourceId)
|
|
286
|
+
for (const tool of agent.tools) ids.add(tool.resource.resourceId)
|
|
287
|
+
for (const port of agent.materialPorts) ids.add(port.resource.resourceId)
|
|
288
|
+
return Object.freeze([...ids].sort(compareUtf16))
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function assertCanonicalKindDefinition(
|
|
292
|
+
input: HolonExecutionBindingProjectionInput,
|
|
293
|
+
expected: {
|
|
294
|
+
readonly resourceKind: string
|
|
295
|
+
readonly fqn: string
|
|
296
|
+
readonly bytes: Readonly<Uint8Array>
|
|
297
|
+
readonly code: string
|
|
298
|
+
},
|
|
299
|
+
): void {
|
|
300
|
+
const kindDefinition = input.registry.kindDefinitions.get(expected.resourceKind)?.definition
|
|
301
|
+
if (!kindDefinition || kindDefinition.resourceId !== expected.fqn) {
|
|
302
|
+
throw new EidolonResourceRegistryError(
|
|
303
|
+
expected.code,
|
|
304
|
+
`Kind '${expected.resourceKind}' must be registered by canonical KindDefinition '${expected.fqn}'.`,
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
const expectedDigest = sha256Digest(Uint8Array.from(expected.bytes))
|
|
308
|
+
if (input.kindDefinitionAuthorityDigests.get(expected.resourceKind) !== expectedDigest) {
|
|
309
|
+
throw new EidolonResourceRegistryError(
|
|
310
|
+
expected.code,
|
|
311
|
+
`KindDefinition '${expected.fqn}' bytes do not match the canonical owner bytes.`,
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function canonicalBase64Property(resource: ResourceRecord, property: string): Uint8Array {
|
|
317
|
+
const value = resource.node.properties[property]
|
|
318
|
+
if (typeof value !== "string" || value.length === 0 || value !== value.trim()) {
|
|
319
|
+
throw new EidolonResourceRegistryError(
|
|
320
|
+
"EIDOLON_HOLON_RESOURCE_BYTES_MISSING",
|
|
321
|
+
`Resource '${resource.resourceId}' requires exact '${property}' base64 bytes.`,
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
let bytes: Uint8Array
|
|
325
|
+
try {
|
|
326
|
+
bytes = Uint8Array.from(Buffer.from(value, "base64"))
|
|
327
|
+
} catch {
|
|
328
|
+
throw new EidolonResourceRegistryError(
|
|
329
|
+
"EIDOLON_HOLON_RESOURCE_BYTES_INVALID",
|
|
330
|
+
`Resource '${resource.resourceId}' '${property}' is not valid base64.`,
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
if (bytes.byteLength === 0 || Buffer.from(bytes).toString("base64") !== value) {
|
|
334
|
+
throw new EidolonResourceRegistryError(
|
|
335
|
+
"EIDOLON_HOLON_RESOURCE_BYTES_INVALID",
|
|
336
|
+
`Resource '${resource.resourceId}' '${property}' is not canonical base64.`,
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
return bytes
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function assertTargetExists(binding: HolonExecutionBinding, snapshot: HolonEffectiveSnapshot): void {
|
|
343
|
+
const expectedKind = binding.target.kind === "member" ? "Member" : "Role"
|
|
344
|
+
const targetRef = binding.target.kind === "member" ? binding.target.memberRef : binding.target.roleRef
|
|
345
|
+
if (!snapshot.records.some((record) => record.kind === expectedKind && record.id === targetRef)) {
|
|
346
|
+
throw new EidolonResourceRegistryError(
|
|
347
|
+
"EIDOLON_HOLON_BINDING_TARGET_MISSING",
|
|
348
|
+
`${expectedKind} '${targetRef}' is not present in frozen snapshot '${snapshot.snapshotId}'.`,
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function resolveBindingClosureResourceIds(
|
|
354
|
+
root: HolonExecutionBinding,
|
|
355
|
+
bindings: ReadonlyMap<HolonExecutionResourceRef, Readonly<{
|
|
356
|
+
readonly resource: ResourceRecord
|
|
357
|
+
readonly binding: HolonExecutionBinding
|
|
358
|
+
readonly bindingBytes: Uint8Array
|
|
359
|
+
}>>,
|
|
360
|
+
registry: EffectiveResourceRegistry,
|
|
361
|
+
): readonly string[] {
|
|
362
|
+
const selected = new Set<string>()
|
|
363
|
+
const visited = new Set<HolonExecutionResourceRef>()
|
|
364
|
+
const add = (ref: HolonExecutionResourceRef, expectedKind?: string): void => {
|
|
365
|
+
const id = resourceId(ref)
|
|
366
|
+
const resource = exactResource(registry, id)
|
|
367
|
+
if (expectedKind && resource.kind !== expectedKind) {
|
|
368
|
+
throw new EidolonResourceRegistryError(
|
|
369
|
+
"EIDOLON_HOLON_BINDING_RESOURCE_KIND_MISMATCH",
|
|
370
|
+
`Resource '${id}' must have kind '${expectedKind}', got '${resource.kind}'.`,
|
|
371
|
+
)
|
|
372
|
+
}
|
|
373
|
+
selected.add(id)
|
|
374
|
+
}
|
|
375
|
+
const visit = (binding: HolonExecutionBinding): void => {
|
|
376
|
+
if (visited.has(binding.bindingRef)) return
|
|
377
|
+
visited.add(binding.bindingRef)
|
|
378
|
+
add(binding.bindingRef, HOLON_EXECUTION_BINDING_KIND)
|
|
379
|
+
add(binding.snapshotRef, HOLON_EFFECTIVE_SNAPSHOT_KIND)
|
|
380
|
+
add(binding.policy.taskProfileRef)
|
|
381
|
+
for (const ref of binding.policy.capabilityRefs) add(ref)
|
|
382
|
+
for (const ref of binding.policy.toolRefs) add(ref)
|
|
383
|
+
for (const ref of binding.policy.materialRefs) add(ref)
|
|
384
|
+
if (binding.adapter.kind === "ai-agent") {
|
|
385
|
+
add(binding.adapter.agentDefinitionRef, "AIAgentDefinition")
|
|
386
|
+
add(binding.adapter.runtimeProfileRef)
|
|
387
|
+
} else if (binding.adapter.kind === "human-endpoint") {
|
|
388
|
+
add(binding.adapter.humanEndpointRef)
|
|
389
|
+
add(binding.adapter.inboxProfileRef)
|
|
390
|
+
} else if (binding.adapter.kind === "service") {
|
|
391
|
+
add(binding.adapter.serviceAdapterRef)
|
|
392
|
+
add(binding.adapter.runtimeProfileRef)
|
|
393
|
+
} else {
|
|
394
|
+
add(binding.adapter.policyRef)
|
|
395
|
+
for (const candidateRef of binding.adapter.candidateBindingRefs) {
|
|
396
|
+
const candidate = bindings.get(candidateRef)
|
|
397
|
+
if (!candidate) throw new EidolonResourceRegistryError(
|
|
398
|
+
"EIDOLON_HOLON_BINDING_CANDIDATE_MISSING",
|
|
399
|
+
`Hybrid candidate '${candidateRef}' is not present in the admitted registry.`,
|
|
400
|
+
)
|
|
401
|
+
visit(candidate.binding)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
visit(root)
|
|
406
|
+
return Object.freeze([...selected].sort(compareUtf16))
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function exactResource(registry: EffectiveResourceRegistry, resourceId: string): ResourceRecord {
|
|
410
|
+
const resource = registry.byId.get(resourceId)?.resource
|
|
411
|
+
if (!resource) {
|
|
412
|
+
throw new EidolonResourceRegistryError(
|
|
413
|
+
"EIDOLON_HOLON_BINDING_RESOURCE_MISSING",
|
|
414
|
+
`Resource '${resourceId}' is not present in the admitted registry.`,
|
|
415
|
+
)
|
|
416
|
+
}
|
|
417
|
+
return resource
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function requiredIdentity(
|
|
421
|
+
identities: ReadonlyMap<string, ResourceContentIdentity>,
|
|
422
|
+
resourceId: string,
|
|
423
|
+
): ResourceContentIdentity {
|
|
424
|
+
const identity = identities.get(resourceId)
|
|
425
|
+
if (!identity) throw new EidolonResourceRegistryError(
|
|
426
|
+
"EIDOLON_HOLON_BINDING_CONTENT_IDENTITY_MISSING",
|
|
427
|
+
`Resource '${resourceId}' has no exact content identity.`,
|
|
428
|
+
)
|
|
429
|
+
return identity
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function resourceId(ref: HolonExecutionResourceRef): string {
|
|
433
|
+
return ref.slice("resource://".length)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function resourceRef(resourceId: string): HolonExecutionResourceRef {
|
|
437
|
+
return `resource://${resourceId}`
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function compareUtf16(left: string, right: string): number {
|
|
441
|
+
return left < right ? -1 : left > right ? 1 : 0
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function exactSha256(value: string, path: string): `sha256:${string}` {
|
|
445
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(value)) throw new EidolonResourceRegistryError(
|
|
446
|
+
"EIDOLON_HOLON_BINDING_DIGEST_INVALID",
|
|
447
|
+
`Expected one canonical SHA-256 digest at '${path}'.`,
|
|
448
|
+
)
|
|
449
|
+
return value as `sha256:${string}`
|
|
450
|
+
}
|
package/src/index.ts
ADDED