@platless/axom-cli 0.6.36 → 0.6.52

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 (159) hide show
  1. package/README.md +7 -1
  2. package/dist/axom-patch/cli/README.md +31 -2
  3. package/dist/axom-patch/cli/account-limits/generate.py +2 -2
  4. package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
  5. package/dist/axom-patch/cli/codex-terminal-output.json +2 -2
  6. package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
  7. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +1 -1
  8. package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
  9. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
  10. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +137 -41
  11. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +5 -88
  12. package/dist/axom-patch/cli/qoder-native-acp.json +23 -23
  13. package/dist/axom-patch/cli/runtime.json +3 -1
  14. package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
  15. package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
  16. package/dist/axom-patch/cli/zcode-account-limits.json +7 -7
  17. package/dist/axom-patch/cli/zcode-env-account.json +58 -0
  18. package/dist/axom-patch/cli/zcode-models.json +9 -9
  19. package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -10
  20. package/dist/cli.js +9 -11
  21. package/dist/commands/auth.js +4 -1
  22. package/dist/commands/update.js +68 -7
  23. package/dist/commands/workstation.js +94 -223
  24. package/dist/commands/workstationRegistrations.js +116 -0
  25. package/dist/credentialBroker.js +13 -2
  26. package/dist/credentials.js +16 -0
  27. package/dist/install/claude/commands.js +2 -2
  28. package/dist/keepers/terminal/keeper.mjs +1089 -673
  29. package/dist/keepers/terminal/manifest.json +4 -4
  30. package/dist/native/darwin-x64/axom-quic +0 -0
  31. package/dist/workstation/acp/acpAgents.js +11 -4
  32. package/dist/workstation/acp/outbox.js +12 -5
  33. package/dist/workstation/acp/permission.js +2 -2
  34. package/dist/workstation/acp/processor/checkpoint.js +4 -3
  35. package/dist/workstation/acp/processor/control.js +2 -2
  36. package/dist/workstation/acp/processor/jsonrpc.js +8 -0
  37. package/dist/workstation/acp/processor/session.js +3 -2
  38. package/dist/workstation/acp/processor/state.js +1 -0
  39. package/dist/workstation/acp/processor/transport/local.js +19 -3
  40. package/dist/workstation/acp/processor/updates.js +25 -0
  41. package/dist/workstation/acp/processorSession.js +12 -1
  42. package/dist/workstation/acp/registry/catalog.js +6 -6
  43. package/dist/workstation/acp/test/keeperWorld.js +4 -3
  44. package/dist/workstation/agentSessions.js +76 -23
  45. package/dist/workstation/channels/agent.js +9 -1
  46. package/dist/workstation/channels/computer.js +27 -4
  47. package/dist/workstation/channels/management.js +36 -5
  48. package/dist/workstation/config.js +18 -6
  49. package/dist/workstation/control/bindingStore.js +4 -0
  50. package/dist/workstation/control/commands.js +1 -0
  51. package/dist/workstation/control/daemonIdentity.js +90 -0
  52. package/dist/workstation/control/deviceCommands.js +191 -0
  53. package/dist/workstation/control/deviceProtocol.js +68 -0
  54. package/dist/workstation/control/locks.js +51 -4
  55. package/dist/workstation/control/rootFormat.js +16 -0
  56. package/dist/workstation/control/runtimeClient.js +60 -5
  57. package/dist/workstation/control/runtimeHost.js +96 -9
  58. package/dist/workstation/control/serverIdentity.js +2 -2
  59. package/dist/workstation/control/updateTransition.js +91 -0
  60. package/dist/workstation/daemon.js +262 -360
  61. package/dist/workstation/daemonctl.js +150 -12
  62. package/dist/workstation/desktopControl/automationDevice.js +219 -0
  63. package/dist/workstation/desktopControl/automationJson.js +74 -0
  64. package/dist/workstation/desktopControl/automationLocalState.js +23 -0
  65. package/dist/workstation/desktopControl/automationPreparation.js +1 -0
  66. package/dist/workstation/desktopControl/automationProtocol.js +253 -0
  67. package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
  68. package/dist/workstation/desktopControl/automationSession.js +81 -0
  69. package/dist/workstation/desktopControl/automationTransport.js +138 -0
  70. package/dist/workstation/desktopControl/capabilities.js +2 -1
  71. package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
  72. package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
  73. package/dist/workstation/desktopControl/deviceSockets.js +2 -2
  74. package/dist/workstation/desktopControl/helperProcess.js +250 -1
  75. package/dist/workstation/desktopControl/hostGrant.js +65 -0
  76. package/dist/workstation/desktopControl/lobby.js +4 -2
  77. package/dist/workstation/desktopControl/macProvider.js +41 -40
  78. package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
  79. package/dist/workstation/desktopControl/nativeProvider.js +31 -5
  80. package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
  81. package/dist/workstation/desktopControl/protocol.js +15 -6
  82. package/dist/workstation/desktopControl/supervisor.js +110 -17
  83. package/dist/workstation/desktopControl/wallpaper.js +26 -6
  84. package/dist/workstation/exec/policy.js +5 -0
  85. package/dist/workstation/files/client.js +10 -1
  86. package/dist/workstation/files/host.js +9 -2
  87. package/dist/workstation/journal.js +5 -2
  88. package/dist/workstation/keepers/acp/client.js +38 -2
  89. package/dist/workstation/keepers/kit/buildVersion.js +35 -11
  90. package/dist/workstation/keepers/kit/client.js +2 -2
  91. package/dist/workstation/keepers/kit/launcher.js +80 -4
  92. package/dist/workstation/keepers/legacy.js +16 -2
  93. package/dist/workstation/keepers/policy.js +5 -34
  94. package/dist/workstation/keepers/terminal/client.js +115 -23
  95. package/dist/workstation/keepers/terminal/exec.js +6 -3
  96. package/dist/workstation/keepers/terminal/frames.js +73 -0
  97. package/dist/workstation/keepers/terminal/generations.js +5 -8
  98. package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
  99. package/dist/workstation/keepers/terminal/input.js +146 -0
  100. package/dist/workstation/keepers/terminal/keeper.js +22 -8
  101. package/dist/workstation/keepers/terminal/main.js +1 -1
  102. package/dist/workstation/keepers/terminal/metadata.js +138 -0
  103. package/dist/workstation/keepers/terminal/protocol.js +4 -28
  104. package/dist/workstation/keepers/terminal/pty.js +1 -330
  105. package/dist/workstation/keepers/terminal/screen.js +246 -0
  106. package/dist/workstation/keepers/terminal/sessions.js +526 -0
  107. package/dist/workstation/oauth/deviceAuthorization.js +31 -17
  108. package/dist/workstation/protocol/management.js +23 -4
  109. package/dist/workstation/rd/artifact.js +4 -3
  110. package/dist/workstation/rd/control.js +3 -5
  111. package/dist/workstation/rd/host.js +213 -0
  112. package/dist/workstation/rd/migrationReadiness.js +46 -0
  113. package/dist/workstation/rd/provider.js +44 -10
  114. package/dist/workstation/rd/state.js +14 -2
  115. package/dist/workstation/rd/update.js +5 -8
  116. package/dist/workstation/rd/upkeep.js +44 -2
  117. package/dist/workstation/registrations/enrollment.js +250 -0
  118. package/dist/workstation/registrations/enrollmentPending.js +361 -0
  119. package/dist/workstation/registrations/migration.js +356 -0
  120. package/dist/workstation/registrations/model.js +34 -0
  121. package/dist/workstation/registrations/removal.js +222 -0
  122. package/dist/workstation/registrations/store.js +737 -0
  123. package/dist/workstation/runtime/desktopBinding.js +20 -0
  124. package/dist/workstation/runtime/deviceDesktop.js +193 -0
  125. package/dist/workstation/runtime/deviceRuntime.js +568 -0
  126. package/dist/workstation/runtime/deviceServices.js +117 -0
  127. package/dist/workstation/runtime/events.js +20 -0
  128. package/dist/workstation/runtime/ownership.js +100 -0
  129. package/dist/workstation/runtime/registrationRuntime.js +575 -0
  130. package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
  131. package/dist/workstation/runtime/removalActivity.js +184 -0
  132. package/dist/workstation/service/installer.js +93 -1
  133. package/dist/workstation/service/launchd.js +1 -0
  134. package/dist/workstation/service/systemd.js +1 -0
  135. package/dist/workstation/systemMetrics.js +3 -0
  136. package/dist/workstation/tools/client.js +8 -27
  137. package/dist/workstation/tools/info.js +6 -13
  138. package/dist/workstation/tools/terminalRouter.js +17 -172
  139. package/dist/workstation/tools/validation.js +55 -1
  140. package/dist/workstation/update/archiveEvidence.js +206 -0
  141. package/dist/workstation/update/coordinator.js +34 -13
  142. package/dist/workstation/update/deviceCompatibility.js +119 -0
  143. package/dist/workstation/update/deviceCoordinator.js +369 -0
  144. package/dist/workstation/update/deviceHealth.js +104 -0
  145. package/dist/workstation/update/deviceHealthProtocol.js +26 -0
  146. package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
  147. package/dist/workstation/update/deviceRollback.js +78 -0
  148. package/dist/workstation/update/globalInstall.js +80 -10
  149. package/dist/workstation/update/legacyBridge.js +414 -0
  150. package/dist/workstation/update/legacyOwner.js +39 -0
  151. package/dist/workstation/update/npmInstaller.js +2 -2
  152. package/dist/workstation/update/privateDurability.js +61 -0
  153. package/dist/workstation/update/recoveryPlan.js +124 -0
  154. package/dist/workstation/update/runUpdate.js +7 -1
  155. package/dist/workstation/update/taskRegistry.js +161 -3
  156. package/dist/workstation/update/watchdog.js +27427 -14410
  157. package/dist/workstation/update/watchdogProcess.js +72 -0
  158. package/package.json +7 -3
  159. package/dist/native/darwin-arm64/axom-quic +0 -0
