@addai/node 0.30.13 → 0.30.14

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.
Files changed (143) hide show
  1. package/dist/auto-update.d.ts +3 -0
  2. package/dist/auto-update.js +15 -0
  3. package/dist/capabilities.d.ts +17 -3
  4. package/dist/capabilities.js +18 -8
  5. package/dist/claude-models.d.ts +4 -12
  6. package/dist/claude-models.js +12 -88
  7. package/dist/claude-print.d.ts +15 -0
  8. package/dist/claude-print.js +16 -0
  9. package/dist/codex-models.d.ts +81 -0
  10. package/dist/codex-models.js +385 -0
  11. package/dist/codex-spawn.d.ts +14 -8
  12. package/dist/codex-spawn.js +30 -10
  13. package/dist/command-runner.js +31 -9
  14. package/dist/desktop/docker.js +2 -0
  15. package/dist/desktop/manager.d.ts +2 -1
  16. package/dist/desktop/manager.js +51 -1
  17. package/dist/desktop/provider.d.ts +1 -1
  18. package/dist/desktop/provider.js +2 -0
  19. package/dist/desktop/relay-client.js +7 -0
  20. package/dist/desktop/spec.d.ts +2 -0
  21. package/dist/direct/aios-bridge.d.ts +13 -0
  22. package/dist/direct/aios-bridge.js +177 -0
  23. package/dist/direct/aios-mcp.d.ts +2 -0
  24. package/dist/direct/aios-mcp.js +132 -0
  25. package/dist/direct/aios-tools.d.ts +351 -0
  26. package/dist/direct/aios-tools.js +120 -0
  27. package/dist/direct/board-chat-provider.d.ts +64 -0
  28. package/dist/direct/board-chat-provider.js +151 -0
  29. package/dist/direct/board-chat.d.ts +40 -0
  30. package/dist/direct/board-chat.js +414 -0
  31. package/dist/direct/config.d.ts +2 -0
  32. package/dist/direct/config.js +60 -0
  33. package/dist/direct/launch.d.ts +14 -0
  34. package/dist/direct/launch.js +121 -0
  35. package/dist/direct/protocol.d.ts +46 -0
  36. package/dist/direct/protocol.js +84 -0
  37. package/dist/direct/relay-main.d.ts +1 -0
  38. package/dist/direct/relay-main.js +21 -0
  39. package/dist/direct/relay.d.ts +26 -0
  40. package/dist/direct/relay.js +334 -0
  41. package/dist/direct/sessions.d.ts +61 -0
  42. package/dist/direct/sessions.js +368 -0
  43. package/dist/direct/supervisor.d.ts +9 -0
  44. package/dist/direct/supervisor.js +137 -0
  45. package/dist/direct/terminal.d.ts +1 -0
  46. package/dist/direct/terminal.js +106 -0
  47. package/dist/direct/worker.d.ts +1 -0
  48. package/dist/direct/worker.js +278 -0
  49. package/dist/entitybox/container-action.cjs +85 -0
  50. package/dist/entitybox/container-watchdog.cjs +70 -0
  51. package/dist/entitybox/model-entry.d.ts +3 -0
  52. package/dist/entitybox/model-entry.js +233 -0
  53. package/dist/entitybox/model-mcp-proxy.d.ts +3 -0
  54. package/dist/entitybox/model-mcp-proxy.js +85 -0
  55. package/dist/entitybox-application-boundary.d.ts +59 -0
  56. package/dist/entitybox-application-boundary.js +161 -0
  57. package/dist/entitybox-application-guard-core.d.ts +44 -0
  58. package/dist/entitybox-application-guard-core.js +164 -0
  59. package/dist/entitybox-application-guard-process.d.ts +1 -0
  60. package/dist/entitybox-application-guard-process.js +83 -0
  61. package/dist/entitybox-application-guard.d.ts +46 -0
  62. package/dist/entitybox-application-guard.js +239 -0
  63. package/dist/entitybox-capacity-health-host.d.ts +12 -0
  64. package/dist/entitybox-capacity-health-host.js +80 -0
  65. package/dist/entitybox-capacity-health-worker.d.ts +22 -0
  66. package/dist/entitybox-capacity-health-worker.js +168 -0
  67. package/dist/entitybox-capacity-host.d.ts +17 -0
  68. package/dist/entitybox-capacity-host.js +155 -0
  69. package/dist/entitybox-capacity-owner-process.d.ts +1 -0
  70. package/dist/entitybox-capacity-owner-process.js +32 -0
  71. package/dist/entitybox-capacity-owner.d.ts +16 -0
  72. package/dist/entitybox-capacity-owner.js +90 -0
  73. package/dist/entitybox-capacity-programs.d.ts +7 -0
  74. package/dist/entitybox-capacity-programs.js +49 -0
  75. package/dist/entitybox-capacity-provisioner.d.ts +82 -0
  76. package/dist/entitybox-capacity-provisioner.js +404 -0
  77. package/dist/entitybox-capacity-publication.d.ts +29 -0
  78. package/dist/entitybox-capacity-publication.js +199 -0
  79. package/dist/entitybox-capacity-registration.d.ts +36 -0
  80. package/dist/entitybox-capacity-registration.js +75 -0
  81. package/dist/entitybox-capacity-worker.d.ts +29 -0
  82. package/dist/entitybox-capacity-worker.js +113 -0
  83. package/dist/entitybox-computer-task.d.ts +69 -0
  84. package/dist/entitybox-computer-task.js +176 -0
  85. package/dist/entitybox-controller-recovery.d.ts +40 -0
  86. package/dist/entitybox-controller-recovery.js +156 -0
  87. package/dist/entitybox-desktop-action-server.d.ts +34 -0
  88. package/dist/entitybox-desktop-action-server.js +176 -0
  89. package/dist/entitybox-desktop-admission.d.ts +32 -0
  90. package/dist/entitybox-desktop-admission.js +79 -0
  91. package/dist/entitybox-display-binding.d.ts +13 -0
  92. package/dist/entitybox-display-binding.js +31 -0
  93. package/dist/entitybox-execution-container.d.ts +82 -0
  94. package/dist/entitybox-execution-container.js +450 -0
  95. package/dist/entitybox-execution-host.d.ts +40 -0
  96. package/dist/entitybox-execution-host.js +168 -0
  97. package/dist/entitybox-execution-lease.d.ts +81 -0
  98. package/dist/entitybox-execution-lease.js +332 -0
  99. package/dist/entitybox-file-inputs.d.ts +51 -0
  100. package/dist/entitybox-file-inputs.js +182 -0
  101. package/dist/entitybox-file-session.d.ts +38 -0
  102. package/dist/entitybox-file-session.js +82 -0
  103. package/dist/entitybox-host-process.d.ts +12 -0
  104. package/dist/entitybox-host-process.js +120 -0
  105. package/dist/entitybox-install.d.ts +10 -0
  106. package/dist/entitybox-install.js +40 -0
  107. package/dist/entitybox-mcp-broker.d.ts +77 -0
  108. package/dist/entitybox-mcp-broker.js +420 -0
  109. package/dist/entitybox-mcp-contract.d.ts +10 -0
  110. package/dist/entitybox-mcp-contract.js +24 -0
  111. package/dist/entitybox-mcp-guard.d.ts +3 -0
  112. package/dist/entitybox-mcp-guard.js +167 -0
  113. package/dist/entitybox-model-artifact.d.ts +9 -0
  114. package/dist/entitybox-model-artifact.js +83 -0
  115. package/dist/entitybox-native-services.d.ts +33 -0
  116. package/dist/entitybox-native-services.js +172 -0
  117. package/dist/entitybox-operation.d.ts +4 -0
  118. package/dist/entitybox-operation.js +34 -0
  119. package/dist/entitybox-provider-broker.d.ts +41 -0
  120. package/dist/entitybox-provider-broker.js +149 -0
  121. package/dist/entitybox-queue-request.d.ts +32 -0
  122. package/dist/entitybox-queue-request.js +141 -0
  123. package/dist/entitybox-recovery-host.d.ts +12 -0
  124. package/dist/entitybox-recovery-host.js +80 -0
  125. package/dist/entitybox-recovery-store.d.ts +33 -0
  126. package/dist/entitybox-recovery-store.js +150 -0
  127. package/dist/entitybox-recovery-worker.d.ts +30 -0
  128. package/dist/entitybox-recovery-worker.js +134 -0
  129. package/dist/entitybox-vault-provider.d.ts +27 -0
  130. package/dist/entitybox-vault-provider.js +127 -0
  131. package/dist/harness-registry.js +3 -1
  132. package/dist/index.js +72 -38
  133. package/dist/install.d.ts +14 -2
  134. package/dist/install.js +115 -10
  135. package/dist/model-cache.d.ts +38 -0
  136. package/dist/model-cache.js +137 -0
  137. package/dist/request-pump.d.ts +3 -0
  138. package/dist/request-pump.js +21 -7
  139. package/dist/session-runner.d.ts +8 -1
  140. package/dist/session-runner.js +48 -3
  141. package/dist/supabase-client.js +4 -1
  142. package/package.json +5 -1
  143. package/scripts/copy-assets.js +4 -2
