@nylorun/runtime 0.15.2-beta → 0.16.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 +76 -0
- package/README.md +33 -6
- package/dist/adapters/execution/restate.d.ts +9 -1
- package/dist/adapters/execution/restate.js +64 -5
- package/dist/adapters/sandbox/local.d.ts +10 -0
- package/dist/adapters/sandbox/local.js +195 -0
- package/dist/adapters/sandbox/virtual.js +36 -1
- package/dist/admin-openapi.json +37 -3
- package/dist/api/a2a/tasks.js +6 -1
- package/dist/api/ag-ui/history.js +6 -1
- package/dist/api/ag-ui/routes.js +5 -1
- package/dist/api/components.d.ts +2673 -655
- package/dist/api/components.js +17 -1
- package/dist/api/gate/keys.d.ts +4 -3
- package/dist/api/gate/routes.d.ts +19 -3
- package/dist/api/gate/routes.js +162 -53
- package/dist/api/http/app.js +4 -0
- package/dist/api/http/define.d.ts +10 -0
- package/dist/api/http/define.js +2 -1
- package/dist/api/http/routes/artifacts.d.ts +11 -0
- package/dist/api/http/routes/artifacts.js +518 -0
- package/dist/api/http/routes/sandboxes.d.ts +9 -0
- package/dist/api/http/routes/sandboxes.js +152 -0
- package/dist/api/http/routes/sessions.js +3 -1
- package/dist/api/http/routes/tenant.js +36 -2
- package/dist/artifacts/config.d.ts +23 -0
- package/dist/artifacts/config.js +27 -0
- package/dist/artifacts/export.d.ts +8 -0
- package/dist/artifacts/export.js +205 -0
- package/dist/artifacts/files.d.ts +36 -0
- package/dist/artifacts/files.js +45 -0
- package/dist/artifacts/folders.d.ts +60 -0
- package/dist/artifacts/folders.js +209 -0
- package/dist/artifacts/links.d.ts +28 -0
- package/dist/artifacts/links.js +92 -0
- package/dist/artifacts/media-types.d.ts +13 -0
- package/dist/artifacts/media-types.js +80 -0
- package/dist/artifacts/parts.d.ts +34 -0
- package/dist/artifacts/parts.js +38 -0
- package/dist/artifacts/service.d.ts +74 -0
- package/dist/artifacts/service.js +301 -0
- package/dist/artifacts/workspace.d.ts +60 -0
- package/dist/artifacts/workspace.js +57 -0
- package/dist/artifacts/zip.d.ts +10 -0
- package/dist/artifacts/zip.js +126 -0
- package/dist/blob/fs.d.ts +13 -0
- package/dist/blob/fs.js +194 -0
- package/dist/blob/index.d.ts +9 -0
- package/dist/blob/index.js +9 -0
- package/dist/blob/s3.d.ts +42 -0
- package/dist/blob/s3.js +366 -0
- package/dist/blob/types.d.ts +108 -0
- package/dist/blob/types.js +85 -0
- package/dist/core/sandbox-routes.d.ts +2 -2
- package/dist/core/sandbox-routes.js +4 -3
- package/dist/execution/memory.d.ts +11 -1
- package/dist/execution/memory.js +77 -3
- package/dist/execution/types.d.ts +54 -1
- package/dist/execution/types.js +31 -0
- package/dist/gates/contract.d.ts +14 -13
- package/dist/gates/contract.js +13 -12
- package/dist/gates/egress.d.ts +72 -0
- package/dist/gates/egress.js +319 -0
- package/dist/gates/handler.d.ts +3 -0
- package/dist/gates/handler.js +3 -2
- package/dist/gates/http-client.d.ts +3 -2
- package/dist/gates/http-client.js +17 -6
- package/dist/gates/in-process.d.ts +13 -1
- package/dist/gates/in-process.js +11 -0
- package/dist/gates/inflight.d.ts +23 -4
- package/dist/gates/inflight.js +18 -4
- package/dist/gates/model-gate.d.ts +6 -2
- package/dist/gates/tool-calls.d.ts +9 -5
- package/dist/gates/tool-calls.js +6 -6
- package/dist/gates/tool-client.d.ts +9 -2
- package/dist/gates/tool-client.js +56 -22
- package/dist/gates/tool-contract.d.ts +12 -7
- package/dist/gates/tool-contract.js +10 -4
- package/dist/gates/tool-gate.d.ts +1 -0
- package/dist/harness/calls.d.ts +52 -0
- package/dist/harness/calls.js +113 -0
- package/dist/harness/executors.d.ts +22 -0
- package/dist/harness/executors.js +96 -0
- package/dist/harness/main.d.ts +6 -0
- package/dist/harness/main.js +103 -0
- package/dist/harness/pod.d.ts +21 -0
- package/dist/harness/pod.js +119 -0
- package/dist/harness/run-tokens.d.ts +18 -0
- package/dist/harness/run-tokens.js +15 -0
- package/dist/harness/service.d.ts +46 -0
- package/dist/harness/service.js +179 -0
- package/dist/harness/ws-client.d.ts +43 -0
- package/dist/harness/ws-client.js +122 -0
- package/dist/harness/ws-port.d.ts +22 -0
- package/dist/harness/ws-port.js +78 -0
- package/dist/harness-api/in-process.d.ts +24 -0
- package/dist/harness-api/in-process.js +119 -0
- package/dist/harness-api/record.d.ts +44 -0
- package/dist/harness-api/record.js +250 -0
- package/dist/harness-api/renew.d.ts +37 -0
- package/dist/harness-api/renew.js +77 -0
- package/dist/harness-api/server.d.ts +104 -0
- package/dist/harness-api/server.js +408 -0
- package/dist/harness-api/start.d.ts +36 -0
- package/dist/harness-api/start.js +118 -0
- package/dist/harness-api/workspace.d.ts +53 -0
- package/dist/harness-api/workspace.js +201 -0
- package/dist/harness-api/ws-server.d.ts +34 -0
- package/dist/harness-api/ws-server.js +227 -0
- package/dist/host/app.js +23 -2
- package/dist/host/gates.d.ts +27 -2
- package/dist/host/gates.js +46 -1
- package/dist/host/http.d.ts +4 -1
- package/dist/host/http.js +6 -1
- package/dist/host/main.js +135 -27
- package/dist/host/stack-config.d.ts +101 -1
- package/dist/host/stack-config.js +199 -8
- package/dist/mcp/snapshot.d.ts +8 -1
- package/dist/mcp/snapshot.js +28 -0
- package/dist/media.d.ts +5 -24
- package/dist/media.js +5 -0
- package/dist/model/pi-model.d.ts +6 -2
- package/dist/model/pi-model.js +39 -22
- package/dist/node/index.d.ts +1 -1
- package/dist/node/index.js +1 -1
- package/dist/openapi.json +15245 -8850
- package/dist/record/index.d.ts +1 -0
- package/dist/record/index.js +1 -0
- package/dist/record/sandbox.d.ts +25 -0
- package/dist/record/sandbox.js +43 -0
- package/dist/sandbox/capability.d.ts +3 -0
- package/dist/sandbox/capability.js +10 -0
- package/dist/sandbox/egress-token.d.ts +26 -0
- package/dist/sandbox/egress-token.js +8 -0
- package/dist/sandbox/join.d.ts +37 -0
- package/dist/sandbox/join.js +165 -0
- package/dist/sandbox/manager.d.ts +99 -34
- package/dist/sandbox/manager.js +216 -84
- package/dist/sandbox/placement.d.ts +27 -0
- package/dist/sandbox/placement.js +44 -0
- package/dist/sandbox/pods/client.d.ts +90 -0
- package/dist/sandbox/pods/client.js +79 -0
- package/dist/sandbox/pods/lifecycle.d.ts +91 -0
- package/dist/sandbox/pods/lifecycle.js +178 -0
- package/dist/sandbox/pods/name.d.ts +1 -0
- package/dist/sandbox/pods/name.js +28 -0
- package/dist/sandbox/pods/network-gate.d.ts +25 -0
- package/dist/sandbox/pods/network-gate.js +71 -0
- package/dist/sandbox/pods/reconcile.d.ts +11 -0
- package/dist/sandbox/pods/reconcile.js +152 -0
- package/dist/sandbox/pods/spec.d.ts +45 -0
- package/dist/sandbox/pods/spec.js +62 -0
- package/dist/sandbox/records.d.ts +29 -0
- package/dist/sandbox/records.js +79 -0
- package/dist/sandbox/resolve.d.ts +5 -0
- package/dist/sandbox/resolve.js +3 -2
- package/dist/sandbox/session-sandbox.js +7 -5
- package/dist/sandbox/share.d.ts +11 -0
- package/dist/sandbox/share.js +10 -0
- package/dist/sandbox/tenant-config.d.ts +16 -1
- package/dist/sandbox/tenant-config.js +9 -0
- package/dist/sandbox/types.d.ts +29 -1
- package/dist/sandbox/types.js +13 -1
- package/dist/store/postgres/drizzle/0004_sandbox_resources.sql +20 -0
- package/dist/store/postgres/drizzle/0005_artifacts.sql +27 -0
- package/dist/store/postgres/drizzle/0006_folder_artifacts.sql +12 -0
- package/dist/store/postgres/drizzle/0007_sandbox_pods.sql +14 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
- package/dist/store/postgres/record-writer.d.ts +6 -1
- package/dist/store/postgres/record-writer.js +20 -2
- package/dist/store/postgres/schema.d.ts +894 -0
- package/dist/store/postgres/schema.js +130 -2
- package/dist/store/postgres/store.js +289 -4
- package/dist/store/types.d.ts +143 -6
- package/dist/streams/control.d.ts +2 -0
- package/dist/streams/control.js +5 -0
- package/dist/streams/types.d.ts +10 -1
- package/dist/tenant/advance.js +218 -160
- package/dist/tenant/artifact-tool.d.ts +5 -0
- package/dist/tenant/artifact-tool.js +119 -0
- package/dist/tenant/commands.js +30 -5
- package/dist/tenant/context.d.ts +64 -5
- package/dist/tenant/effects.d.ts +19 -13
- package/dist/tenant/effects.js +5 -382
- package/dist/tenant/history.d.ts +13 -23
- package/dist/tenant/history.js +14 -62
- package/dist/tenant/host-token.d.ts +92 -0
- package/dist/tenant/host-token.js +179 -0
- package/dist/tenant/module.js +1 -0
- package/dist/tenant/paths.d.ts +2 -1
- package/dist/tenant/paths.js +3 -1
- package/dist/tenant/reset.d.ts +5 -2
- package/dist/tenant/reset.js +22 -2
- package/dist/tenant/run-grants.d.ts +49 -0
- package/dist/tenant/run-grants.js +62 -0
- package/dist/tenant/run-token.d.ts +70 -0
- package/dist/tenant/run-token.js +138 -0
- package/dist/tenant/runtime.d.ts +38 -0
- package/dist/tenant/runtime.js +120 -32
- package/dist/tenant/sandboxes.d.ts +119 -0
- package/dist/tenant/sandboxes.js +551 -0
- package/dist/tenant/session.d.ts +2 -13
- package/dist/tenant/session.js +4 -28
- package/dist/tenant/sessions.d.ts +5 -0
- package/dist/tenant/sessions.js +28 -1
- package/dist/tenant/status.d.ts +8 -3
- package/dist/tenant/status.js +5 -0
- package/dist/tenant/streams.d.ts +7 -0
- package/dist/tenant/streams.js +24 -1
- package/dist/tenant/sweep.js +37 -4
- package/dist/tenant/tokens.js +9 -2
- package/dist/tenant/types.d.ts +18 -1
- package/dist/tenant/worker.d.ts +11 -5
- package/dist/tenant/worker.js +12 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +13 -10
- package/dist/adapters/media.d.ts +0 -24
- package/dist/adapters/media.js +0 -111
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run tokens (F5, gate trust): the ES256 JWT core mints for one advance's lease of a session.
|
|
3
|
+
* It is the only credential of a model call at the gates, and the credential of a session's
|
|
4
|
+
* remote MCP calls. The gates take session, turn and agent from it, never from the body, and
|
|
5
|
+
* check on every call that the lease it names is still the session's (`staleRun`).
|
|
6
|
+
*
|
|
7
|
+
* Signed by the keys service with the Tenant's current signing key, like subject and delivery
|
|
8
|
+
* tokens, and told apart from them by `typ` and `aud`. Internal: not in `openapi.json`. A token
|
|
9
|
+
* lives `RUN_TOKEN_TTL_SECONDS`, no longer than a delivery token, so a key rotation never
|
|
10
|
+
* revokes a live one (`signing-keys.ts`).
|
|
11
|
+
*
|
|
12
|
+
* ```text
|
|
13
|
+
* header { alg: ES256, typ: nylorun-run+jwt, kid }
|
|
14
|
+
* claims { iss: urn:nylorun:tenant:<id>, aud: nylorun-gates, sub: <sessionId>,
|
|
15
|
+
* trn: <turnId>, agt: <root agentId>, epc: <lease epoch>, iat, exp, jti }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { randomUUID } from "node:crypto";
|
|
19
|
+
import { errors, importJWK, jwtVerify } from "jose";
|
|
20
|
+
import { subjectTokenIssuer } from "@nylorun/core/contracts";
|
|
21
|
+
import { CLOCK_TOLERANCE_SECONDS, tokenKeyId } from "./jwt.js";
|
|
22
|
+
export const RUN_TOKEN_TYP = "nylorun-run+jwt";
|
|
23
|
+
export const RUN_TOKEN_AUD = "nylorun-gates";
|
|
24
|
+
/** How long a run token lives. */
|
|
25
|
+
export const RUN_TOKEN_TTL_SECONDS = 15 * 60;
|
|
26
|
+
/** The lease heartbeat re-mints a token with less than this left. */
|
|
27
|
+
export const RUN_TOKEN_RENEW_SECONDS = 5 * 60;
|
|
28
|
+
/** Allowed spread between `iat` and `exp` beyond the lifetime. */
|
|
29
|
+
const LIFETIME_SLACK_SECONDS = 60;
|
|
30
|
+
/**
|
|
31
|
+
* Mints the run token of `lease` over `session`'s active turn. Throws when the session has
|
|
32
|
+
* no active turn: there is no run to name.
|
|
33
|
+
*/
|
|
34
|
+
export async function mintRunToken(signer, lease, session) {
|
|
35
|
+
const turnId = session.activeTurnId;
|
|
36
|
+
if (!turnId)
|
|
37
|
+
throw new Error(`Session ${lease.sessionId} has no active turn to mint a run token for`);
|
|
38
|
+
const tenantId = signer.config.tenantId;
|
|
39
|
+
const iat = Math.floor(Date.now() / 1000);
|
|
40
|
+
const exp = iat + RUN_TOKEN_TTL_SECONDS;
|
|
41
|
+
// Signed by the keys service (F4.2): this process never holds the private key.
|
|
42
|
+
const { token } = await signer.keys.sign({
|
|
43
|
+
typ: RUN_TOKEN_TYP,
|
|
44
|
+
claims: {
|
|
45
|
+
iss: subjectTokenIssuer(tenantId),
|
|
46
|
+
aud: RUN_TOKEN_AUD,
|
|
47
|
+
sub: lease.sessionId,
|
|
48
|
+
trn: turnId,
|
|
49
|
+
agt: session.agentId,
|
|
50
|
+
epc: lease.epoch,
|
|
51
|
+
iat,
|
|
52
|
+
exp,
|
|
53
|
+
jti: randomUUID(),
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
token,
|
|
58
|
+
claims: {
|
|
59
|
+
tenantId,
|
|
60
|
+
sessionId: lease.sessionId,
|
|
61
|
+
turnId,
|
|
62
|
+
agentId: session.agentId,
|
|
63
|
+
epoch: lease.epoch,
|
|
64
|
+
expiresAt: exp * 1000,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Verifies a run token of Tenant `tenantId` against the public keys in `store`: everything a
|
|
70
|
+
* forger controls (size, shape, header) is checked before any key is read, then the
|
|
71
|
+
* signature, `iss`, `aud`, `typ`, lifetime and claims. A revoked key refuses even a token it
|
|
72
|
+
* signed while it was live.
|
|
73
|
+
*/
|
|
74
|
+
export async function verifyRunToken(store, tenantId, raw, cache = new Map()) {
|
|
75
|
+
const refused = (reason) => ({ ok: false, reason: `run_token_${reason}` });
|
|
76
|
+
const checked = tokenKeyId(raw, RUN_TOKEN_TYP);
|
|
77
|
+
if ("refused" in checked)
|
|
78
|
+
return refused(checked.refused);
|
|
79
|
+
const { kid } = checked;
|
|
80
|
+
const row = await store.tx((t) => t.signingKey(kid));
|
|
81
|
+
if (!row)
|
|
82
|
+
return refused("key_unknown");
|
|
83
|
+
let key = cache.get(kid);
|
|
84
|
+
if (!key) {
|
|
85
|
+
key = await importJWK({ ...JSON.parse(row.publicJwk), alg: "ES256" }, "ES256");
|
|
86
|
+
cache.set(kid, key);
|
|
87
|
+
}
|
|
88
|
+
let payload;
|
|
89
|
+
try {
|
|
90
|
+
const verified = await jwtVerify(raw, key, {
|
|
91
|
+
algorithms: ["ES256"],
|
|
92
|
+
issuer: subjectTokenIssuer(tenantId),
|
|
93
|
+
audience: RUN_TOKEN_AUD,
|
|
94
|
+
typ: RUN_TOKEN_TYP,
|
|
95
|
+
clockTolerance: CLOCK_TOLERANCE_SECONDS,
|
|
96
|
+
requiredClaims: ["sub", "iat", "exp", "jti"],
|
|
97
|
+
});
|
|
98
|
+
payload = verified.payload;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return refused(error instanceof errors.JWTExpired ? "expired" : "invalid");
|
|
102
|
+
}
|
|
103
|
+
if (row.state === "revoked")
|
|
104
|
+
return refused("key_revoked");
|
|
105
|
+
const { sub, trn, agt, epc, iat, exp } = payload;
|
|
106
|
+
if (typeof sub !== "string" ||
|
|
107
|
+
sub === "" ||
|
|
108
|
+
typeof trn !== "string" ||
|
|
109
|
+
trn === "" ||
|
|
110
|
+
typeof agt !== "string" ||
|
|
111
|
+
agt === "" ||
|
|
112
|
+
typeof epc !== "number" ||
|
|
113
|
+
!Number.isSafeInteger(epc) ||
|
|
114
|
+
epc < 1 ||
|
|
115
|
+
exp - iat > RUN_TOKEN_TTL_SECONDS + LIFETIME_SLACK_SECONDS)
|
|
116
|
+
return refused("claims");
|
|
117
|
+
return {
|
|
118
|
+
ok: true,
|
|
119
|
+
claims: { tenantId, sessionId: sub, turnId: trn, agentId: agt, epoch: epc, expiresAt: exp * 1000 },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Why calls under `claims` are stale, or undefined while its lease is still the session's
|
|
124
|
+
* (G4): the session's epoch is the token's, its active turn is the token's, and it is not
|
|
125
|
+
* cancelled. One indexed read of the session row.
|
|
126
|
+
*/
|
|
127
|
+
export async function staleRun(store, claims) {
|
|
128
|
+
const state = await store.tx((t) => t.runState(claims.sessionId));
|
|
129
|
+
if (!state)
|
|
130
|
+
return "the session is gone";
|
|
131
|
+
if (state.epoch !== claims.epoch)
|
|
132
|
+
return "another advance owns the session now";
|
|
133
|
+
if (state.activeTurnId !== claims.turnId)
|
|
134
|
+
return "the turn has ended";
|
|
135
|
+
if (state.status === "cancelled")
|
|
136
|
+
return "the turn was cancelled";
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
package/dist/tenant/runtime.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ import { type StreamsStatus } from "./streams.js";
|
|
|
8
8
|
import { type TenantExecution, type TenantWorker } from "./worker.js";
|
|
9
9
|
import { type ToolGate } from "../gates/tool-gate.js";
|
|
10
10
|
import { type Keys } from "../keys/keys.js";
|
|
11
|
+
import type { RunGrants } from "./run-grants.js";
|
|
12
|
+
import { type BlobStore } from "../blob/index.js";
|
|
13
|
+
import type { HarnessChannel, MemoryPortsOptions } from "@nylorun/core/harness-api";
|
|
14
|
+
import { type HarnessPeer } from "../harness-api/server.js";
|
|
15
|
+
import { type HostAuthority } from "../sandbox/join.js";
|
|
16
|
+
import type { TenantPods } from "./context.js";
|
|
11
17
|
/** TENANTS-CCR: test/injection hooks until TenantConfig gains them. */
|
|
12
18
|
export type TenantOpenHooks = {
|
|
13
19
|
modelProvider?: ModelProvider;
|
|
@@ -26,6 +32,11 @@ export type TenantOpenHooks = {
|
|
|
26
32
|
* process never reads, creates or holds the vault key. Without one, it does them here.
|
|
27
33
|
*/
|
|
28
34
|
keys?: Keys;
|
|
35
|
+
/**
|
|
36
|
+
* The run grants the gates service's clients read (F5): each advance mints its session's
|
|
37
|
+
* run token into them. Give the same object to `modelGate` and `toolGate`.
|
|
38
|
+
*/
|
|
39
|
+
runGrants?: RunGrants;
|
|
29
40
|
vaultKek?: Buffer | string | null;
|
|
30
41
|
/** When true, create the KEK file on first vault write (tests / new Tenants). */
|
|
31
42
|
createKekIfMissing?: boolean;
|
|
@@ -50,6 +61,28 @@ export type TenantOpenHooks = {
|
|
|
50
61
|
hostRelay?: boolean;
|
|
51
62
|
/** How long a retired stream basin is kept after a reset (tests). Default 60 s. */
|
|
52
63
|
retireGraceMs?: number;
|
|
64
|
+
/**
|
|
65
|
+
* The Object store, owned by the caller (the Host's `s3` BlobStore, D35). Without one the
|
|
66
|
+
* Tenant keeps blobs on disk under `paths.blobs` (the `fs` adapter: embedding, tests).
|
|
67
|
+
*/
|
|
68
|
+
blobs?: BlobStore;
|
|
69
|
+
/**
|
|
70
|
+
* The Tenant's harness (F6.1, F6.2): `memory` (default) runs one in this process over a memory
|
|
71
|
+
* channel, with the Tenant's MCP pool and SandboxManager; `json` the same through JSON with
|
|
72
|
+
* every frame validated (tests). `remote` runs none: harnesses attach with
|
|
73
|
+
* `TenantHandle.attachHarness` (the Harness API listener, `NYLORUN_HARNESS=remote`), and the
|
|
74
|
+
* one that declares `workspace` holds the workspaces; this process has no MCP pool and no
|
|
75
|
+
* SandboxManager. `ws` is `remote` with a harness service in this process over a loopback
|
|
76
|
+
* WebSocket (tests).
|
|
77
|
+
*/
|
|
78
|
+
harness?: "memory" | "json" | "ws" | "remote";
|
|
79
|
+
/** Sees each frame of the in-process harness's channel (tests and benchmarks). */
|
|
80
|
+
harnessTap?: MemoryPortsOptions["tap"];
|
|
81
|
+
/**
|
|
82
|
+
* Sandbox pods (F7.2): the sandboxes service (`NYLORUN_SANDBOXES_URL`) and the Runtime image
|
|
83
|
+
* pods copy the engine from. Without it, kind `pod` is `sandbox_unavailable`.
|
|
84
|
+
*/
|
|
85
|
+
pods?: TenantPods;
|
|
53
86
|
} & OpenedTenant;
|
|
54
87
|
export declare class TenantRuntime implements TenantHandle {
|
|
55
88
|
private readonly ctx;
|
|
@@ -59,7 +92,11 @@ export declare class TenantRuntime implements TenantHandle {
|
|
|
59
92
|
private readonly detach;
|
|
60
93
|
/** How long close waits for running advances: the execution's advance grace period. */
|
|
61
94
|
private readonly closeGraceMs;
|
|
95
|
+
private readonly harness?;
|
|
96
|
+
private readonly hosts?;
|
|
62
97
|
private constructor();
|
|
98
|
+
/** The Tenant's side of a sandbox pod's join (F7.2). */
|
|
99
|
+
hostAuthority(): HostAuthority | undefined;
|
|
63
100
|
static open(config: TenantConfig, hooks: TenantOpenHooks): Promise<TenantRuntime>;
|
|
64
101
|
fetch(request: Request, node: NodeBindings): Promise<Response>;
|
|
65
102
|
authorize(sessionId: string, request: {
|
|
@@ -80,6 +117,7 @@ export declare class TenantRuntime implements TenantHandle {
|
|
|
80
117
|
streamsStatus(): Promise<StreamsStatus>;
|
|
81
118
|
/** Adds a callback to the Tenant sweep. Returns a function that removes it. */
|
|
82
119
|
onSweep(hook: () => Promise<void>): () => void;
|
|
120
|
+
attachHarness(channel: HarnessChannel, peer: HarnessPeer): () => void;
|
|
83
121
|
close(): Promise<void>;
|
|
84
122
|
}
|
|
85
123
|
/**
|
package/dist/tenant/runtime.js
CHANGED
|
@@ -7,6 +7,7 @@ import { SigningKeys } from "./signing-keys.js";
|
|
|
7
7
|
import { VaultService } from "../vault/service.js";
|
|
8
8
|
import { McpPool } from "../mcp/pool.js";
|
|
9
9
|
import { SandboxManager } from "../sandbox/manager.js";
|
|
10
|
+
import { storeSandboxRecords } from "../sandbox/records.js";
|
|
10
11
|
import { defaultSandboxBackends } from "../sandbox/select.js";
|
|
11
12
|
import { MemoryExecution } from "../execution/memory.js";
|
|
12
13
|
import { openError } from "./cause.js";
|
|
@@ -22,6 +23,14 @@ import { authorize } from "./effects.js";
|
|
|
22
23
|
import { inProcessToolGate } from "../gates/tool-gate.js";
|
|
23
24
|
import { inProcessKeys } from "../keys/keys.js";
|
|
24
25
|
import { tenantApi } from "../api/http/app.js";
|
|
26
|
+
import { createFsBlobStore } from "../blob/index.js";
|
|
27
|
+
import { createHarnessApiServer, } from "../harness-api/server.js";
|
|
28
|
+
import { harnessStatusOf } from "./status.js";
|
|
29
|
+
import { startInProcessHarness, startLoopbackHarness, } from "../harness-api/in-process.js";
|
|
30
|
+
import { localWorkspace, remoteWorkspace, withPodWorkspaces, } from "../harness-api/workspace.js";
|
|
31
|
+
import { hostAuthority } from "../sandbox/join.js";
|
|
32
|
+
import { reconcileSandbox } from "../sandbox/pods/reconcile.js";
|
|
33
|
+
import { sandboxWorkspaceReader } from "../artifacts/workspace.js";
|
|
25
34
|
/** Default ownership lease of an advance; the heartbeat renews it every third. */
|
|
26
35
|
const DEFAULT_OWNER_LEASE_MS = 30_000;
|
|
27
36
|
function validateConfig(config) {
|
|
@@ -45,16 +54,24 @@ export class TenantRuntime {
|
|
|
45
54
|
worker;
|
|
46
55
|
detach;
|
|
47
56
|
closeGraceMs;
|
|
57
|
+
harness;
|
|
58
|
+
hosts;
|
|
48
59
|
constructor(ctx, envelope,
|
|
49
60
|
/** This Tenant's Worker handlers, as registered with the execution. */
|
|
50
61
|
worker, detach,
|
|
51
62
|
/** How long close waits for running advances: the execution's advance grace period. */
|
|
52
|
-
closeGraceMs) {
|
|
63
|
+
closeGraceMs, harness, hosts) {
|
|
53
64
|
this.ctx = ctx;
|
|
54
65
|
this.envelope = envelope;
|
|
55
66
|
this.worker = worker;
|
|
56
67
|
this.detach = detach;
|
|
57
68
|
this.closeGraceMs = closeGraceMs;
|
|
69
|
+
this.harness = harness;
|
|
70
|
+
this.hosts = hosts;
|
|
71
|
+
}
|
|
72
|
+
/** The Tenant's side of a sandbox pod's join (F7.2). */
|
|
73
|
+
hostAuthority() {
|
|
74
|
+
return this.ctx.closing || this.ctx.closed ? undefined : this.hosts;
|
|
58
75
|
}
|
|
59
76
|
static async open(config, hooks) {
|
|
60
77
|
const envelope = hooks.envelope;
|
|
@@ -69,6 +86,7 @@ export class TenantRuntime {
|
|
|
69
86
|
const store = hooks.store;
|
|
70
87
|
let wired;
|
|
71
88
|
let detach;
|
|
89
|
+
let harness;
|
|
72
90
|
try {
|
|
73
91
|
// With the keys service (F4.2) the key lives in the gateway: this process never reads,
|
|
74
92
|
// creates or holds it, and the gateway reports a missing key on its readiness.
|
|
@@ -108,31 +126,63 @@ export class TenantRuntime {
|
|
|
108
126
|
const toolGate = hooks.toolGate ?? inProcessToolGate(config.delivery ?? {});
|
|
109
127
|
const signingKeys = new SigningKeys({ tenantId: config.tenantId, kek: ensureKek });
|
|
110
128
|
const keys = hooks.keys ?? inProcessKeys({ store: opened, vault, signingKeys, kek: ensureKek });
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
const harnessMode = hooks.harness ?? "memory";
|
|
130
|
+
const inProcess = harnessMode === "memory" || harnessMode === "json";
|
|
131
|
+
const preference = seededBackend ?? config.sandbox.backend;
|
|
132
|
+
// With a harness in this process, the Tenant's MCP pool and SandboxManager are its
|
|
133
|
+
// executors; otherwise the harness holds both (F6.2).
|
|
134
|
+
const mcp = inProcess
|
|
135
|
+
? new McpPool({
|
|
136
|
+
pluginData: paths.pluginData,
|
|
137
|
+
childEnv: config.childEnv,
|
|
138
|
+
authorize: (sessionId, request) => authorize(ctx, sessionId, request),
|
|
139
|
+
...(toolGate.openMcp ? { openRemote: (server) => toolGate.openMcp(server) } : {}),
|
|
140
|
+
})
|
|
141
|
+
: undefined;
|
|
117
142
|
const ephemeral = config.mode === "ephemeral";
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
143
|
+
const manager = inProcess
|
|
144
|
+
? new SandboxManager({
|
|
145
|
+
scope: config.tenantId,
|
|
146
|
+
records: storeSandboxRecords(opened),
|
|
147
|
+
backends: config.sandbox.backends ??
|
|
148
|
+
defaultSandboxBackends({ root: paths.sandboxes }),
|
|
149
|
+
preference,
|
|
150
|
+
ephemeral,
|
|
151
|
+
emit: async (sessionId, turnId, type, payload) => {
|
|
152
|
+
if (ctx.closed)
|
|
153
|
+
return;
|
|
154
|
+
try {
|
|
155
|
+
await opened.tx((t) => t.event(sessionId, turnId, type, payload));
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
/* the session is gone (reset) or the store closed */
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
: undefined;
|
|
163
|
+
let harnessServer;
|
|
164
|
+
const workspaces = manager
|
|
165
|
+
? localWorkspace(manager)
|
|
166
|
+
: remoteWorkspace({
|
|
167
|
+
get server() {
|
|
168
|
+
return harnessServer;
|
|
169
|
+
},
|
|
170
|
+
store: opened,
|
|
171
|
+
logger: config.logger,
|
|
172
|
+
preference,
|
|
173
|
+
});
|
|
174
|
+
// A pod sandbox's workspace is in its pod, served by its engine (F7.2).
|
|
175
|
+
const sandbox = hooks.pods
|
|
176
|
+
? withPodWorkspaces(workspaces, {
|
|
177
|
+
get server() {
|
|
178
|
+
return harnessServer;
|
|
179
|
+
},
|
|
180
|
+
store: opened,
|
|
181
|
+
logger: config.logger,
|
|
182
|
+
preference,
|
|
183
|
+
isPod: async (sandboxId) => (await opened.tx((t) => t.sandboxResource(sandboxId)))?.kind === "pod",
|
|
184
|
+
})
|
|
185
|
+
: workspaces;
|
|
136
186
|
const useVaultModel = config.model.kind === "vault";
|
|
137
187
|
let modelProvider;
|
|
138
188
|
if (hooks.modelProvider)
|
|
@@ -143,6 +193,7 @@ export class TenantRuntime {
|
|
|
143
193
|
modelProvider = toolFixtureModel();
|
|
144
194
|
else
|
|
145
195
|
modelProvider = scriptedModel();
|
|
196
|
+
const blobs = hooks.blobs ?? createFsBlobStore({ root: paths.blobs });
|
|
146
197
|
// With the gates service the loop never reads a model credential: only a Runtime
|
|
147
198
|
// without one (embedding, the ephemeral Runtime, tests) reads it here.
|
|
148
199
|
const modelGate = hooks.modelGate ??
|
|
@@ -151,40 +202,47 @@ export class TenantRuntime {
|
|
|
151
202
|
kek: ensureKek,
|
|
152
203
|
root: paths.home,
|
|
153
204
|
logger: config.logger,
|
|
205
|
+
blobs,
|
|
154
206
|
...(config.modelCall ? { settings: config.modelCall } : {}),
|
|
155
207
|
});
|
|
156
208
|
const sessionStreams = createSessionStreams();
|
|
209
|
+
const workers = hooks.execution?.workers ?? new TenantWorkers();
|
|
157
210
|
const local = hooks.execution
|
|
158
211
|
? undefined
|
|
159
212
|
: new MemoryExecution({
|
|
160
213
|
sweepIntervalMs: config.sweepIntervalMs ?? Math.min(config.leaseMs ?? 30_000, 5000),
|
|
214
|
+
// Close's `detach` step waits no longer for running work than for its advances.
|
|
215
|
+
stopGraceMs: workers.graceMs,
|
|
161
216
|
onError: (error) => config.logger.error("tenant execution failed", {
|
|
162
217
|
message: error instanceof Error ? error.message : String(error),
|
|
163
218
|
}),
|
|
164
219
|
});
|
|
165
|
-
const
|
|
166
|
-
execution: local,
|
|
167
|
-
workers: new TenantWorkers(),
|
|
168
|
-
};
|
|
220
|
+
const execution = hooks.execution?.execution ?? local;
|
|
169
221
|
const sweepHooks = new Set();
|
|
170
222
|
ctx = {
|
|
171
223
|
config,
|
|
172
224
|
envelope,
|
|
173
225
|
store: opened,
|
|
174
226
|
vault,
|
|
175
|
-
mcp,
|
|
227
|
+
...(mcp ? { mcp } : {}),
|
|
176
228
|
sandbox,
|
|
177
229
|
flowLimits,
|
|
178
230
|
modelProvider,
|
|
179
231
|
useVaultModel,
|
|
180
232
|
modelGate,
|
|
181
233
|
toolGate,
|
|
234
|
+
get harness() {
|
|
235
|
+
return harnessServer;
|
|
236
|
+
},
|
|
182
237
|
closing: false,
|
|
183
238
|
closed: false,
|
|
184
239
|
work: createWorkState(),
|
|
185
240
|
sessionStreams,
|
|
186
241
|
signingKeys,
|
|
187
242
|
keys,
|
|
243
|
+
blobs,
|
|
244
|
+
workspaces: sandboxWorkspaceReader(sandbox),
|
|
245
|
+
...(hooks.runGrants ? { runGrants: hooks.runGrants } : {}),
|
|
188
246
|
workerId: hooks.workerId ?? WORKER_ID,
|
|
189
247
|
ownerLeaseMs: config.ownerLeaseMs ?? DEFAULT_OWNER_LEASE_MS,
|
|
190
248
|
wake: async (sessionId, wake) => {
|
|
@@ -198,6 +256,12 @@ export class TenantRuntime {
|
|
|
198
256
|
return;
|
|
199
257
|
await execution.deliver(config.tenantId, actionId);
|
|
200
258
|
},
|
|
259
|
+
...(hooks.pods ? { pods: hooks.pods } : {}),
|
|
260
|
+
sandboxSignal: async (sandboxId, signal) => {
|
|
261
|
+
if (ctx.closing || ctx.closed || !execution.sandbox)
|
|
262
|
+
return;
|
|
263
|
+
await execution.sandbox(config.tenantId, sandboxId, signal);
|
|
264
|
+
},
|
|
201
265
|
...(hooks.execution?.stuckInvocations
|
|
202
266
|
? {
|
|
203
267
|
stuckInvocations: () => hooks.execution.stuckInvocations(config.tenantId),
|
|
@@ -209,6 +273,16 @@ export class TenantRuntime {
|
|
|
209
273
|
return () => sweepHooks.delete(hook);
|
|
210
274
|
},
|
|
211
275
|
};
|
|
276
|
+
harnessServer = createHarnessApiServer(ctx, { sandboxPreference: preference });
|
|
277
|
+
if (mcp && manager)
|
|
278
|
+
harness = await startInProcessHarness(ctx, { mcp, sandbox: manager }, {
|
|
279
|
+
json: harnessMode === "json",
|
|
280
|
+
...(hooks.harnessTap ? { tap: hooks.harnessTap } : {}),
|
|
281
|
+
});
|
|
282
|
+
else if (harnessMode === "ws")
|
|
283
|
+
harness = await startLoopbackHarness(ctx, {
|
|
284
|
+
...(config.sandbox.backends ? { sandboxBackends: config.sandbox.backends } : {}),
|
|
285
|
+
});
|
|
212
286
|
wired = await wireStreams(ctx, {
|
|
213
287
|
store: opened,
|
|
214
288
|
streams: hooks.streams ?? new MemoryStreams(),
|
|
@@ -223,6 +297,7 @@ export class TenantRuntime {
|
|
|
223
297
|
advance: (sessionId, signal) => advance(ctx, sessionId, signal),
|
|
224
298
|
deliver: (actionId, signal) => deliverAction(ctx, actionId, signal),
|
|
225
299
|
sweep: () => sweep(ctx),
|
|
300
|
+
sandbox: (sandboxId, trigger) => reconcileSandbox(ctx, sandboxId, trigger),
|
|
226
301
|
};
|
|
227
302
|
const unregister = workers.register(config.tenantId, worker);
|
|
228
303
|
detach = async () => {
|
|
@@ -232,10 +307,11 @@ export class TenantRuntime {
|
|
|
232
307
|
if (local)
|
|
233
308
|
await local.start(workers.handlers);
|
|
234
309
|
await execution.armSweep(config.tenantId);
|
|
235
|
-
return new TenantRuntime(ctx, envelope, worker, detach, workers.graceMs);
|
|
310
|
+
return new TenantRuntime(ctx, envelope, worker, detach, workers.graceMs, harness, hooks.pods ? hostAuthority(ctx) : undefined);
|
|
236
311
|
}
|
|
237
312
|
catch (error) {
|
|
238
313
|
await detach?.().catch(() => undefined);
|
|
314
|
+
await harness?.stop().catch(() => undefined);
|
|
239
315
|
await wired?.close().catch(() => undefined);
|
|
240
316
|
await store.close().catch(() => undefined);
|
|
241
317
|
throw error;
|
|
@@ -256,6 +332,7 @@ export class TenantRuntime {
|
|
|
256
332
|
inFlightDeliveries: [...this.ctx.work.deliveries.values()].reduce((n, set) => n + set.size, 0),
|
|
257
333
|
pendingActions: counts.pendingActions,
|
|
258
334
|
uncertainEffects: counts.uncertainEffects,
|
|
335
|
+
harness: harnessStatusOf(this.ctx),
|
|
259
336
|
};
|
|
260
337
|
}
|
|
261
338
|
drain(activeWork, timeoutMs = 30_000) {
|
|
@@ -279,6 +356,9 @@ export class TenantRuntime {
|
|
|
279
356
|
onSweep(hook) {
|
|
280
357
|
return this.ctx.onSweep(hook);
|
|
281
358
|
}
|
|
359
|
+
attachHarness(channel, peer) {
|
|
360
|
+
return this.ctx.harness.attach(channel, peer);
|
|
361
|
+
}
|
|
282
362
|
async close() {
|
|
283
363
|
const ctx = this.ctx;
|
|
284
364
|
ctx.closing = true;
|
|
@@ -289,10 +369,18 @@ export class TenantRuntime {
|
|
|
289
369
|
// error is rethrown at the end.
|
|
290
370
|
const steps = [
|
|
291
371
|
["detach", () => this.detach()],
|
|
292
|
-
["mcp", () => ctx.mcp
|
|
372
|
+
["mcp", () => ctx.mcp?.close()],
|
|
293
373
|
["session streams", () => endAllStreams(ctx.sessionStreams)],
|
|
294
374
|
// Bounded: an advance that ignores its abort is abandoned; its lease lapses (§11.4).
|
|
295
375
|
["idle", () => waitForIdle(ctx, Math.max(0, idleBy - Date.now()))],
|
|
376
|
+
[
|
|
377
|
+
"harness",
|
|
378
|
+
async () => {
|
|
379
|
+
ctx.harness.close();
|
|
380
|
+
// Advances still running were abandoned at "idle": their runs are left behind too.
|
|
381
|
+
await this.harness?.stop(Math.max(0, idleBy - Date.now()));
|
|
382
|
+
},
|
|
383
|
+
],
|
|
296
384
|
["sandbox", () => ctx.sandbox.close()],
|
|
297
385
|
[
|
|
298
386
|
"streams",
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandboxes as a resource (blueprint D39, F7.1; Host feature `sandboxes`): `PUT`, `GET` and
|
|
3
|
+
* `DELETE /v1/sandboxes/{id}`, the list by label, sessions attaching with `sandbox: { id }`, and
|
|
4
|
+
* the checks every turn start makes on the sandbox its session is attached to.
|
|
5
|
+
*
|
|
6
|
+
* - **No scope.** The Runtime decides nothing about who shares a sandbox. It stores the
|
|
7
|
+
* sandbox's id, kind, spec and labels, and the session body names the sandbox it is attached
|
|
8
|
+
* to (`Session.sandboxId`); deleting a session (a sessions reset) only detaches it.
|
|
9
|
+
* - **Access.** An application key reaches every sandbox, and so does an application key acting
|
|
10
|
+
* for a subject (`Nylorun-Subject`), which needs `sandboxes:write` to change one. A subject
|
|
11
|
+
* token reaches only the ids its `sbx` grants match, and changes them only with
|
|
12
|
+
* `sandboxes:write`; any other id is the 404 of a missing one.
|
|
13
|
+
* - **Serial turns.** One turn at a time per sandbox: a session whose sandbox another attached
|
|
14
|
+
* session's turn holds is refused (`409 sandbox_busy`), as the session sandbox route is
|
|
15
|
+
* refused while its session's turn runs. Commands within the workspace keep queueing in the
|
|
16
|
+
* Sandbox Manager.
|
|
17
|
+
* - **Kinds.** `virtual`: its workspace is the virtual backend's, keyed by the sandbox id
|
|
18
|
+
* (`SandboxManager.sandboxKeyOf`) and created on the first tool call. `pod` (F7.2, Host feature
|
|
19
|
+
* `sandbox-pods`): an agent-sandbox pod on the Tenant's cluster, created at once, whose engine
|
|
20
|
+
* runs the turns of the sessions attached to it. Its lifecycle (`sandbox/pods/lifecycle.ts`)
|
|
21
|
+
* runs in the `Sandbox` object: this module records what is wanted (`desired`, `rev`) and asks
|
|
22
|
+
* for a reconcile after the commit (`ctx.sandboxSignal`). Without sandbox pods kind `pod` is
|
|
23
|
+
* `409 sandbox_unavailable`. `POST .../stop` suspends a pod (the volume is kept; the next turn
|
|
24
|
+
* starts it), `POST .../reset` gives it a new pod on a new volume. An expired pod refuses
|
|
25
|
+
* turns (`sandbox_expired`) until a `PUT` sets a longer `lifecycle.ttl`; a lost one
|
|
26
|
+
* (`sandbox_lost`) until a reset. Deleting a pod sandbox keeps its row (`desired: deleted`,
|
|
27
|
+
* invisible here) until its Sandbox is gone.
|
|
28
|
+
* - **Lifecycle events** go to the sandbox's own stream in the record (`Tx.sandboxEvent`,
|
|
29
|
+
* `record/sandbox.ts`); the session's log records `sandbox.attached` too.
|
|
30
|
+
*/
|
|
31
|
+
import { type PutSandboxRequest, type SandboxView } from "@nylorun/core/contracts";
|
|
32
|
+
import type { SandboxResource, Tx } from "../store/types.js";
|
|
33
|
+
import type { AuthScope, Session, TenantContext } from "./context.js";
|
|
34
|
+
/** The `sbx` grants that limit `scope`, or undefined when it reaches every sandbox. */
|
|
35
|
+
export declare function sandboxGrantsOf(scope: AuthScope): readonly string[] | undefined;
|
|
36
|
+
/** A sandbox resource, unless it is gone or being deleted. */
|
|
37
|
+
export declare function liveSandbox(t: Tx, id: string, options?: {
|
|
38
|
+
lock?: boolean;
|
|
39
|
+
}): Promise<SandboxResource | undefined>;
|
|
40
|
+
/**
|
|
41
|
+
* `PUT /v1/sandboxes/{id}`: creates the sandbox (within the Tenant's limit on their number), or
|
|
42
|
+
* finds the one with this id, so "get or create" is one call. The spec is fixed once it exists;
|
|
43
|
+
* labels, when sent, replace its labels. On an existing pod sandbox it also starts a stopped or
|
|
44
|
+
* failed pod again, and a new `lifecycle.ttl` sets a new expiry (reviving an expired one).
|
|
45
|
+
*/
|
|
46
|
+
export declare function putSandbox(ctx: TenantContext, id: string, body: PutSandboxRequest, scope: AuthScope): Promise<SandboxView>;
|
|
47
|
+
/** `GET /v1/sandboxes/{id}`. */
|
|
48
|
+
export declare function getSandbox(ctx: TenantContext, id: string, scope: AuthScope): Promise<SandboxView>;
|
|
49
|
+
/** `GET /v1/sandboxes?label=k=v`: every sandbox with all the labels, that the caller reaches. */
|
|
50
|
+
export declare function listSandboxes(ctx: TenantContext, labels: Record<string, string>, scope: AuthScope): Promise<{
|
|
51
|
+
sandboxes: SandboxView[];
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* `DELETE /v1/sandboxes/{id}`: deletes the sandbox and its workspace. Refused while a session
|
|
55
|
+
* attached to it has an active turn. Sessions stay attached by id: their next turn is refused
|
|
56
|
+
* (`sandbox_unavailable`) unless a sandbox with this id exists again. A pod sandbox's row stays
|
|
57
|
+
* (`desired: deleted`) until its Sandbox is gone.
|
|
58
|
+
*/
|
|
59
|
+
export declare function deleteSandbox(ctx: TenantContext, id: string, scope: AuthScope): Promise<{
|
|
60
|
+
id: string;
|
|
61
|
+
deleted: boolean;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* `POST /v1/sandboxes/{id}/stop`: suspends a pod sandbox. Its volume is kept, and the next turn
|
|
65
|
+
* of a session attached to it (or a `PUT`) starts it again. Refused during a turn.
|
|
66
|
+
*/
|
|
67
|
+
export declare function stopSandbox(ctx: TenantContext, id: string, scope: AuthScope): Promise<SandboxView>;
|
|
68
|
+
/**
|
|
69
|
+
* `POST /v1/sandboxes/{id}/reset`: a new pod on a new, empty volume (the next volume
|
|
70
|
+
* generation), with a new join token; the old Sandbox and volume are deleted. Leaves `lost` and
|
|
71
|
+
* `failed`; an expired sandbox stays expired. Refused during a turn, and while an earlier
|
|
72
|
+
* reset's Sandbox is still being deleted.
|
|
73
|
+
*/
|
|
74
|
+
export declare function resetSandbox(ctx: TenantContext, id: string, scope: AuthScope): Promise<SandboxView>;
|
|
75
|
+
/** `GET /v1/sandboxes/{id}/events`: the sandbox's lifecycle stream. */
|
|
76
|
+
export declare function sandboxEventsOf(ctx: TenantContext, id: string, scope: AuthScope, fromSeq: number | undefined): Promise<{
|
|
77
|
+
events: {
|
|
78
|
+
schema: "nylorun.sandbox-event/1";
|
|
79
|
+
eventId: string;
|
|
80
|
+
tenantId: string;
|
|
81
|
+
sandboxId: string;
|
|
82
|
+
seq: number;
|
|
83
|
+
time: string;
|
|
84
|
+
type: "sandbox.attached" | "sandbox.created" | "sandbox.deleted" | "sandbox.detached" | "sandbox.expired" | "sandbox.failed" | "sandbox.lost" | "sandbox.relaunched" | "sandbox.reset" | "sandbox.running" | "sandbox.suspended";
|
|
85
|
+
payload: Record<string, unknown>;
|
|
86
|
+
}[];
|
|
87
|
+
}>;
|
|
88
|
+
/**
|
|
89
|
+
* Attaches a new session to sandbox `id` (`PutSessionRequest.sandbox = { id }`): the sandbox
|
|
90
|
+
* must exist and be reachable with `grants`. Returns its spec, which the session pins. Runs in
|
|
91
|
+
* the session's transaction, after its session row lock.
|
|
92
|
+
*/
|
|
93
|
+
export declare function attachSandbox(t: Tx, id: string, grants: readonly string[] | undefined): Promise<SandboxResource>;
|
|
94
|
+
/** Records the attachment on the session's log and on the sandbox's stream. */
|
|
95
|
+
export declare function recordAttachment(t: Tx, sessionId: string, sandboxId: string): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Checks a turn start on a session attached to a sandbox, in the command's transaction after
|
|
98
|
+
* the session row lock: the sandbox still exists, a subject token's `sbx` grants reach it, and
|
|
99
|
+
* no other session attached to it has a turn running. Holds the sandbox row's lock until the
|
|
100
|
+
* transaction ends, so two turn starts on one sandbox are decided one after the other.
|
|
101
|
+
*
|
|
102
|
+
* A pod sandbox must also be usable: not lost (`sandbox_lost`) or expired (`sandbox_expired`),
|
|
103
|
+
* on a Runtime with sandbox pods (`sandbox_unavailable`). A stopped or failed pod is asked to
|
|
104
|
+
* run again, after the commit.
|
|
105
|
+
*/
|
|
106
|
+
export declare function checkSandboxTurn(t: Tx, session: Session, scope: AuthScope, ctx?: TenantContext): Promise<void>;
|
|
107
|
+
/** A sessions reset detaches every session from its sandbox: each sandbox's stream says so. */
|
|
108
|
+
export declare function detachAllSessions(t: Tx): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Marks the end of a turn's segment on pod sandbox `id`: idle counts from now, and the idle
|
|
111
|
+
* timer is armed (D34). Best effort.
|
|
112
|
+
*/
|
|
113
|
+
export declare function touchPodSandbox(ctx: TenantContext, id: string): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Re-arms the idle timer of every running pod sandbox after the Tenant's settings change, so a
|
|
116
|
+
* shorter `limits.idle` applies to pods already idle (D36: the settings are read whenever a
|
|
117
|
+
* timer is set). Best effort.
|
|
118
|
+
*/
|
|
119
|
+
export declare function rearmPodTimers(ctx: TenantContext): Promise<void>;
|