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.
- package/dist/HolarchyEidolonAdapterError.d.ts +4 -0
- package/dist/HolarchyEidolonAdapterError.js +8 -0
- package/dist/HolonDeploymentDefinition.d.ts +22 -0
- package/dist/HolonDeploymentDefinition.js +196 -0
- package/dist/HolonDeploymentRuntime.d.ts +61 -0
- package/dist/HolonDeploymentRuntime.js +295 -0
- package/dist/HolonExecutionBinding.d.ts +90 -0
- package/dist/HolonExecutionBinding.js +453 -0
- package/dist/HolonExecutionBindingProjection.d.ts +57 -0
- package/dist/HolonExecutionBindingProjection.js +289 -0
- package/dist/WorkbenchProtocol.d.ts +12 -0
- package/dist/WorkbenchProtocol.js +102 -0
- package/dist/WorkflowHolonTaskContract.d.ts +90 -0
- package/dist/WorkflowHolonTaskContract.js +211 -0
- package/dist/canonicalBase64.d.ts +2 -0
- package/dist/canonicalBase64.js +7 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/package.json +36 -20
- package/src/HolarchyEidolonAdapterError.ts +0 -9
- package/src/HolonDeploymentDefinition.ts +0 -241
- package/src/HolonDeploymentRuntime.ts +0 -388
- package/src/HolonExecutionBinding.ts +0 -558
- package/src/HolonExecutionBindingProjection.ts +0 -450
- package/src/index.ts +0 -5
|
@@ -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,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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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.
|
|
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": "./
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"./
|
|
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
|
-
"
|
|
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.
|
|
39
|
+
"ai-workflow-contract": "0.2.3",
|
|
24
40
|
"depa-actor": "0.2.2",
|
|
25
|
-
"halfcode-compiler.xnl": "0.3.
|
|
26
|
-
"holarchy-core-contract": "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,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
|
-
}
|