@@ -0,0 +1,404 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.EntityboxCapacityProvisioner = exports.entityboxCapacityLabels = void 0;
37
+ exports.readEntityboxCapacity = readEntityboxCapacity;
38
+ exports.observeEntityboxCapacity = observeEntityboxCapacity;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const crypto_1 = require("crypto");
42
+ const child_process_1 = require("child_process");
43
+ const perf_hooks_1 = require("perf_hooks");
44
+ const entitybox_execution_lease_1 = require("./entitybox-execution-lease");
45
+ const entitybox_application_boundary_1 = require("./entitybox-application-boundary");
46
+ const entitybox_display_binding_1 = require("./entitybox-display-binding");
47
+ const entitybox_recovery_store_1 = require("./entitybox-recovery-store");
48
+ const programs = __importStar(require("./entitybox-capacity-programs"));
49
+ const uuid = /^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$/;
50
+ const fail = () => new entitybox_execution_lease_1.EntityboxExecutionError('Dedicated computer provisioning could not be verified');
51
+ const sha = (s) => (0, crypto_1.createHash)('sha256').update(s).digest('hex');
52
+ const programHash = sha((0, entitybox_recovery_store_1.entityboxCanonical)(programs));
53
+ const labels = (plan, role) => ({
54
+ 'ai.add.entitybox.provision.version': '1', 'ai.add.entitybox.provision.capacity': plan.capacityId,
55
+ 'ai.add.entitybox.provision.desktop': plan.desktopId, 'ai.add.entitybox.provision.runtime': plan.runtimeId,
56
+ 'ai.add.entitybox.provision.plan': sha((0, entitybox_recovery_store_1.entityboxCanonical)(plan)), 'ai.add.entitybox.provision.role': role,
57
+ });
58
+ exports.entityboxCapacityLabels = labels;
59
+ const match = (actual, expected) => !!actual && Object.entries(expected).every(([k, v]) => actual[k] === v);
60
+ /** Docker mutations have no observation timeout: a timed-out CLI is not proof
61
+ * that the daemon stopped creating/starting a resource. A hung operation keeps
62
+ * the durable reservation, rather than starting a replacement. */
63
+ function nativeDocker(context) {
64
+ return (args, input) => new Promise((resolve, reject) => {
65
+ const child = (0, child_process_1.spawn)('docker', ['--context', context, ...args], { windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] });
66
+ let output = '', size = 0, invalid = false;
67
+ child.stdout.on('data', b => { size += b.length; if (size > 2 * 1024 * 1024)
68
+ invalid = true;
69
+ else
70
+ output += b.toString(); });
71
+ child.stderr.resume();
72
+ child.stdin.on('error', () => { invalid = true; });
73
+ child.once('error', () => reject(fail()));
74
+ child.once('close', code => code === 0 && !invalid ? resolve(output.trim()) : reject(fail()));
75
+ child.stdin.end(input);
76
+ });
77
+ }
78
+ function readJson(file) {
79
+ try {
80
+ const stat = fs.lstatSync(file);
81
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 32768)
82
+ throw fail();
83
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
84
+ }
85
+ catch {
86
+ throw fail();
87
+ }
88
+ }
89
+ /** Publish an immutable complete file. A partial write cannot look like a
90
+ * reusable reservation or receipt. An existing name never grants ownership. */
91
+ function publish(directory, name, value) {
92
+ const pending = path.join(directory, 'pending-' + (0, crypto_1.randomUUID)() + '.json');
93
+ let fd;
94
+ try {
95
+ fd = fs.openSync(pending, 'wx', 0o600);
96
+ fs.writeFileSync(fd, (0, entitybox_recovery_store_1.entityboxCanonical)(value));
97
+ fs.fsyncSync(fd);
98
+ fs.closeSync(fd);
99
+ fd = undefined;
100
+ fs.linkSync(pending, path.join(directory, name));
101
+ if (process.platform !== 'win32') {
102
+ const handle = fs.openSync(directory, 'r');
103
+ try {
104
+ fs.fsyncSync(handle);
105
+ }
106
+ finally {
107
+ fs.closeSync(handle);
108
+ }
109
+ }
110
+ }
111
+ catch {
112
+ throw fail();
113
+ }
114
+ finally {
115
+ if (fd !== undefined)
116
+ fs.closeSync(fd);
117
+ try {
118
+ fs.unlinkSync(pending);
119
+ }
120
+ catch { }
121
+ }
122
+ }
123
+ function directoryFor(value) {
124
+ if (typeof value !== 'string' || !path.isAbsolute(value) || value.startsWith('\\\\'))
125
+ throw fail();
126
+ fs.mkdirSync(value, { recursive: true, mode: 0o700 });
127
+ if (fs.lstatSync(value).isSymbolicLink())
128
+ throw fail();
129
+ return fs.realpathSync(value);
130
+ }
131
+ function parsePlan(value) {
132
+ if (!value || Object.keys(value).sort().join(',') !== 'capacityId,desktopId,dockerContext,imageId,programsSha256,runtimeId,version' || value.version !== 1
133
+ || ![value.capacityId, value.runtimeId, value.desktopId].every(v => uuid.test(v)) || !/^sha256:[a-f0-9]{64}$/.test(value.imageId)
134
+ || !/^[-a-zA-Z0-9_.]{1,80}$/.test(value.dockerContext) || value.programsSha256 !== programHash)
135
+ throw fail();
136
+ return value;
137
+ }
138
+ function portOf(c) {
139
+ const ports = c?.NetworkSettings?.Ports, bindings = c?.HostConfig?.PortBindings, entries = ports?.['5900/tcp'], requested = bindings?.['5900/tcp'];
140
+ if (!ports || Object.entries(ports).some(([key, value]) => key !== '5900/tcp' && value != null) || !Array.isArray(entries) || entries.length !== 1 || entries[0].HostIp !== '127.0.0.1'
141
+ || !bindings || Object.keys(bindings).length !== 1 || !Array.isArray(requested) || requested.length !== 1 || requested[0].HostIp !== '127.0.0.1')
142
+ throw fail();
143
+ const port = Number(entries[0].HostPort);
144
+ if (!Number.isSafeInteger(port) || port < 1024 || port > 65535)
145
+ throw fail();
146
+ return port;
147
+ }
148
+ /** NEW offline dedicated capacity only. This is deliberately unregistered in
149
+ * the shared desktop command runner/reconciler. The server must reserve a new
150
+ * native desktop identity before invoking it, then publish the verified VNC
151
+ * descriptor through its existing authorization path. */
152
+ class EntityboxCapacityProvisioner {
153
+ docker;
154
+ plan;
155
+ directory;
156
+ result;
157
+ constructor(config, docker, reserved) {
158
+ this.docker = docker;
159
+ this.plan = Object.freeze(reserved ?? { version: 1, capacityId: (0, crypto_1.randomUUID)(), runtimeId: config.runtimeId, desktopId: config.desktopId, imageId: config.imageId, dockerContext: config.dockerContext ?? 'desktop-linux', programsSha256: programHash });
160
+ this.directory = directoryFor(config.directory);
161
+ this.password = config.vncPassword;
162
+ }
163
+ password;
164
+ static create(config, docker) {
165
+ if (config.enabled !== true)
166
+ return undefined;
167
+ if (!uuid.test(config.runtimeId) || !uuid.test(config.desktopId) || !/^sha256:[a-f0-9]{64}$/.test(config.imageId)
168
+ || !/^[-a-zA-Z0-9_.]{1,80}$/.test(config.dockerContext ?? 'desktop-linux') || typeof config.vncPassword !== 'string' || !/^[A-Za-z0-9_-]{8}$/.test(config.vncPassword))
169
+ throw fail();
170
+ return new EntityboxCapacityProvisioner(config, docker ?? nativeDocker(config.dockerContext ?? 'desktop-linux'));
171
+ }
172
+ /** Continue a server-claimed plan in its one physical helper. This is not
173
+ * resumption: run() still atomically reserves the desktop before ANY Docker
174
+ * operation and rejects an existing reservation, even for the same plan. */
175
+ static createReserved(config, plan, docker) {
176
+ if (config.enabled !== true)
177
+ return undefined;
178
+ const reserved = parsePlan(JSON.parse((0, entitybox_recovery_store_1.entityboxCanonical)(plan)));
179
+ if (reserved.runtimeId !== config.runtimeId || reserved.desktopId !== config.desktopId || reserved.imageId !== config.imageId || reserved.dockerContext !== (config.dockerContext ?? 'desktop-linux'))
180
+ throw fail();
181
+ // Reuse the normal configuration validation before constructing the helper.
182
+ if (!EntityboxCapacityProvisioner.create(config, docker))
183
+ throw fail();
184
+ return new EntityboxCapacityProvisioner(config, docker ?? nativeDocker(reserved.dockerContext), reserved);
185
+ }
186
+ /** Calls on this one owner join. A new object/process cannot repeat the
187
+ * operation: the desktop reservation remains, even after an ambiguous error. */
188
+ provision() {
189
+ this.result ??= this.run().catch(() => { throw fail(); }).finally(() => { this.password = ''; });
190
+ return this.result;
191
+ }
192
+ async inspect(id, role) {
193
+ const result = JSON.parse(await this.docker(['container', 'inspect', id]));
194
+ const c = result?.[0];
195
+ if (!Array.isArray(result) || result.length !== 1 || !/^[a-f0-9]{64}$/.test(id) || c?.Id !== id || c.Image !== this.plan.imageId || !match(c.Config?.Labels, labels(this.plan, role)))
196
+ throw fail();
197
+ return c;
198
+ }
199
+ async until(test, milliseconds = 30000) {
200
+ const end = Date.now() + milliseconds;
201
+ do {
202
+ if (await test())
203
+ return;
204
+ await new Promise(resolve => setTimeout(resolve, 150));
205
+ } while (Date.now() < end);
206
+ throw fail();
207
+ }
208
+ async run() {
209
+ const p = this.plan, planSha256 = sha((0, entitybox_recovery_store_1.entityboxCanonical)(p)), base = { capacityId: p.capacityId, desktopId: p.desktopId, runtimeId: p.runtimeId }, volumes = (0, entitybox_application_boundary_1.entityboxApplicationVolumes)(base);
210
+ // The reservation precedes every external operation, including discovery.
211
+ publish(this.directory, 'desktop-' + p.desktopId + '.json', p);
212
+ const file = (suffix) => 'capacity-' + p.capacityId + '-' + suffix + '.json';
213
+ const names = { init: 'entitybox-capacity-init-' + p.capacityId, display: 'entitybox-capacity-display-' + p.capacityId, application: 'entitybox-capacity-application-' + p.capacityId };
214
+ const knownContainers = (await this.docker(['container', 'ls', '--all', '--format', '{{.Names}}'])).split(/\r?\n/);
215
+ const knownVolumes = (await this.docker(['volume', 'ls', '--format', '{{.Name}}'])).split(/\r?\n/);
216
+ if (Object.values(names).some(n => knownContainers.includes(n)) || Object.values(volumes).some(n => knownVolumes.includes(n)))
217
+ throw fail();
218
+ const argsFor = (value) => Object.entries(value).flatMap(([k, v]) => ['--label', k + '=' + v]);
219
+ const mount = (src, dst, readonly = false) => ['--mount', `type=volume,src=${src},dst=${dst},volume-nocopy${readonly ? ',readonly' : ''}`];
220
+ const applicationLabels = (0, entitybox_application_boundary_1.entityboxApplicationLabels)(base), displayLabels = { 'ai.add.entitybox.display.version': '1', 'ai.add.entitybox.display.capacity': p.capacityId, 'ai.add.entitybox.display.desktop': p.desktopId, 'ai.add.entitybox.display.runtime': p.runtimeId };
221
+ for (const [kind, name] of Object.entries(volumes)) {
222
+ const expected = { ...labels(p, 'volume-' + kind), ...(kind === 'socket' ? displayLabels : applicationLabels) };
223
+ if (await this.docker(['volume', 'create', '--driver', 'local', ...argsFor(expected), name]) !== name)
224
+ throw fail();
225
+ const rows = JSON.parse(await this.docker(['volume', 'inspect', name]));
226
+ if (!Array.isArray(rows) || rows.length !== 1 || rows[0].Name !== name || rows[0].Driver !== 'local' || Object.keys(rows[0].Options ?? {}).length || !match(rows[0].Labels, expected))
227
+ throw fail();
228
+ publish(this.directory, file('volume-' + kind), { name, labels: expected });
229
+ }
230
+ const limits = ['--init', '--restart', 'no', '--ipc', 'private', '--memory', '1024m', '--memory-swap', '1024m', '--cpus', '1', '--pids-limit', '256', '--cap-drop', 'ALL', '--security-opt', 'no-new-privileges', '--read-only'];
231
+ const temp = ['--tmpfs', '/tmp:rw,nosuid,nodev,size=128m,mode=1777', '--tmpfs', '/run:rw,nosuid,nodev,size=8m,mode=700,uid=10001,gid=10001'];
232
+ const create = async (role, extra, args, code) => {
233
+ const id = await this.docker(['create', '--name', names[role], ...argsFor({ ...labels(p, role), ...extra }), ...limits, ...args, '--entrypoint', 'node', p.imageId, '-e', code]);
234
+ const c = await this.inspect(id, role);
235
+ if (c.State?.Status !== 'created' || c.State.Running || c.State.Pid !== 0)
236
+ throw fail();
237
+ publish(this.directory, file(role), { id, name: names[role], imageId: p.imageId, labels: labels(p, role) });
238
+ return id;
239
+ };
240
+ const init = await create('init', {}, ['--network', 'none', '--user', '0:0', '--cap-add', 'CHOWN', '--cap-add', 'FOWNER', ...mount(volumes.socket, '/socket'), ...mount(volumes.profile, '/profile'), ...mount(volumes.work, '/work')], programs.entityboxCapacityInit);
241
+ await this.docker(['start', init]);
242
+ await this.until(async () => { const c = await this.inspect(init, 'init'); if (c.State.Running)
243
+ return false; if (c.State.Status !== 'exited' || c.State.Pid !== 0 || c.State.ExitCode !== 0)
244
+ throw fail(); return true; });
245
+ // Never use --force and never remove data. Only the proven stopped bootstrap
246
+ // helper created by this exact plan may be retired here.
247
+ await this.docker(['container', 'rm', init]);
248
+ const display = await create('display', displayLabels, ['--network', 'bridge', '--user', '10001:10001', ...temp, '--publish', '127.0.0.1::5900', ...mount(volumes.socket, '/tmp/.X11-unix')], programs.entityboxCapacityDisplay);
249
+ await this.docker(['start', display]);
250
+ await this.inspect(display, 'display');
251
+ await this.docker(['exec', '--interactive', '--user', '10001:10001', display, 'node', '-e', programs.entityboxCapacityPassword], this.password);
252
+ const application = await create('application', applicationLabels, ['--network', 'none', '--user', '10001:10001', ...temp, ...mount(volumes.socket, '/tmp/.X11-unix', true), ...mount(volumes.profile, '/home/entitybox'), ...mount(volumes.work, '/work')], programs.entityboxCapacityApplication);
253
+ // Xvfb must be listening before Chromium starts. The socket is exposed only
254
+ // to the new application, never a host desktop or an arbitrary bind mount.
255
+ await this.until(async () => { await this.inspect(display, 'display'); try {
256
+ return await this.docker(['exec', '--user', '10001:10001', '--env', 'DISPLAY=:1', display, 'xdpyinfo']) !== '';
257
+ }
258
+ catch {
259
+ return false;
260
+ } });
261
+ await this.docker(['start', application]);
262
+ await this.until(async () => { const a = await this.inspect(application, 'application'), d = await this.inspect(display, 'display'); if (!a.State.Running || !d.State.Running)
263
+ throw fail(); try {
264
+ return await this.docker(['exec', '--user', '10001:10001', display, 'node', '-e', programs.entityboxCapacityReady]) === 'ready';
265
+ }
266
+ catch {
267
+ return false;
268
+ } });
269
+ await this.docker(['container', 'pause', application]);
270
+ const a = await this.inspect(application, 'application'), d = await this.inspect(display, 'display');
271
+ const receipt = { version: 1, dockerContext: p.dockerContext, planSha256, vncPort: portOf(d), channels: ['screen'], applicationNetwork: 'none',
272
+ application: { ...base, containerId: application, imageId: p.imageId, startedAt: a.State.StartedAt }, display: { ...base, containerId: display, imageId: p.imageId, startedAt: d.State.StartedAt } };
273
+ await verifyEntityboxCapacityReceipt(p, receipt, this.docker);
274
+ publish(this.directory, file('ready'), receipt);
275
+ return Object.freeze({ ...receipt, application: Object.freeze(receipt.application), display: Object.freeze(receipt.display), channels: Object.freeze(receipt.channels) });
276
+ }
277
+ }
278
+ exports.EntityboxCapacityProvisioner = EntityboxCapacityProvisioner;
279
+ async function verifyEntityboxCapacityReceipt(plan, r, docker, requirePaused = true) {
280
+ parsePlan(plan);
281
+ if (!r || r.version !== 1 || r.planSha256 !== sha((0, entitybox_recovery_store_1.entityboxCanonical)(plan)) || r.dockerContext !== plan.dockerContext || r.applicationNetwork !== 'none'
282
+ || JSON.stringify(r.channels) !== '["screen"]' || r.application?.containerId === r.display?.containerId)
283
+ throw fail();
284
+ const scope = { version: 1, runId: plan.capacityId, desktopId: plan.desktopId, runtimeId: plan.runtimeId };
285
+ for (const [role, b] of [['application', r.application], ['display', r.display]]) {
286
+ if (!b || b.capacityId !== plan.capacityId || b.desktopId !== plan.desktopId || b.runtimeId !== plan.runtimeId || b.imageId !== plan.imageId)
287
+ throw fail();
288
+ const rows = JSON.parse(await docker(['container', 'inspect', b.containerId]));
289
+ if (!Array.isArray(rows) || rows.length !== 1 || !match(rows[0]?.Config?.Labels, labels(plan, role)))
290
+ throw fail();
291
+ if (role === 'display' && (portOf(rows[0]) !== r.vncPort || rows[0].State?.Paused || rows[0].State?.Restarting || rows[0].HostConfig?.RestartPolicy?.Name !== 'no'))
292
+ throw fail();
293
+ }
294
+ const state = await (0, entitybox_application_boundary_1.inspectEntityboxApplication)(r.application, scope, docker);
295
+ if (state === 'stopped' || requirePaused && state !== 'paused')
296
+ throw fail();
297
+ await (0, entitybox_display_binding_1.verifyEntityboxDisplay)(r.display, scope, docker);
298
+ return state;
299
+ }
300
+ /** Read-only reopening after a clean setup; never resumes, creates, repairs or
301
+ * deletes anything. Missing/ambiguous setup remains reserved for explicit
302
+ * recovery. A restarted container or changed published port is not adopted. */
303
+ async function readEntityboxCapacity(directory, desktopId, runtimeId, docker) {
304
+ if (!uuid.test(desktopId) || !uuid.test(runtimeId) || !path.isAbsolute(directory) || directory.startsWith('\\\\'))
305
+ throw fail();
306
+ try {
307
+ if (fs.lstatSync(directory).isSymbolicLink())
308
+ throw fail();
309
+ const dir = fs.realpathSync(directory), p = parsePlan(readJson(path.join(dir, 'desktop-' + desktopId + '.json')));
310
+ if (p.desktopId !== desktopId || p.runtimeId !== runtimeId)
311
+ throw fail();
312
+ const r = readJson(path.join(dir, 'capacity-' + p.capacityId + '-ready.json'));
313
+ await verifyEntityboxCapacityReceipt(p, r, docker ?? nativeDocker(p.dockerContext));
314
+ return Object.freeze({ ...r, application: Object.freeze(r.application), display: Object.freeze(r.display), channels: Object.freeze(r.channels) });
315
+ }
316
+ catch {
317
+ throw fail();
318
+ }
319
+ }
320
+ /** A health observation is never execution, ownership, stop proof or permission
321
+ * to replace a computer. Every physical read is limited to this published
322
+ * receipt; unavailable includes missing evidence and inconclusive inspection.
323
+ * The existing task reader continues to require a paused application. */
324
+ async function observeEntityboxCapacity(config, docker) {
325
+ if (config.enabled !== true)
326
+ return undefined;
327
+ try {
328
+ // Snapshot all input before the first await; late caller changes cannot
329
+ // redirect the remaining reads to another computer or Docker context.
330
+ const { directory, desktopId, runtimeId, dockerContext } = config;
331
+ if (!uuid.test(desktopId) || !uuid.test(runtimeId) || !path.isAbsolute(directory) || directory.startsWith('\\\\') || !/^[-a-zA-Z0-9_.]{1,80}$/.test(dockerContext))
332
+ throw fail();
333
+ const published = JSON.parse((0, entitybox_recovery_store_1.entityboxCanonical)(config.receipt));
334
+ if (fs.lstatSync(directory).isSymbolicLink())
335
+ throw fail();
336
+ const dir = fs.realpathSync(directory), plan = parsePlan(readJson(path.join(dir, 'desktop-' + desktopId + '.json')));
337
+ const receipt = readJson(path.join(dir, 'capacity-' + plan.capacityId + '-ready.json'));
338
+ if (plan.desktopId !== desktopId || plan.runtimeId !== runtimeId || plan.dockerContext !== dockerContext || published?.dockerContext !== dockerContext
339
+ || (0, entitybox_recovery_store_1.entityboxCanonical)(receipt) !== (0, entitybox_recovery_store_1.entityboxCanonical)(published))
340
+ throw fail();
341
+ // Structural validation precedes any command, including a custom fixture
342
+ // transport. No name lookup or ordinary desktop discovery is permitted.
343
+ if (Object.keys(receipt).sort().join(',') !== 'application,applicationNetwork,channels,display,dockerContext,planSha256,version,vncPort'
344
+ || !Number.isSafeInteger(receipt.vncPort) || receipt.vncPort < 1024 || receipt.vncPort > 65535)
345
+ throw fail();
346
+ for (const b of [receipt.application, receipt.display]) {
347
+ if (!b || Object.keys(b).sort().join(',') !== 'capacityId,containerId,desktopId,imageId,runtimeId,startedAt'
348
+ || b.capacityId !== plan.capacityId || b.desktopId !== desktopId || b.runtimeId !== runtimeId || b.imageId !== plan.imageId
349
+ || !/^[a-f0-9]{64}$/.test(b.containerId) || typeof b.startedAt !== 'string' || b.startedAt.length > 80 || !Number.isFinite(Date.parse(b.startedAt)))
350
+ throw fail();
351
+ }
352
+ const allowedContainers = new Set([receipt.application.containerId, receipt.display.containerId]);
353
+ const allowedVolumes = new Set(Object.values((0, entitybox_application_boundary_1.entityboxApplicationVolumes)(receipt.application)));
354
+ const deadline = perf_hooks_1.performance.now() + 20000;
355
+ const transport = docker ?? entityboxCapacityHealthDocker(dockerContext);
356
+ const read = async (args, input) => {
357
+ if (input !== undefined || perf_hooks_1.performance.now() >= deadline || args[1] !== 'inspect'
358
+ || !(args[0] === 'container' && args.length === 3 && allowedContainers.has(args[2])
359
+ || args[0] === 'volume' && args.length >= 3 && args.length <= 5 && args.slice(2).every(v => allowedVolumes.has(v))))
360
+ throw fail();
361
+ const result = await transport([...args]);
362
+ if (perf_hooks_1.performance.now() >= deadline)
363
+ throw fail();
364
+ return result;
365
+ };
366
+ // Cache one complete observation: ownership, ports, labels and state must
367
+ // be assessed from the same container record, not different CLI responses.
368
+ const reads = new Map();
369
+ const snapshot = args => { const key = JSON.stringify(args); let value = reads.get(key); if (!value) {
370
+ value = read(args);
371
+ reads.set(key, value);
372
+ } return value; };
373
+ const state = await verifyEntityboxCapacityReceipt(plan, receipt, snapshot, false);
374
+ return Object.freeze({ state });
375
+ }
376
+ catch {
377
+ return Object.freeze({ state: 'unavailable' });
378
+ }
379
+ }
380
+ /** A deadline can cancel an inspect CLI because it has no mutation to join.
381
+ * Always await its actual exit. This transport is never used by provisioning,
382
+ * task execution or cleanup, whose mutation lifetimes retain their rules. */
383
+ function entityboxCapacityHealthDocker(context) {
384
+ return args => new Promise((resolve, reject) => {
385
+ const child = (0, child_process_1.spawn)('docker', ['--context', context, ...args], { windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
386
+ let output = '', size = 0, invalid = false;
387
+ const stop = () => { invalid = true; try {
388
+ child.kill();
389
+ }
390
+ catch { } };
391
+ const timer = setTimeout(stop, 5000);
392
+ timer.unref?.();
393
+ child.stdout.on('data', b => { size += b.length; if (size > 2 * 1024 * 1024)
394
+ stop();
395
+ else
396
+ output += b.toString(); });
397
+ child.stderr.resume();
398
+ child.once('error', () => { invalid = true; });
399
+ child.once('close', code => { clearTimeout(timer); if (code === 0 && !invalid)
400
+ resolve(output.trim());
401
+ else
402
+ reject(fail()); });
403
+ });
404
+ }
@@ -0,0 +1,29 @@
1
+ import { type EntityboxCapacityPlan, type EntityboxCapacityReceipt } from './entitybox-capacity-provisioner';
2
+ import type { EntityboxCapacityAllocation, EntityboxCapacityRegistrationConfiguration, EntityboxCapacityRegistrationIO } from './entitybox-capacity-registration';
3
+ interface Publication {
4
+ version: 1;
5
+ allocation: EntityboxCapacityAllocation;
6
+ plan: EntityboxCapacityPlan;
7
+ nonce: string;
8
+ password: string;
9
+ }
10
+ /** The original publication authority is encrypted under the existing pairing
11
+ * secret. It is not a public capacity receipt and is never an execution grant.
12
+ * No pairing token or plaintext VNC password is written into this directory. */
13
+ export declare class EntityboxCapacityPublicationStore {
14
+ private readonly directory;
15
+ constructor(directory: string, create?: boolean);
16
+ private file;
17
+ private key;
18
+ private aad;
19
+ read(id: string, runtimeId: string, token: string): Publication;
20
+ write(value: Publication, token: string): void;
21
+ }
22
+ /** Complete only a physically finished setup. This never invokes a provisioner
23
+ * or mutates Docker. Concurrent fresh processes publish the SAME original
24
+ * nonce, plan, password and receipt through the existing idempotent SQL path.
25
+ * A missing ready receipt remains unresolved; it is not permission to recreate. */
26
+ export declare function createEntityboxCapacityPublicationRecovery(config: EntityboxCapacityRegistrationConfiguration, io: EntityboxCapacityRegistrationIO): {
27
+ provision: () => Promise<Readonly<EntityboxCapacityReceipt>>;
28
+ } | undefined;
29
+ export {};
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.EntityboxCapacityPublicationStore = void 0;
37
+ exports.createEntityboxCapacityPublicationRecovery = createEntityboxCapacityPublicationRecovery;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const crypto_1 = require("crypto");
41
+ const entitybox_recovery_store_1 = require("./entitybox-recovery-store");
42
+ const entitybox_capacity_provisioner_1 = require("./entitybox-capacity-provisioner");
43
+ const entitybox_execution_lease_1 = require("./entitybox-execution-lease");
44
+ const uuid = /^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$/, fail = () => new entitybox_execution_lease_1.EntityboxExecutionError('Computer setup publication could not be recovered');
45
+ const sha = (s) => (0, crypto_1.createHash)('sha256').update(s).digest('hex');
46
+ function validate(value) {
47
+ const a = value?.allocation, p = value?.plan;
48
+ if (!value || Object.keys(value).sort().join(',') !== 'allocation,nonce,password,plan,version' || value.version !== 1
49
+ || !a || Object.keys(a).sort().join(',') !== 'desktopId,id,runtimeId' || ![a.id, a.desktopId, a.runtimeId, value.nonce].every(v => typeof v === 'string' && uuid.test(v))
50
+ || !p || Object.keys(p).sort().join(',') !== 'capacityId,desktopId,dockerContext,imageId,programsSha256,runtimeId,version' || p.version !== 1 || !uuid.test(p.capacityId)
51
+ || p.runtimeId !== a.runtimeId || p.desktopId !== a.desktopId || !/^sha256:[a-f0-9]{64}$/.test(p.imageId) || !/^[-a-zA-Z0-9_.]{1,80}$/.test(p.dockerContext) || !/^[a-f0-9]{64}$/.test(p.programsSha256)
52
+ || typeof value.password !== 'string' || !/^[A-Za-z0-9_-]{8}$/.test(value.password))
53
+ throw fail();
54
+ return value;
55
+ }
56
+ /** The original publication authority is encrypted under the existing pairing
57
+ * secret. It is not a public capacity receipt and is never an execution grant.
58
+ * No pairing token or plaintext VNC password is written into this directory. */
59
+ class EntityboxCapacityPublicationStore {
60
+ directory;
61
+ constructor(directory, create = false) {
62
+ if (!path.isAbsolute(directory) || directory.startsWith('\\\\'))
63
+ throw fail();
64
+ if (create)
65
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
66
+ if (fs.lstatSync(directory).isSymbolicLink())
67
+ throw fail();
68
+ const root = fs.realpathSync(directory), target = path.join(root, 'publication-private');
69
+ if (create)
70
+ fs.mkdirSync(target, { recursive: true, mode: 0o700 });
71
+ if (fs.lstatSync(target).isSymbolicLink() || !fs.lstatSync(target).isDirectory())
72
+ throw fail();
73
+ this.directory = fs.realpathSync(target);
74
+ }
75
+ file(id) { if (!uuid.test(id))
76
+ throw fail(); return path.join(this.directory, id + '.sealed'); }
77
+ key(token, runtimeId) { if (typeof token !== 'string' || !token || token.length > 16384 || !uuid.test(runtimeId))
78
+ throw fail(); return (0, crypto_1.createHmac)('sha256', token).update('entitybox-capacity-publication-v1\0' + runtimeId).digest(); }
79
+ aad(id, runtimeId) { return Buffer.from((0, entitybox_recovery_store_1.entityboxCanonical)({ version: 1, id, runtimeId })); }
80
+ read(id, runtimeId, token) {
81
+ try {
82
+ const file = this.file(id), stat = fs.lstatSync(file);
83
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 16384)
84
+ throw fail();
85
+ const envelope = JSON.parse(fs.readFileSync(file, 'utf8'));
86
+ if (!envelope || Object.keys(envelope).sort().join(',') !== 'body,iv,tag,version' || envelope.version !== 1)
87
+ throw fail();
88
+ const decode = (value) => { if (typeof value !== 'string' || !value)
89
+ throw fail(); const b = Buffer.from(value, 'base64'); if (b.toString('base64') !== value)
90
+ throw fail(); return b; };
91
+ const iv = decode(envelope.iv), tag = decode(envelope.tag), body = decode(envelope.body);
92
+ if (iv.length !== 12 || tag.length !== 16 || body.length > 8192)
93
+ throw fail();
94
+ const decipher = (0, crypto_1.createDecipheriv)('aes-256-gcm', this.key(token, runtimeId), iv);
95
+ decipher.setAAD(this.aad(id, runtimeId));
96
+ decipher.setAuthTag(tag);
97
+ const value = validate(JSON.parse(Buffer.concat([decipher.update(body), decipher.final()]).toString('utf8')));
98
+ if (value.allocation.id !== id || value.allocation.runtimeId !== runtimeId)
99
+ throw fail();
100
+ return value;
101
+ }
102
+ catch {
103
+ throw fail();
104
+ }
105
+ }
106
+ write(value, token) {
107
+ validate(value);
108
+ const { id, runtimeId } = value.allocation, file = this.file(id), iv = (0, crypto_1.randomBytes)(12), cipher = (0, crypto_1.createCipheriv)('aes-256-gcm', this.key(token, runtimeId), iv);
109
+ cipher.setAAD(this.aad(id, runtimeId));
110
+ const body = Buffer.concat([cipher.update((0, entitybox_recovery_store_1.entityboxCanonical)(value), 'utf8'), cipher.final()]);
111
+ const text = (0, entitybox_recovery_store_1.entityboxCanonical)({ version: 1, iv: iv.toString('base64'), tag: cipher.getAuthTag().toString('base64'), body: body.toString('base64') }), pending = path.join(this.directory, 'pending-' + (0, crypto_1.randomUUID)() + '.sealed');
112
+ let fd;
113
+ try {
114
+ fd = fs.openSync(pending, 'wx', 0o600);
115
+ fs.writeFileSync(fd, text);
116
+ fs.fsyncSync(fd);
117
+ fs.closeSync(fd);
118
+ fd = undefined;
119
+ try {
120
+ fs.linkSync(pending, file);
121
+ }
122
+ catch (error) {
123
+ if (error.code !== 'EEXIST' || (0, entitybox_recovery_store_1.entityboxCanonical)(this.read(id, runtimeId, token)) !== (0, entitybox_recovery_store_1.entityboxCanonical)(value))
124
+ throw fail();
125
+ }
126
+ if (process.platform !== 'win32') {
127
+ const dir = fs.openSync(this.directory, 'r');
128
+ try {
129
+ fs.fsyncSync(dir);
130
+ }
131
+ finally {
132
+ fs.closeSync(dir);
133
+ }
134
+ }
135
+ }
136
+ catch {
137
+ throw fail();
138
+ }
139
+ finally {
140
+ if (fd !== undefined)
141
+ fs.closeSync(fd);
142
+ try {
143
+ fs.unlinkSync(pending);
144
+ }
145
+ catch { }
146
+ }
147
+ }
148
+ }
149
+ exports.EntityboxCapacityPublicationStore = EntityboxCapacityPublicationStore;
150
+ /** Complete only a physically finished setup. This never invokes a provisioner
151
+ * or mutates Docker. Concurrent fresh processes publish the SAME original
152
+ * nonce, plan, password and receipt through the existing idempotent SQL path.
153
+ * A missing ready receipt remains unresolved; it is not permission to recreate. */
154
+ function createEntityboxCapacityPublicationRecovery(config, io) {
155
+ if (config.enabled !== true || config.recoverPublication !== true)
156
+ return undefined;
157
+ const allocation = Object.freeze({ id: config.allocation?.id, runtimeId: config.allocation?.runtimeId, desktopId: config.allocation?.desktopId }), directory = config.directory, imageId = config.imageId, context = config.dockerContext ?? 'desktop-linux';
158
+ if (![allocation.id, allocation.runtimeId, allocation.desktopId].every(v => typeof v === 'string' && uuid.test(v)) || !/^sha256:[a-f0-9]{64}$/.test(imageId))
159
+ throw fail();
160
+ let flight;
161
+ async function run() {
162
+ const pairing = io.pairing();
163
+ if (!pairing || pairing.runtimeId !== allocation.runtimeId || !pairing.daemonToken)
164
+ throw fail();
165
+ const originalToken = pairing.daemonToken;
166
+ const token = () => { const p = io.pairing(); if (!p || p.runtimeId !== allocation.runtimeId || p.daemonToken !== originalToken)
167
+ throw fail(); return originalToken; };
168
+ const state = new EntityboxCapacityPublicationStore(directory).read(allocation.id, allocation.runtimeId, token());
169
+ if ((0, entitybox_recovery_store_1.entityboxCanonical)(state.allocation) !== (0, entitybox_recovery_store_1.entityboxCanonical)(allocation) || state.plan.imageId !== imageId || state.plan.dockerContext !== context)
170
+ throw fail();
171
+ const read = () => (io.read ?? entitybox_capacity_provisioner_1.readEntityboxCapacity)(directory, allocation.desktopId, allocation.runtimeId);
172
+ const receipt = await read();
173
+ token();
174
+ if (receipt.planSha256 !== sha((0, entitybox_recovery_store_1.entityboxCanonical)(state.plan)) || receipt.dockerContext !== context)
175
+ throw fail();
176
+ const validateReply = (r, phase) => {
177
+ if (!r || r.id !== allocation.id || r.runtimeId !== allocation.runtimeId || r.desktopId !== allocation.desktopId || r.phase !== phase || (0, entitybox_recovery_store_1.entityboxCanonical)(r.plan) !== (0, entitybox_recovery_store_1.entityboxCanonical)(state.plan)
178
+ || phase === 'ready' && (0, entitybox_recovery_store_1.entityboxCanonical)(r.receipt) !== (0, entitybox_recovery_store_1.entityboxCanonical)(receipt))
179
+ throw fail();
180
+ };
181
+ const claimed = await io.rpc('entitybox_native_capacity_claim', { p_token: token(), p_id: allocation.id, p_nonce: state.nonce, p_plan: state.plan });
182
+ token();
183
+ if (claimed?.phase === 'ready') {
184
+ validateReply(claimed, 'ready');
185
+ return receipt;
186
+ }
187
+ validateReply(claimed, 'provisioning');
188
+ const current = await read();
189
+ token();
190
+ if ((0, entitybox_recovery_store_1.entityboxCanonical)(current) !== (0, entitybox_recovery_store_1.entityboxCanonical)(receipt))
191
+ throw fail();
192
+ const published = await io.rpc('entitybox_native_capacity_publish', { p_token: token(), p_id: allocation.id, p_nonce: state.nonce, p_receipt: receipt, p_vnc_password: state.password });
193
+ token();
194
+ validateReply(published, 'ready');
195
+ return receipt;
196
+ }
197
+ return { provision: () => { if (!flight)
198
+ flight = run().catch(() => { throw fail(); }).finally(() => { flight = undefined; }); return flight; } };
199
+ }