@nylorun/runtime 0.21.1-beta → 0.23.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +17 -3
- package/dist/adapters/execution/restate.d.ts +15 -3
- package/dist/adapters/execution/restate.js +77 -83
- package/dist/adapters/sandbox/local.js +1 -1
- package/dist/adapters/sandbox/virtual.js +1 -1
- package/dist/adapters/streams/s2.js +1 -13
- package/dist/api/components.d.ts +42 -2
- package/dist/core/flow-host.d.ts +1 -19
- package/dist/core/flow-host.js +8 -37
- package/dist/execution/types.d.ts +10 -8
- package/dist/execution/types.js +5 -7
- package/dist/gates/egress-credentials.d.ts +48 -0
- package/dist/gates/egress-credentials.js +147 -0
- package/dist/gates/egress.d.ts +3 -0
- package/dist/gates/egress.js +14 -1
- package/dist/gates/tenant-vaults.d.ts +4 -0
- package/dist/gates/tenant-vaults.js +2 -0
- package/dist/harness/calls.d.ts +2 -0
- package/dist/harness/calls.js +1 -0
- package/dist/harness/executors.js +1 -0
- package/dist/harness/main.js +4 -1
- package/dist/harness/pod.d.ts +2 -0
- package/dist/harness/pod.js +36 -1
- package/dist/harness-api/start.d.ts +2 -0
- package/dist/harness-api/start.js +4 -2
- package/dist/harness-api/ws-server.js +2 -2
- package/dist/host/execution.d.ts +8 -6
- package/dist/host/gates.js +11 -0
- package/dist/host/main.js +10 -16
- package/dist/host/stack-config.d.ts +6 -0
- package/dist/host/stack-config.js +6 -0
- package/dist/infra/execution.d.ts +18 -1
- package/dist/infra/execution.js +28 -4
- package/dist/infra/index.d.ts +1 -1
- package/dist/keys/egress-ca.d.ts +15 -0
- package/dist/keys/egress-ca.js +66 -0
- package/dist/keys/keys.d.ts +8 -1
- package/dist/keys/keys.js +6 -0
- package/dist/keys/x509.d.ts +29 -0
- package/dist/keys/x509.js +160 -0
- package/dist/management-openapi.json +195 -9
- package/dist/openapi.json +2 -1
- package/dist/retry.d.ts +21 -0
- package/dist/retry.js +34 -0
- package/dist/sandbox/join.d.ts +2 -0
- package/dist/sandbox/join.js +14 -10
- package/dist/sandbox/manager.d.ts +2 -0
- package/dist/sandbox/manager.js +1 -1
- package/dist/sandbox/pods/reconcile.js +3 -3
- package/dist/sandbox/tools.d.ts +3 -1
- package/dist/sandbox/tools.js +4 -2
- package/dist/sandbox/types.d.ts +5 -0
- package/dist/store/postgres/control.js +8 -1
- package/dist/store/postgres/drizzle/0018_wake_outbox_retries.sql +3 -0
- package/dist/store/postgres/drizzle/0019_sandbox_signal_outbox.sql +18 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +14 -0
- package/dist/store/postgres/schema.d.ts +265 -4
- package/dist/store/postgres/schema.js +35 -4
- package/dist/store/postgres/store.js +165 -52
- package/dist/store/types.d.ts +96 -39
- package/dist/streams/relay/core.js +1 -13
- package/dist/tenant/advance.js +6 -7
- package/dist/tenant/commands.js +1 -2
- package/dist/tenant/context.d.ts +19 -11
- package/dist/tenant/control.d.ts +8 -1
- package/dist/tenant/control.js +2 -0
- package/dist/tenant/runtime.js +23 -3
- package/dist/tenant/sandboxes.d.ts +2 -1
- package/dist/tenant/sandboxes.js +23 -19
- package/dist/tenant/scheduler.d.ts +7 -0
- package/dist/tenant/scheduler.js +38 -7
- package/dist/tenant/session-streams.d.ts +0 -1
- package/dist/tenant/session-streams.js +1 -13
- package/dist/tenant/sessions.js +26 -0
- package/dist/tenant/streams.js +14 -29
- package/dist/tenant/sweep.d.ts +39 -12
- package/dist/tenant/sweep.js +157 -58
- package/dist/tenant/transcript.d.ts +8 -8
- package/dist/vault/service.d.ts +34 -0
- package/dist/vault/service.js +148 -15
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -3
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"openapi": "3.2.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "Nylorun Management API",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.23.0-beta",
|
|
6
6
|
"x-nylorun-protocol": {
|
|
7
7
|
"min": 4,
|
|
8
8
|
"max": 10,
|
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
"definition_files_missing",
|
|
98
98
|
"credential_rejected",
|
|
99
99
|
"mcp_preview_failed",
|
|
100
|
+
"credential_conflict",
|
|
100
101
|
"internal_error"
|
|
101
102
|
]
|
|
102
103
|
},
|
|
@@ -2113,7 +2114,9 @@
|
|
|
2113
2114
|
"type": "string",
|
|
2114
2115
|
"enum": [
|
|
2115
2116
|
"bearer",
|
|
2116
|
-
"headers"
|
|
2117
|
+
"headers",
|
|
2118
|
+
"environment_secret",
|
|
2119
|
+
"environment_variable"
|
|
2117
2120
|
]
|
|
2118
2121
|
},
|
|
2119
2122
|
"binding": {
|
|
@@ -2121,11 +2124,41 @@
|
|
|
2121
2124
|
"properties": {
|
|
2122
2125
|
"url": {
|
|
2123
2126
|
"type": "string"
|
|
2127
|
+
},
|
|
2128
|
+
"secretName": {
|
|
2129
|
+
"type": "string"
|
|
2130
|
+
},
|
|
2131
|
+
"allowedHosts": {
|
|
2132
|
+
"type": "array",
|
|
2133
|
+
"items": {
|
|
2134
|
+
"type": "string"
|
|
2135
|
+
}
|
|
2136
|
+
},
|
|
2137
|
+
"inject": {
|
|
2138
|
+
"type": "object",
|
|
2139
|
+
"properties": {
|
|
2140
|
+
"header": {
|
|
2141
|
+
"type": "string",
|
|
2142
|
+
"pattern": "^[!#$%&'*+.^_`|~0-9A-Za-z-]+$"
|
|
2143
|
+
},
|
|
2144
|
+
"format": {
|
|
2145
|
+
"type": "string",
|
|
2146
|
+
"minLength": 1
|
|
2147
|
+
}
|
|
2148
|
+
},
|
|
2149
|
+
"required": [
|
|
2150
|
+
"header",
|
|
2151
|
+
"format"
|
|
2152
|
+
],
|
|
2153
|
+
"additionalProperties": false
|
|
2154
|
+
},
|
|
2155
|
+
"variableName": {
|
|
2156
|
+
"type": "string"
|
|
2157
|
+
},
|
|
2158
|
+
"variableValue": {
|
|
2159
|
+
"type": "string"
|
|
2124
2160
|
}
|
|
2125
2161
|
},
|
|
2126
|
-
"required": [
|
|
2127
|
-
"url"
|
|
2128
|
-
],
|
|
2129
2162
|
"additionalProperties": false
|
|
2130
2163
|
},
|
|
2131
2164
|
"headerNames": {
|
|
@@ -2269,6 +2302,85 @@
|
|
|
2269
2302
|
"headers"
|
|
2270
2303
|
],
|
|
2271
2304
|
"additionalProperties": false
|
|
2305
|
+
},
|
|
2306
|
+
{
|
|
2307
|
+
"type": "object",
|
|
2308
|
+
"properties": {
|
|
2309
|
+
"type": {
|
|
2310
|
+
"type": "string",
|
|
2311
|
+
"enum": [
|
|
2312
|
+
"environment_secret"
|
|
2313
|
+
]
|
|
2314
|
+
},
|
|
2315
|
+
"secretName": {
|
|
2316
|
+
"type": "string",
|
|
2317
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]{0,127}$"
|
|
2318
|
+
},
|
|
2319
|
+
"secretValue": {
|
|
2320
|
+
"type": "string",
|
|
2321
|
+
"minLength": 1,
|
|
2322
|
+
"maxLength": 8192
|
|
2323
|
+
},
|
|
2324
|
+
"allowedHosts": {
|
|
2325
|
+
"type": "array",
|
|
2326
|
+
"items": {
|
|
2327
|
+
"type": "string"
|
|
2328
|
+
},
|
|
2329
|
+
"minItems": 1,
|
|
2330
|
+
"maxItems": 32
|
|
2331
|
+
},
|
|
2332
|
+
"inject": {
|
|
2333
|
+
"type": "object",
|
|
2334
|
+
"properties": {
|
|
2335
|
+
"header": {
|
|
2336
|
+
"type": "string",
|
|
2337
|
+
"pattern": "^[!#$%&'*+.^_`|~0-9A-Za-z-]+$"
|
|
2338
|
+
},
|
|
2339
|
+
"format": {
|
|
2340
|
+
"type": "string",
|
|
2341
|
+
"minLength": 1
|
|
2342
|
+
}
|
|
2343
|
+
},
|
|
2344
|
+
"required": [
|
|
2345
|
+
"header",
|
|
2346
|
+
"format"
|
|
2347
|
+
],
|
|
2348
|
+
"additionalProperties": false
|
|
2349
|
+
}
|
|
2350
|
+
},
|
|
2351
|
+
"required": [
|
|
2352
|
+
"type",
|
|
2353
|
+
"secretName",
|
|
2354
|
+
"secretValue",
|
|
2355
|
+
"allowedHosts"
|
|
2356
|
+
],
|
|
2357
|
+
"additionalProperties": false
|
|
2358
|
+
},
|
|
2359
|
+
{
|
|
2360
|
+
"type": "object",
|
|
2361
|
+
"properties": {
|
|
2362
|
+
"type": {
|
|
2363
|
+
"type": "string",
|
|
2364
|
+
"enum": [
|
|
2365
|
+
"environment_variable"
|
|
2366
|
+
]
|
|
2367
|
+
},
|
|
2368
|
+
"variableName": {
|
|
2369
|
+
"type": "string",
|
|
2370
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]{0,127}$"
|
|
2371
|
+
},
|
|
2372
|
+
"variableValue": {
|
|
2373
|
+
"type": "string",
|
|
2374
|
+
"minLength": 1,
|
|
2375
|
+
"maxLength": 8192
|
|
2376
|
+
}
|
|
2377
|
+
},
|
|
2378
|
+
"required": [
|
|
2379
|
+
"type",
|
|
2380
|
+
"variableName",
|
|
2381
|
+
"variableValue"
|
|
2382
|
+
],
|
|
2383
|
+
"additionalProperties": false
|
|
2272
2384
|
}
|
|
2273
2385
|
]
|
|
2274
2386
|
}
|
|
@@ -2302,7 +2414,9 @@
|
|
|
2302
2414
|
"type": "string",
|
|
2303
2415
|
"enum": [
|
|
2304
2416
|
"bearer",
|
|
2305
|
-
"headers"
|
|
2417
|
+
"headers",
|
|
2418
|
+
"environment_secret",
|
|
2419
|
+
"environment_variable"
|
|
2306
2420
|
]
|
|
2307
2421
|
},
|
|
2308
2422
|
"binding": {
|
|
@@ -2310,11 +2424,41 @@
|
|
|
2310
2424
|
"properties": {
|
|
2311
2425
|
"url": {
|
|
2312
2426
|
"type": "string"
|
|
2427
|
+
},
|
|
2428
|
+
"secretName": {
|
|
2429
|
+
"type": "string"
|
|
2430
|
+
},
|
|
2431
|
+
"allowedHosts": {
|
|
2432
|
+
"type": "array",
|
|
2433
|
+
"items": {
|
|
2434
|
+
"type": "string"
|
|
2435
|
+
}
|
|
2436
|
+
},
|
|
2437
|
+
"inject": {
|
|
2438
|
+
"type": "object",
|
|
2439
|
+
"properties": {
|
|
2440
|
+
"header": {
|
|
2441
|
+
"type": "string",
|
|
2442
|
+
"pattern": "^[!#$%&'*+.^_`|~0-9A-Za-z-]+$"
|
|
2443
|
+
},
|
|
2444
|
+
"format": {
|
|
2445
|
+
"type": "string",
|
|
2446
|
+
"minLength": 1
|
|
2447
|
+
}
|
|
2448
|
+
},
|
|
2449
|
+
"required": [
|
|
2450
|
+
"header",
|
|
2451
|
+
"format"
|
|
2452
|
+
],
|
|
2453
|
+
"additionalProperties": false
|
|
2454
|
+
},
|
|
2455
|
+
"variableName": {
|
|
2456
|
+
"type": "string"
|
|
2457
|
+
},
|
|
2458
|
+
"variableValue": {
|
|
2459
|
+
"type": "string"
|
|
2313
2460
|
}
|
|
2314
2461
|
},
|
|
2315
|
-
"required": [
|
|
2316
|
-
"url"
|
|
2317
|
-
],
|
|
2318
2462
|
"additionalProperties": false
|
|
2319
2463
|
},
|
|
2320
2464
|
"headerNames": {
|
|
@@ -2463,6 +2607,48 @@
|
|
|
2463
2607
|
"headers"
|
|
2464
2608
|
],
|
|
2465
2609
|
"additionalProperties": false
|
|
2610
|
+
},
|
|
2611
|
+
{
|
|
2612
|
+
"type": "object",
|
|
2613
|
+
"properties": {
|
|
2614
|
+
"type": {
|
|
2615
|
+
"type": "string",
|
|
2616
|
+
"enum": [
|
|
2617
|
+
"environment_secret"
|
|
2618
|
+
]
|
|
2619
|
+
},
|
|
2620
|
+
"secretValue": {
|
|
2621
|
+
"type": "string",
|
|
2622
|
+
"minLength": 1,
|
|
2623
|
+
"maxLength": 8192
|
|
2624
|
+
}
|
|
2625
|
+
},
|
|
2626
|
+
"required": [
|
|
2627
|
+
"type",
|
|
2628
|
+
"secretValue"
|
|
2629
|
+
],
|
|
2630
|
+
"additionalProperties": false
|
|
2631
|
+
},
|
|
2632
|
+
{
|
|
2633
|
+
"type": "object",
|
|
2634
|
+
"properties": {
|
|
2635
|
+
"type": {
|
|
2636
|
+
"type": "string",
|
|
2637
|
+
"enum": [
|
|
2638
|
+
"environment_variable"
|
|
2639
|
+
]
|
|
2640
|
+
},
|
|
2641
|
+
"variableValue": {
|
|
2642
|
+
"type": "string",
|
|
2643
|
+
"minLength": 1,
|
|
2644
|
+
"maxLength": 8192
|
|
2645
|
+
}
|
|
2646
|
+
},
|
|
2647
|
+
"required": [
|
|
2648
|
+
"type",
|
|
2649
|
+
"variableValue"
|
|
2650
|
+
],
|
|
2651
|
+
"additionalProperties": false
|
|
2466
2652
|
}
|
|
2467
2653
|
]
|
|
2468
2654
|
}
|
package/dist/openapi.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"openapi": "3.2.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "Nylorun Runtime API",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.23.0-beta",
|
|
6
6
|
"x-nylorun-protocol": {
|
|
7
7
|
"min": 4,
|
|
8
8
|
"max": 10,
|
|
@@ -6338,6 +6338,7 @@
|
|
|
6338
6338
|
"definition_files_missing",
|
|
6339
6339
|
"credential_rejected",
|
|
6340
6340
|
"mcp_preview_failed",
|
|
6341
|
+
"credential_conflict",
|
|
6341
6342
|
"internal_error"
|
|
6342
6343
|
]
|
|
6343
6344
|
},
|
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Waits `ms`, or until `signal` aborts; resolves either way and never rejects. */
|
|
2
|
+
export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
3
|
+
export interface RetryOptions {
|
|
4
|
+
/** The first wait after a failure. Doubled after each, up to `maxMs`. */
|
|
5
|
+
minMs: number;
|
|
6
|
+
maxMs: number;
|
|
7
|
+
/** Attempts, the first included, before giving up. Default: no limit. */
|
|
8
|
+
attempts?: number;
|
|
9
|
+
/** Gives up when the next wait would end later than this long after the first attempt. */
|
|
10
|
+
deadlineMs?: number;
|
|
11
|
+
/** Gives up when it aborts, after the attempt or wait under way. */
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
/** Called with each failure that is retried, and how many attempts have failed so far. */
|
|
14
|
+
onError?: (error: unknown, failures: number) => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Runs `attempt` until it resolves, waiting between failures with exponential backoff
|
|
18
|
+
* (`minMs`, doubling to `maxMs`). Rejects with the last failure once `attempts`, `deadlineMs`
|
|
19
|
+
* or `signal` ends the retries.
|
|
20
|
+
*/
|
|
21
|
+
export declare function retry<T>(attempt: () => Promise<T>, options: RetryOptions): Promise<T>;
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waiting and retrying, shared by the Runtime's background loops and its calls to other
|
|
3
|
+
* services. Nothing here logs or decides what is worth retrying: callers do.
|
|
4
|
+
*/
|
|
5
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
6
|
+
/** Waits `ms`, or until `signal` aborts; resolves either way and never rejects. */
|
|
7
|
+
export function sleep(ms, signal) {
|
|
8
|
+
return delay(ms, undefined, signal ? { signal } : undefined).catch(() => undefined);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Runs `attempt` until it resolves, waiting between failures with exponential backoff
|
|
12
|
+
* (`minMs`, doubling to `maxMs`). Rejects with the last failure once `attempts`, `deadlineMs`
|
|
13
|
+
* or `signal` ends the retries.
|
|
14
|
+
*/
|
|
15
|
+
export async function retry(attempt, options) {
|
|
16
|
+
const deadline = options.deadlineMs === undefined ? Infinity : Date.now() + options.deadlineMs;
|
|
17
|
+
let wait = options.minMs;
|
|
18
|
+
for (let failures = 1;; failures += 1) {
|
|
19
|
+
try {
|
|
20
|
+
return await attempt();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (failures >= (options.attempts ?? Infinity) ||
|
|
24
|
+
Date.now() + wait > deadline ||
|
|
25
|
+
options.signal?.aborted)
|
|
26
|
+
throw error;
|
|
27
|
+
options.onError?.(error, failures);
|
|
28
|
+
await sleep(wait, options.signal);
|
|
29
|
+
if (options.signal?.aborted)
|
|
30
|
+
throw error;
|
|
31
|
+
wait = Math.min(wait * 2, options.maxMs);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/sandbox/join.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface HostJoinAnswer {
|
|
|
15
15
|
readonly epoch: number;
|
|
16
16
|
/** ISO time the tokens expire. */
|
|
17
17
|
readonly expiresAt: string;
|
|
18
|
+
/** The egress CA certificate (PEM) the sandbox trusts (R2c). */
|
|
19
|
+
readonly caCertificate: string;
|
|
18
20
|
}
|
|
19
21
|
/** A refused join, renewal or host connection. */
|
|
20
22
|
export declare class HostAuthError extends Error {
|
package/dist/sandbox/join.js
CHANGED
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
* and the pod (`tenant/host-token.ts`). `renew` mints a new pair at the same epoch while the
|
|
25
25
|
* host is current; it is refused once the epoch moved.
|
|
26
26
|
*
|
|
27
|
+
* The answer also carries the installation's egress CA certificate (R2c, D50): the engine adds it
|
|
28
|
+
* to the trust bundle every command gets, so a host egress-gate terminates TLS for (one an
|
|
29
|
+
* `environment_secret` is bound to) is trusted. Only the certificate: its key stays in keys.
|
|
30
|
+
*
|
|
27
31
|
* Every refusal answers the same 401 (the reason is logged), so a caller learns nothing about
|
|
28
32
|
* which check failed.
|
|
29
33
|
*/
|
|
@@ -53,13 +57,14 @@ function sameHash(a, b) {
|
|
|
53
57
|
const right = Buffer.from(b, "hex");
|
|
54
58
|
return left.length === right.length && left.length > 0 && timingSafeEqual(left, right);
|
|
55
59
|
}
|
|
56
|
-
function answerOf(grant) {
|
|
60
|
+
function answerOf(grant, caCertificate) {
|
|
57
61
|
return {
|
|
58
62
|
hostToken: grant.hostToken,
|
|
59
63
|
egressToken: grant.egressToken,
|
|
60
64
|
sandboxId: grant.claims.sandboxId,
|
|
61
65
|
epoch: grant.claims.epoch,
|
|
62
66
|
expiresAt: new Date(grant.claims.expiresAt).toISOString(),
|
|
67
|
+
caCertificate,
|
|
63
68
|
};
|
|
64
69
|
}
|
|
65
70
|
export function hostAuthority(ctx) {
|
|
@@ -129,6 +134,9 @@ export function hostAuthority(ctx) {
|
|
|
129
134
|
const reason = "The sandbox's volume was replaced: its files are gone";
|
|
130
135
|
await t.updateSandboxPod(sandboxId, { observed: "lost", reason, hostEpoch: epoch }, now);
|
|
131
136
|
await t.sandboxEvent(sandboxId, "sandbox.lost", { reason });
|
|
137
|
+
// Its Sandbox is deleted; only a reset brings the sandbox back.
|
|
138
|
+
await t.signal({ type: "host.revoked", sandboxId, epoch });
|
|
139
|
+
await t.signalSandbox(sandboxId, { kind: "reconcile" });
|
|
132
140
|
return { lost: epoch };
|
|
133
141
|
}
|
|
134
142
|
await t.updateSandboxPod(sandboxId, {
|
|
@@ -143,22 +151,18 @@ export function hostAuthority(ctx) {
|
|
|
143
151
|
else
|
|
144
152
|
await t.sandboxEvent(sandboxId, "sandbox.running", { hostEpoch: epoch });
|
|
145
153
|
}
|
|
154
|
+
// The previous host's connection (an older epoch) serves nothing more, on any process.
|
|
155
|
+
await t.signal({ type: "host.revoked", sandboxId, epoch });
|
|
146
156
|
return epoch;
|
|
147
157
|
});
|
|
148
|
-
if (typeof joined === "object")
|
|
149
|
-
ctx.harness.revokeHost(sandboxId, joined.lost);
|
|
150
|
-
// Its Sandbox is deleted; only a reset brings the sandbox back.
|
|
151
|
-
await ctx.sandboxSignal(sandboxId, { kind: "reconcile" });
|
|
158
|
+
if (typeof joined === "object")
|
|
152
159
|
return refuse("the volume was replaced: the sandbox is lost");
|
|
153
|
-
}
|
|
154
|
-
// The previous host's connection (an older epoch) serves nothing more.
|
|
155
|
-
ctx.harness.revokeHost(sandboxId, joined);
|
|
156
160
|
ctx.config.logger.info("sandbox host joined", { sandboxId, epoch: joined });
|
|
157
|
-
return answerOf(await mintHostTokens(ctx, { sandboxId, epoch: joined, podUid }));
|
|
161
|
+
return answerOf(await mintHostTokens(ctx, { sandboxId, epoch: joined, podUid }), await ctx.keys.egressCa());
|
|
158
162
|
},
|
|
159
163
|
async renew(hostToken) {
|
|
160
164
|
const claims = await verified(hostToken);
|
|
161
|
-
return answerOf(await mintHostTokens(ctx, claims));
|
|
165
|
+
return answerOf(await mintHostTokens(ctx, claims), await ctx.keys.egressCa());
|
|
162
166
|
},
|
|
163
167
|
verify: verified,
|
|
164
168
|
};
|
|
@@ -34,6 +34,8 @@ export interface SandboxSessionRef {
|
|
|
34
34
|
* so every session attached to it shares the files; absent, the workspace is the session's.
|
|
35
35
|
*/
|
|
36
36
|
readonly sandboxId?: string;
|
|
37
|
+
/** Variables every command of the call gets (R2c): secret names set to the sentinel, plain values. */
|
|
38
|
+
readonly environment?: Readonly<Record<string, string>>;
|
|
37
39
|
/**
|
|
38
40
|
* Who the call's events are claimed for, passed back to `emit`: in a harness, the run that
|
|
39
41
|
* made the call (its run id).
|
package/dist/sandbox/manager.js
CHANGED
|
@@ -285,7 +285,7 @@ export class SandboxManager {
|
|
|
285
285
|
try {
|
|
286
286
|
const outcome = await runSandboxTool(live.handle, toolName, (input ?? {}), signal, (value) => {
|
|
287
287
|
report = value;
|
|
288
|
-
});
|
|
288
|
+
}, session.environment);
|
|
289
289
|
await this.options.emit(session.id, session.activeTurnId, "sandbox.exec", {
|
|
290
290
|
tool: toolName,
|
|
291
291
|
...report,
|
|
@@ -135,7 +135,9 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
135
135
|
await t.updateSandboxPod(sandboxId, finalPatch, new Date().toISOString());
|
|
136
136
|
for (const item of decision.events)
|
|
137
137
|
await t.sandboxEvent(sandboxId, item.type, item.payload);
|
|
138
|
-
|
|
138
|
+
if (epochMoved)
|
|
139
|
+
await t.signal({ type: "host.revoked", sandboxId, epoch: finalPatch.hostEpoch });
|
|
140
|
+
return { kind: "written" };
|
|
139
141
|
});
|
|
140
142
|
if (written.kind === "changed")
|
|
141
143
|
return { retryAfterMs: 0 };
|
|
@@ -143,8 +145,6 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
143
145
|
await ctx.sandbox.removeSandbox(sandboxId).catch(() => undefined);
|
|
144
146
|
return {};
|
|
145
147
|
}
|
|
146
|
-
if (written.kind === "written" && written.epoch !== undefined)
|
|
147
|
-
ctx.harness.revokeHost(sandboxId, written.epoch);
|
|
148
148
|
return {
|
|
149
149
|
...(decision.retryAfterMs === undefined ? {} : { retryAfterMs: decision.retryAfterMs }),
|
|
150
150
|
...(decision.arm.length > 0 ? { arm: decision.arm } : {}),
|
package/dist/sandbox/tools.d.ts
CHANGED
|
@@ -20,4 +20,6 @@ export declare function truncateMiddle(value: string, limit?: number): {
|
|
|
20
20
|
text: string;
|
|
21
21
|
truncated: boolean;
|
|
22
22
|
};
|
|
23
|
-
export declare function runSandboxTool(handle: SandboxHandle, name: SandboxToolName, input: Record<string, any>, signal: AbortSignal, report: (value: SandboxToolReport) => void
|
|
23
|
+
export declare function runSandboxTool(handle: SandboxHandle, name: SandboxToolName, input: Record<string, any>, signal: AbortSignal, report: (value: SandboxToolReport) => void,
|
|
24
|
+
/** Variables every command gets (R2c): secret names set to the sentinel, plain values. */
|
|
25
|
+
environment?: Readonly<Record<string, string>>): Promise<SandboxToolOutcome>;
|
package/dist/sandbox/tools.js
CHANGED
|
@@ -39,10 +39,12 @@ const failed = (code, message) => ({
|
|
|
39
39
|
code,
|
|
40
40
|
message,
|
|
41
41
|
});
|
|
42
|
-
export async function runSandboxTool(handle, name, input, signal, report
|
|
42
|
+
export async function runSandboxTool(handle, name, input, signal, report,
|
|
43
|
+
/** Variables every command gets (R2c): secret names set to the sentinel, plain values. */
|
|
44
|
+
environment) {
|
|
43
45
|
const workspace = handle.workspace ?? SANDBOX_WORKSPACE;
|
|
44
46
|
const resolvePath = (path) => resolveSandboxPath(path, workspace);
|
|
45
|
-
const exec = (command, timeoutMs = 60_000) => handle.exec({ command, cwd: workspace, timeoutMs }, signal);
|
|
47
|
+
const exec = (command, timeoutMs = 60_000) => handle.exec({ command, cwd: workspace, timeoutMs, ...(environment ? { env: environment } : {}) }, signal);
|
|
46
48
|
switch (name) {
|
|
47
49
|
case "bash": {
|
|
48
50
|
const command = String(input.command);
|
package/dist/sandbox/types.d.ts
CHANGED
|
@@ -36,6 +36,11 @@ export interface ExecRequest {
|
|
|
36
36
|
readonly command: string;
|
|
37
37
|
readonly cwd: string;
|
|
38
38
|
readonly timeoutMs: number;
|
|
39
|
+
/**
|
|
40
|
+
* Variables this command gets on top of the backend's own (R2c): never a secret. The backend's
|
|
41
|
+
* proxy and trust variables win over them.
|
|
42
|
+
*/
|
|
43
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
39
44
|
}
|
|
40
45
|
export interface ExecResult {
|
|
41
46
|
readonly exitCode: number;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The control bus on Postgres (blueprint D21, D48): how one process tells the others with the
|
|
3
3
|
* Tenant open to act now. `session.cancel` aborts the advance of a cancelled turn;
|
|
4
|
-
* `sessions.reset` moves the session streams to the Tenant's new basin generation
|
|
4
|
+
* `sessions.reset` moves the session streams to the Tenant's new basin generation;
|
|
5
|
+
* `host.revoked` closes a pod sandbox's host connections of an older epoch. Nothing
|
|
5
6
|
* internal goes through S2, which only serves API listeners.
|
|
6
7
|
*
|
|
7
8
|
* - **Write.** `writeSignal` inserts a `control_signals` row and calls `pg_notify` on
|
|
@@ -36,6 +37,8 @@ const SIGNAL = {
|
|
|
36
37
|
sessionId: controlSignals.sessionId,
|
|
37
38
|
turnId: controlSignals.turnId,
|
|
38
39
|
generation: controlSignals.generation,
|
|
40
|
+
sandboxId: controlSignals.sandboxId,
|
|
41
|
+
epoch: controlSignals.epoch,
|
|
39
42
|
};
|
|
40
43
|
/** Puts `signal` on the bus in the transaction `db` runs: a row, notified at commit. */
|
|
41
44
|
export async function writeSignal(db, signal) {
|
|
@@ -46,6 +49,8 @@ export async function writeSignal(db, signal) {
|
|
|
46
49
|
sessionId: signal.type === "session.cancel" ? signal.sessionId : null,
|
|
47
50
|
turnId: signal.type === "session.cancel" ? (signal.turnId ?? null) : null,
|
|
48
51
|
generation: signal.type === "sessions.reset" ? signal.generation : null,
|
|
52
|
+
sandboxId: signal.type === "host.revoked" ? signal.sandboxId : null,
|
|
53
|
+
epoch: signal.type === "host.revoked" ? signal.epoch : null,
|
|
49
54
|
})
|
|
50
55
|
.returning({ id: controlSignals.id });
|
|
51
56
|
// The payload is only the id: a follower reads the rows back either way.
|
|
@@ -183,5 +188,7 @@ function signalOf(row) {
|
|
|
183
188
|
};
|
|
184
189
|
if (row.kind === "sessions.reset" && row.generation !== null)
|
|
185
190
|
return { type: "sessions.reset", generation: row.generation };
|
|
191
|
+
if (row.kind === "host.revoked" && row.sandboxId !== null && row.epoch !== null)
|
|
192
|
+
return { type: "host.revoked", sandboxId: row.sandboxId, epoch: row.epoch };
|
|
186
193
|
return undefined;
|
|
187
194
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
ALTER TABLE "nylorun"."wakes" ADD COLUMN "attempts" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
|
2
|
+
ALTER TABLE "nylorun"."wakes" ADD COLUMN "retry_at" timestamp with time zone;--> statement-breakpoint
|
|
3
|
+
ALTER TABLE "nylorun"."wakes" ADD COLUMN "parked_at" timestamp with time zone;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
CREATE TABLE "nylorun"."sandbox_signals" (
|
|
2
|
+
"id" text COLLATE "C" PRIMARY KEY NOT NULL,
|
|
3
|
+
"sandbox_id" text COLLATE "C" NOT NULL,
|
|
4
|
+
"kind" text NOT NULL,
|
|
5
|
+
"timer" text,
|
|
6
|
+
"at" bigint,
|
|
7
|
+
"created_at" timestamp with time zone DEFAULT clock_timestamp() NOT NULL,
|
|
8
|
+
"attempts" integer DEFAULT 0 NOT NULL,
|
|
9
|
+
"retry_at" timestamp with time zone,
|
|
10
|
+
"parked_at" timestamp with time zone,
|
|
11
|
+
CONSTRAINT "sandbox_signals_kind_check" CHECK ("nylorun"."sandbox_signals"."kind" IN ('reconcile', 'arm'))
|
|
12
|
+
);
|
|
13
|
+
--> statement-breakpoint
|
|
14
|
+
ALTER TABLE "nylorun"."control_signals" DROP CONSTRAINT "control_signals_kind_check";--> statement-breakpoint
|
|
15
|
+
ALTER TABLE "nylorun"."control_signals" ADD COLUMN "sandbox_id" text COLLATE "C";--> statement-breakpoint
|
|
16
|
+
ALTER TABLE "nylorun"."control_signals" ADD COLUMN "epoch" integer;--> statement-breakpoint
|
|
17
|
+
CREATE INDEX "sandbox_signals_created_at" ON "nylorun"."sandbox_signals" USING btree ("created_at");--> statement-breakpoint
|
|
18
|
+
ALTER TABLE "nylorun"."control_signals" ADD CONSTRAINT "control_signals_kind_check" CHECK ("nylorun"."control_signals"."kind" IN ('session.cancel', 'sessions.reset', 'host.revoked'));
|
|
@@ -127,6 +127,20 @@
|
|
|
127
127
|
"when": 1791455573196,
|
|
128
128
|
"tag": "0017_wake_outbox",
|
|
129
129
|
"breakpoints": true
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"idx": 18,
|
|
133
|
+
"version": "7",
|
|
134
|
+
"when": 1791529391438,
|
|
135
|
+
"tag": "0018_wake_outbox_retries",
|
|
136
|
+
"breakpoints": true
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"idx": 19,
|
|
140
|
+
"version": "7",
|
|
141
|
+
"when": 1791530515498,
|
|
142
|
+
"tag": "0019_sandbox_signal_outbox",
|
|
143
|
+
"breakpoints": true
|
|
130
144
|
}
|
|
131
145
|
]
|
|
132
146
|
}
|