@viewportai/daemon 0.25.12 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/cli/check-command.d.ts +2 -0
  2. package/dist/cli/check-command.d.ts.map +1 -0
  3. package/dist/cli/check-command.js +168 -0
  4. package/dist/cli/check-command.js.map +1 -0
  5. package/dist/cli/commands.d.ts +2 -0
  6. package/dist/cli/commands.d.ts.map +1 -1
  7. package/dist/cli/commands.js +2 -0
  8. package/dist/cli/commands.js.map +1 -1
  9. package/dist/cli/diagnostic-command.d.ts +2 -0
  10. package/dist/cli/diagnostic-command.d.ts.map +1 -0
  11. package/dist/cli/diagnostic-command.js +102 -0
  12. package/dist/cli/diagnostic-command.js.map +1 -0
  13. package/dist/cli/lifecycle-commands.d.ts.map +1 -1
  14. package/dist/cli/lifecycle-commands.js +2 -1
  15. package/dist/cli/lifecycle-commands.js.map +1 -1
  16. package/dist/cli/lifecycle-pair-command.d.ts.map +1 -1
  17. package/dist/cli/lifecycle-pair-command.js +31 -0
  18. package/dist/cli/lifecycle-pair-command.js.map +1 -1
  19. package/dist/cli/policy-schema-validator.d.ts +221 -0
  20. package/dist/cli/policy-schema-validator.d.ts.map +1 -0
  21. package/dist/cli/policy-schema-validator.js +280 -0
  22. package/dist/cli/policy-schema-validator.js.map +1 -0
  23. package/dist/cli/support-packet.d.ts +7 -0
  24. package/dist/cli/support-packet.d.ts.map +1 -0
  25. package/dist/cli/support-packet.js +19 -0
  26. package/dist/cli/support-packet.js.map +1 -0
  27. package/dist/cli/worker-command.d.ts.map +1 -1
  28. package/dist/cli/worker-command.js +367 -14
  29. package/dist/cli/worker-command.js.map +1 -1
  30. package/dist/cli/worker-process-lock.d.ts +27 -0
  31. package/dist/cli/worker-process-lock.d.ts.map +1 -0
  32. package/dist/cli/worker-process-lock.js +150 -0
  33. package/dist/cli/worker-process-lock.js.map +1 -0
  34. package/dist/cli/worker-profile.d.ts +20 -0
  35. package/dist/cli/worker-profile.d.ts.map +1 -1
  36. package/dist/cli/worker-profile.js +38 -0
  37. package/dist/cli/worker-profile.js.map +1 -1
  38. package/dist/cli/worker-runtime.d.ts +52 -2
  39. package/dist/cli/worker-runtime.d.ts.map +1 -1
  40. package/dist/cli/worker-runtime.js +319 -102
  41. package/dist/cli/worker-runtime.js.map +1 -1
  42. package/dist/cli/workflow-managed-worker-format.d.ts.map +1 -1
  43. package/dist/cli/workflow-managed-worker-format.js +3 -1
  44. package/dist/cli/workflow-managed-worker-format.js.map +1 -1
  45. package/dist/cli/workflow-managed-worker.d.ts.map +1 -1
  46. package/dist/cli/workflow-managed-worker.js +157 -20
  47. package/dist/cli/workflow-managed-worker.js.map +1 -1
  48. package/dist/core/logger.d.ts.map +1 -1
  49. package/dist/core/logger.js +6 -0
  50. package/dist/core/logger.js.map +1 -1
  51. package/dist/core/output.d.ts.map +1 -1
  52. package/dist/core/output.js +5 -4
  53. package/dist/core/output.js.map +1 -1
  54. package/dist/core/redaction.d.ts +4 -0
  55. package/dist/core/redaction.d.ts.map +1 -0
  56. package/dist/core/redaction.js +76 -0
  57. package/dist/core/redaction.js.map +1 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +5 -1
  61. package/dist/index.js.map +1 -1
  62. package/dist/workflows/action-provider-adapters.js +52 -4
  63. package/dist/workflows/action-provider-adapters.js.map +1 -1
  64. package/dist/workflows/checkout-node.d.ts +1 -0
  65. package/dist/workflows/checkout-node.d.ts.map +1 -1
  66. package/dist/workflows/checkout-node.js +9 -4
  67. package/dist/workflows/checkout-node.js.map +1 -1
  68. package/dist/workflows/event-types.d.ts +1 -1
  69. package/dist/workflows/event-types.d.ts.map +1 -1
  70. package/dist/workflows/git-publish-node.d.ts +20 -0
  71. package/dist/workflows/git-publish-node.d.ts.map +1 -1
  72. package/dist/workflows/git-publish-node.js +87 -3
  73. package/dist/workflows/git-publish-node.js.map +1 -1
  74. package/dist/workflows/node-executor.d.ts +6 -0
  75. package/dist/workflows/node-executor.d.ts.map +1 -1
  76. package/dist/workflows/node-executor.js +53 -2
  77. package/dist/workflows/node-executor.js.map +1 -1
  78. package/dist/workflows/node-registry.d.ts +2 -2
  79. package/dist/workflows/node-registry.d.ts.map +1 -1
  80. package/dist/workflows/node-registry.js +129 -5
  81. package/dist/workflows/node-registry.js.map +1 -1
  82. package/dist/workflows/parser.d.ts.map +1 -1
  83. package/dist/workflows/parser.js +17 -1
  84. package/dist/workflows/parser.js.map +1 -1
  85. package/dist/workflows/platform-command-applier.d.ts +4 -0
  86. package/dist/workflows/platform-command-applier.d.ts.map +1 -1
  87. package/dist/workflows/platform-command-applier.js +152 -1
  88. package/dist/workflows/platform-command-applier.js.map +1 -1
  89. package/dist/workflows/platform-runtime-command.d.ts +2 -0
  90. package/dist/workflows/platform-runtime-command.d.ts.map +1 -1
  91. package/dist/workflows/platform-runtime-command.js +2 -0
  92. package/dist/workflows/platform-runtime-command.js.map +1 -1
  93. package/dist/workflows/platform-sync-payload.js +18 -4
  94. package/dist/workflows/platform-sync-payload.js.map +1 -1
  95. package/dist/workflows/platform-sync.js +7 -2
  96. package/dist/workflows/platform-sync.js.map +1 -1
  97. package/dist/workflows/policy-enforcement.d.ts +43 -0
  98. package/dist/workflows/policy-enforcement.d.ts.map +1 -0
  99. package/dist/workflows/policy-enforcement.js +0 -0
  100. package/dist/workflows/policy-enforcement.js.map +1 -0
  101. package/dist/workflows/run-types.d.ts +1 -0
  102. package/dist/workflows/run-types.d.ts.map +1 -1
  103. package/dist/workflows/runner.d.ts +4 -1
  104. package/dist/workflows/runner.d.ts.map +1 -1
  105. package/dist/workflows/runner.js +46 -12
  106. package/dist/workflows/runner.js.map +1 -1
  107. package/dist/workflows/types.d.ts +33 -0
  108. package/dist/workflows/types.d.ts.map +1 -1
  109. package/dist/workflows/workflow-authority-contract.d.ts +1 -1
  110. package/dist/workflows/workflow-authority-contract.d.ts.map +1 -1
  111. package/dist/workflows/workflow-authority-contract.js +23 -0
  112. package/dist/workflows/workflow-authority-contract.js.map +1 -1
  113. package/dist/workflows/workflow-production-schema.d.ts +13 -0
  114. package/dist/workflows/workflow-production-schema.d.ts.map +1 -1
  115. package/dist/workflows/workflow-production-schema.js +26 -0
  116. package/dist/workflows/workflow-production-schema.js.map +1 -1
  117. package/dist/workflows/workflow-production-types.d.ts +6 -0
  118. package/dist/workflows/workflow-production-types.d.ts.map +1 -1
  119. package/dist/workflows/workflow-schema.d.ts +50 -0
  120. package/dist/workflows/workflow-schema.d.ts.map +1 -1
  121. package/dist/workflows/workflow-schema.js +41 -0
  122. package/dist/workflows/workflow-schema.js.map +1 -1
  123. package/package.json +6 -3
