holarchy-eidolon-adapter 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.
@@ -0,0 +1,211 @@
1
+ import { canonicalOwnDataClone } from 'task-manager-contract';
2
+ export const HOLON_TASK_TARGET_SCHEMA_VERSION = 'ai-workflow.holon-task-target/v1';
3
+ export const HOLON_TASK_SNAPSHOT_RECEIPT_SCHEMA_VERSION = 'eidolon.holon-task-snapshot-receipt/v2';
4
+ export const AI_ORGANIZATION_TASK_PROFILE_KIND = 'depa.ai.organization-task';
5
+ export function holonTaskTargetFromNodeConfig(configInput, expected) {
6
+ const config = record(canonicalOwnDataClone(configInput), '$config');
7
+ if (!Object.prototype.hasOwnProperty.call(config, 'holonTaskTarget'))
8
+ return undefined;
9
+ const target = normalizeHolonTaskTarget(config.holonTaskTarget);
10
+ if (target.invocation.workflowKind !== expected.workflowKind
11
+ || target.invocation.workflowRef !== expected.workflowRef
12
+ || target.invocation.nodeId !== expected.nodeId) {
13
+ invalid('$config.holonTaskTarget.invocation', 'Target invocation identity does not match the authored node');
14
+ }
15
+ return target;
16
+ }
17
+ export class HolonTaskContractError extends Error {
18
+ path;
19
+ constructor(path, message) {
20
+ super(`${path}: ${message}`);
21
+ this.path = path;
22
+ this.name = 'HolonTaskContractError';
23
+ }
24
+ }
25
+ export function normalizeHolonTaskTarget(value) {
26
+ const root = record(canonicalOwnDataClone(value), '$target');
27
+ exactKeys(root, ['kind', 'schemaVersion', 'invocation', 'holon', 'executionBinding', 'taskSpace', 'output'], '$target');
28
+ exactLiteral(root.kind, 'holon-task-target', '$target.kind');
29
+ exactLiteral(root.schemaVersion, HOLON_TASK_TARGET_SCHEMA_VERSION, '$target.schemaVersion');
30
+ const invocation = record(root.invocation, '$target.invocation');
31
+ exactKeys(invocation, ['workflowKind', 'workflowRef', 'nodeId', 'invocationId'], '$target.invocation');
32
+ const workflowKind = oneOf(invocation.workflowKind, ['AICtrlWorkflow', 'AIDataWorkflow'], '$target.invocation.workflowKind');
33
+ const holon = record(root.holon, '$target.holon');
34
+ exactKeys(holon, ['rootHolonRef', 'effectiveAt'], '$target.holon');
35
+ const executionBinding = record(root.executionBinding, '$target.executionBinding');
36
+ exactKeys(executionBinding, ['ref', 'digest'], '$target.executionBinding');
37
+ const taskSpace = record(root.taskSpace, '$target.taskSpace');
38
+ exactKeys(taskSpace, ['profileRef', 'policyRef', 'requiredRoleRefs', 'requiredCapabilityRefs'], '$target.taskSpace');
39
+ const output = record(root.output, '$target.output');
40
+ exactKeys(output, ['schemaRef', 'materialPortRefs'], '$target.output');
41
+ return Object.freeze({
42
+ kind: 'holon-task-target',
43
+ schemaVersion: HOLON_TASK_TARGET_SCHEMA_VERSION,
44
+ invocation: Object.freeze({
45
+ workflowKind,
46
+ workflowRef: resourceRef(invocation.workflowRef, '$target.invocation.workflowRef'),
47
+ nodeId: exactString(invocation.nodeId, '$target.invocation.nodeId'),
48
+ invocationId: exactString(invocation.invocationId, '$target.invocation.invocationId'),
49
+ }),
50
+ holon: Object.freeze({
51
+ rootHolonRef: exactString(holon.rootHolonRef, '$target.holon.rootHolonRef'),
52
+ effectiveAt: utcInstant(holon.effectiveAt, '$target.holon.effectiveAt'),
53
+ }),
54
+ executionBinding: Object.freeze({
55
+ ref: resourceRef(executionBinding.ref, '$target.executionBinding.ref'),
56
+ digest: digest(executionBinding.digest, '$target.executionBinding.digest'),
57
+ }),
58
+ taskSpace: Object.freeze({
59
+ profileRef: resourceRef(taskSpace.profileRef, '$target.taskSpace.profileRef'),
60
+ policyRef: resourceRef(taskSpace.policyRef, '$target.taskSpace.policyRef'),
61
+ requiredRoleRefs: stringArray(taskSpace.requiredRoleRefs, '$target.taskSpace.requiredRoleRefs'),
62
+ requiredCapabilityRefs: resourceRefArray(taskSpace.requiredCapabilityRefs, '$target.taskSpace.requiredCapabilityRefs'),
63
+ }),
64
+ output: Object.freeze({
65
+ schemaRef: resourceRef(output.schemaRef, '$target.output.schemaRef'),
66
+ materialPortRefs: resourceRefArray(output.materialPortRefs, '$target.output.materialPortRefs'),
67
+ }),
68
+ });
69
+ }
70
+ export function normalizeHolonTaskSnapshotReceipt(value) {
71
+ const root = record(canonicalOwnDataClone(value), '$snapshotReceipt');
72
+ const legacy = root.schemaVersion === 'ai-workflow.holon-task-snapshot-receipt/v1' || root.schemaVersion === 'eidolon.holon-task-snapshot-receipt/v1';
73
+ exactKeys(root, [
74
+ 'kind', 'schemaVersion', 'taskSpaceId', 'holonRef', 'effectiveAt', 'holonSnapshotRef',
75
+ 'holonSnapshotDigest', 'snapshotArtifactDigest', 'issuerReceiptId',
76
+ ...(legacy ? ['issuerReceiptArtifactDigest'] : []), 'executionBindingRef', 'executionBindingDigest',
77
+ 'eligibleMemberRefs', 'eligibleRoleRefs',
78
+ ], '$snapshotReceipt');
79
+ exactLiteral(root.kind, 'holon-task-snapshot-receipt', '$snapshotReceipt.kind');
80
+ if (!legacy)
81
+ exactLiteral(root.schemaVersion, HOLON_TASK_SNAPSHOT_RECEIPT_SCHEMA_VERSION, '$snapshotReceipt.schemaVersion');
82
+ if (legacy && root.issuerReceiptArtifactDigest !== root.issuerReceiptId)
83
+ invalid('$snapshotReceipt', 'Conflicting legacy receipt digests');
84
+ return Object.freeze({
85
+ kind: 'holon-task-snapshot-receipt',
86
+ schemaVersion: HOLON_TASK_SNAPSHOT_RECEIPT_SCHEMA_VERSION,
87
+ taskSpaceId: exactString(root.taskSpaceId, '$snapshotReceipt.taskSpaceId'),
88
+ holonRef: exactString(root.holonRef, '$snapshotReceipt.holonRef'),
89
+ effectiveAt: utcInstant(root.effectiveAt, '$snapshotReceipt.effectiveAt'),
90
+ holonSnapshotRef: exactString(root.holonSnapshotRef, '$snapshotReceipt.holonSnapshotRef'),
91
+ holonSnapshotDigest: digest(root.holonSnapshotDigest, '$snapshotReceipt.holonSnapshotDigest'),
92
+ snapshotArtifactDigest: digest(root.snapshotArtifactDigest, '$snapshotReceipt.snapshotArtifactDigest'),
93
+ issuerReceiptId: digest(root.issuerReceiptId, '$snapshotReceipt.issuerReceiptId'),
94
+ executionBindingRef: resourceRef(root.executionBindingRef, '$snapshotReceipt.executionBindingRef'),
95
+ executionBindingDigest: digest(root.executionBindingDigest, '$snapshotReceipt.executionBindingDigest'),
96
+ eligibleMemberRefs: stringArray(root.eligibleMemberRefs, '$snapshotReceipt.eligibleMemberRefs'),
97
+ eligibleRoleRefs: stringArray(root.eligibleRoleRefs, '$snapshotReceipt.eligibleRoleRefs'),
98
+ });
99
+ }
100
+ export function createAIOrganizationTaskProfile(targetInput, receiptInput, adoptionInputs = []) {
101
+ const target = normalizeHolonTaskTarget(targetInput);
102
+ const snapshotReceipt = normalizeHolonTaskSnapshotReceipt(receiptInput);
103
+ if (snapshotReceipt.taskSpaceId.length === 0
104
+ || snapshotReceipt.holonRef !== target.holon.rootHolonRef
105
+ || snapshotReceipt.effectiveAt !== target.holon.effectiveAt
106
+ || snapshotReceipt.executionBindingRef !== target.executionBinding.ref
107
+ || snapshotReceipt.executionBindingDigest !== target.executionBinding.digest) {
108
+ invalid('$profile', 'Snapshot receipt does not bind the exact target facts');
109
+ }
110
+ return Object.freeze({
111
+ kind: 'task-profile',
112
+ profileKind: AI_ORGANIZATION_TASK_PROFILE_KIND,
113
+ schemaVersion: 1,
114
+ facts: Object.freeze({
115
+ target,
116
+ snapshotReceipt,
117
+ assignmentReceipt: null,
118
+ memberRuntimeRef: null,
119
+ settlementReceipt: null,
120
+ snapshotAdoptions: Object.freeze(adoptionInputs.map(normalizeHolonTaskSnapshotAdoptionReceipt)),
121
+ }),
122
+ });
123
+ }
124
+ export function normalizeHolonTaskSnapshotAdoptionReceipt(value) {
125
+ const root = record(canonicalOwnDataClone(value), '$snapshotAdoptionReceipt');
126
+ exactKeys(root, [
127
+ 'kind', 'schemaVersion', 'taskSpaceId', 'previousTaskId', 'successorTaskId',
128
+ 'previousIssuerReceiptId', 'successorIssuerReceiptId', 'executionBindingDigest', 'adoptedAt',
129
+ ], '$snapshotAdoptionReceipt');
130
+ exactLiteral(root.kind, 'holon-task-snapshot-adoption-receipt', '$snapshotAdoptionReceipt.kind');
131
+ exactLiteral(root.schemaVersion, 'ai-workflow.holon-task-snapshot-adoption-receipt/v1', '$snapshotAdoptionReceipt.schemaVersion');
132
+ return Object.freeze({
133
+ kind: 'holon-task-snapshot-adoption-receipt',
134
+ schemaVersion: 'ai-workflow.holon-task-snapshot-adoption-receipt/v1',
135
+ taskSpaceId: exactString(root.taskSpaceId, '$snapshotAdoptionReceipt.taskSpaceId'),
136
+ previousTaskId: exactString(root.previousTaskId, '$snapshotAdoptionReceipt.previousTaskId'),
137
+ successorTaskId: exactString(root.successorTaskId, '$snapshotAdoptionReceipt.successorTaskId'),
138
+ previousIssuerReceiptId: digest(root.previousIssuerReceiptId, '$snapshotAdoptionReceipt.previousIssuerReceiptId'),
139
+ successorIssuerReceiptId: digest(root.successorIssuerReceiptId, '$snapshotAdoptionReceipt.successorIssuerReceiptId'),
140
+ executionBindingDigest: digest(root.executionBindingDigest, '$snapshotAdoptionReceipt.executionBindingDigest'),
141
+ adoptedAt: utcInstant(root.adoptedAt, '$snapshotAdoptionReceipt.adoptedAt'),
142
+ });
143
+ }
144
+ function record(value, path) {
145
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
146
+ invalid(path, 'Expected an object');
147
+ return value;
148
+ }
149
+ function exactKeys(value, expected, path) {
150
+ const actual = Object.keys(value);
151
+ const expectedSet = new Set(expected);
152
+ const extra = actual.find((key) => !expectedSet.has(key));
153
+ if (extra !== undefined)
154
+ invalid(`${path}.${extra}`, 'Unsupported field');
155
+ const missing = expected.find((key) => !Object.prototype.hasOwnProperty.call(value, key));
156
+ if (missing !== undefined)
157
+ invalid(`${path}.${missing}`, 'Required field is missing');
158
+ }
159
+ function exactString(value, path) {
160
+ if (typeof value !== 'string' || value.length === 0 || value !== value.trim())
161
+ invalid(path, 'Expected an exact non-empty string');
162
+ return value;
163
+ }
164
+ function exactLiteral(value, expected, path) {
165
+ if (value !== expected)
166
+ invalid(path, `Expected '${expected}'`);
167
+ return expected;
168
+ }
169
+ function oneOf(value, expected, path) {
170
+ if (typeof value !== 'string' || !expected.includes(value))
171
+ invalid(path, `Expected one of ${expected.join('|')}`);
172
+ return value;
173
+ }
174
+ function resourceRef(value, path) {
175
+ const ref = exactString(value, path);
176
+ if (!/^resource:\/\/[^/\s][^\s]*$/.test(ref))
177
+ invalid(path, 'Expected a canonical resource:// reference');
178
+ return ref;
179
+ }
180
+ function digest(value, path) {
181
+ if (typeof value !== 'string' || !/^sha256:[0-9a-f]{64}$/.test(value))
182
+ invalid(path, 'Expected a canonical SHA-256 digest');
183
+ return value;
184
+ }
185
+ function utcInstant(value, path) {
186
+ const instant = exactString(value, path);
187
+ const parsed = Date.parse(instant);
188
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(instant)
189
+ || Number.isNaN(parsed) || new Date(parsed).toISOString() !== instant)
190
+ invalid(path, 'Expected a canonical UTC instant');
191
+ return instant;
192
+ }
193
+ function stringArray(value, path) {
194
+ if (!Array.isArray(value))
195
+ invalid(path, 'Expected an array');
196
+ const result = Object.freeze(value.map((item, index) => exactString(item, `${path}[${index}]`)));
197
+ if (new Set(result).size !== result.length)
198
+ invalid(path, 'Duplicate values are not allowed');
199
+ return result;
200
+ }
201
+ function resourceRefArray(value, path) {
202
+ if (!Array.isArray(value))
203
+ invalid(path, 'Expected an array');
204
+ const result = Object.freeze(value.map((item, index) => resourceRef(item, `${path}[${index}]`)));
205
+ if (new Set(result).size !== result.length)
206
+ invalid(path, 'Duplicate values are not allowed');
207
+ return result;
208
+ }
209
+ function invalid(path, message) {
210
+ throw new HolonTaskContractError(path, message);
211
+ }
@@ -0,0 +1,2 @@
1
+ /** 纯平台标准转换;公开适配包不依赖主机Buffer或Node类型。 */
2
+ export declare function decodeCanonicalBase64(value: string): Uint8Array;
@@ -0,0 +1,7 @@
1
+ /** 纯平台标准转换;公开适配包不依赖主机Buffer或Node类型。 */
2
+ export function decodeCanonicalBase64(value) {
3
+ const binary = atob(value);
4
+ if (!binary.length || btoa(binary) !== value)
5
+ throw new TypeError('Expected canonical non-empty base64');
6
+ return Uint8Array.from(binary, character => character.charCodeAt(0));
7
+ }
@@ -0,0 +1,8 @@
1
+ export * from "./HolarchyEidolonAdapterError.js";
2
+ export * from "./HolonDeploymentDefinition.js";
3
+ export * from "./HolonDeploymentRuntime.js";
4
+ export * from "./HolonExecutionBinding.js";
5
+ export * from "./HolonExecutionBindingProjection.js";
6
+ export { parseOrganizationWorkbenchRequest, parseOrganizationWorkbenchReply } from './WorkbenchProtocol.js';
7
+ export type { OrganizationWorkbenchRequest, OrganizationWorkbenchReply } from './WorkbenchProtocol.js';
8
+ export * from "./WorkflowHolonTaskContract.js";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from "./HolarchyEidolonAdapterError.js";
2
+ export * from "./HolonDeploymentDefinition.js";
3
+ export * from "./HolonDeploymentRuntime.js";
4
+ export * from "./HolonExecutionBinding.js";
5
+ export * from "./HolonExecutionBindingProjection.js";
6
+ export { parseOrganizationWorkbenchRequest, parseOrganizationWorkbenchReply } from './WorkbenchProtocol.js';
7
+ export * from "./WorkflowHolonTaskContract.js";
package/package.json CHANGED
@@ -1,34 +1,50 @@
1
1
  {
2
2
  "name": "holarchy-eidolon-adapter",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "private": false,
5
5
  "description": "Eidolon ResourcePackage adapter for frozen Holarchy organization snapshots and execution bindings.",
6
6
  "type": "module",
7
- "main": "./src/index.ts",
7
+ "main": "./dist/index.js",
8
8
  "exports": {
9
- ".": "./src/index.ts",
10
- "./HolonDeploymentDefinition": "./src/HolonDeploymentDefinition.ts",
11
- "./HolonDeploymentRuntime": "./src/HolonDeploymentRuntime.ts",
12
- "./HolonExecutionBinding": "./src/HolonExecutionBinding.ts",
13
- "./HolonExecutionBindingProjection": "./src/HolonExecutionBindingProjection.ts"
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./HolonDeploymentDefinition": {
14
+ "types": "./dist/HolonDeploymentDefinition.d.ts",
15
+ "import": "./dist/HolonDeploymentDefinition.js"
16
+ },
17
+ "./HolonDeploymentRuntime": {
18
+ "types": "./dist/HolonDeploymentRuntime.d.ts",
19
+ "import": "./dist/HolonDeploymentRuntime.js"
20
+ },
21
+ "./HolonExecutionBinding": {
22
+ "types": "./dist/HolonExecutionBinding.d.ts",
23
+ "import": "./dist/HolonExecutionBinding.js"
24
+ },
25
+ "./HolonExecutionBindingProjection": {
26
+ "types": "./dist/HolonExecutionBindingProjection.d.ts",
27
+ "import": "./dist/HolonExecutionBindingProjection.js"
28
+ },
29
+ "./WorkbenchProtocol": {
30
+ "types": "./dist/WorkbenchProtocol.d.ts",
31
+ "import": "./dist/WorkbenchProtocol.js"
32
+ }
14
33
  },
