@nylorun/runtime 0.25.0-beta → 0.27.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 +169 -0
- package/README.md +26 -16
- package/dist/adapters/execution/restate.d.ts +15 -18
- package/dist/adapters/execution/restate.js +31 -27
- package/dist/adapters/replication/pgoutput.d.ts +1 -1
- package/dist/adapters/replication/pgoutput.js +3 -2
- package/dist/adapters/sandbox/local.js +8 -5
- package/dist/adapters/sandbox/virtual.js +2 -2
- package/dist/adapters/streams/s2.d.ts +1 -1
- package/dist/adapters/streams/s2.js +18 -14
- package/dist/api/a2a/card.js +1 -1
- package/dist/api/a2a/endpoint.js +3 -4
- package/dist/api/a2a/protocol.js +4 -0
- package/dist/api/a2a/routes.d.ts +2 -6
- package/dist/api/a2a/routes.js +12 -18
- package/dist/api/ag-ui/endpoint.js +2 -3
- package/dist/api/ag-ui/routes.js +4 -4
- package/dist/api/components.d.ts +326 -45
- package/dist/api/components.js +5 -1
- package/dist/api/gate/keys.js +7 -4
- package/dist/api/gate/routes.d.ts +6 -2
- package/dist/api/gate/routes.js +15 -11
- package/dist/api/http/app.d.ts +2 -2
- package/dist/api/http/app.js +2 -2
- package/dist/api/http/body.d.ts +15 -3
- package/dist/api/http/body.js +15 -7
- package/dist/api/http/define.d.ts +1 -1
- package/dist/api/http/respond.js +7 -3
- package/dist/api/http/routes/access.js +5 -4
- package/dist/api/http/routes/artifacts.js +19 -7
- package/dist/api/http/routes/coverage.js +1 -1
- package/dist/api/http/routes/environment-manifests.js +1 -1
- package/dist/api/http/routes/environments.js +6 -6
- package/dist/api/http/routes/files.d.ts +2 -1
- package/dist/api/http/routes/files.js +31 -5
- package/dist/api/http/routes/mcp.js +1 -1
- package/dist/api/http/routes/reads.d.ts +35 -0
- package/dist/api/http/routes/reads.js +50 -22
- package/dist/api/http/routes/sessions.js +12 -11
- package/dist/api/http/routes/tenant.d.ts +2 -2
- package/dist/api/http/routes/tenant.js +69 -16
- package/dist/api/inbound-files.d.ts +3 -9
- package/dist/api/inbound-files.js +18 -24
- package/dist/api/openapi.js +6 -2
- package/dist/artifacts/disk.d.ts +0 -4
- package/dist/artifacts/disk.js +77 -73
- package/dist/artifacts/files.d.ts +13 -4
- package/dist/artifacts/files.js +27 -9
- package/dist/artifacts/folder-upload.d.ts +75 -0
- package/dist/artifacts/folder-upload.js +263 -0
- package/dist/artifacts/folders.d.ts +14 -8
- package/dist/artifacts/folders.js +19 -11
- package/dist/artifacts/links.js +7 -6
- package/dist/artifacts/parts.d.ts +9 -14
- package/dist/artifacts/parts.js +11 -9
- package/dist/artifacts/pipe.d.ts +15 -0
- package/dist/artifacts/pipe.js +71 -0
- package/dist/artifacts/quota.d.ts +36 -0
- package/dist/artifacts/quota.js +44 -0
- package/dist/artifacts/service.d.ts +25 -1
- package/dist/artifacts/service.js +70 -25
- package/dist/blob/fs.js +2 -1
- package/dist/blob/s3.d.ts +6 -2
- package/dist/blob/s3.js +9 -7
- package/dist/blob/types.d.ts +1 -1
- package/dist/blob/types.js +1 -1
- package/dist/core/limits.d.ts +11 -35
- package/dist/core/limits.js +5 -73
- package/dist/core/sandbox-routes.js +0 -1
- package/dist/core/turn-manifest.d.ts +1 -2
- package/dist/core/turn-manifest.js +4 -28
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +8 -6
- package/dist/execution/types.d.ts +1 -6
- package/dist/execution/types.js +0 -5
- package/dist/gates/contract.d.ts +3 -2
- package/dist/gates/contract.js +1 -1
- package/dist/gates/egress-credentials.js +4 -3
- package/dist/gates/egress.d.ts +9 -9
- package/dist/gates/egress.js +19 -19
- package/dist/gates/fixture-model.js +13 -2
- package/dist/gates/handler.d.ts +1 -3
- package/dist/gates/handler.js +4 -3
- package/dist/gates/http-client.d.ts +4 -3
- package/dist/gates/http-client.js +5 -7
- package/dist/gates/http-tool.d.ts +24 -4
- package/dist/gates/http-tool.js +53 -17
- package/dist/gates/in-process.d.ts +2 -1
- package/dist/gates/in-process.js +17 -9
- package/dist/gates/inflight.d.ts +2 -2
- package/dist/gates/inflight.js +3 -2
- package/dist/gates/journal.js +5 -7
- package/dist/gates/mcp-handler.d.ts +4 -3
- package/dist/gates/mcp-handler.js +15 -5
- package/dist/gates/model-gate.d.ts +11 -2
- package/dist/gates/policy.d.ts +28 -5
- package/dist/gates/policy.js +60 -17
- package/dist/gates/provider-gate.d.ts +22 -0
- package/dist/gates/provider-gate.js +30 -0
- package/dist/gates/tool-calls.d.ts +1 -3
- package/dist/gates/tool-calls.js +2 -3
- package/dist/gates/tool-client.d.ts +1 -1
- package/dist/gates/tool-client.js +5 -6
- package/dist/gates/tool-contract.d.ts +8 -5
- package/dist/gates/tool-contract.js +1 -1
- package/dist/gates/tool-gate.d.ts +18 -7
- package/dist/gates/tool-gate.js +49 -7
- package/dist/harness/calls.d.ts +27 -25
- package/dist/harness/calls.js +40 -46
- package/dist/harness/executors.d.ts +10 -3
- package/dist/harness/executors.js +53 -23
- package/dist/harness/main.js +2 -2
- package/dist/harness/pod.d.ts +0 -2
- package/dist/harness/pod.js +7 -6
- package/dist/harness/run-tokens.d.ts +4 -1
- package/dist/harness/run-tokens.js +6 -2
- package/dist/harness/service.d.ts +17 -14
- package/dist/harness/service.js +21 -35
- package/dist/harness/setup.d.ts +0 -2
- package/dist/harness/setup.js +4 -9
- package/dist/harness/ws-client.d.ts +2 -2
- package/dist/harness/ws-client.js +7 -5
- package/dist/harness/ws-port.d.ts +3 -4
- package/dist/harness/ws-port.js +3 -4
- package/dist/harness-api/artifacts.d.ts +20 -0
- package/dist/harness-api/artifacts.js +478 -0
- package/dist/harness-api/in-process.d.ts +2 -2
- package/dist/harness-api/in-process.js +18 -16
- package/dist/harness-api/record.d.ts +10 -9
- package/dist/harness-api/record.js +102 -157
- package/dist/harness-api/renew.d.ts +1 -1
- package/dist/harness-api/renew.js +2 -2
- package/dist/harness-api/server.d.ts +1 -16
- package/dist/harness-api/server.js +82 -73
- package/dist/harness-api/start.d.ts +20 -2
- package/dist/harness-api/start.js +57 -18
- package/dist/harness-api/tool-journal.d.ts +15 -10
- package/dist/harness-api/tool-journal.js +26 -24
- package/dist/harness-api/workspace.d.ts +0 -4
- package/dist/harness-api/workspace.js +2 -45
- package/dist/harness-api/ws-server.js +10 -10
- package/dist/host/create-host.js +2 -3
- package/dist/host/execution.js +2 -3
- package/dist/host/gates.d.ts +2 -9
- package/dist/host/gates.js +15 -12
- package/dist/host/http.d.ts +0 -2
- package/dist/host/http.js +0 -27
- package/dist/host/main.js +10 -3
- package/dist/host/operate.js +12 -3
- package/dist/host/stack-config.d.ts +11 -0
- package/dist/host/stack-config.js +21 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/infra/index.js +2 -1
- package/dist/infra/readiness.d.ts +0 -2
- package/dist/infra/readiness.js +4 -3
- package/dist/keys/client.d.ts +1 -0
- package/dist/keys/client.js +2 -3
- package/dist/keys/contract.d.ts +1 -3
- package/dist/keys/contract.js +1 -3
- package/dist/keys/egress-ca.d.ts +0 -2
- package/dist/keys/egress-ca.js +2 -3
- package/dist/keys/keys.js +6 -1
- package/dist/keys/x509.d.ts +8 -4
- package/dist/keys/x509.js +14 -5
- package/dist/management-openapi.json +1961 -994
- package/dist/mcp/connect.d.ts +35 -26
- package/dist/mcp/connect.js +112 -141
- package/dist/mcp/pool.d.ts +27 -22
- package/dist/mcp/pool.js +34 -48
- package/dist/mcp/preview.d.ts +13 -6
- package/dist/mcp/preview.js +37 -20
- package/dist/mcp/snapshot.d.ts +35 -86
- package/dist/mcp/snapshot.js +93 -135
- package/dist/model/auth-store.js +5 -3
- package/dist/model/capabilities.d.ts +1 -1
- package/dist/model/catalog.d.ts +9 -36
- package/dist/model/catalog.js +6 -33
- package/dist/model/descriptor.d.ts +51 -0
- package/dist/model/descriptor.js +83 -0
- package/dist/model/models.d.ts +0 -3
- package/dist/model/models.js +3 -5
- package/dist/model/native-blocks.js +1 -1
- package/dist/model/pi-model.d.ts +16 -5
- package/dist/model/pi-model.js +86 -105
- package/dist/openapi.json +866 -1218
- package/dist/reads/cursor.js +3 -1
- package/dist/record/append.d.ts +34 -2
- package/dist/record/append.js +50 -1
- package/dist/record/environment.d.ts +2 -0
- package/dist/record/environment.js +2 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/redact.d.ts +1 -1
- package/dist/redact.js +3 -2
- package/dist/sandbox/egress-token.d.ts +3 -3
- package/dist/sandbox/egress-token.js +3 -3
- package/dist/sandbox/join.js +3 -2
- package/dist/sandbox/manager.d.ts +41 -41
- package/dist/sandbox/manager.js +112 -44
- package/dist/sandbox/placement.d.ts +1 -1
- package/dist/sandbox/placement.js +5 -8
- package/dist/sandbox/pods/client.d.ts +1 -10
- package/dist/sandbox/pods/client.js +8 -4
- package/dist/sandbox/pods/lifecycle.d.ts +19 -11
- package/dist/sandbox/pods/lifecycle.js +45 -23
- package/dist/sandbox/pods/network-gate.d.ts +4 -5
- package/dist/sandbox/pods/network-gate.js +7 -7
- package/dist/sandbox/pods/reconcile.js +4 -1
- package/dist/sandbox/pods/spec.js +8 -5
- package/dist/sandbox/policy.d.ts +7 -1
- package/dist/sandbox/policy.js +10 -3
- package/dist/sandbox/resolve.d.ts +8 -3
- package/dist/sandbox/resolve.js +12 -2
- package/dist/sandbox/setup.d.ts +3 -1
- package/dist/sandbox/setup.js +34 -11
- package/dist/sandbox/share.d.ts +5 -14
- package/dist/sandbox/share.js +5 -34
- package/dist/sandbox/skills.d.ts +7 -13
- package/dist/sandbox/skills.js +16 -31
- package/dist/sandbox/tenant-config.d.ts +2 -2
- package/dist/sandbox/tenant-config.js +4 -3
- package/dist/sandbox/tools.d.ts +7 -1
- package/dist/sandbox/tools.js +26 -26
- package/dist/sandbox/types.d.ts +10 -3
- package/dist/sandbox/types.js +9 -0
- package/dist/store/postgres/connect.d.ts +6 -5
- package/dist/store/postgres/connect.js +14 -11
- package/dist/store/postgres/control.js +13 -13
- package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
- package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
- package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
- package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
- package/dist/store/postgres/migrate.js +1 -1
- package/dist/store/postgres/reads.js +10 -2
- package/dist/store/postgres/schema.d.ts +19 -0
- package/dist/store/postgres/schema.js +7 -0
- package/dist/store/postgres/store.js +55 -26
- package/dist/store/types.d.ts +17 -7
- package/dist/streams/relay/core.d.ts +3 -3
- package/dist/streams/relay/core.js +9 -10
- package/dist/tenant/advance.js +21 -31
- package/dist/tenant/commands.js +9 -16
- package/dist/tenant/context.d.ts +14 -17
- package/dist/tenant/definition-files.d.ts +25 -4
- package/dist/tenant/definition-files.js +85 -47
- package/dist/tenant/effects.js +9 -36
- package/dist/tenant/environment-manifests.js +9 -1
- package/dist/tenant/environments.d.ts +0 -2
- package/dist/tenant/environments.js +54 -30
- package/dist/tenant/ephemeral.d.ts +1 -1
- package/dist/tenant/ephemeral.js +2 -1
- package/dist/tenant/history.d.ts +1 -1
- package/dist/tenant/history.js +1 -1
- package/dist/tenant/host-token.d.ts +3 -3
- package/dist/tenant/host-token.js +12 -11
- package/dist/tenant/http.d.ts +5 -8
- package/dist/tenant/http.js +9 -0
- package/dist/tenant/identity-file.d.ts +24 -1
- package/dist/tenant/identity-file.js +35 -7
- package/dist/tenant/issuers.d.ts +5 -5
- package/dist/tenant/issuers.js +31 -23
- package/dist/tenant/jwt.d.ts +8 -2
- package/dist/tenant/jwt.js +10 -2
- package/dist/tenant/limits.d.ts +90 -0
- package/dist/tenant/limits.js +175 -0
- package/dist/tenant/module.d.ts +4 -1
- package/dist/tenant/module.js +9 -8
- package/dist/tenant/outbound.d.ts +17 -19
- package/dist/tenant/outbound.js +18 -21
- package/dist/tenant/run-grants.js +3 -2
- package/dist/tenant/run-token.d.ts +0 -4
- package/dist/tenant/run-token.js +6 -11
- package/dist/tenant/runtime-limits.d.ts +24 -0
- package/dist/tenant/runtime-limits.js +27 -0
- package/dist/tenant/runtime.js +30 -43
- package/dist/tenant/session-streams.js +7 -6
- package/dist/tenant/session.d.ts +1 -1
- package/dist/tenant/session.js +1 -1
- package/dist/tenant/sessions.js +32 -29
- package/dist/tenant/signing-keys.d.ts +1 -1
- package/dist/tenant/signing-keys.js +5 -6
- package/dist/tenant/status.js +4 -3
- package/dist/tenant/streams.js +18 -20
- package/dist/tenant/sweep.d.ts +1 -21
- package/dist/tenant/sweep.js +39 -48
- package/dist/tenant/transcript.d.ts +70 -23
- package/dist/tenant/transcript.js +28 -22
- package/dist/tenant/types.d.ts +11 -16
- package/dist/tenant/worker.d.ts +2 -10
- package/dist/tenant/worker.js +5 -14
- package/dist/traces/ids.js +5 -1
- package/dist/traces/map.d.ts +9 -6
- package/dist/traces/map.js +9 -11
- package/dist/traces/otlp.d.ts +3 -3
- package/dist/traces/otlp.js +5 -3
- package/dist/traces/projector.d.ts +9 -4
- package/dist/traces/projector.js +13 -7
- package/dist/traces/read.js +6 -1
- package/dist/tuning.d.ts +383 -0
- package/dist/tuning.js +658 -0
- package/dist/vault/headers.js +4 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/dist/artifacts/export.d.ts +0 -8
- package/dist/artifacts/export.js +0 -204
- package/dist/artifacts/media-types.d.ts +0 -30
- package/dist/artifacts/media-types.js +0 -137
- package/dist/artifacts/workspace.d.ts +0 -60
- package/dist/artifacts/workspace.js +0 -45
- package/dist/media.d.ts +0 -10
- package/dist/media.js +0 -58
- package/dist/sandbox/runtime-tools.d.ts +0 -9
- package/dist/sandbox/runtime-tools.js +0 -16
- package/dist/sandbox/session-sandbox.d.ts +0 -16
- package/dist/sandbox/session-sandbox.js +0 -77
- package/dist/tenant/artifact-tool.d.ts +0 -13
- package/dist/tenant/artifact-tool.js +0 -243
- package/dist/tenant/skill-tool.d.ts +0 -20
- package/dist/tenant/skill-tool.js +0 -67
- package/dist/tenant/tool-results.d.ts +0 -57
- package/dist/tenant/tool-results.js +0 -298
- package/dist/tenant/tool-search.d.ts +0 -39
- package/dist/tenant/tool-search.js +0 -103
|
@@ -3,15 +3,22 @@
|
|
|
3
3
|
* today a skill's folder (`SkillManifest.files`). A client uploads each one once
|
|
4
4
|
* (`PUT /v1/files/sha256:<hex>`); its bytes go to the Object store at
|
|
5
5
|
* `definitions/sha256/<hex>`, and a `definition_files` row says the Tenant holds it. A
|
|
6
|
-
* definition naming a file the Tenant does not hold is refused (`definition_files_missing`)
|
|
7
|
-
*
|
|
6
|
+
* definition naming a file the Tenant does not hold is refused (`definition_files_missing`), and
|
|
7
|
+
* one whose skill's files hold more than the Tenant's skill limit together (`limit_exceeded`,
|
|
8
|
+
* `skills.config`, default 2 GiB). One file may be as large as a whole skill.
|
|
9
|
+
* The Runtime serves them to a run's harness (`definition.file`: our engine's skill tools read
|
|
10
|
+
* them) and gives a sandbox the skills' files.
|
|
8
11
|
*
|
|
9
12
|
* Files are never deleted here: `definition_file_uses` records which definition version names
|
|
10
13
|
* which file, for a later sweep.
|
|
11
14
|
*/
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
16
|
+
import { definitionFilesOf, isDefinitionFileHash, skillsOf, } from "@nylorun/core/contracts";
|
|
17
|
+
import { BlobTooLargeError } from "../blob/index.js";
|
|
14
18
|
import { fail } from "./http.js";
|
|
19
|
+
import { readSkillLimits } from "./limits.js";
|
|
20
|
+
/** Where an upload's bytes wait while their hash is checked; never read as a definition file. */
|
|
21
|
+
const STAGING_PREFIX = "definitions/uploads/";
|
|
15
22
|
/** Where a definition file's bytes are in the Object store. */
|
|
16
23
|
export function definitionFileKey(sha256) {
|
|
17
24
|
return `definitions/sha256/${sha256.slice("sha256:".length)}`;
|
|
@@ -26,73 +33,104 @@ export async function hasDefinitionFile(ctx, file) {
|
|
|
26
33
|
const sha256 = checkHash(file);
|
|
27
34
|
return (await ctx.store.tx((t) => t.definitionFile(sha256))) !== undefined;
|
|
28
35
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const tooLarge = () => fail(413, `A definition file may hold at most ${DEFINITION_FILE_MAX_BYTES} bytes`, {
|
|
36
|
+
function tooLarge(limitBytes) {
|
|
37
|
+
return fail(413, `A definition file may hold at most ${limitBytes} bytes, the Tenant's limit for a whole skill`, {
|
|
32
38
|
code: "limit_exceeded",
|
|
33
|
-
details: { limitBytes
|
|
39
|
+
details: { limitBytes },
|
|
34
40
|
});
|
|
35
|
-
if (declared !== undefined && declared > DEFINITION_FILE_MAX_BYTES) {
|
|
36
|
-
await body?.cancel().catch(() => undefined);
|
|
37
|
-
tooLarge();
|
|
38
|
-
}
|
|
39
|
-
if (body === null)
|
|
40
|
-
return new Uint8Array();
|
|
41
|
-
const chunks = [];
|
|
42
|
-
let size = 0;
|
|
43
|
-
const reader = body.getReader();
|
|
44
|
-
for (;;) {
|
|
45
|
-
const { done, value } = await reader.read();
|
|
46
|
-
if (done)
|
|
47
|
-
break;
|
|
48
|
-
size += value.byteLength;
|
|
49
|
-
if (size > DEFINITION_FILE_MAX_BYTES) {
|
|
50
|
-
await reader.cancel().catch(() => undefined);
|
|
51
|
-
tooLarge();
|
|
52
|
-
}
|
|
53
|
-
chunks.push(value);
|
|
54
|
-
}
|
|
55
|
-
return Buffer.concat(chunks);
|
|
56
41
|
}
|
|
57
42
|
/**
|
|
58
43
|
* Stores the bytes of definition file `file`: `201` when they are new, `200` when the Tenant
|
|
59
|
-
* holds them already (the body is not read). Bytes that do not hash to `file` are a `400
|
|
44
|
+
* holds them already (the body is not read). Bytes that do not hash to `file` are a `400`; more
|
|
45
|
+
* than the Tenant's skill limit (`skills.config`) a `413`. The body streams to a staging key and is copied to
|
|
46
|
+
* the file's key only once its hash is checked, so no file is held in memory and bytes of
|
|
47
|
+
* another hash never replace a file at its key.
|
|
60
48
|
*/
|
|
61
49
|
export async function putDefinitionFile(ctx, file, request, signal) {
|
|
62
50
|
const sha256 = checkHash(file);
|
|
63
|
-
const held = await ctx.store.tx((t) =>
|
|
51
|
+
const { held, maxBytes } = await ctx.store.tx(async (t) => ({
|
|
52
|
+
held: await t.definitionFile(sha256),
|
|
53
|
+
maxBytes: (await readSkillLimits(t)).totalBytes,
|
|
54
|
+
}));
|
|
64
55
|
if (held) {
|
|
65
56
|
await request.body?.cancel().catch(() => undefined);
|
|
66
57
|
return { status: 200, view: { sha256, size: held.size } };
|
|
67
58
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
details: { expected: sha256, actual },
|
|
74
|
-
});
|
|
75
|
-
// The key is the content's hash: a concurrent upload of the same file writes the same bytes.
|
|
76
|
-
await ctx.blobs.put(definitionFileKey(sha256), bytes, {
|
|
59
|
+
if (request.declaredBytes !== undefined && request.declaredBytes > maxBytes) {
|
|
60
|
+
await request.body?.cancel().catch(() => undefined);
|
|
61
|
+
tooLarge(maxBytes);
|
|
62
|
+
}
|
|
63
|
+
const options = {
|
|
77
64
|
...(request.contentType === undefined ? {} : { contentType: request.contentType }),
|
|
78
65
|
...(signal ? { signal } : {}),
|
|
79
|
-
}
|
|
66
|
+
};
|
|
67
|
+
const staging = `${STAGING_PREFIX}${randomUUID()}`;
|
|
68
|
+
let size;
|
|
69
|
+
try {
|
|
70
|
+
let staged;
|
|
71
|
+
try {
|
|
72
|
+
staged = await ctx.blobs.put(staging, request.body ?? new Uint8Array(), {
|
|
73
|
+
...options,
|
|
74
|
+
maxBytes,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error instanceof BlobTooLargeError)
|
|
79
|
+
tooLarge(maxBytes);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
const actual = `sha256:${staged.sha256}`;
|
|
83
|
+
if (actual !== sha256)
|
|
84
|
+
fail(400, `The body's SHA-256 is ${actual}, not ${sha256}: upload a file at its own hash`, {
|
|
85
|
+
code: "invalid_request",
|
|
86
|
+
details: { expected: sha256, actual },
|
|
87
|
+
});
|
|
88
|
+
const got = await ctx.blobs.get(staging, signal ? { signal } : {});
|
|
89
|
+
if (!got)
|
|
90
|
+
throw new Error(`The Object store lost the upload of ${sha256} at ${staging}`);
|
|
91
|
+
// The key is the content's hash: a concurrent upload of the same file writes the same bytes.
|
|
92
|
+
await ctx.blobs.put(definitionFileKey(sha256), got.body, options);
|
|
93
|
+
size = staged.size;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await ctx.blobs.delete(staging).catch(() => undefined);
|
|
97
|
+
}
|
|
80
98
|
const inserted = await ctx.store.tx((t) => t.insertDefinitionFile({
|
|
81
99
|
sha256,
|
|
82
100
|
kind: "skill",
|
|
83
|
-
size
|
|
101
|
+
size,
|
|
84
102
|
contentType: request.contentType ?? null,
|
|
85
103
|
createdAt: new Date().toISOString(),
|
|
86
104
|
}));
|
|
87
|
-
return { status: inserted ? 201 : 200, view: { sha256, size
|
|
105
|
+
return { status: inserted ? 201 : 200, view: { sha256, size } };
|
|
88
106
|
}
|
|
89
|
-
/**
|
|
90
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Why `document` cannot be stored for the files it names: the files the Tenant does not hold
|
|
109
|
+
* (`missing`), else each skill whose files hold more than `limitBytes` together (`oversized`).
|
|
110
|
+
* A file a skill names at two paths counts twice: a sandbox holds both.
|
|
111
|
+
*/
|
|
112
|
+
export async function definitionFileIssues(t, document, limitBytes) {
|
|
91
113
|
const named = [...definitionFilesOf(document)].sort();
|
|
92
114
|
if (named.length === 0)
|
|
93
|
-
return [];
|
|
115
|
+
return { missing: [], oversized: [] };
|
|
94
116
|
const held = await t.heldDefinitionFiles(named);
|
|
95
|
-
|
|
117
|
+
const missing = named.filter((sha256) => !held.has(sha256));
|
|
118
|
+
if (missing.length > 0)
|
|
119
|
+
return { missing, oversized: [] };
|
|
120
|
+
const oversized = [];
|
|
121
|
+
for (const skill of skillsOf(document)) {
|
|
122
|
+
let bytes = 0;
|
|
123
|
+
for (const hash of Object.values(skill.files ?? {}))
|
|
124
|
+
if (typeof hash === "string")
|
|
125
|
+
bytes += held.get(hash) ?? 0;
|
|
126
|
+
if (bytes > limitBytes)
|
|
127
|
+
oversized.push({ name: String(skill.name), bytes });
|
|
128
|
+
}
|
|
129
|
+
return { missing, oversized };
|
|
130
|
+
}
|
|
131
|
+
/** A definition file's bytes as a stream, or undefined when the Object store does not have them. */
|
|
132
|
+
export async function openDefinitionFile(blobs, sha256, options = {}) {
|
|
133
|
+
return (await blobs.get(definitionFileKey(sha256), options))?.body;
|
|
96
134
|
}
|
|
97
135
|
/** A definition file's bytes, or undefined when the Object store does not have them. */
|
|
98
136
|
export async function readDefinitionFile(blobs, sha256, signal) {
|
package/dist/tenant/effects.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { embeddedAgent, flowDelegateManifest, hashManifest, } from "@nylorun/core/define";
|
|
2
2
|
import { countActiveFlowWork, deriveAgentEffectSessionId, isFlowToolEffect, isWorkflowManifest, linkedMessageInput, linkedMessageKey, linkedTurnEnd, settleAgentEffect, } from "../core/flow-host.js";
|
|
3
3
|
import { mayDispatchMore } from "../core/limits.js";
|
|
4
|
+
import { readRuntimeLimits } from "./runtime-limits.js";
|
|
4
5
|
import { sessionCredentials } from "../vault/sources.js";
|
|
5
6
|
import { isRemoteMcpCall } from "../harness/calls.js";
|
|
6
7
|
import { isHttpToolCall } from "../gates/http-tool.js";
|
|
7
|
-
import { sandboxSpecOf } from "../sandbox/share.js";
|
|
8
|
-
import { withRuntimeTools, withSandbox } from "../sandbox/session-sandbox.js";
|
|
9
8
|
import { loadSession, ownedSession, sessionOf, } from "./context.js";
|
|
10
9
|
import { fail } from "./http.js";
|
|
11
10
|
import { turnManifestOf } from "./session.js";
|
|
@@ -41,7 +40,7 @@ export function recoversToolCalls(ctx) {
|
|
|
41
40
|
}
|
|
42
41
|
/** True when `request` calls a tool of a declared MCP server (every one is remote) or an HTTP tool. */
|
|
43
42
|
export function isGateToolEffect(s, request) {
|
|
44
|
-
return (isRemoteMcpCall({ rootManifest: s.manifest
|
|
43
|
+
return (isRemoteMcpCall({ rootManifest: s.manifest }, request) ||
|
|
45
44
|
isHttpToolCall(s.manifest, request));
|
|
46
45
|
}
|
|
47
46
|
/**
|
|
@@ -50,7 +49,7 @@ export function isGateToolEffect(s, request) {
|
|
|
50
49
|
* the running call or gets its outcome, instead of becoming `uncertain`.
|
|
51
50
|
*/
|
|
52
51
|
export function recoversModelCalls(ctx) {
|
|
53
|
-
return ctx.
|
|
52
|
+
return ctx.modelGate.recovers === true;
|
|
54
53
|
}
|
|
55
54
|
/** Journal and dispatch a new flow effect (agent or tool node). */
|
|
56
55
|
export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
@@ -87,7 +86,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
87
86
|
const active = await countActiveFlowWork(t, request.sessionId, request.turnId);
|
|
88
87
|
// When the run journaled the effect (R7.1); a queued effect keeps it once dispatched.
|
|
89
88
|
const startedAt = existing?.startedAt ?? new Date().toISOString();
|
|
90
|
-
if (!mayDispatchMore(active,
|
|
89
|
+
if (!mayDispatchMore(active, await readRuntimeLimits(t))) {
|
|
91
90
|
if (!existing || existing.status !== "queued")
|
|
92
91
|
await t.put("effects", request.effectId, {
|
|
93
92
|
request,
|
|
@@ -162,16 +161,14 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
162
161
|
if (exists)
|
|
163
162
|
return;
|
|
164
163
|
const definition = leafDefinition(workflow, body, request);
|
|
165
|
-
// The tree's Environment reaches every agent in it: its sandbox
|
|
166
|
-
const
|
|
167
|
-
// Without one, the agent still gets its runtime block: read_artifact (R2b C11) and its skill tools.
|
|
168
|
-
const pinned = inherited?.manifest ? inherited : runtimeTools(definition.manifest);
|
|
164
|
+
// The tree's Environment reaches every agent in it: its sandbox and its vault.
|
|
165
|
+
const sandbox = workflow.sandbox;
|
|
169
166
|
const created = {
|
|
170
167
|
id: agentSessionId,
|
|
171
168
|
agentId: body.agentId,
|
|
172
169
|
ownerUserId: workflow.ownerUserId,
|
|
173
|
-
manifest:
|
|
174
|
-
manifestHash:
|
|
170
|
+
manifest: definition.manifest,
|
|
171
|
+
manifestHash: definition.manifestHash,
|
|
175
172
|
definitionHash: definition.manifestHash,
|
|
176
173
|
implementationVersion: definition.implementationVersion,
|
|
177
174
|
status: "idle",
|
|
@@ -191,7 +188,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
191
188
|
? {}
|
|
192
189
|
: { environmentVersion: workflow.environmentVersion }),
|
|
193
190
|
}),
|
|
194
|
-
...(
|
|
191
|
+
...(sandbox === undefined ? {} : { sandbox }),
|
|
195
192
|
};
|
|
196
193
|
await t.put("sessions", agentSessionId, created);
|
|
197
194
|
});
|
|
@@ -247,30 +244,6 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
247
244
|
return { status: "pending" };
|
|
248
245
|
});
|
|
249
246
|
}
|
|
250
|
-
/**
|
|
251
|
-
* The sandbox a linked session inherits from its tree's owner when that sandbox was chosen at
|
|
252
|
-
* open (pinned on the owner). A definition that declares its own sandbox keeps it, as before.
|
|
253
|
-
*/
|
|
254
|
-
function inheritedSandbox(owner, definition) {
|
|
255
|
-
const spec = owner?.sandbox;
|
|
256
|
-
if (!spec || sandboxSpecOf(definition.manifest))
|
|
257
|
-
return undefined;
|
|
258
|
-
if (isWorkflowManifest(definition.manifest))
|
|
259
|
-
return { spec };
|
|
260
|
-
const sandboxed = withSandbox(definition.manifest, spec);
|
|
261
|
-
if (!sandboxed.ok)
|
|
262
|
-
throw new Error(sandboxed.message);
|
|
263
|
-
return { spec, manifest: sandboxed.manifest, manifestHash: sandboxed.manifestHash };
|
|
264
|
-
}
|
|
265
|
-
/** A linked agent session's `runtime` block without a sandbox (R2b C11); undefined for a workflow. */
|
|
266
|
-
function runtimeTools(manifest) {
|
|
267
|
-
if (isWorkflowManifest(manifest))
|
|
268
|
-
return undefined;
|
|
269
|
-
const pinned = withRuntimeTools(manifest);
|
|
270
|
-
if (!pinned.ok)
|
|
271
|
-
throw new Error(pinned.message);
|
|
272
|
-
return { manifest: pinned.manifest, manifestHash: pinned.manifestHash };
|
|
273
|
-
}
|
|
274
247
|
/**
|
|
275
248
|
* The credential of an MCP server or HTTP tool request made on behalf of a session: from its
|
|
276
249
|
* Environment's vault (`vault/sources.ts`).
|
|
@@ -18,6 +18,8 @@ import {} from "@nylorun/core/contracts";
|
|
|
18
18
|
import { hashManifest } from "@nylorun/core/compatibility";
|
|
19
19
|
import { canonical } from "../store/canonical.js";
|
|
20
20
|
import { resolveRecipe } from "../sandbox/resolve.js";
|
|
21
|
+
import { limitIssues, manifestShapeIssues, readTenantLimits } from "./limits.js";
|
|
22
|
+
import { readRuntimeLimits } from "./runtime-limits.js";
|
|
21
23
|
import { effectiveSandboxConfig, readSandboxConfig } from "../sandbox/tenant-config.js";
|
|
22
24
|
import { actorOf, fileErrors } from "./environments.js";
|
|
23
25
|
import { fail } from "./http.js";
|
|
@@ -32,6 +34,7 @@ export function manifestRecipeRequest(manifest) {
|
|
|
32
34
|
...(manifest.lifecycle === undefined ? {} : { lifecycle: manifest.lifecycle }),
|
|
33
35
|
...(manifest.setup === undefined ? {} : { setup: manifest.setup }),
|
|
34
36
|
...(manifest.files === undefined ? {} : { files: manifest.files }),
|
|
37
|
+
...(manifest.limits === undefined ? {} : { limits: manifest.limits }),
|
|
35
38
|
...(manifest.secrets === undefined ? {} : { secrets: manifest.secrets }),
|
|
36
39
|
};
|
|
37
40
|
}
|
|
@@ -89,7 +92,12 @@ function warningsOf(ctx, manifest) {
|
|
|
89
92
|
async function errorsOf(t, manifest) {
|
|
90
93
|
const config = effectiveSandboxConfig(await readSandboxConfig(t));
|
|
91
94
|
const resolved = resolveRecipe(manifest.kind ?? "virtual", manifestRecipeRequest(manifest), config);
|
|
92
|
-
return [
|
|
95
|
+
return [
|
|
96
|
+
...(resolved.kind === "error" ? resolved.errors : []),
|
|
97
|
+
...limitIssues(manifest.limits, await readTenantLimits(t)),
|
|
98
|
+
...manifestShapeIssues(manifest, await readRuntimeLimits(t)),
|
|
99
|
+
...(await fileErrors(t, manifest.files)),
|
|
100
|
+
];
|
|
93
101
|
}
|
|
94
102
|
export async function saveEnvironmentManifest(ctx, name, body, scope, options = {}) {
|
|
95
103
|
const { requestId: _, ...manifest } = body;
|
|
@@ -116,8 +116,6 @@ export declare function newVolume(ctx: TenantContext, t: Tx, environment: Enviro
|
|
|
116
116
|
};
|
|
117
117
|
reason?: "restore" | "lost";
|
|
118
118
|
}): Promise<number>;
|
|
119
|
-
/** How long a pending seed holds the disk before another process may take it over. */
|
|
120
|
-
export declare const SEED_LEASE_MS: number;
|
|
121
119
|
/** `GET /v1/environments/{id}/events`: the Environment's lifecycle stream. */
|
|
122
120
|
export declare function environmentEventsOf(ctx: TenantContext, id: string, scope: AuthScope, query?: EnvironmentEventsQuery): Promise<{
|
|
123
121
|
events: {
|
|
@@ -54,8 +54,11 @@ import { SETUP_FAILED } from "../sandbox/pods/lifecycle.js";
|
|
|
54
54
|
import { requirePods } from "../sandbox/placement.js";
|
|
55
55
|
import { deleteDiskArtifact, forkSeedOf, runPendingSeed, snapshotEnvironment } from "../artifacts/disk.js";
|
|
56
56
|
import { manifestChanges, manifestRecipeRequest } from "./environment-manifests.js";
|
|
57
|
+
import { labelIssues, limitIssues, limitsInForce, manifestShapeIssues, readTenantLimits, secretRequestIssues, } from "./limits.js";
|
|
58
|
+
import { readRuntimeLimits } from "./runtime-limits.js";
|
|
57
59
|
import { harnessOf, sessionOf } from "./context.js";
|
|
58
|
-
import { fail } from "./http.js";
|
|
60
|
+
import { fail, failRetryLater } from "./http.js";
|
|
61
|
+
import { hostTuning } from "../tuning.js";
|
|
59
62
|
/** The Environment grants that limit `scope`, or undefined when it reaches every Environment. */
|
|
60
63
|
export function environmentGrantsOf(scope) {
|
|
61
64
|
return scope.kind === "token" ? (scope.environments ?? []) : undefined;
|
|
@@ -109,6 +112,7 @@ async function viewOf(ctx, t, environment, owner) {
|
|
|
109
112
|
...(environment.manifestName === undefined ? {} : { manifestName: environment.manifestName }),
|
|
110
113
|
...(environment.manifestVersion === undefined ? {} : { manifestVersion: environment.manifestVersion }),
|
|
111
114
|
...(environment.manifestHash === undefined ? {} : { manifestHash: environment.manifestHash }),
|
|
115
|
+
limits: limitsInForce(await readTenantLimits(t), environment.applied.limits),
|
|
112
116
|
...(environment.forkedFrom === undefined ? {} : { forkedFrom: environment.forkedFrom }),
|
|
113
117
|
...(slots === undefined ? {} : { slots }),
|
|
114
118
|
version: environment.version,
|
|
@@ -173,6 +177,7 @@ const INLINE_FIELDS = [
|
|
|
173
177
|
"lifecycle",
|
|
174
178
|
"setup",
|
|
175
179
|
"files",
|
|
180
|
+
"limits",
|
|
176
181
|
"secrets",
|
|
177
182
|
];
|
|
178
183
|
/** The hash of what an Environment runs, recorded when it is made. */
|
|
@@ -196,6 +201,7 @@ function recipeRequest(body, existing) {
|
|
|
196
201
|
...pick("lifecycle", body.lifecycle),
|
|
197
202
|
...pick("setup", body.setup),
|
|
198
203
|
...pick("files", body.files),
|
|
204
|
+
...pick("limits", body.limits),
|
|
199
205
|
...pick("secrets", body.secrets),
|
|
200
206
|
};
|
|
201
207
|
}
|
|
@@ -305,16 +311,25 @@ export async function putEnvironment(ctx, id, body, scope, options = {}) {
|
|
|
305
311
|
const view = await ctx.store.tx(async (t) => {
|
|
306
312
|
const now = new Date().toISOString();
|
|
307
313
|
const config = effectiveSandboxConfig(await readSandboxConfig(t));
|
|
308
|
-
const
|
|
314
|
+
const tenantLimits = await readTenantLimits(t);
|
|
315
|
+
const runtimeLimits = await readRuntimeLimits(t);
|
|
316
|
+
// `limits` and the manifest's shape are checked against the Tenant's only when an Environment
|
|
317
|
+
// is made: a get-or-create on one made before the Tenant lowered a limit still answers, and
|
|
318
|
+
// the lower limit applies. Labels are checked whenever they are set.
|
|
319
|
+
const labels = labelIssues(body.labels, runtimeLimits);
|
|
320
|
+
if (labels.length > 0)
|
|
321
|
+
fail(400, labels.join(" "));
|
|
322
|
+
const resolve = (kind, request, creating) => {
|
|
309
323
|
const resolved = resolveRecipe(kind, request, config);
|
|
310
|
-
|
|
324
|
+
const errors = [
|
|
325
|
+
...(resolved.kind === "error" ? resolved.errors : []),
|
|
326
|
+
...(creating ? [...limitIssues(request.limits, tenantLimits), ...manifestShapeIssues(request, runtimeLimits)] : []),
|
|
327
|
+
];
|
|
328
|
+
return resolved.kind === "error" || errors.length > 0 ? fail(400, errors.join(" ")) : resolved.recipe;
|
|
311
329
|
};
|
|
312
330
|
const deleting = (row) => {
|
|
313
331
|
if (row && !live(row))
|
|
314
|
-
|
|
315
|
-
code: "environment_busy",
|
|
316
|
-
details: { retryAfterSeconds: 5 },
|
|
317
|
-
}, { "retry-after": "5" });
|
|
332
|
+
failRetryLater(`Environment ${id} is being deleted. Try again once it is gone.`, "environment_busy");
|
|
318
333
|
};
|
|
319
334
|
const found = async (existing) => {
|
|
320
335
|
if (fork !== undefined)
|
|
@@ -333,7 +348,7 @@ export async function putEnvironment(ctx, id, body, scope, options = {}) {
|
|
|
333
348
|
immutable(["kind"], existing);
|
|
334
349
|
if (body.storage !== undefined && body.storage !== existing.recipe.storage)
|
|
335
350
|
immutable(["storage"], existing);
|
|
336
|
-
const recipe = resolve(existing.kind, recipeRequest(body, existing));
|
|
351
|
+
const recipe = resolve(existing.kind, recipeRequest(body, existing), false);
|
|
337
352
|
const differing = Object.keys({ ...recipe, ...existing.recipe })
|
|
338
353
|
.filter((field) => canonical(recipe[field]) !== canonical(existing.recipe[field]))
|
|
339
354
|
.sort();
|
|
@@ -363,7 +378,7 @@ export async function putEnvironment(ctx, id, body, scope, options = {}) {
|
|
|
363
378
|
const kind = manifest ? (manifest.body.kind ?? "virtual") : (body.kind ?? "virtual");
|
|
364
379
|
if (manifest && kind === "pod")
|
|
365
380
|
await requirePods(ctx.pods, `Environment manifest ${manifest.name} is of kind pod, and a pod Environment`);
|
|
366
|
-
const recipe = resolve(kind, manifest ? manifestRecipeRequest(manifest.body) : recipeRequest(body));
|
|
381
|
+
const recipe = resolve(kind, manifest ? manifestRecipeRequest(manifest.body) : recipeRequest(body), true);
|
|
367
382
|
await checkFiles(t, recipe.files);
|
|
368
383
|
const from = body.from === undefined ? undefined : await forkSource(t, scope, kind, body.from);
|
|
369
384
|
// A fork onto other setup runs it on the written disk (Q17).
|
|
@@ -390,7 +405,7 @@ export async function putEnvironment(ctx, id, body, scope, options = {}) {
|
|
|
390
405
|
kind: "seed",
|
|
391
406
|
version: seed.version,
|
|
392
407
|
startedAt: now,
|
|
393
|
-
until: new Date(Date.now() +
|
|
408
|
+
until: new Date(Date.now() + hostTuning().tenant.seedLeaseMs).toISOString(),
|
|
394
409
|
},
|
|
395
410
|
}
|
|
396
411
|
: {}),
|
|
@@ -461,6 +476,14 @@ export async function putEnvironment(ctx, id, body, scope, options = {}) {
|
|
|
461
476
|
* accepted only when its binding is the slot's. Without slots, today's typed body.
|
|
462
477
|
*/
|
|
463
478
|
export async function secretRequestFor(ctx, id, name, raw) {
|
|
479
|
+
const request = await parseSecretRequest(ctx, id, name, raw);
|
|
480
|
+
// Within the Tenant's Runtime limits `secretValueChars` and `secretHosts`, read for the write.
|
|
481
|
+
const issues = secretRequestIssues(request, await ctx.store.tx((t) => readRuntimeLimits(t)));
|
|
482
|
+
if (issues.length > 0)
|
|
483
|
+
fail(400, issues.join(" "));
|
|
484
|
+
return request;
|
|
485
|
+
}
|
|
486
|
+
async function parseSecretRequest(ctx, id, name, raw) {
|
|
464
487
|
const environment = await ctx.store.tx((t) => liveEnvironment(t, id));
|
|
465
488
|
const slots = environment?.recipe.secrets;
|
|
466
489
|
if (!environment || slots === undefined)
|
|
@@ -538,8 +561,14 @@ export async function forkEnvironment(ctx, sourceId, body, scope) {
|
|
|
538
561
|
: `Environment manifest ${name} has no version ${ref.version}.`, { code: "manifest_not_found" });
|
|
539
562
|
const kind = manifest.body.kind ?? "virtual";
|
|
540
563
|
const resolved = resolveRecipe(kind, manifestRecipeRequest(manifest.body), effectiveSandboxConfig(await readSandboxConfig(t)));
|
|
541
|
-
|
|
542
|
-
|
|
564
|
+
const errors = [
|
|
565
|
+
...(resolved.kind === "error" ? resolved.errors : []),
|
|
566
|
+
...limitIssues(manifest.body.limits, await readTenantLimits(t)),
|
|
567
|
+
...manifestShapeIssues(manifest.body, await readRuntimeLimits(t)),
|
|
568
|
+
...labelIssues(body.labels, await readRuntimeLimits(t)),
|
|
569
|
+
];
|
|
570
|
+
if (resolved.kind === "error" || errors.length > 0)
|
|
571
|
+
return fail(400, errors.join(" "));
|
|
543
572
|
const recipe = resolved.recipe;
|
|
544
573
|
const asked = body.disk ?? "latest";
|
|
545
574
|
const disk = kind === "none" || source.kind === "none" || asked === "empty"
|
|
@@ -666,19 +695,13 @@ export function diskHeld(environment, now = Date.now()) {
|
|
|
666
695
|
/** Refused while a snapshot or restore copies the disk (a pending seed does not count). */
|
|
667
696
|
export function refuseDiskCopy(environment) {
|
|
668
697
|
if (diskHeld(environment) && environment.diskOp.kind !== "seed")
|
|
669
|
-
|
|
670
|
-
code: "environment_busy",
|
|
671
|
-
details: { retryAfterSeconds: 5 },
|
|
672
|
-
}, { "retry-after": "5" });
|
|
698
|
+
failRetryLater(`Environment ${environment.id}'s disk is being copied (${environment.diskOp.kind}). Try again shortly.`, "environment_busy");
|
|
673
699
|
}
|
|
674
700
|
/** Refused while a session that names Environment `id` has a turn running. */
|
|
675
701
|
async function refuseBusy(t, id) {
|
|
676
702
|
const busy = (await t.sessionsOnEnvironment(id)).some((session) => session.activeTurnId !== null);
|
|
677
703
|
if (busy)
|
|
678
|
-
|
|
679
|
-
code: "environment_busy",
|
|
680
|
-
details: { retryAfterSeconds: 5 },
|
|
681
|
-
}, { "retry-after": "5" });
|
|
704
|
+
failRetryLater(`Environment ${id} has a turn running. Cancel it, or wait for it to end.`, "environment_busy");
|
|
682
705
|
}
|
|
683
706
|
/**
|
|
684
707
|
* Deletes `environment` (locked, live) in `t`: its vault and credentials at once, its workspace
|
|
@@ -813,10 +836,7 @@ export async function resetEnvironment(ctx, id, scope) {
|
|
|
813
836
|
refuseDiskCopy(environment);
|
|
814
837
|
const pod = environment.pod;
|
|
815
838
|
if (pod.retiring !== undefined)
|
|
816
|
-
|
|
817
|
-
code: "environment_busy",
|
|
818
|
-
details: { retryAfterSeconds: 5 },
|
|
819
|
-
}, { "retry-after": "5" });
|
|
839
|
+
failRetryLater(`Environment ${id} is still deleting its previous volume. Try again shortly.`, "environment_busy");
|
|
820
840
|
await newVolume(ctx, t, environment);
|
|
821
841
|
return viewOf(ctx, t, (await t.environment(id)) ?? environment, subjectOf(scope));
|
|
822
842
|
});
|
|
@@ -845,7 +865,7 @@ export async function newVolume(ctx, t, environment, options = {}) {
|
|
|
845
865
|
kind: "seed",
|
|
846
866
|
version: options.seed.version,
|
|
847
867
|
startedAt: now,
|
|
848
|
-
until: new Date(Date.now() +
|
|
868
|
+
until: new Date(Date.now() + hostTuning().tenant.seedLeaseMs).toISOString(),
|
|
849
869
|
},
|
|
850
870
|
}),
|
|
851
871
|
}, now);
|
|
@@ -873,12 +893,13 @@ export async function newVolume(ctx, t, environment, options = {}) {
|
|
|
873
893
|
await revokeHost(t, id, epoch);
|
|
874
894
|
return volumeGen;
|
|
875
895
|
}
|
|
876
|
-
/** How long a pending seed holds the disk before another process may take it over. */
|
|
877
|
-
export const SEED_LEASE_MS = 10 * 60_000;
|
|
878
896
|
/** `GET /v1/environments/{id}/events`: the Environment's lifecycle stream. */
|
|
879
897
|
export async function environmentEventsOf(ctx, id, scope, query = {}) {
|
|
880
898
|
reachable(environmentGrantsOf(scope), id);
|
|
881
|
-
|
|
899
|
+
// One read returns at most the Tenant's Runtime limit `environmentEventItems`; `from` reads on.
|
|
900
|
+
return ctx.store.tx(async (t) => ({
|
|
901
|
+
events: await t.environmentEvents(id, { ...query, limit: (await readRuntimeLimits(t)).environmentEventItems }),
|
|
902
|
+
}));
|
|
882
903
|
}
|
|
883
904
|
/**
|
|
884
905
|
* The owner guard (Q7): refuses a session of `ownerUserId` on an Environment guarded for
|
|
@@ -935,11 +956,14 @@ export async function checkEnvironmentTurn(t, session, scope, ctx) {
|
|
|
935
956
|
code: "environment_owner_mismatch",
|
|
936
957
|
});
|
|
937
958
|
// The harness the turn would run on (R4.1, Q9): a pod about to wake runs its recipe's. A
|
|
938
|
-
// session never moves to another harness: its state is that harness's.
|
|
959
|
+
// session never moves to another harness: its state is that harness's. An Environment's
|
|
960
|
+
// harness never changes since protocol 14, so this refuses a session whose Environment id was
|
|
961
|
+
// deleted and made again with another harness, and an Environment changed in place before
|
|
962
|
+
// protocol 14 whose recipe (a pod's, at its wake) names another harness than it applied.
|
|
939
963
|
const waking = environment.pod !== undefined && (environment.pod.desired !== "running" || environment.pod.observed === "failed");
|
|
940
964
|
const runs = waking ? environment.recipe.harness : environment.applied.harness;
|
|
941
965
|
if (!isWorkflowManifest(session.manifest) && runs !== harnessOf(session))
|
|
942
|
-
fail(409, `Environment ${id}
|
|
966
|
+
fail(409, `Environment ${id} runs the ${runs} harness, and this session runs ${harnessOf(session)}: open a new session to run on it.`, {
|
|
943
967
|
code: "harness_mismatch",
|
|
944
968
|
details: { harness: harnessOf(session), environmentHarness: runs },
|
|
945
969
|
});
|
|
@@ -31,7 +31,7 @@ export interface StartEphemeralRuntimeOptions {
|
|
|
31
31
|
/** When true, close() leaves hostRoot on disk. */
|
|
32
32
|
retainRoot?: boolean;
|
|
33
33
|
/**
|
|
34
|
-
* Trusted issuers whose JWTs the
|
|
34
|
+
* Trusted issuers whose JWTs the Runtime API accepts (Host feature `trusted-issuers`): the
|
|
35
35
|
* issuers of an identity file (`parseIdentityFile`), or ones already built with
|
|
36
36
|
* `createTrustedIssuers`. Default none.
|
|
37
37
|
*/
|
package/dist/tenant/ephemeral.js
CHANGED
|
@@ -25,6 +25,7 @@ import { hostPaths, tenantPaths } from "./paths.js";
|
|
|
25
25
|
import { hostPrincipals } from "./principals.js";
|
|
26
26
|
import { openTenantRuntime } from "./runtime.js";
|
|
27
27
|
import { createTrustedIssuers } from "./issuers.js";
|
|
28
|
+
import { hostTuning } from "../tuning.js";
|
|
28
29
|
const nodeRequire = createRequire(import.meta.url);
|
|
29
30
|
function coreVersion() {
|
|
30
31
|
try {
|
|
@@ -127,7 +128,7 @@ export async function startEphemeralRuntime(options) {
|
|
|
127
128
|
await host?.close();
|
|
128
129
|
await module.close();
|
|
129
130
|
await streams.close();
|
|
130
|
-
await ownPool?.end({ timeout:
|
|
131
|
+
await ownPool?.end({ timeout: hostTuning().postgres.closeTimeoutSeconds });
|
|
131
132
|
if (!options.retainRoot)
|
|
132
133
|
rmSync(hostRoot, { recursive: true, force: true });
|
|
133
134
|
};
|
package/dist/tenant/history.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* transcript on the row too and checks the fold against it at every segment start.
|
|
17
17
|
*/
|
|
18
18
|
import type { LiveEvent } from "@nylorun/core/contracts";
|
|
19
|
-
export {
|
|
19
|
+
export { TranscriptFoldError, applyUpdate, applyUpdates, transcriptOf, transcriptUpdates, withTranscript, type TranscriptUpdate, } from "@nylorun/core/harness-api";
|
|
20
20
|
/** Where a session's fold starts. */
|
|
21
21
|
export interface SessionHistory {
|
|
22
22
|
/** The fold reads the record from this seq: a snapshot at or before the turn's start. */
|
package/dist/tenant/history.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { applyUpdate, transcriptOf } from "@nylorun/core/harness-api";
|
|
2
|
-
export {
|
|
2
|
+
export { TranscriptFoldError, applyUpdate, applyUpdates, transcriptOf, transcriptUpdates, withTranscript, } from "@nylorun/core/harness-api";
|
|
3
3
|
let shadow = false;
|
|
4
4
|
/** Shadow mode: keep the transcript on the session row and check the fold against it. */
|
|
5
5
|
export function transcriptShadow() {
|
|
@@ -5,8 +5,8 @@ export declare const HOST_TOKEN_TYP = "nylorun-host+jwt";
|
|
|
5
5
|
export declare const HOST_TOKEN_AUD = "nylorun-harness-api";
|
|
6
6
|
export declare const EGRESS_TOKEN_TYP = "nylorun-egress+jwt";
|
|
7
7
|
export declare const EGRESS_TOKEN_AUD = "nylorun-egress";
|
|
8
|
-
/** How long a host or egress token lives. The engine renews them before. */
|
|
9
|
-
export declare const
|
|
8
|
+
/** How long a host or egress token lives (`keys.hostTokenTtlSeconds`). The engine renews them before. */
|
|
9
|
+
export declare const hostTokenTtlSeconds: () => number;
|
|
10
10
|
/** What a verified host or egress token says. */
|
|
11
11
|
export interface HostClaims {
|
|
12
12
|
readonly tenantId: string;
|
|
@@ -37,7 +37,7 @@ export interface EgressTokenRequest {
|
|
|
37
37
|
/** The host epoch the join set. */
|
|
38
38
|
readonly epoch: number;
|
|
39
39
|
readonly podUid: string;
|
|
40
|
-
/** Seconds. Default and maximum `
|
|
40
|
+
/** Seconds. Default and maximum `keys.hostTokenTtlSeconds`. */
|
|
41
41
|
readonly ttl?: number;
|
|
42
42
|
}
|
|
43
43
|
/**
|