@@ -11,99 +11,169 @@ import { GitTracker } from '../tracking/git-tracker.js';
11
11
  import { workflowRunToSyncPayload } from '../workflows/platform-sync-payload.js';
12
12
  import { WorkflowRunStore } from '../workflows/store.js';
13
13
  import { transportFetch } from './network.js';
14
+ import { acquireWorkerProcessLock } from './worker-process-lock.js';
15
+ import { readWorkerPairingRecord, workerProfileIntegrity, } from './worker-profile.js';
16
+ class HttpPollingTransport {
17
+ profile;
18
+ mode;
19
+ constructor(profile, mode) {
20
+ this.profile = profile;
21
+ this.mode = mode;
22
+ }
23
+ claim(body) {
24
+ return claimLeaseHttp(this.profile, body);
25
+ }
26
+ heartbeat(options) {
27
+ return heartbeatHttp(this.profile, {
28
+ ...options,
29
+ transport: this.mode,
30
+ });
31
+ }
32
+ sync(lease, execution) {
33
+ return syncLeaseHttp(this.profile, lease, execution);
34
+ }
35
+ cleanup(lease) {
36
+ return cleanupLeaseHttp(this.profile, lease);
37
+ }
38
+ pollRuntimeCommands(lease) {
39
+ return fetchHostedAssignmentHttp(this.profile, lease);
40
+ }
41
+ }
14
42
  const DEFAULT_HOSTED_LEASE_SECONDS = 1_800;