15
34
  "files": [
16
- "src"
35
+ "dist",
36
+ "README.md"
17
37
  ],
18
- "scripts": {
19
- "typecheck": "tsc -p tsconfig.json --noEmit",
20
- "test": "bun test"
21
- },
22
38
  "dependencies": {
23
- "ai-workflow-contract": "0.2.0",
39
+ "ai-workflow-contract": "0.2.3",
24
40
  "depa-actor": "0.2.2",
25
- "halfcode-compiler.xnl": "0.3.0",
26
- "holarchy-core-contract": "0.2.0"
27
- },
28
- "devDependencies": {
29
- "typescript": "^5.4.0"
41
+ "halfcode-compiler.xnl": "0.3.2",
42
+ "holarchy-core-contract": "0.3.1",
43
+ "task-manager-contract": "0.1.4"
30
44
  },
31
45
  "publishConfig": {
32
- "access": "public"
33
- }
46
+ "access": "public",
47
+ "registry": "https://registry.npmjs.com/"
48
+ },
49
+ "types": "./dist/index.d.ts"
34
50
  }
@@ -1,9 +0,0 @@
1
- export class HolarchyEidolonAdapterError extends Error {
2
- constructor(
3
- readonly code: string,
4
- message: string,
5
- ) {
6
- super(`${code}: ${message}`)
7
- this.name = "HolarchyEidolonAdapterError"
8
- }
9
- }
@@ -1,241 +0,0 @@
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
- }