holarchy-eidolon-adapter 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -4
- package/src/HolonDeploymentDefinition.ts +241 -0
- package/src/HolonDeploymentRuntime.ts +388 -0
- package/src/HolonExecutionBinding.ts +154 -10
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "holarchy-eidolon-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Eidolon ResourcePackage adapter for frozen Holarchy organization snapshots and execution bindings.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./src/index.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./src/index.ts",
|
|
10
|
+
"./HolonDeploymentDefinition": "./src/HolonDeploymentDefinition.ts",
|
|
11
|
+
"./HolonDeploymentRuntime": "./src/HolonDeploymentRuntime.ts",
|
|
10
12
|
"./HolonExecutionBinding": "./src/HolonExecutionBinding.ts",
|
|
11
13
|
"./HolonExecutionBindingProjection": "./src/HolonExecutionBindingProjection.ts"
|
|
12
14
|
},
|
|
@@ -18,9 +20,10 @@
|
|
|
18
20
|
"test": "bun test"
|
|
19
21
|
},
|
|
20
22
|
"dependencies": {
|
|
21
|
-
"ai-workflow-contract": "0.
|
|
22
|
-
"
|
|
23
|
-
"
|
|
23
|
+
"ai-workflow-contract": "0.2.0",
|
|
24
|
+
"depa-actor": "0.2.2",
|
|
25
|
+
"halfcode-compiler.xnl": "0.3.0",
|
|
26
|
+
"holarchy-core-contract": "0.2.0"
|
|
24
27
|
},
|
|
25
28
|
"devDependencies": {
|
|
26
29
|
"typescript": "^5.4.0"
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { HolarchyEidolonAdapterError } from "./HolarchyEidolonAdapterError"
|
|
2
|
+
|
|
3
|
+
export const HOLON_DEPLOYMENT_DEFINITION_SCHEMA_VERSION =
|
|
4
|
+
"eidolon.holon-deployment-definition/v1" as const
|
|
5
|
+
|
|
6
|
+
export interface HolonDeploymentDefinitionFile {
|
|
7
|
+
readonly path: string
|
|
8
|
+
readonly digest: `sha256:${string}`
|
|
9
|
+
readonly sizeBytes: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface HolonDeploymentDefinition {
|
|
13
|
+
readonly schemaVersion: typeof HOLON_DEPLOYMENT_DEFINITION_SCHEMA_VERSION
|
|
14
|
+
readonly deploymentId: string
|
|
15
|
+
readonly rootHolonRef: string
|
|
16
|
+
readonly bindingRef: `resource://${string}`
|
|
17
|
+
readonly snapshotRef: `resource://${string}`
|
|
18
|
+
readonly registryRevision: string
|
|
19
|
+
readonly snapshotTreeDigest: `sha256:${string}`
|
|
20
|
+
readonly snapshotReceiptDigest: `sha256:${string}`
|
|
21
|
+
readonly bindingBytesDigest: `sha256:${string}`
|
|
22
|
+
readonly bindingSemanticFingerprint: `sha256:${string}`
|
|
23
|
+
readonly files: readonly HolonDeploymentDefinitionFile[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const REQUIRED_AUTHORITY_FILES = Object.freeze([
|
|
27
|
+
"authority/execution-binding-freeze-receipt.json",
|
|
28
|
+
"authority/execution-binding.json",
|
|
29
|
+
"authority/organization-snapshot-receipt.json",
|
|
30
|
+
"authority/organization-snapshot.json",
|
|
31
|
+
] as const)
|
|
32
|
+
|
|
33
|
+
const compareUtf16 = (left: string, right: string): number =>
|
|
34
|
+
left < right ? -1 : left > right ? 1 : 0
|
|
35
|
+
|
|
36
|
+
const invalid = (path: string, message: string): never => {
|
|
37
|
+
throw new HolarchyEidolonAdapterError(
|
|
38
|
+
"EIDOLON_HOLON_DEPLOYMENT_DEFINITION_INVALID",
|
|
39
|
+
`${path}: ${message}`,
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function closedObject(value: unknown, location: string): Readonly<Record<string, unknown>> {
|
|
44
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
45
|
+
return invalid(location, "Expected a plain object")
|
|
46
|
+
}
|
|
47
|
+
const prototype = Object.getPrototypeOf(value)
|
|
48
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
49
|
+
return invalid(location, "Expected a plain object")
|
|
50
|
+
}
|
|
51
|
+
const descriptors = Object.getOwnPropertyDescriptors(value)
|
|
52
|
+
const keys = Reflect.ownKeys(descriptors)
|
|
53
|
+
if (keys.some((key) => typeof key !== "string")) {
|
|
54
|
+
return invalid(location, "Symbol properties are not allowed")
|
|
55
|
+
}
|
|
56
|
+
const result: Record<string, unknown> = Object.create(null)
|
|
57
|
+
for (const key of keys as string[]) {
|
|
58
|
+
const descriptor = descriptors[key]
|
|
59
|
+
if (!descriptor || !("value" in descriptor) || descriptor.enumerable !== true) {
|
|
60
|
+
return invalid(`${location}.${key}`, "Expected one enumerable own-data property")
|
|
61
|
+
}
|
|
62
|
+
result[key] = descriptor.value
|
|
63
|
+
}
|
|
64
|
+
return result
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function exactKeys(
|
|
68
|
+
value: Readonly<Record<string, unknown>>,
|
|
69
|
+
expected: readonly string[],
|
|
70
|
+
location: string,
|
|
71
|
+
): void {
|
|
72
|
+
const expectedSet = new Set(expected)
|
|
73
|
+
const extra = Object.keys(value).find((key) => !expectedSet.has(key))
|
|
74
|
+
if (extra !== undefined) invalid(`${location}.${extra}`, "Unsupported field")
|
|
75
|
+
const missing = expected.find((key) => !Object.prototype.hasOwnProperty.call(value, key))
|
|
76
|
+
if (missing !== undefined) invalid(`${location}.${missing}`, "Required field is missing")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function denseArray(value: unknown, location: string): readonly unknown[] {
|
|
80
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
|
|
81
|
+
return invalid(location, "Expected a plain dense array")
|
|
82
|
+
}
|
|
83
|
+
const descriptors = Object.getOwnPropertyDescriptors(value)
|
|
84
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
85
|
+
const descriptor = descriptors[String(index)]
|
|
86
|
+
if (!descriptor || !("value" in descriptor) || descriptor.enumerable !== true) {
|
|
87
|
+
return invalid(`${location}[${index}]`, "Expected a dense enumerable own-data entry")
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
91
|
+
if (key === "length") continue
|
|
92
|
+
if (typeof key !== "string" || !/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= value.length) {
|
|
93
|
+
return invalid(location, "Symbol and extra array properties are not allowed")
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return value
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function exactString(value: unknown, location: string): string {
|
|
100
|
+
if (typeof value !== "string" || value.length === 0 || value !== value.trim()) {
|
|
101
|
+
return invalid(location, "Expected an exact non-empty string")
|
|
102
|
+
}
|
|
103
|
+
return value
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function exactLiteral<T extends string>(value: unknown, expected: T, location: string): T {
|
|
107
|
+
if (value !== expected) invalid(location, `Expected '${expected}'`)
|
|
108
|
+
return expected
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function exactIdentity(value: unknown, location: string): string {
|
|
112
|
+
const identity = exactString(value, location)
|
|
113
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(identity)) {
|
|
114
|
+
return invalid(location, "Expected one portable identity")
|
|
115
|
+
}
|
|
116
|
+
return identity
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function resourceRef(value: unknown, location: string): `resource://${string}` {
|
|
120
|
+
const ref = exactString(value, location)
|
|
121
|
+
if (!ref.startsWith("resource://") || ref.slice("resource://".length).includes("://")) {
|
|
122
|
+
return invalid(location, "Expected one exact resource:// identity")
|
|
123
|
+
}
|
|
124
|
+
const identity = ref.slice("resource://".length)
|
|
125
|
+
if (!identity) invalid(location, "Expected one exact resource:// identity")
|
|
126
|
+
return ref as `resource://${string}`
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function sha256(value: unknown, location: string): `sha256:${string}` {
|
|
130
|
+
if (typeof value !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value)) {
|
|
131
|
+
return invalid(location, "Expected one canonical sha256 digest")
|
|
132
|
+
}
|
|
133
|
+
return value as `sha256:${string}`
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function relativeFilePath(value: unknown, location: string): string {
|
|
137
|
+
const filePath = exactString(value, location)
|
|
138
|
+
const segments = filePath.split("/")
|
|
139
|
+
if (filePath.startsWith("/") || filePath.includes("\\")
|
|
140
|
+
|| segments.some((segment) => !segment || segment === "." || segment === "..")) {
|
|
141
|
+
return invalid(location, "Expected one portable containment-safe relative file path")
|
|
142
|
+
}
|
|
143
|
+
return filePath
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function file(value: unknown, index: number): HolonDeploymentDefinitionFile {
|
|
147
|
+
const location = `$.files[${index}]`
|
|
148
|
+
const record = closedObject(value, location)
|
|
149
|
+
exactKeys(record, ["path", "digest", "sizeBytes"], location)
|
|
150
|
+
if (!Number.isSafeInteger(record.sizeBytes) || (record.sizeBytes as number) < 0) {
|
|
151
|
+
invalid(`${location}.sizeBytes`, "Expected a non-negative safe integer")
|
|
152
|
+
}
|
|
153
|
+
return Object.freeze({
|
|
154
|
+
path: relativeFilePath(record.path, `${location}.path`),
|
|
155
|
+
digest: sha256(record.digest, `${location}.digest`),
|
|
156
|
+
sizeBytes: record.sizeBytes as number,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function normalizeHolonDeploymentDefinition(value: unknown): HolonDeploymentDefinition {
|
|
161
|
+
const record = closedObject(value, "$")
|
|
162
|
+
exactKeys(record, [
|
|
163
|
+
"schemaVersion",
|
|
164
|
+
"deploymentId",
|
|
165
|
+
"rootHolonRef",
|
|
166
|
+
"bindingRef",
|
|
167
|
+
"snapshotRef",
|
|
168
|
+
"registryRevision",
|
|
169
|
+
"snapshotTreeDigest",
|
|
170
|
+
"snapshotReceiptDigest",
|
|
171
|
+
"bindingBytesDigest",
|
|
172
|
+
"bindingSemanticFingerprint",
|
|
173
|
+
"files",
|
|
174
|
+
], "$")
|
|
175
|
+
const files = denseArray(record.files, "$.files")
|
|
176
|
+
.map((entry, index) => file(entry, index))
|
|
177
|
+
.sort((left, right) => compareUtf16(left.path, right.path))
|
|
178
|
+
if (files.length === 0 || new Set(files.map((entry) => entry.path)).size !== files.length) {
|
|
179
|
+
invalid("$.files", "Expected a non-empty list with unique file paths")
|
|
180
|
+
}
|
|
181
|
+
for (const required of REQUIRED_AUTHORITY_FILES) {
|
|
182
|
+
if (!files.some((entry) => entry.path === required)) {
|
|
183
|
+
invalid("$.files", `Required authority file '${required}' is missing`)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (!files.some((entry) => /^\.agent-resources\/(global|workspace)\/manifest\.xnl$/.test(entry.path))) {
|
|
187
|
+
invalid("$.files", "At least one frozen ResourcePackage layer manifest is required")
|
|
188
|
+
}
|
|
189
|
+
return Object.freeze({
|
|
190
|
+
schemaVersion: exactLiteral(
|
|
191
|
+
record.schemaVersion,
|
|
192
|
+
HOLON_DEPLOYMENT_DEFINITION_SCHEMA_VERSION,
|
|
193
|
+
"$.schemaVersion",
|
|
194
|
+
),
|
|
195
|
+
deploymentId: exactIdentity(record.deploymentId, "$.deploymentId"),
|
|
196
|
+
rootHolonRef: exactIdentity(record.rootHolonRef, "$.rootHolonRef"),
|
|
197
|
+
bindingRef: resourceRef(record.bindingRef, "$.bindingRef"),
|
|
198
|
+
snapshotRef: resourceRef(record.snapshotRef, "$.snapshotRef"),
|
|
199
|
+
registryRevision: sha256(record.registryRevision, "$.registryRevision"),
|
|
200
|
+
snapshotTreeDigest: sha256(record.snapshotTreeDigest, "$.snapshotTreeDigest"),
|
|
201
|
+
snapshotReceiptDigest: sha256(record.snapshotReceiptDigest, "$.snapshotReceiptDigest"),
|
|
202
|
+
bindingBytesDigest: sha256(record.bindingBytesDigest, "$.bindingBytesDigest"),
|
|
203
|
+
bindingSemanticFingerprint: sha256(
|
|
204
|
+
record.bindingSemanticFingerprint,
|
|
205
|
+
"$.bindingSemanticFingerprint",
|
|
206
|
+
),
|
|
207
|
+
files: Object.freeze(files),
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function sameBytes(left: Uint8Array, right: Uint8Array): boolean {
|
|
212
|
+
if (left.byteLength !== right.byteLength) return false
|
|
213
|
+
for (let index = 0; index < left.byteLength; index += 1) {
|
|
214
|
+
if (left[index] !== right[index]) return false
|
|
215
|
+
}
|
|
216
|
+
return true
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function canonicalHolonDeploymentDefinitionBytes(value: unknown): Uint8Array {
|
|
220
|
+
return new TextEncoder().encode(JSON.stringify(normalizeHolonDeploymentDefinition(value)))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function parseHolonDeploymentDefinitionBytes(bytes: Uint8Array): HolonDeploymentDefinition {
|
|
224
|
+
let decoded: string
|
|
225
|
+
try {
|
|
226
|
+
decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes)
|
|
227
|
+
} catch {
|
|
228
|
+
return invalid("$bytes", "Definition bytes are not valid UTF-8")
|
|
229
|
+
}
|
|
230
|
+
let value: unknown
|
|
231
|
+
try {
|
|
232
|
+
value = JSON.parse(decoded)
|
|
233
|
+
} catch {
|
|
234
|
+
return invalid("$bytes", "Definition bytes are not valid JSON")
|
|
235
|
+
}
|
|
236
|
+
const definition = normalizeHolonDeploymentDefinition(value)
|
|
237
|
+
if (!sameBytes(bytes, canonicalHolonDeploymentDefinitionBytes(definition))) {
|
|
238
|
+
return invalid("$bytes", "Definition bytes are not canonical")
|
|
239
|
+
}
|
|
240
|
+
return definition
|
|
241
|
+
}
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseActorRegistrationReceipt,
|
|
3
|
+
type ActorRegistrationReceipt,
|
|
4
|
+
} from "depa-actor"
|
|
5
|
+
import type { AIAgentSelector } from "ai-workflow-contract"
|
|
6
|
+
|
|
7
|
+
import { HolarchyEidolonAdapterError } from "./HolarchyEidolonAdapterError"
|
|
8
|
+
|
|
9
|
+
export const HOLON_DEPLOYMENT_RUNTIME_SCHEMA_VERSION =
|
|
10
|
+
"eidolon.holon-deployment-runtime/v1" as const
|
|
11
|
+
|
|
12
|
+
export type HolonCoordinatorLifecycleStatus = "registered" | "ready" | "stopped"
|
|
13
|
+
export type HolonMemberRuntimeLifecycleStatus = "registered" | "ready" | "stopped"
|
|
14
|
+
export type HolonTaskSpaceSubscriptionStatus = "observing" | "paused" | "closed"
|
|
15
|
+
|
|
16
|
+
export type HolonMemberRuntimeIsolation =
|
|
17
|
+
| Readonly<{ readonly mode: "shared" }>
|
|
18
|
+
| Readonly<{
|
|
19
|
+
readonly mode: "isolated"
|
|
20
|
+
readonly scope: "task-space" | "workflow-run"
|
|
21
|
+
readonly isolationKey: string
|
|
22
|
+
}>
|
|
23
|
+
|
|
24
|
+
export type HolonGenericSessionRef =
|
|
25
|
+
| Readonly<{
|
|
26
|
+
readonly mode: "task-attempt"
|
|
27
|
+
readonly taskSpaceId: string
|
|
28
|
+
readonly taskId: string
|
|
29
|
+
readonly claimId: string
|
|
30
|
+
readonly attempt: number
|
|
31
|
+
readonly workflowInstanceId?: string
|
|
32
|
+
readonly runId?: string
|
|
33
|
+
readonly sessionRef: string
|
|
34
|
+
}>
|
|
35
|
+
| Readonly<{
|
|
36
|
+
readonly mode: "targeted-agent-instance"
|
|
37
|
+
readonly selector: AIAgentSelector
|
|
38
|
+
readonly sessionRef: string
|
|
39
|
+
}>
|
|
40
|
+
|
|
41
|
+
export interface HolonCoordinatorLifecycleFact {
|
|
42
|
+
readonly holonRef: string
|
|
43
|
+
readonly status: HolonCoordinatorLifecycleStatus
|
|
44
|
+
readonly actorRef: string
|
|
45
|
+
readonly registrationReceipt: ActorRegistrationReceipt
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface HolonMemberRuntimeLifecycleFact {
|
|
49
|
+
readonly runtimeRef: string
|
|
50
|
+
readonly holonRef: string
|
|
51
|
+
readonly memberRef: string
|
|
52
|
+
readonly isolation: HolonMemberRuntimeIsolation
|
|
53
|
+
readonly status: HolonMemberRuntimeLifecycleStatus
|
|
54
|
+
readonly actorRef: string
|
|
55
|
+
readonly registrationReceipt: ActorRegistrationReceipt
|
|
56
|
+
readonly sessions: readonly HolonGenericSessionRef[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface HolonTaskSpaceSubscriptionFact {
|
|
60
|
+
readonly taskSpaceId: string
|
|
61
|
+
readonly holonRef: string
|
|
62
|
+
readonly cursor: string
|
|
63
|
+
readonly status: HolonTaskSpaceSubscriptionStatus
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface HolonDeploymentRuntimeSnapshot {
|
|
67
|
+
readonly schemaVersion: typeof HOLON_DEPLOYMENT_RUNTIME_SCHEMA_VERSION
|
|
68
|
+
readonly deploymentId: string
|
|
69
|
+
readonly definitionSemanticFingerprint: `sha256:${string}`
|
|
70
|
+
readonly revision: number
|
|
71
|
+
readonly coordinators: readonly HolonCoordinatorLifecycleFact[]
|
|
72
|
+
readonly members: readonly HolonMemberRuntimeLifecycleFact[]
|
|
73
|
+
readonly subscriptions: readonly HolonTaskSpaceSubscriptionFact[]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const compareUtf16 = (left: string, right: string): number =>
|
|
77
|
+
left < right ? -1 : left > right ? 1 : 0
|
|
78
|
+
|
|
79
|
+
const invalid = (location: string, message: string): never => {
|
|
80
|
+
throw new HolarchyEidolonAdapterError(
|
|
81
|
+
"EIDOLON_HOLON_DEPLOYMENT_RUNTIME_INVALID",
|
|
82
|
+
`${location}: ${message}`,
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function closedObject(value: unknown, location: string): Readonly<Record<string, unknown>> {
|
|
87
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
88
|
+
return invalid(location, "Expected a plain object")
|
|
89
|
+
}
|
|
90
|
+
const prototype = Object.getPrototypeOf(value)
|
|
91
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
92
|
+
return invalid(location, "Expected a plain object")
|
|
93
|
+
}
|
|
94
|
+
const descriptors = Object.getOwnPropertyDescriptors(value)
|
|
95
|
+
const keys = Reflect.ownKeys(descriptors)
|
|
96
|
+
if (keys.some((key) => typeof key !== "string")) {
|
|
97
|
+
return invalid(location, "Symbol properties are not allowed")
|
|
98
|
+
}
|
|
99
|
+
const result: Record<string, unknown> = Object.create(null)
|
|
100
|
+
for (const key of keys as string[]) {
|
|
101
|
+
const descriptor = descriptors[key]
|
|
102
|
+
if (!descriptor || !("value" in descriptor) || descriptor.enumerable !== true) {
|
|
103
|
+
return invalid(`${location}.${key}`, "Expected one enumerable own-data property")
|
|
104
|
+
}
|
|
105
|
+
result[key] = descriptor.value
|
|
106
|
+
}
|
|
107
|
+
return result
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function exactKeys(value: Readonly<Record<string, unknown>>, expected: readonly string[], location: string): void {
|
|
111
|
+
const allowed = new Set(expected)
|
|
112
|
+
const extra = Object.keys(value).find((key) => !allowed.has(key))
|
|
113
|
+
if (extra !== undefined) invalid(`${location}.${extra}`, "Unsupported field")
|
|
114
|
+
const missing = expected.find((key) => !Object.prototype.hasOwnProperty.call(value, key))
|
|
115
|
+
if (missing !== undefined) invalid(`${location}.${missing}`, "Required field is missing")
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function denseArray(value: unknown, location: string): readonly unknown[] {
|
|
119
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
|
|
120
|
+
return invalid(location, "Expected a plain dense array")
|
|
121
|
+
}
|
|
122
|
+
const descriptors = Object.getOwnPropertyDescriptors(value)
|
|
123
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
124
|
+
const descriptor = descriptors[String(index)]
|
|
125
|
+
if (!descriptor || !("value" in descriptor) || descriptor.enumerable !== true) {
|
|
126
|
+
return invalid(`${location}[${index}]`, "Expected a dense enumerable own-data entry")
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
130
|
+
if (key === "length") continue
|
|
131
|
+
if (typeof key !== "string" || !/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= value.length) {
|
|
132
|
+
return invalid(location, "Symbol and extra array properties are not allowed")
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return value
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function exactString(value: unknown, location: string): string {
|
|
139
|
+
if (typeof value !== "string" || value.length === 0 || value !== value.trim()
|
|
140
|
+
|| value !== value.normalize("NFC") || /[\u0000-\u001f\u007f]/.test(value)) {
|
|
141
|
+
return invalid(location, "Expected one exact canonical non-empty string")
|
|
142
|
+
}
|
|
143
|
+
return value
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function exactLiteral<T extends string>(value: unknown, expected: T, location: string): T {
|
|
147
|
+
if (value !== expected) invalid(location, `Expected '${expected}'`)
|
|
148
|
+
return expected
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function exactUnion<T extends string>(value: unknown, expected: readonly T[], location: string): T {
|
|
152
|
+
const exact = exactString(value, location)
|
|
153
|
+
if (!expected.includes(exact as T)) invalid(location, `Expected ${expected.join(" | ")}`)
|
|
154
|
+
return exact as T
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function digest(value: unknown, location: string): `sha256:${string}` {
|
|
158
|
+
if (typeof value !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value)) {
|
|
159
|
+
return invalid(location, "Expected one canonical sha256 digest")
|
|
160
|
+
}
|
|
161
|
+
return value as `sha256:${string}`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function receipt(value: unknown, location: string): ActorRegistrationReceipt {
|
|
165
|
+
try {
|
|
166
|
+
return parseActorRegistrationReceipt(value)
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return invalid(location, error instanceof Error ? error.message : "Invalid depa-actor registration receipt")
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isolation(value: unknown, location: string): HolonMemberRuntimeIsolation {
|
|
173
|
+
const record = closedObject(value, location)
|
|
174
|
+
const mode = exactString(record.mode, `${location}.mode`)
|
|
175
|
+
if (mode === "shared") {
|
|
176
|
+
exactKeys(record, ["mode"], location)
|
|
177
|
+
return Object.freeze({ mode })
|
|
178
|
+
}
|
|
179
|
+
if (mode === "isolated") {
|
|
180
|
+
exactKeys(record, ["mode", "scope", "isolationKey"], location)
|
|
181
|
+
return Object.freeze({
|
|
182
|
+
mode,
|
|
183
|
+
scope: exactUnion(record.scope, ["task-space", "workflow-run"] as const, `${location}.scope`),
|
|
184
|
+
isolationKey: exactString(record.isolationKey, `${location}.isolationKey`),
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
return invalid(`${location}.mode`, "Expected shared | isolated")
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function coordinator(value: unknown, index: number): HolonCoordinatorLifecycleFact {
|
|
191
|
+
const location = `$.coordinators[${index}]`
|
|
192
|
+
const record = closedObject(value, location)
|
|
193
|
+
exactKeys(record, ["holonRef", "status", "actorRef", "registrationReceipt"], location)
|
|
194
|
+
const registrationReceipt = receipt(record.registrationReceipt, `${location}.registrationReceipt`)
|
|
195
|
+
if (registrationReceipt.address.actorKind !== "coordinator") {
|
|
196
|
+
invalid(`${location}.registrationReceipt.address.actorKind`, "Expected coordinator")
|
|
197
|
+
}
|
|
198
|
+
return Object.freeze({
|
|
199
|
+
holonRef: exactString(record.holonRef, `${location}.holonRef`),
|
|
200
|
+
status: exactUnion(record.status, ["registered", "ready", "stopped"] as const, `${location}.status`),
|
|
201
|
+
actorRef: exactString(record.actorRef, `${location}.actorRef`),
|
|
202
|
+
registrationReceipt,
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function session(value: unknown, location: string): HolonGenericSessionRef {
|
|
207
|
+
const record = closedObject(value, location)
|
|
208
|
+
const mode = exactString(record.mode, `${location}.mode`)
|
|
209
|
+
if (mode === "task-attempt") {
|
|
210
|
+
const optional = ["workflowInstanceId", "runId"].filter((key) => (
|
|
211
|
+
Object.prototype.hasOwnProperty.call(record, key)
|
|
212
|
+
))
|
|
213
|
+
exactKeys(record, [
|
|
214
|
+
"mode", "taskSpaceId", "taskId", "claimId", "attempt", "sessionRef", ...optional,
|
|
215
|
+
], location)
|
|
216
|
+
if (!Number.isSafeInteger(record.attempt) || (record.attempt as number) <= 0) {
|
|
217
|
+
invalid(`${location}.attempt`, "Expected one positive safe integer")
|
|
218
|
+
}
|
|
219
|
+
return Object.freeze({
|
|
220
|
+
mode,
|
|
221
|
+
taskSpaceId: exactString(record.taskSpaceId, `${location}.taskSpaceId`),
|
|
222
|
+
taskId: exactString(record.taskId, `${location}.taskId`),
|
|
223
|
+
claimId: exactString(record.claimId, `${location}.claimId`),
|
|
224
|
+
attempt: record.attempt as number,
|
|
225
|
+
...(record.workflowInstanceId === undefined
|
|
226
|
+
? {}
|
|
227
|
+
: { workflowInstanceId: exactString(record.workflowInstanceId, `${location}.workflowInstanceId`) }),
|
|
228
|
+
...(record.runId === undefined ? {} : { runId: exactString(record.runId, `${location}.runId`) }),
|
|
229
|
+
sessionRef: exactString(record.sessionRef, `${location}.sessionRef`),
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
if (mode === "targeted-agent-instance") {
|
|
233
|
+
exactKeys(record, ["mode", "selector", "sessionRef"], location)
|
|
234
|
+
const selectorRecord = closedObject(record.selector, `${location}.selector`)
|
|
235
|
+
const hasId = Object.prototype.hasOwnProperty.call(selectorRecord, "byId")
|
|
236
|
+
const hasName = Object.prototype.hasOwnProperty.call(selectorRecord, "byName")
|
|
237
|
+
if (hasId === hasName) invalid(`${location}.selector`, "Expected exactly one targeted Agent selector")
|
|
238
|
+
exactKeys(selectorRecord, [hasId ? "byId" : "byName"], `${location}.selector`)
|
|
239
|
+
const selector = (hasId
|
|
240
|
+
? Object.freeze({ byId: exactString(selectorRecord.byId, `${location}.selector.byId`) })
|
|
241
|
+
: Object.freeze({ byName: exactString(selectorRecord.byName, `${location}.selector.byName`) })) as unknown as AIAgentSelector
|
|
242
|
+
return Object.freeze({
|
|
243
|
+
mode,
|
|
244
|
+
selector,
|
|
245
|
+
sessionRef: exactString(record.sessionRef, `${location}.sessionRef`),
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
return invalid(`${location}.mode`, "Expected task-attempt | targeted-agent-instance")
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function sessionIdentity(value: HolonGenericSessionRef): string {
|
|
252
|
+
return value.mode === "task-attempt"
|
|
253
|
+
? JSON.stringify([value.mode, value.taskSpaceId, value.taskId, value.claimId, value.attempt])
|
|
254
|
+
: JSON.stringify([value.mode, value.selector])
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function member(value: unknown, index: number): HolonMemberRuntimeLifecycleFact {
|
|
258
|
+
const location = `$.members[${index}]`
|
|
259
|
+
const record = closedObject(value, location)
|
|
260
|
+
exactKeys(record, [
|
|
261
|
+
"runtimeRef",
|
|
262
|
+
"holonRef",
|
|
263
|
+
"memberRef",
|
|
264
|
+
"isolation",
|
|
265
|
+
"status",
|
|
266
|
+
"actorRef",
|
|
267
|
+
"registrationReceipt",
|
|
268
|
+
"sessions",
|
|
269
|
+
], location)
|
|
270
|
+
const registrationReceipt = receipt(record.registrationReceipt, `${location}.registrationReceipt`)
|
|
271
|
+
if (registrationReceipt.address.actorKind !== "member") {
|
|
272
|
+
invalid(`${location}.registrationReceipt.address.actorKind`, "Expected member")
|
|
273
|
+
}
|
|
274
|
+
const sessions = denseArray(record.sessions, `${location}.sessions`)
|
|
275
|
+
.map((entry, sessionIndex) => session(entry, `${location}.sessions[${sessionIndex}]`))
|
|
276
|
+
.sort((left, right) => compareUtf16(sessionIdentity(left), sessionIdentity(right)))
|
|
277
|
+
if (new Set(sessions.map(sessionIdentity)).size !== sessions.length) {
|
|
278
|
+
invalid(`${location}.sessions`, "Session scope identities must be unique")
|
|
279
|
+
}
|
|
280
|
+
return Object.freeze({
|
|
281
|
+
runtimeRef: exactString(record.runtimeRef, `${location}.runtimeRef`),
|
|
282
|
+
holonRef: exactString(record.holonRef, `${location}.holonRef`),
|
|
283
|
+
memberRef: exactString(record.memberRef, `${location}.memberRef`),
|
|
284
|
+
isolation: isolation(record.isolation, `${location}.isolation`),
|
|
285
|
+
status: exactUnion(record.status, ["registered", "ready", "stopped"] as const, `${location}.status`),
|
|
286
|
+
actorRef: exactString(record.actorRef, `${location}.actorRef`),
|
|
287
|
+
registrationReceipt,
|
|
288
|
+
sessions: Object.freeze(sessions),
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function subscription(value: unknown, index: number): HolonTaskSpaceSubscriptionFact {
|
|
293
|
+
const location = `$.subscriptions[${index}]`
|
|
294
|
+
const record = closedObject(value, location)
|
|
295
|
+
exactKeys(record, ["taskSpaceId", "holonRef", "cursor", "status"], location)
|
|
296
|
+
return Object.freeze({
|
|
297
|
+
taskSpaceId: exactString(record.taskSpaceId, `${location}.taskSpaceId`),
|
|
298
|
+
holonRef: exactString(record.holonRef, `${location}.holonRef`),
|
|
299
|
+
cursor: exactString(record.cursor, `${location}.cursor`),
|
|
300
|
+
status: exactUnion(record.status, ["observing", "paused", "closed"] as const, `${location}.status`),
|
|
301
|
+
})
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function unique<T>(values: readonly T[], identity: (value: T) => string, location: string): void {
|
|
305
|
+
if (new Set(values.map(identity)).size !== values.length) invalid(location, "Contains a duplicate identity")
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function normalizeHolonDeploymentRuntimeSnapshot(value: unknown): HolonDeploymentRuntimeSnapshot {
|
|
309
|
+
const record = closedObject(value, "$")
|
|
310
|
+
exactKeys(record, [
|
|
311
|
+
"schemaVersion",
|
|
312
|
+
"deploymentId",
|
|
313
|
+
"definitionSemanticFingerprint",
|
|
314
|
+
"revision",
|
|
315
|
+
"coordinators",
|
|
316
|
+
"members",
|
|
317
|
+
"subscriptions",
|
|
318
|
+
], "$")
|
|
319
|
+
if (!Number.isSafeInteger(record.revision) || (record.revision as number) < 0) {
|
|
320
|
+
invalid("$.revision", "Expected one non-negative safe integer")
|
|
321
|
+
}
|
|
322
|
+
const coordinators = denseArray(record.coordinators, "$.coordinators")
|
|
323
|
+
.map(coordinator)
|
|
324
|
+
.sort((left, right) => compareUtf16(left.holonRef, right.holonRef))
|
|
325
|
+
const members = denseArray(record.members, "$.members")
|
|
326
|
+
.map(member)
|
|
327
|
+
.sort((left, right) => compareUtf16(left.runtimeRef, right.runtimeRef))
|
|
328
|
+
const subscriptions = denseArray(record.subscriptions, "$.subscriptions")
|
|
329
|
+
.map(subscription)
|
|
330
|
+
.sort((left, right) => compareUtf16(left.taskSpaceId, right.taskSpaceId))
|
|
331
|
+
unique(coordinators, (entry) => entry.holonRef, "$.coordinators")
|
|
332
|
+
unique(members, (entry) => entry.runtimeRef, "$.members")
|
|
333
|
+
unique(subscriptions, (entry) => entry.taskSpaceId, "$.subscriptions")
|
|
334
|
+
const deploymentId = exactString(record.deploymentId, "$.deploymentId")
|
|
335
|
+
for (const entry of [...coordinators, ...members]) {
|
|
336
|
+
if (entry.registrationReceipt.address.deploymentId !== deploymentId) {
|
|
337
|
+
invalid("$.deploymentId", "All actor registration receipts must belong to this deployment")
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return Object.freeze({
|
|
341
|
+
schemaVersion: exactLiteral(
|
|
342
|
+
record.schemaVersion,
|
|
343
|
+
HOLON_DEPLOYMENT_RUNTIME_SCHEMA_VERSION,
|
|
344
|
+
"$.schemaVersion",
|
|
345
|
+
),
|
|
346
|
+
deploymentId,
|
|
347
|
+
definitionSemanticFingerprint: digest(
|
|
348
|
+
record.definitionSemanticFingerprint,
|
|
349
|
+
"$.definitionSemanticFingerprint",
|
|
350
|
+
),
|
|
351
|
+
revision: record.revision as number,
|
|
352
|
+
coordinators: Object.freeze(coordinators),
|
|
353
|
+
members: Object.freeze(members),
|
|
354
|
+
subscriptions: Object.freeze(subscriptions),
|
|
355
|
+
})
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function sameBytes(left: Uint8Array, right: Uint8Array): boolean {
|
|
359
|
+
if (left.byteLength !== right.byteLength) return false
|
|
360
|
+
for (let index = 0; index < left.byteLength; index += 1) {
|
|
361
|
+
if (left[index] !== right[index]) return false
|
|
362
|
+
}
|
|
363
|
+
return true
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function canonicalHolonDeploymentRuntimeSnapshotBytes(value: unknown): Uint8Array {
|
|
367
|
+
return new TextEncoder().encode(JSON.stringify(normalizeHolonDeploymentRuntimeSnapshot(value)))
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function parseHolonDeploymentRuntimeSnapshotBytes(bytes: Uint8Array): HolonDeploymentRuntimeSnapshot {
|
|
371
|
+
let decoded: string
|
|
372
|
+
try {
|
|
373
|
+
decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes)
|
|
374
|
+
} catch {
|
|
375
|
+
return invalid("$bytes", "Runtime bytes are not valid UTF-8")
|
|
376
|
+
}
|
|
377
|
+
let value: unknown
|
|
378
|
+
try {
|
|
379
|
+
value = JSON.parse(decoded)
|
|
380
|
+
} catch {
|
|
381
|
+
return invalid("$bytes", "Runtime bytes are not valid JSON")
|
|
382
|
+
}
|
|
383
|
+
const snapshot = normalizeHolonDeploymentRuntimeSnapshot(value)
|
|
384
|
+
if (!sameBytes(bytes, canonicalHolonDeploymentRuntimeSnapshotBytes(snapshot))) {
|
|
385
|
+
return invalid("$bytes", "Runtime bytes are not canonical")
|
|
386
|
+
}
|
|
387
|
+
return snapshot
|
|
388
|
+
}
|
|
@@ -1,19 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
digestCanonical,
|
|
3
|
+
type JsonSchema,
|
|
4
|
+
type KindReaderRegistration,
|
|
5
|
+
type KindSemanticContract,
|
|
6
|
+
type PortableSpec,
|
|
7
|
+
} from "halfcode-compiler.xnl/resource-core"
|
|
8
|
+
import {
|
|
9
|
+
createKindSpecRevision,
|
|
10
|
+
createKindSubjectOwner,
|
|
11
|
+
} from "halfcode-compiler.xnl/kind-definition"
|
|
12
|
+
|
|
1
13
|
export const HOLON_EXECUTION_BINDING_KIND = "HolonExecutionBinding" as const
|
|
2
14
|
export const HOLON_EXECUTION_BINDING_KIND_DEFINITION_FQN =
|
|
3
15
|
"Eidolon.AI.KindDefinition.HolonExecutionBinding" as const
|
|
4
16
|
export const HOLON_EXECUTION_BINDING_API_VERSION = "eidolon.ai/v1" as const
|
|
5
|
-
export const
|
|
17
|
+
export const HOLON_EXECUTION_BINDING_KIND_DEFINITION_SPEC_VERSION = 1 as const
|
|
18
|
+
export const HOLON_EXECUTION_BINDING_KIND_OWNER_PACKAGE_ID =
|
|
19
|
+
"holarchy-eidolon-adapter" as const
|
|
20
|
+
|
|
21
|
+
export const HOLON_EXECUTION_BINDING_KIND_OWNER_PACKAGE_FINGERPRINT = digestCanonical(Object.freeze({
|
|
22
|
+
authority: "holarchy-eidolon-adapter/holon-execution-binding-kind/v1",
|
|
23
|
+
ownerPackageId: HOLON_EXECUTION_BINDING_KIND_OWNER_PACKAGE_ID,
|
|
24
|
+
subjects: Object.freeze([HOLON_EXECUTION_BINDING_KIND_DEFINITION_FQN]),
|
|
25
|
+
semanticContractVersion: HOLON_EXECUTION_BINDING_API_VERSION,
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
export const HOLON_EXECUTION_BINDING_KIND_OWNER = createKindSubjectOwner({
|
|
29
|
+
kind: HOLON_EXECUTION_BINDING_KIND,
|
|
30
|
+
subjectFqn: HOLON_EXECUTION_BINDING_KIND_DEFINITION_FQN,
|
|
31
|
+
ownerPackageId: HOLON_EXECUTION_BINDING_KIND_OWNER_PACKAGE_ID,
|
|
32
|
+
ownerPackageFingerprint: HOLON_EXECUTION_BINDING_KIND_OWNER_PACKAGE_FINGERPRINT,
|
|
33
|
+
sourceShapes: Object.freeze(["single-file"]),
|
|
34
|
+
documentCardinality: "one",
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const HOLON_EXECUTION_BINDING_SPEC_SCHEMA_V1: JsonSchema = Object.freeze({
|
|
38
|
+
type: "object",
|
|
39
|
+
required: Object.freeze(["properties", "body", "subdomains"]),
|
|
40
|
+
properties: Object.freeze({
|
|
41
|
+
properties: Object.freeze({
|
|
42
|
+
type: "object",
|
|
43
|
+
required: Object.freeze(["bindingBytesBase64"]),
|
|
44
|
+
properties: Object.freeze({
|
|
45
|
+
bindingBytesBase64: Object.freeze({ type: "string", minLength: 1 }),
|
|
46
|
+
}),
|
|
47
|
+
additionalProperties: false,
|
|
48
|
+
}),
|
|
49
|
+
body: Object.freeze({ type: "array", maxItems: 0 }),
|
|
50
|
+
subdomains: Object.freeze({ type: "object", maxProperties: 0 }),
|
|
51
|
+
}),
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
export const HOLON_EXECUTION_BINDING_SEMANTIC_CONTRACT_V1: KindSemanticContract = Object.freeze({
|
|
56
|
+
semanticValidatorFingerprint: digestCanonical(Object.freeze({
|
|
57
|
+
authority: "holarchy-eidolon-adapter/holon-execution-binding-kind/v1",
|
|
58
|
+
role: "canonical-binding-and-graph-validation",
|
|
59
|
+
apiVersion: HOLON_EXECUTION_BINDING_API_VERSION,
|
|
60
|
+
validators: Object.freeze([
|
|
61
|
+
"parseHolonExecutionBindingBytes",
|
|
62
|
+
"validateHolonExecutionBindingGraph",
|
|
63
|
+
]),
|
|
64
|
+
})),
|
|
65
|
+
referenceProjectionFingerprint: digestCanonical(Object.freeze({
|
|
66
|
+
authority: "holarchy-eidolon-adapter/holon-execution-binding-kind/v1",
|
|
67
|
+
role: "closed-execution-binding-reference-projection",
|
|
68
|
+
fields: Object.freeze([
|
|
69
|
+
"snapshotRef",
|
|
70
|
+
"adapter",
|
|
71
|
+
"policy.taskProfileRef",
|
|
72
|
+
"policy.capabilityRefs",
|
|
73
|
+
"policy.toolRefs",
|
|
74
|
+
"policy.materialRefs",
|
|
75
|
+
]),
|
|
76
|
+
})),
|
|
77
|
+
compilerInputFingerprint: digestCanonical(Object.freeze({
|
|
78
|
+
authority: "holarchy-eidolon-adapter/holon-execution-binding-kind/v1",
|
|
79
|
+
role: "authored-resource-to-canonical-binding-input",
|
|
80
|
+
fields: Object.freeze(["properties.bindingBytesBase64"]),
|
|
81
|
+
encoding: "canonical-base64",
|
|
82
|
+
})),
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
export const HOLON_EXECUTION_BINDING_KIND_SPEC_REVISION_V1 = createKindSpecRevision({
|
|
86
|
+
kind: HOLON_EXECUTION_BINDING_KIND_OWNER.kind,
|
|
87
|
+
subjectFqn: HOLON_EXECUTION_BINDING_KIND_OWNER.subjectFqn,
|
|
88
|
+
specVersion: HOLON_EXECUTION_BINDING_KIND_DEFINITION_SPEC_VERSION,
|
|
89
|
+
specSchema: HOLON_EXECUTION_BINDING_SPEC_SCHEMA_V1,
|
|
90
|
+
semanticContract: HOLON_EXECUTION_BINDING_SEMANTIC_CONTRACT_V1,
|
|
91
|
+
sourceContractFingerprint: HOLON_EXECUTION_BINDING_KIND_OWNER.sourceContract.sourceContractFingerprint,
|
|
92
|
+
stability: "stable",
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
function renderHolonExecutionBindingKindDefinition(): string {
|
|
96
|
+
const owner = HOLON_EXECUTION_BINDING_KIND_OWNER
|
|
97
|
+
const revision = HOLON_EXECUTION_BINDING_KIND_SPEC_REVISION_V1
|
|
98
|
+
return [
|
|
99
|
+
`<KindDefinition #${HOLON_EXECUTION_BINDING_KIND_DEFINITION_FQN} envelopeVersion="halfcode.resource-envelope/v1" specVersion=1 {`,
|
|
100
|
+
' lifecycle = "Stable"',
|
|
101
|
+
` resourceKind = "${owner.kind}"`,
|
|
102
|
+
` subjectFqn = "${owner.subjectFqn}"`,
|
|
103
|
+
' sourceShapes = ["single-file"]',
|
|
104
|
+
` documentCardinality = "${owner.sourceContract.documentCardinality}"`,
|
|
105
|
+
' description = "Maps one frozen Holon Member or Role identity to an exact Eidolon execution adapter closure."',
|
|
106
|
+
'} (',
|
|
107
|
+
' <SpecRevisions [',
|
|
108
|
+
` <SpecRevision #v${revision.specVersion} {`,
|
|
109
|
+
` specVersion = ${revision.specVersion}`,
|
|
110
|
+
' schemaRef = "vfs://./spec-v1.schema.json"',
|
|
111
|
+
` schemaFingerprint = "${revision.schemaFingerprint}"`,
|
|
112
|
+
` contractFingerprint = "${revision.contractFingerprint}"`,
|
|
113
|
+
` semanticValidatorFingerprint = "${revision.semanticContract.semanticValidatorFingerprint}"`,
|
|
114
|
+
` referenceProjectionFingerprint = "${revision.semanticContract.referenceProjectionFingerprint}"`,
|
|
115
|
+
` compilerInputFingerprint = "${revision.semanticContract.compilerInputFingerprint}"`,
|
|
116
|
+
` stability = "${revision.stability}"`,
|
|
117
|
+
' }>',
|
|
118
|
+
' ]>',
|
|
119
|
+
')>',
|
|
120
|
+
'',
|
|
121
|
+
].join("\n")
|
|
122
|
+
}
|
|
6
123
|
|
|
7
124
|
export const HOLON_EXECUTION_BINDING_KIND_DEFINITION_SOURCE =
|
|
8
|
-
|
|
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
|
|
125
|
+
renderHolonExecutionBindingKindDefinition()
|
|
17
126
|
|
|
18
127
|
export const HOLON_EXECUTION_BINDING_KIND_DEFINITION_BYTES: Readonly<Uint8Array> =
|
|
19
128
|
new TextEncoder().encode(HOLON_EXECUTION_BINDING_KIND_DEFINITION_SOURCE)
|
|
@@ -331,6 +440,41 @@ export function parseHolonExecutionBindingBytes(bytes: Uint8Array): HolonExecuti
|
|
|
331
440
|
return binding
|
|
332
441
|
}
|
|
333
442
|
|
|
443
|
+
function canonicalBase64SpecBytes(spec: PortableSpec, property: string): Uint8Array {
|
|
444
|
+
const properties = closedObject(spec.properties, "$.properties")
|
|
445
|
+
exactKeys(properties, [property], "$.properties")
|
|
446
|
+
const value = exactString(properties[property], `$.properties.${property}`)
|
|
447
|
+
let bytes: Uint8Array
|
|
448
|
+
try {
|
|
449
|
+
bytes = Uint8Array.from(Buffer.from(value, "base64"))
|
|
450
|
+
} catch {
|
|
451
|
+
return invalid(`$.properties.${property}`, "Expected canonical base64")
|
|
452
|
+
}
|
|
453
|
+
if (bytes.byteLength === 0 || Buffer.from(bytes).toString("base64") !== value) {
|
|
454
|
+
return invalid(`$.properties.${property}`, "Expected canonical non-empty base64")
|
|
455
|
+
}
|
|
456
|
+
return bytes
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export const HOLON_EXECUTION_BINDING_KIND_READER_REGISTRATION_V1:
|
|
460
|
+
KindReaderRegistration<HolonExecutionBinding> = Object.freeze({
|
|
461
|
+
readerId: "holarchy-eidolon-adapter.holon-execution-binding.v1",
|
|
462
|
+
subjectFqn: HOLON_EXECUTION_BINDING_KIND_DEFINITION_FQN,
|
|
463
|
+
readerSpecVersion: HOLON_EXECUTION_BINDING_KIND_DEFINITION_SPEC_VERSION,
|
|
464
|
+
contractFingerprint: HOLON_EXECUTION_BINDING_KIND_SPEC_REVISION_V1.contractFingerprint,
|
|
465
|
+
readerImplementationFingerprint: digestCanonical(Object.freeze({
|
|
466
|
+
authority: "holarchy-eidolon-adapter/holon-execution-binding-reader/v1",
|
|
467
|
+
implementation: Object.freeze([
|
|
468
|
+
"canonicalBase64SpecBytes",
|
|
469
|
+
"parseHolonExecutionBindingBytes",
|
|
470
|
+
]),
|
|
471
|
+
})),
|
|
472
|
+
compatibilityPolicy: "exact",
|
|
473
|
+
read: (spec: PortableSpec) => parseHolonExecutionBindingBytes(
|
|
474
|
+
canonicalBase64SpecBytes(spec, "bindingBytesBase64"),
|
|
475
|
+
),
|
|
476
|
+
})
|
|
477
|
+
|
|
334
478
|
export function validateHolonExecutionBindingGraph(
|
|
335
479
|
values: readonly HolonExecutionBinding[],
|
|
336
480
|
): readonly HolonExecutionBinding[] {
|