15
43
  export async function runStandaloneWorker(options) {
16
- const profile = await loadWorkerRuntimeProfile();
17
- await validateWorkerWorkspaceRoot(profile.workspaceRoot);
18
- const transport = options.transport ?? profile.transport;
19
- if (transport === 'inbound') {
20
- validateInboundWorkerGate(profile);
21
- }
22
- if (transport === 'relay') {
23
- throw new Error('Relay worker transport is not supported by the standalone runtime yet.');
24
- }
25
- let lastHeartbeatAt = Date.now();
26
- await heartbeat(profile, {
27
- status: 'online',
28
- healthStatus: 'idle',
29
- lifecycle: options.lifecycle,
30
- transport,
31
- });
32
- if (options.leaseToken) {
33
- const lease = { id: options.leaseToken, leaseToken: options.leaseToken };
34
- await syncLease(profile, lease, { status: 'completed' });
35
- await cleanupLease(profile, lease);
36
- return { claimed: 1, completed: 1, blocked: 0, failed: 0, cleanup: 1, denied: 0 };
37
- }
38
- const result = {
39
- claimed: 0,
40
- completed: 0,
41
- blocked: 0,
42
- failed: 0,
43
- cleanup: 0,
44
- denied: 0,
45
- };
44
+ const bootstrap = await loadWorkerRuntimeBootstrap(options.bootstrapPath);
45
+ const profile = bootstrap.profile;
46
+ let processLock = null;
46
47
  try {
47
- while (!options.abortSignal?.aborted) {
48
- const lease = await claimLease(profile, {
48
+ await validateWorkerWorkspaceRoot(profile.workspaceRoot);
49
+ const transport = options.transport ?? profile.transport;
50
+ if (transport === 'inbound') {
51
+ validateInboundWorkerGate(profile);
52
+ }
53
+ if (transport === 'relay') {
54
+ throw new Error('Relay worker transport is not supported by the standalone runtime yet.');
55
+ }
56
+ const workerTransport = new HttpPollingTransport(profile, transport);
57
+ processLock =
58
+ options.lifecycle === 'persistent' && !options.once
59
+ ? acquireWorkerProcessLock({
60
+ server: profile.serverUrl,
61
+ workspaceId: profile.workspaceId ?? profile.publicKeyFingerprint,
62
+ executorId: profile.managedExecutorId ?? profile.publicKeyFingerprint,
63
+ runnerProfile: runnerPoolFromCapabilities(profile.capabilities),
64
+ accessMode: transport,
65
+ })
66
+ : null;
67
+ let lastHeartbeatAt = Date.now();
68
+ await workerTransport.heartbeat({
69
+ status: 'online',
70
+ healthStatus: 'idle',
71
+ lifecycle: options.lifecycle,
72
+ });
73
+ if (bootstrap.lease) {
74
+ const result = await executeBootstrapLease(profile, workerTransport, bootstrap.lease);
75
+ await workerTransport.heartbeat({
76
+ status: 'offline',
77
+ healthStatus: 'offline',
49
78
  lifecycle: options.lifecycle,
50
- transport,
51
- leaseSeconds: options.leaseSeconds,
52
79
  });
53
- if (!lease) {
54
- if (options.once || options.lifecycle !== 'persistent')
55
- break;
56
- const now = Date.now();
57
- if (now - lastHeartbeatAt > 30_000) {
58
- await heartbeat(profile, {
59
- status: 'online',
60
- healthStatus: 'idle',
61
- lifecycle: options.lifecycle,
62
- transport,
63
- });
64
- lastHeartbeatAt = now;
80
+ return result;
81
+ }
82
+ if (options.leaseToken) {
83
+ const lease = { id: options.leaseToken, leaseToken: options.leaseToken };
84
+ await workerTransport.sync(lease, { status: 'completed' });
85
+ await workerTransport.cleanup(lease);
86
+ return { claimed: 1, completed: 1, blocked: 0, failed: 0, cleanup: 1, denied: 0 };
87
+ }
88
+ const result = {
89
+ claimed: 0,
90
+ completed: 0,
91
+ blocked: 0,
92
+ failed: 0,
93
+ cleanup: 0,
94
+ denied: 0,
95
+ };
96
+ try {
97
+ while (!options.abortSignal?.aborted) {
98
+ const lease = await workerTransport.claim({
99
+ lifecycle: options.lifecycle,
100
+ transport,
101
+ leaseSeconds: options.leaseSeconds,
102
+ });
103
+ if (!lease) {
104
+ if (options.once || options.lifecycle !== 'persistent')
105
+ break;
106
+ const now = Date.now();
107
+ if (now - lastHeartbeatAt > 30_000) {
108
+ await workerTransport.heartbeat({
109
+ status: 'online',
110
+ healthStatus: 'idle',
111
+ lifecycle: options.lifecycle,
112
+ });
113
+ lastHeartbeatAt = now;
114
+ }
115
+ await sleepWithAbort(options.pollIntervalMs ?? 5_000, options.abortSignal);
116
+ continue;
65
117
  }
66
- await sleepWithAbort(options.pollIntervalMs ?? 5_000, options.abortSignal);
67
- continue;
68
- }
69
- result.claimed += 1;
70
- let execution = await executeClaim(profile, lease);
71
- if (isHostedManagedExecutorProfile(profile) &&
72
- execution.status === 'blocked' &&
73
- execution.run) {
74
- await syncLease(profile, lease, execution);
75
- execution = await resumeBlockedHostedExecution(profile, lease, execution);
76
- if (execution.status !== 'blocked') {
77
- await syncLease(profile, lease, execution);
118
+ result.claimed += 1;
119
+ let execution = await executeClaim(profile, workerTransport, lease);
120
+ if (isHostedManagedExecutorProfile(profile) &&
121
+ execution.status === 'blocked' &&
122
+ execution.run) {
123
+ await workerTransport.sync(lease, execution);
124
+ execution = await resumeBlockedHostedExecution(profile, workerTransport, lease, execution);
125
+ if (execution.status !== 'blocked') {
126
+ await workerTransport.sync(lease, execution);
127
+ }
78
128
  }
129
+ else {
130
+ await workerTransport.sync(lease, execution);
131
+ }
132
+ if (execution.status === 'completed') {
133
+ result.completed += 1;
134
+ }
135
+ else if (execution.status === 'blocked') {
136
+ result.blocked += 1;
137
+ }
138
+ else {
139
+ result.failed += 1;
140
+ }
141
+ await workerTransport.cleanup(lease);
142
+ result.cleanup += 1;
143
+ if (options.once)
144
+ break;
79
145
  }
80
- else {
81
- await syncLease(profile, lease, execution);
82
- }
83
- if (execution.status === 'completed') {
84
- result.completed += 1;
85
- }
86
- else if (execution.status === 'blocked') {
87
- result.blocked += 1;
88
- }
89
- else {
90
- result.failed += 1;
91
- }
92
- await cleanupLease(profile, lease);
93
- result.cleanup += 1;
94
- if (options.once)
95
- break;
96
146
  }
147
+ finally {
148
+ await workerTransport.heartbeat({
149
+ status: 'offline',
150
+ healthStatus: 'offline',
151
+ lifecycle: options.lifecycle,
152
+ });
153
+ }
154
+ return result;
97
155
  }
98
156
  finally {
99
- await heartbeat(profile, {
100
- status: 'offline',
101
- healthStatus: 'offline',
102
- lifecycle: options.lifecycle,
103
- transport,
104
- });
157
+ processLock?.release();
158
+ await bootstrap.cleanup?.();
105
159
  }
106
- return result;
160
+ }
161
+ async function executeBootstrapLease(profile, transport, lease) {
162
+ const execution = await executeClaim(profile, transport, lease);
163
+ await transport.sync(lease, execution);
164
+ await transport.cleanup(lease);
165
+ return {
166
+ claimed: 1,
167
+ completed: execution.status === 'completed' ? 1 : 0,
168
+ blocked: execution.status === 'blocked' ? 1 : 0,
169
+ failed: execution.status === 'failed' || execution.status === 'canceled' ? 1 : 0,
170
+ cleanup: 1,
171
+ denied: 0,
172
+ };
173
+ }
174
+ function runnerPoolFromCapabilities(capabilities) {
175
+ const value = capabilities['runner_pool'] ?? capabilities['runnerPool'];
176
+ return typeof value === 'string' && value.trim() !== '' ? value : undefined;
107
177
  }
108
178
  async function validateWorkerWorkspaceRoot(workspaceRoot) {
109
179
  const resolved = path.resolve(workspaceRoot);
@@ -124,11 +194,17 @@ async function validateWorkerWorkspaceRoot(workspaceRoot) {
124
194
  throw new Error(`Worker workspace root is not readable and writable: ${resolved}.`);
125
195
  }
126
196
  }
127
- async function executeClaim(profile, lease) {
197
+ async function executeClaim(profile, transport, lease) {
128
198
  if (!isHostedManagedExecutorProfile(profile)) {
129
199
  return { status: 'completed' };
130
200
  }
131
- return executeHostedWorkflowClaim(profile, lease);
201
+ return executeHostedWorkflowClaim(profile, transport, lease);
202
+ }
203
+ async function loadWorkerRuntimeBootstrap(bootstrapPath) {
204
+ if (bootstrapPath) {
205
+ return loadSandboxBootstrap(bootstrapPath);
206
+ }
207
+ return { profile: await loadWorkerRuntimeProfile() };
132
208
  }
133
209
  async function loadWorkerRuntimeProfile() {
134
210
  const manager = new ConfigManager();
@@ -144,6 +220,11 @@ async function loadWorkerRuntimeProfile() {
144
220
  if (missing.length > 0) {
145
221
  throw new Error(`Worker profile is not configured: missing ${missing.join(', ')}.`);
146
222
  }
223
+ const pairing = await readWorkerPairingRecord(worker.stateDir);
224
+ const integrity = workerProfileIntegrity(worker, pairing);
225
+ if (!integrity.ok) {
226
+ throw new Error(`Worker profile does not match the approved pairing record: ${integrity.mismatches.join(', ')}. Run \`vpd worker reset\`, then pair this worker again.`);
227
+ }
147
228
  return {
148
229
  serverUrl: worker.serverUrl,
149
230
  serverId: worker.serverId,
@@ -161,6 +242,82 @@ async function loadWorkerRuntimeProfile() {
161
242
  capabilities: worker.capabilities ?? {},
162
243
  };
163
244
  }
245
+ async function loadSandboxBootstrap(bootstrapPath) {
246
+ const raw = JSON.parse(await fs.readFile(bootstrapPath, 'utf8'));
247
+ if (raw['schema'] !== 'viewport.sandbox_bootstrap/v1') {
248
+ throw new Error('Sandbox bootstrap file must use schema viewport.sandbox_bootstrap/v1.');
249
+ }
250
+ const workspaceRoot = requiredString(raw['workspace_root'] ?? raw['workspaceRoot'], 'workspace_root');
251
+ const identity = recordValue(raw['identity']);
252
+ const identityFile = await materializeBootstrapIdentity(identity, workspaceRoot);
253
+ const profile = {
254
+ serverUrl: requiredString(raw['server_url'] ?? raw['serverUrl'], 'server_url'),
255
+ serverId: stringValue(raw['server_id'] ?? raw['serverId']),
256
+ lifecycle: 'ephemeral',
257
+ transport: workerTransportValue(raw['transport']) ?? 'polling',
258
+ workspaceId: requiredString(raw['workspace_id'] ?? raw['workspaceId'], 'workspace_id'),
259
+ managedExecutorId: requiredString(raw['executor_id'] ?? raw['executorId'], 'executor_id'),
260
+ credential: requiredString(raw['credential'], 'credential'),
261
+ workspaceRoot,
262
+ identityKeyPath: identityFile.path,
263
+ publicKeyFingerprint: identityFile.publicKeyFingerprint,
264
+ capabilities: recordValue(raw['capabilities']) ?? {},
265
+ };
266
+ return {
267
+ profile,
268
+ lease: claimedLeaseFromBootstrap(recordValue(raw['lease'])),
269
+ cleanup: async () => {
270
+ if (identityFile.ephemeral) {
271
+ await fs.rm(identityFile.path, { force: true });
272
+ }
273
+ },
274
+ };
275
+ }
276
+ function claimedLeaseFromBootstrap(rawLease) {
277
+ if (!rawLease)
278
+ return undefined;
279
+ const id = requiredString(rawLease['id'] ?? rawLease['lease_id'] ?? rawLease['leaseId'], 'lease.id');
280
+ return {
281
+ id,
282
+ runId: stringValue(rawLease['workflow_run_id'] ?? rawLease['run_id'] ?? rawLease['runId']),
283
+ runtimeRunId: stringValue(rawLease['runtime_run_id'] ?? rawLease['runtimeRunId']),
284
+ leaseToken: stringValue(rawLease['lease_token'] ?? rawLease['leaseToken']),
285
+ assignmentClaimToken: stringValue(rawLease['assignment_claim_token'] ?? rawLease['assignmentClaimToken']),
286
+ yamlSnapshot: stringValue(rawLease['yaml_snapshot'] ?? rawLease['yamlSnapshot']),
287
+ sourceRef: stringValue(rawLease['source_ref'] ?? rawLease['sourceRef']),
288
+ directoryPath: stringValue(rawLease['directory_path'] ?? rawLease['directoryPath']),
289
+ inputSnapshot: recordValue(rawLease['input_snapshot'] ?? rawLease['inputSnapshot']),
290
+ resourceManifest: recordValue(rawLease['resource_manifest'] ?? rawLease['resourceManifest']),
291
+ workflowAuthorityContract: recordValue(rawLease['workflow_authority_contract'] ?? rawLease['workflowAuthorityContract']),
292
+ executionProfileSnapshot: recordValue(rawLease['execution_profile_snapshot'] ?? rawLease['executionProfileSnapshot']),
293
+ workflowSnapshot: recordValue(rawLease['workflow_snapshot'] ?? rawLease['workflowSnapshot']),
294
+ runtimeTargetId: stringValue(rawLease['runtime_target_id'] ?? rawLease['runtimeTargetId']),
295
+ dataCapturePolicy: dataCapturePolicyValue(rawLease['data_capture_policy'] ?? rawLease['dataCapturePolicy']),
296
+ };
297
+ }
298
+ async function materializeBootstrapIdentity(identity, workspaceRoot) {
299
+ if (!identity) {
300
+ throw new Error('Sandbox bootstrap file is missing identity.');
301
+ }
302
+ const publicKey = requiredString(identity['public_key'] ?? identity['publicKey'], 'identity.public_key');
303
+ const privateKey = requiredString(identity['private_key'] ?? identity['privateKey'], 'identity.private_key');
304
+ const publicKeyFingerprint = requiredString(identity['public_key_fingerprint'] ?? identity['publicKeyFingerprint'], 'identity.public_key_fingerprint');
305
+ const dir = path.join(workspaceRoot, '.viewport', 'bootstrap');
306
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 });
307
+ const filePath = path.join(dir, `identity-${crypto.randomUUID()}.json`);
308
+ await fs.writeFile(filePath, `${JSON.stringify({ publicKey, privateKey, publicKeyFingerprint })}\n`, { mode: 0o600 });
309
+ return { path: filePath, publicKeyFingerprint, ephemeral: true };
310
+ }
311
+ function workerTransportValue(value) {
312
+ return value === 'polling' || value === 'relay' || value === 'inbound' ? value : undefined;
313
+ }
314
+ function requiredString(value, label) {
315
+ const result = stringValue(value);
316
+ if (!result) {
317
+ throw new Error(`Sandbox bootstrap file is missing ${label}.`);
318
+ }
319
+ return result;
320
+ }
164
321
  function validateInboundWorkerGate(profile) {
165
322
  const enabled = profile.inbound?.enabled === true || process.env['VPD_WORKER_INBOUND_EXPERIMENTAL'] === '1';
166
323
  if (!enabled) {
@@ -179,7 +336,7 @@ function validateInboundWorkerGate(profile) {
179
336
  }
180
337
  throw new Error('Inbound worker transport listener is not implemented yet; do not enable inbound without the signed listener proof.');
181
338
  }
182
- async function claimLease(profile, body) {
339
+ async function claimLeaseHttp(profile, body) {
183
340
  const leaseSeconds = positiveInteger(body['leaseSeconds']) ?? DEFAULT_HOSTED_LEASE_SECONDS;
184
341
  const response = isHostedManagedExecutorProfile(profile)
185
342
  ? await hostedManagedExecutorFetch(profile, 'POST', 'claim', {
@@ -214,11 +371,13 @@ async function claimLease(profile, body) {
214
371
  inputSnapshot: recordValue(data['input_snapshot'] ?? rawLease['input_snapshot']),
215
372
  resourceManifest: recordValue(data['resource_manifest'] ?? rawLease['resource_manifest']),
216
373
  workflowAuthorityContract: recordValue(data['workflow_authority_contract'] ?? rawLease['workflow_authority_contract']),
374
+ executionProfileSnapshot: recordValue(data['execution_profile_snapshot'] ?? rawLease['execution_profile_snapshot']),
375
+ workflowSnapshot: recordValue(data['workflow_snapshot'] ?? rawLease['workflow_snapshot']),
217
376
  runtimeTargetId: stringValue(data['runtime_target_id'] ?? rawLease['runtime_target_id']),
218
377
  dataCapturePolicy: dataCapturePolicyValue(data['data_capture_policy'] ?? rawLease['data_capture_policy']),
219
378
  };
220
379
  }
221
- async function heartbeat(profile, options) {
380
+ async function heartbeatHttp(profile, options) {
222
381
  const capabilityPayload = managedExecutorCapabilities(profile.capabilities);
223
382
  if (isHostedManagedExecutorProfile(profile)) {
224
383
  await hostedManagedExecutorFetch(profile, 'POST', 'heartbeat', {
@@ -253,7 +412,7 @@ async function heartbeat(profile, options) {
253
412
  capabilities: profile.capabilities,
254
413
  });
255
414
  }
256
- async function syncLease(profile, lease, execution) {
415
+ async function syncLeaseHttp(profile, lease, execution) {
257
416
  const status = execution.status;
258
417
  if (isHostedManagedExecutorProfile(profile)) {
259
418
  if (!lease.runId) {
@@ -318,7 +477,7 @@ async function syncLease(profile, lease, execution) {
318
477
  runtime_event_id: `phase8-${lease.id}-${status}`,
319
478
  });
320
479
  }
321
- async function executeHostedWorkflowClaim(profile, lease) {
480
+ async function executeHostedWorkflowClaim(profile, transport, lease) {
322
481
  if (!lease.leaseToken) {
323
482
  return {
324
483
  status: 'failed',
@@ -336,8 +495,11 @@ async function executeHostedWorkflowClaim(profile, lease) {
336
495
  const existing = await existingHostedRuntimeRun(lease);
337
496
  if (existing) {
338
497
  if (existing.status === 'blocked') {
339
- const body = await fetchHostedAssignment(profile, lease);
340
- const applied = await daemon.workflowRunner.applyRuntimeCommandBody(existing.id, body);
498
+ const body = await transport.pollRuntimeCommands(lease);
499
+ const runtimeSecretEnv = await materializeHostedRunCredentials(profile, lease);
500
+ const applied = await daemon.workflowRunner.applyRuntimeCommandBody(existing.id, body, {
501
+ runtimeSecretEnv,
502
+ });
341
503
  if (applied > 0) {
342
504
  const completed = await waitForWorkflowRun(daemon, existing.id);
343
505
  return {
@@ -430,7 +592,10 @@ function credentialHandlesFromLease(lease) {
430
592
  const workflow = yamlSnapshotDocument(lease);
431
593
  const handles = new Set();
432
594
  for (const handle of [
433
- ...actionCredentialRefs(isRecord(workflow) ? workflow['nodes'] : undefined),
595
+ ...workflowNodeCredentialRefs(isRecord(workflow) ? workflow['nodes'] : undefined),
596
+ ...topLevelCredentialRefs(isRecord(workflow) ? workflow['credentials'] : undefined),
597
+ ...profileCredentialRefs(lease.executionProfileSnapshot),
598
+ ...profileCredentialRefs(lease.workflowSnapshot),
434
599
  ...providerActionCredentialRefs(lease.workflowAuthorityContract),
435
600
  ]) {
436
601
  handles.add(handle);
@@ -443,29 +608,72 @@ function repositoryForCredentialHandle(lease, handle) {
443
608
  if (!isRecord(nodes))
444
609
  return {};
445
610
  for (const node of Object.values(nodes)) {
446
- if (!isRecord(node) || stringValue(node['type']) !== 'action')
611
+ if (!isRecord(node))
447
612
  continue;
448
613
  const withValue = isRecord(node['with']) ? node['with'] : {};
449
- const credentialRef = stringValue(withValue['credential_ref']) ?? stringValue(withValue['credentialRef']);
614
+ const credentialRef = stringValue(withValue['credential_ref']) ??
615
+ stringValue(withValue['credentialRef']) ??
616
+ stringValue(node['credential_ref']) ??
617
+ stringValue(node['credentialRef']);
450
618
  if (credentialRef !== handle)
451
619
  continue;
452
- const repository = renderLeaseTemplate(stringValue(withValue['repository']) ?? stringValue(withValue['repo']), lease);
620
+ const repository = renderLeaseTemplate(repositoryTemplateForNode(node, withValue), lease);
453
621
  if (repository)
454
622
  return { repository };
455
623
  }
456
624
  return {};
457
625
  }
458
- function actionCredentialRefs(nodes) {
626
+ function workflowNodeCredentialRefs(nodes) {
459
627
  if (!isRecord(nodes))
460
628
  return [];
461
629
  return Object.values(nodes).flatMap((node) => {
462
- if (!isRecord(node) || stringValue(node['type']) !== 'action')
630
+ if (!isRecord(node))
631
+ return [];
632
+ const type = stringValue(node['type']);
633
+ if (type !== 'action' && type !== 'checkout' && type !== 'git_publish')
463
634
  return [];
464
635
  const withValue = isRecord(node['with']) ? node['with'] : {};
465
- const credentialRef = stringValue(withValue['credential_ref']) ?? stringValue(withValue['credentialRef']);
636
+ const credentialRef = stringValue(withValue['credential_ref']) ??
637
+ stringValue(withValue['credentialRef']) ??
638
+ stringValue(node['credential_ref']) ??
639
+ stringValue(node['credentialRef']);
466
640
  return credentialRef ? [credentialRef] : [];
467
641
  });
468
642
  }
643
+ function topLevelCredentialRefs(credentials) {
644
+ if (!isRecord(credentials))
645
+ return [];
646
+ const refs = [];
647
+ for (const value of Object.values(credentials)) {
648
+ refs.push(...credentialEntriesFrom(value));
649
+ }
650
+ return refs;
651
+ }
652
+ function profileCredentialRefs(snapshot) {
653
+ const credentials = pathValue(asRecord(snapshot), ['credentials']);
654
+ if (!isRecord(credentials))
655
+ return [];
656
+ const refs = [];
657
+ for (const value of Object.values(credentials)) {
658
+ refs.push(...credentialEntriesFrom(value));
659
+ }
660
+ return refs;
661
+ }
662
+ function credentialEntriesFrom(value) {
663
+ if (!Array.isArray(value))
664
+ return [];
665
+ return value.flatMap((entry) => {
666
+ if (typeof entry === 'string' && entry.trim() !== '')
667
+ return [entry.trim()];
668
+ if (!isRecord(entry))
669
+ return [];
670
+ const handle = stringValue(entry['handle']) ??
671
+ stringValue(entry['ref']) ??
672
+ stringValue(entry['credential_ref']) ??
673
+ stringValue(entry['credentialRef']);
674
+ return handle ? [handle] : [];
675
+ });
676
+ }
469
677
  function providerActionCredentialRefs(contract) {
470
678
  const entries = pathValue(asRecord(contract), ['credentials', 'provider_actions']);
471
679
  if (!Array.isArray(entries))
@@ -481,6 +689,12 @@ function providerActionCredentialRefs(contract) {
481
689
  return value ? [value] : [];
482
690
  });
483
691
  }
692
+ function repositoryTemplateForNode(node, withValue) {
693
+ return (stringValue(withValue['repository']) ??
694
+ stringValue(withValue['repo']) ??
695
+ stringValue(node['repository']) ??
696
+ stringValue(node['repo']));
697
+ }
484
698
  function renderLeaseTemplate(value, lease) {
485
699
  if (!value)
486
700
  return null;
@@ -514,7 +728,7 @@ async function existingHostedRuntimeRun(lease) {
514
728
  return null;
515
729
  return existing;
516
730
  }
517
- async function resumeBlockedHostedExecution(profile, lease, execution) {
731
+ async function resumeBlockedHostedExecution(profile, transport, lease, execution) {
518
732
  if (!execution.daemon || !execution.run || execution.status !== 'blocked') {
519
733
  return execution;
520
734
  }
@@ -522,14 +736,17 @@ async function resumeBlockedHostedExecution(profile, lease, execution) {
522
736
  const workflowRunId = execution.run.id;
523
737
  const deadline = Date.now() + 10 * 60_000;
524
738
  while (Date.now() < deadline) {
525
- const body = await fetchHostedAssignment(profile, lease);
739
+ const body = await transport.pollRuntimeCommands(lease);
526
740
  if (!hasRuntimeCommands(body)) {
527
741
  if (shouldRetryHostedCommandPoll(body)) {
528
742
  continue;
529
743
  }
530
744
  return execution;
531
745
  }
532
- const applied = await daemon.workflowRunner.applyRuntimeCommandBody(workflowRunId, body);
746
+ const runtimeSecretEnv = await materializeHostedRunCredentials(profile, lease);
747
+ const applied = await daemon.workflowRunner.applyRuntimeCommandBody(workflowRunId, body, {
748
+ runtimeSecretEnv,
749
+ });
533
750
  if (applied > 0) {
534
751
  const completed = await waitForWorkflowRun(daemon, workflowRunId);
535
752
  if (completed.status === 'blocked') {
@@ -538,7 +755,7 @@ async function resumeBlockedHostedExecution(profile, lease, execution) {
538
755
  run: completed,
539
756
  daemon,
540
757
  };
541
- await syncLease(profile, lease, blockedExecution);
758
+ await transport.sync(lease, blockedExecution);
542
759
  execution = blockedExecution;
543
760
  continue;
544
761
  }
@@ -555,7 +772,7 @@ async function resumeBlockedHostedExecution(profile, lease, execution) {
555
772
  }
556
773
  return execution;
557
774
  }
558
- async function fetchHostedAssignment(profile, lease) {
775
+ async function fetchHostedAssignmentHttp(profile, lease) {
559
776
  if (!lease.runId) {
560
777
  throw new Error('Hosted managed executor assignment polling requires a workflow run id.');
561
778
  }
@@ -626,7 +843,7 @@ function workflowRunFailure(run) {
626
843
  retrySafe: false,
627
844
  };
628
845
  }
629
- async function cleanupLease(profile, lease) {
846
+ async function cleanupLeaseHttp(profile, lease) {
630
847
  if (isHostedManagedExecutorProfile(profile)) {
631
848
  return;
632
849
  }