@@ -0,0 +1,568 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { LocalControlError } from '../control/protocol.js';
3
+ import { assertRemovalNamespace } from '../registrations/migration.js';
4
+ export class TargetAdmissionPendingError extends Error {
5
+ hold;
6
+ constructor(hold, cause) {
7
+ super(`Target admission disposition remains pending: ${cause instanceof Error ? cause.message : 'Verification unavailable'}`, { cause });
8
+ this.hold = hold;
9
+ }
10
+ }
11
+ export class DeviceRuntime {
12
+ options;
13
+ members = new Map();
14
+ shutdown = new AbortController();
15
+ queue = Promise.resolve();
16
+ closing = false;
17
+ settledMembers = new Map();
18
+ targetHolds = new WeakMap();
19
+ pins = new Map();
20
+ maintenance;
21
+ membershipMutation;
22
+ constructor(options) {
23
+ this.options = options;
24
+ }
25
+ desktopActivity() {
26
+ try {
27
+ const value = this.options.desktopActivity?.() ?? { known: true, consumers: 0 };
28
+ if (typeof value.known !== 'boolean' || !Number.isSafeInteger(value.consumers) || value.consumers < 0 || value.consumers > 65536)
29
+ throw Error('invalid_desktop_activity');
30
+ return { ...value };
31
+ }
32
+ catch {
33
+ return { known: false, consumers: 1 };
34
+ }
35
+ }
36
+ snapshot() {
37
+ const registrations = [...this.members.values()].map(member => {
38
+ const pending = [...this.pins.get(member) ?? []].find(pin => pin.diagnostic);
39
+ const diagnostic = member.diagnostic ?? pending?.diagnostic;
40
+ return { id: member.registration.id, revision: member.registration.revision, state: member.state, ...(diagnostic ? { diagnostic } : {}), ...(member.cleanupPending ? { cleanupPending: true } : {}), ...(pending ? { admissionPending: true } : {}) };
41
+ });
42
+ return { stopped: this.shutdown.signal.aborted, desktopActivity: this.desktopActivity(), deviceRevision: createHash('sha256').update(JSON.stringify(registrations.map(member => [member.id, member.revision]))).digest('hex'), registrations };
43
+ }
44
+ enqueue(work) {
45
+ const result = this.queue.then(work);
46
+ this.queue = result.catch(() => { });
47
+ return result;
48
+ }
49
+ assertMutationAllowed() {
50
+ if (this.maintenance)
51
+ throw new LocalControlError('BUSY', 'Device update or maintenance owns membership admission');
52
+ }
53
+ beginMembershipMutation() {
54
+ this.assertMutationAllowed();
55
+ if (this.membershipMutation)
56
+ throw new LocalControlError('BUSY', 'An original membership mutation is already pending');
57
+ const original = {};
58
+ this.membershipMutation = original;
59
+ return () => { if (this.membershipMutation === original)
60
+ this.membershipMutation = undefined; };
61
+ }
62
+ registrationIdentity(id) {
63
+ const member = this.members.get(id);
64
+ return member?.state === 'running' && !member.cleanupPending ? Object.freeze({ ...member.registration }) : undefined;
65
+ }
66
+ acquireMaintenance(input) {
67
+ this.assertMutationAllowed();
68
+ if (this.membershipMutation)
69
+ throw new LocalControlError('BUSY', 'Original membership mutation must settle before maintenance');
70
+ if (!input.transactionId || this.closing || this.shutdown.signal.aborted)
71
+ throw new LocalControlError('BUSY', 'Device is not available for maintenance');
72
+ if (input.origin && this.registrationIdentity(input.origin.registrationId)?.revision !== input.origin.revision)
73
+ throw new LocalControlError('BINDING_CONFLICT', 'Update origin changed');
74
+ const state = { transactionId: input.transactionId, members: [], holds: [], released: false };
75
+ this.maintenance = state;
76
+ const ready = state.ready = (async () => {
77
+ await this.enqueue(async () => {
78
+ if (this.maintenance !== state || this.closing || this.shutdown.signal.aborted)
79
+ throw new LocalControlError('BUSY', 'Original maintenance reservation changed');
80
+ state.members = [...this.members.values()];
81
+ const inspection = await this.options.store.inspect();
82
+ if ((await this.options.store.removalIntents?.() ?? []).some(intent => !['completed', 'cancelled'].includes(intent.phase)))
83
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'An original persistent removal obligation prevents device maintenance');
84
+ if (!inspection.known || inspection.entries.length !== state.members.length || inspection.entries.some(entry => entry.state !== 'ready' || !state.members.some(member => member.registration.id === entry.id && member.registration.revision === entry.registration.revision)))
85
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original persistent membership and running members differ');
86
+ if (state.members.some(member => member.state !== 'running' || !member.runtime || member.cleanupPending || this.pins.get(member)?.size))
87
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original membership or admission is unsettled');
88
+ });
89
+ for (const member of state.members) {
90
+ let hold;
91
+ try {
92
+ hold = await this.prepareAdmission({ id: member.registration.id, expectedRevision: member.registration.revision, operationId: state.transactionId }, undefined, state);
93
+ }
94
+ catch (error) {
95
+ if (error instanceof TargetAdmissionPendingError)
96
+ state.holds.push({ hold: error.hold });
97
+ throw error;
98
+ }
99
+ const original = { hold };
100
+ state.holds.push(original);
101
+ original.resources = await this.prepareTargetResources(hold);
102
+ const pin = this.originalPin(hold);
103
+ if (!pin.inner || !pin.runtime.holdTargetEvidenceAdmission)
104
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original evidence maintenance admission is unavailable');
105
+ await pin.runtime.holdTargetEvidenceAdmission(pin.inner);
106
+ }
107
+ for (const member of state.members)
108
+ await member.runtime?.releaseStartupAdmission?.();
109
+ })();
110
+ void ready.catch(() => { });
111
+ return Object.freeze({ transactionId: input.transactionId, ready,
112
+ observe: async () => {
113
+ await ready;
114
+ if (this.maintenance !== state || state.released)
115
+ throw new LocalControlError('BINDING_CONFLICT', 'Original maintenance lease is no longer current');
116
+ const outcomes = await Promise.allSettled(state.holds.map(original => this.observeTargetResources(original.hold, original.resources)));
117
+ const desktop = this.desktopActivity();
118
+ const diagnostics = outcomes.flatMap(result => result.status === 'rejected' ? ['Original resource observation remains unknown'] : result.value.diagnostics);
119
+ if (!desktop.known)
120
+ diagnostics.push('Original device desktop obligations remain unknown');
121
+ const busy = desktop.consumers > 0 || outcomes.some(result => result.status === 'fulfilled' && result.value.status === 'busy');
122
+ const unknown = !desktop.known || outcomes.some(result => result.status === 'rejected' || result.value.status === 'unknown');
123
+ return { status: busy ? 'busy' : unknown || diagnostics.length ? 'unknown' : 'candidate', diagnostics };
124
+ },
125
+ release: complete => {
126
+ if (state.released)
127
+ return Promise.resolve();
128
+ if (state.releasing)
129
+ return state.releasing;
130
+ state.releasing = (async () => {
131
+ await ready.catch(() => { });
132
+ const failures = [];
133
+ for (const original of state.holds) {
134
+ if (original.released)
135
+ continue;
136
+ try {
137
+ if (original.resources)
138
+ await this.releaseTargetResources(original.hold, original.resources);
139
+ else
140
+ await this.releaseTargetAdmission(original.hold);
141
+ original.released = true;
142
+ }
143
+ catch (error) {
144
+ failures.push(error);
145
+ }
146
+ }
147
+ if (failures.length)
148
+ throw new AggregateError(failures, `Original maintenance restoration remains unknown; preserve the update reservation: ${failures.map(error => error instanceof Error ? error.message : 'Unknown original restoration failure').join('; ').slice(0, 512)}`);
149
+ await complete?.();
150
+ await this.enqueue(async () => {
151
+ if (this.maintenance !== state)
152
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original maintenance reservation changed');
153
+ state.released = true;
154
+ this.maintenance = undefined;
155
+ });
156
+ })().finally(() => { state.releasing = undefined; });
157
+ return state.releasing;
158
+ },
159
+ handoff: async () => {
160
+ await ready;
161
+ if (this.maintenance !== state || state.released)
162
+ throw new LocalControlError('BINDING_CONFLICT', 'Original device handoff reservation changed');
163
+ for (const original of state.holds) {
164
+ const observation = await this.observeTargetResources(original.hold, original.resources);
165
+ if (observation.status !== 'candidate')
166
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original device handoff is not positively idle');
167
+ }
168
+ const before = this.desktopActivity();
169
+ if (!before.known || before.consumers)
170
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original desktop handoff remains unsettled');
171
+ await this.stop('detach');
172
+ const after = this.desktopActivity();
173
+ if (!after.known || after.consumers)
174
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original desktop cleanup did not confirm handoff');
175
+ },
176
+ });
177
+ }
178
+ reconcile() {
179
+ return this.enqueue(async () => {
180
+ this.assertMutationAllowed();
181
+ if (this.closing || this.shutdown.signal.aborted)
182
+ return;
183
+ const exclusions = new Set((await this.options.store.removalIntents?.() ?? []).filter(intent => !['completed', 'cancelled'].includes(intent.phase)).map(intent => intent.registration.id));
184
+ const inspection = await this.options.store.inspect();
185
+ if (!inspection.known)
186
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Registration collection needs verification');
187
+ const enrollments = await this.options.store.pendingEnrollments?.() ?? [];
188
+ if (enrollments.some(enrollment => enrollment.registrationId === null))
189
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Pending enrollment identity needs verification before runtime startup');
190
+ for (const enrollment of enrollments)
191
+ if (enrollment.registrationId)
192
+ exclusions.add(enrollment.registrationId);
193
+ const desired = new Map(inspection.entries.filter(entry => entry.state === 'ready').map(entry => [entry.id, entry.registration]));
194
+ for (const [id, member] of this.members) {
195
+ if (exclusions.has(id))
196
+ continue;
197
+ const sameRevision = desired.get(id)?.revision === member.registration.revision;
198
+ const pins = this.pins.get(member);
199
+ if (pins?.size) {
200
+ // A disposed target disappears from the Store before its hold is finalized. Keep that
201
+ // expected removal valid; a replacement identity or failed cleanup still invalidates it.
202
+ if (!sameRevision || member.cleanupPending)
203
+ for (const pin of pins) {
204
+ if (!pin.disposed || desired.has(id) || member.cleanupPending)
205
+ pin.stale = true;
206
+ }
207
+ continue;
208
+ }
209
+ if (sameRevision && !member.cleanupPending)
210
+ continue;
211
+ try {
212
+ await member.runtime?.pauseAdmission().catch(() => { });
213
+ await member.runtime?.stop('detach');
214
+ this.members.delete(id);
215
+ }
216
+ catch {
217
+ member.state = 'failed';
218
+ member.cleanupPending = true;
219
+ member.diagnostic = 'Owned cleanup needs verification';
220
+ }
221
+ }
222
+ for (const registration of desired.values()) {
223
+ if (exclusions.has(registration.id))
224
+ continue;
225
+ const previous = this.members.get(registration.id);
226
+ const now = (this.options.now ?? Date.now)();
227
+ if (previous && (this.pins.get(previous)?.size || previous.state === 'running' || previous.cleanupPending || (previous.retryAt ?? 0) > now))
228
+ continue;
229
+ let runtime;
230
+ try {
231
+ const identity = await this.options.store.read(registration.id);
232
+ if (identity.registration.revision !== registration.revision)
233
+ throw new LocalControlError('BINDING_CONFLICT', 'Registration changed during reconciliation');
234
+ runtime = await this.options.factory(identity);
235
+ await runtime.start();
236
+ this.members.set(registration.id, { registration, runtime, state: 'running' });
237
+ }
238
+ catch (error) {
239
+ let cleanupPending = false;
240
+ try {
241
+ await runtime?.stop('detach');
242
+ }
243
+ catch {
244
+ cleanupPending = true;
245
+ }
246
+ const attempts = (previous?.attempts ?? 0) + 1;
247
+ this.members.set(registration.id, { registration, ...(cleanupPending ? { runtime, cleanupPending } : {}), state: 'failed', attempts, retryAt: now + Math.min(30_000, 1_000 * 2 ** Math.min(attempts - 1, 5)), diagnostic: cleanupPending ? 'Owned startup cleanup needs verification' : error instanceof LocalControlError ? error.code : 'Registration startup failed' });
248
+ }
249
+ }
250
+ });
251
+ }
252
+ /** Adopt a pending removal's durable sessions with new work paused, without starting other excluded registrations. */
253
+ async recoverRemovalRuntime(target) {
254
+ let recovered;
255
+ await this.enqueue(async () => {
256
+ this.assertMutationAllowed();
257
+ if (this.closing || this.shutdown.signal.aborted)
258
+ throw new LocalControlError('BUSY', 'Device is stopping');
259
+ const intent = (await this.options.store.removalIntents?.() ?? []).find(entry => entry.operationId === target.operationId);
260
+ if (!intent || intent.registration.id !== target.id || intent.registration.revision !== target.expectedRevision
261
+ || !['intent', 'sealed'].includes(intent.phase))
262
+ throw new LocalControlError('BINDING_CONFLICT', 'Pending removal target changed');
263
+ const existing = this.members.get(target.id);
264
+ if (existing) {
265
+ if (existing.registration.revision !== target.expectedRevision || existing.state !== 'running' || existing.cleanupPending)
266
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Target runtime cleanup remains pending');
267
+ return;
268
+ }
269
+ const identity = await this.options.store.read(target.id);
270
+ if (JSON.stringify(identity.registration) !== JSON.stringify(intent.registration))
271
+ throw new LocalControlError('BINDING_CONFLICT', 'Pending removal identity changed');
272
+ const runtime = await this.options.factory(identity);
273
+ try {
274
+ await runtime.pauseAdmission();
275
+ await runtime.start();
276
+ recovered = runtime;
277
+ this.members.set(target.id, { registration: identity.registration, runtime, state: 'running' });
278
+ }
279
+ catch (error) {
280
+ try {
281
+ await runtime.stop('detach');
282
+ }
283
+ catch {
284
+ this.members.set(target.id, { registration: identity.registration, runtime, state: 'failed', cleanupPending: true, diagnostic: 'Removal startup cleanup remains pending' });
285
+ }
286
+ throw error;
287
+ }
288
+ });
289
+ return async () => {
290
+ if (!recovered)
291
+ return;
292
+ await this.enqueue(async () => {
293
+ const member = this.members.get(target.id);
294
+ if (!member || member.runtime !== recovered || member.registration.revision !== target.expectedRevision || this.closing || this.shutdown.signal.aborted)
295
+ throw new LocalControlError('BINDING_CONFLICT', 'Recovered target runtime changed');
296
+ await recovered.resumeAdmission();
297
+ });
298
+ };
299
+ }
300
+ async prepareTargetAdmission(target, beforeRestore) {
301
+ return this.prepareAdmission(target, beforeRestore);
302
+ }
303
+ async prepareAdmission(target, beforeRestore, maintenance) {
304
+ const hold = Object.freeze({});
305
+ let pin;
306
+ await this.enqueue(async () => {
307
+ if (this.maintenance && this.maintenance !== maintenance)
308
+ throw new LocalControlError('BUSY', 'Device maintenance owns target admission');
309
+ const member = this.members.get(target.id);
310
+ if (this.closing || this.shutdown.signal.aborted || !target.operationId || !member?.runtime?.prepareTargetAdmission || member.state !== 'running' || member.cleanupPending || member.registration.revision !== target.expectedRevision)
311
+ throw new LocalControlError('BINDING_CONFLICT', 'Target runtime is unavailable or changed');
312
+ pin = { member, runtime: member.runtime, id: target.id, revision: target.expectedRevision, operationId: target.operationId, active: true, stale: false, beforeRestore };
313
+ const pins = this.pins.get(member) ?? new Set();
314
+ pins.add(pin);
315
+ this.pins.set(member, pins);
316
+ this.targetHolds.set(hold, pin);
317
+ });
318
+ try {
319
+ pin.inner = await pin.runtime.prepareTargetAdmission(target);
320
+ const identity = await this.options.store.read(pin.id);
321
+ await this.enqueue(async () => {
322
+ if (identity.registration.revision !== pin.revision)
323
+ pin.stale = true;
324
+ this.originalPin(hold);
325
+ });
326
+ return hold;
327
+ }
328
+ catch (error) {
329
+ await this.enqueue(async () => {
330
+ if (!pin.inner) {
331
+ this.unpin(pin);
332
+ return;
333
+ }
334
+ pin.diagnostic = 'Target admission identity needs verification';
335
+ if (pin.stale)
336
+ this.retirePin(pin);
337
+ });
338
+ if (pin.inner)
339
+ throw new TargetAdmissionPendingError(hold, error);
340
+ throw error;
341
+ }
342
+ }
343
+ originalPin(hold) {
344
+ const pin = this.targetHolds.get(hold);
345
+ if (!pin || pin.stale || this.shutdown.signal.aborted || this.members.get(pin.id) !== pin.member || pin.member.runtime !== pin.runtime || pin.member.registration.revision !== pin.revision)
346
+ throw new LocalControlError('BINDING_CONFLICT', 'Invalid or stale target admission hold');
347
+ return pin;
348
+ }
349
+ unpin(pin) {
350
+ pin.active = false;
351
+ const pins = this.pins.get(pin.member);
352
+ pins?.delete(pin);
353
+ if (pins?.size === 0)
354
+ this.pins.delete(pin.member);
355
+ }
356
+ retirePin(pin) {
357
+ pin.stale = true;
358
+ if (this.members.get(pin.id) === pin.member) {
359
+ pin.member.state = 'failed';
360
+ pin.member.cleanupPending = true;
361
+ pin.member.diagnostic = 'Changed target admission requires original runtime cleanup';
362
+ }
363
+ this.unpin(pin);
364
+ }
365
+ observeTargetAdmission(hold) {
366
+ const pin = this.originalPin(hold);
367
+ if (!pin.active || !pin.inner || !pin.runtime.observeTargetAdmission)
368
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Target preparation is unavailable');
369
+ const observation = pin.runtime.observeTargetAdmission(pin.inner);
370
+ return pin.diagnostic ? { ...observation, status: 'unknown', diagnostics: [pin.diagnostic, ...observation.diagnostics] } : observation;
371
+ }
372
+ async prepareTargetResources(hold) {
373
+ const pin = this.originalPin(hold);
374
+ if (!pin.active || !pin.inner || !pin.runtime.prepareTargetResources)
375
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original target resource preparation is unavailable');
376
+ if (pin.resources)
377
+ throw new LocalControlError('BUSY', 'Original target resources are already prepared');
378
+ const preparation = await pin.runtime.prepareTargetResources(pin.inner, () => this.releaseTargetAdmission(hold));
379
+ if (this.originalPin(hold) !== pin)
380
+ throw new LocalControlError('BINDING_CONFLICT', 'Original target changed during preparation');
381
+ pin.resources = preparation;
382
+ return preparation;
383
+ }
384
+ resourcePin(hold, preparation) {
385
+ const pin = this.originalPin(hold);
386
+ if (!pin.active || pin.disposed || pin.resources !== preparation)
387
+ throw new LocalControlError('BINDING_CONFLICT', 'Invalid or stale original target resources');
388
+ return pin;
389
+ }
390
+ async observeTargetResources(hold, preparation) {
391
+ const pin = this.resourcePin(hold, preparation);
392
+ if (!pin.runtime.observeTargetResources)
393
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original resource observer is unavailable');
394
+ const observation = await pin.runtime.observeTargetResources(preparation);
395
+ this.resourcePin(hold, preparation);
396
+ return observation;
397
+ }
398
+ async sealTargetResources(hold, preparation, mode) {
399
+ const pin = this.resourcePin(hold, preparation);
400
+ if (!pin.runtime.sealTargetResources)
401
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original birth boundary is unavailable');
402
+ await pin.runtime.sealTargetResources(preparation, mode);
403
+ this.resourcePin(hold, preparation);
404
+ }
405
+ async settleTargetResources(hold, preparation, namespace) {
406
+ const pin = this.resourcePin(hold, preparation);
407
+ assertRemovalNamespace(namespace);
408
+ if (namespace.registrationId !== pin.id || namespace.revision !== pin.revision || !pin.runtime.settleTargetResources)
409
+ throw new LocalControlError('BINDING_CONFLICT', 'Original resource namespace changed');
410
+ const settlement = await pin.runtime.settleTargetResources(preparation, namespace);
411
+ this.resourcePin(hold, preparation);
412
+ assertRemovalNamespace(namespace);
413
+ return settlement;
414
+ }
415
+ async disposeTargetResources(hold, preparation, settlement, namespace) {
416
+ const pin = this.resourcePin(hold, preparation);
417
+ assertRemovalNamespace(namespace);
418
+ if (namespace.registrationId !== pin.id || namespace.revision !== pin.revision || !pin.runtime.disposeTargetResources)
419
+ throw new LocalControlError('BINDING_CONFLICT', 'Original disposition namespace changed');
420
+ await pin.runtime.disposeTargetResources(settlement, namespace);
421
+ this.resourcePin(hold, preparation);
422
+ assertRemovalNamespace(namespace);
423
+ pin.disposed = true;
424
+ pin.diagnostic = 'Target resources disposed; durable removal remains pending';
425
+ }
426
+ async prepareTargetDisposition(hold, preparation, namespace) {
427
+ const pin = this.resourcePin(hold, preparation);
428
+ assertRemovalNamespace(namespace);
429
+ if (namespace.registrationId !== pin.id || namespace.revision !== pin.revision || !pin.runtime.prepareTargetDisposition)
430
+ throw new LocalControlError('BINDING_CONFLICT', 'Original target disposition preparation changed');
431
+ const settlement = await pin.runtime.prepareTargetDisposition(preparation, namespace);
432
+ this.resourcePin(hold, preparation);
433
+ assertRemovalNamespace(namespace);
434
+ return settlement;
435
+ }
436
+ async releaseTargetResources(hold, preparation) {
437
+ const pin = this.originalPin(hold);
438
+ if (pin.disposed || pin.resources !== preparation)
439
+ throw new LocalControlError('BINDING_CONFLICT', 'Invalid or stale original target resources');
440
+ if (pin.resourcesReleased)
441
+ return;
442
+ if (!pin.runtime.releaseTargetResources)
443
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original resource restoration is unavailable');
444
+ await pin.runtime.releaseTargetResources(preparation);
445
+ if (this.originalPin(hold) !== pin || pin.active)
446
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original target admission restoration is incomplete');
447
+ pin.resourcesReleased = true;
448
+ }
449
+ async releaseTargetAdmission(hold) {
450
+ const original = this.targetHolds.get(hold);
451
+ if (!original)
452
+ throw new LocalControlError('BINDING_CONFLICT', 'Invalid target admission hold');
453
+ if (!original.active && !original.stale) {
454
+ this.originalPin(hold);
455
+ return;
456
+ }
457
+ if (original.restoration)
458
+ return original.restoration;
459
+ let restoration;
460
+ let identity;
461
+ try {
462
+ identity = await this.options.store.read(original.id);
463
+ }
464
+ catch (error) {
465
+ await this.enqueue(async () => { original.diagnostic = 'Target admission identity needs verification'; });
466
+ throw new TargetAdmissionPendingError(hold, error);
467
+ }
468
+ await this.enqueue(async () => {
469
+ if (identity.registration.revision !== original.revision)
470
+ original.stale = true;
471
+ try {
472
+ const pin = this.originalPin(hold);
473
+ if (!pin.inner || !pin.runtime.releaseTargetAdmission)
474
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Target restoration is unavailable');
475
+ restoration = pin.restoration ??= this.restorePin(pin, hold);
476
+ }
477
+ catch (error) {
478
+ if (original.stale || this.shutdown.signal.aborted)
479
+ this.retirePin(original);
480
+ else
481
+ original.diagnostic = 'Target admission restoration needs verification';
482
+ throw new TargetAdmissionPendingError(hold, error);
483
+ }
484
+ });
485
+ await restoration;
486
+ }
487
+ async restorePin(original, hold) {
488
+ try {
489
+ await original.beforeRestore?.();
490
+ await original.runtime.releaseTargetAdmission(original.inner);
491
+ }
492
+ catch (error) {
493
+ await this.enqueue(async () => { original.restoration = undefined; original.diagnostic = 'Target admission restoration needs verification'; });
494
+ throw new TargetAdmissionPendingError(hold, error);
495
+ }
496
+ await this.enqueue(async () => {
497
+ original.restoration = undefined;
498
+ if (original.stale || this.shutdown.signal.aborted) {
499
+ this.retirePin(original);
500
+ throw new TargetAdmissionPendingError(hold, new Error('Target changed during disposition'));
501
+ }
502
+ original.diagnostic = undefined;
503
+ this.unpin(original);
504
+ });
505
+ }
506
+ async finalizeTargetRemoval(hold) {
507
+ await this.enqueue(async () => {
508
+ const pin = this.originalPin(hold);
509
+ if (!pin.active || !pin.disposed)
510
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original disposition is incomplete');
511
+ const inspection = await this.options.store.inspect();
512
+ if (!inspection.known || inspection.entries.some(entry => entry.id === pin.id))
513
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original registration membership remains present');
514
+ this.members.delete(pin.id);
515
+ this.unpin(pin);
516
+ this.targetHolds.delete(hold);
517
+ });
518
+ }
519
+ async activities() {
520
+ const result = new Map();
521
+ for (const [id, member] of this.members)
522
+ result.set(id, member.runtime ? await member.runtime.activity().catch(() => null) : null);
523
+ return result;
524
+ }
525
+ async activitySnapshot() { return { registrations: await this.activities(), desktop: this.desktopActivity() }; }
526
+ async connections() {
527
+ const result = new Map();
528
+ for (const [id, member] of this.members)
529
+ result.set(id, await member.runtime?.connection?.().catch(() => 'unknown') ?? 'unknown');
530
+ return result;
531
+ }
532
+ stop(mode) {
533
+ this.closing = true;
534
+ return this.enqueue(async () => {
535
+ const members = [...this.members.values()];
536
+ const pauses = await Promise.allSettled(members.map(async (member) => member.runtime?.pauseAdmission()));
537
+ const failures = pauses.flatMap(result => result.status === 'rejected' ? [result.reason] : []);
538
+ for (const member of members) {
539
+ try {
540
+ if (!member.runtime && mode === 'terminate')
541
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original registration shutdown authority is unavailable');
542
+ await member.runtime?.stop(mode);
543
+ this.settledMembers.set(member.registration.id, { revision: member.registration.revision, mode });
544
+ this.members.delete(member.registration.id);
545
+ }
546
+ catch (error) {
547
+ member.state = 'failed';
548
+ member.cleanupPending = true;
549
+ member.diagnostic = 'Owned shutdown needs verification';
550
+ failures.push(error);
551
+ }
552
+ }
553
+ if (mode === 'terminate') {
554
+ try {
555
+ const inspection = await this.options.store.inspect();
556
+ if (!inspection.known || inspection.entries.some(entry => entry.state !== 'ready' || this.settledMembers.get(entry.id)?.revision !== entry.registration.revision || this.settledMembers.get(entry.id)?.mode !== 'terminate'))
557
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Every registration requires its original shutdown settlement');
558
+ }
559
+ catch (error) {
560
+ failures.push(error);
561
+ }
562
+ }
563
+ if (failures.length)
564
+ throw new AggregateError(failures, 'Original device shutdown remains pending');
565
+ this.shutdown.abort();
566
+ });
567
+ }
568
+ }