@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
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What artifacts, skills and disk versions may hold: the Tenant's limits (`artifacts.config`,
|
|
3
|
+
* `skills.config` here, and the Runtime limits `diskVersion*` of `limits.config`), and an
|
|
4
|
+
* Environment's, which its manifest sets (`limits`). A limit the manifest leaves out is the
|
|
5
|
+
* Tenant's as it is when the limit is checked, and none is ever above the Tenant's: a manifest
|
|
6
|
+
* asking for more is refused when the Environment is made, and a Tenant that lowers a limit
|
|
7
|
+
* later lowers it for every Environment.
|
|
8
|
+
*
|
|
9
|
+
* Also the shape a manifest and an Environment's labels must fit, which the Tenant's Runtime
|
|
10
|
+
* limits set (`manifestShapeIssues`, `labelIssues`), checked when they are put rather than by
|
|
11
|
+
* their schemas (B15).
|
|
12
|
+
*/
|
|
13
|
+
import { SKILL_TOTAL_BYTES_DEFAULT, TenantSkillsConfigSchema, } from "@nylorun/core/contracts";
|
|
14
|
+
import { parseSandboxDuration } from "@nylorun/core/define";
|
|
15
|
+
import { readArtifactLimits } from "../artifacts/config.js";
|
|
16
|
+
import { readRuntimeLimits } from "./runtime-limits.js";
|
|
17
|
+
export const SKILLS_CONFIG_SETTING = "skills.config";
|
|
18
|
+
/** The skill limits in force: the Tenant's, with the default for what it leaves unset. */
|
|
19
|
+
export function effectiveSkillLimits(config) {
|
|
20
|
+
return { totalBytes: config.limits?.totalBytes ?? SKILL_TOTAL_BYTES_DEFAULT };
|
|
21
|
+
}
|
|
22
|
+
export async function readSkillsConfig(t) {
|
|
23
|
+
const raw = await t.getSetting(SKILLS_CONFIG_SETTING);
|
|
24
|
+
if (raw === undefined)
|
|
25
|
+
return {};
|
|
26
|
+
const parsed = TenantSkillsConfigSchema.safeParse(JSON.parse(raw));
|
|
27
|
+
return parsed.success ? parsed.data : {};
|
|
28
|
+
}
|
|
29
|
+
export async function readSkillLimits(t) {
|
|
30
|
+
return effectiveSkillLimits(await readSkillsConfig(t));
|
|
31
|
+
}
|
|
32
|
+
export async function writeSkillsConfig(t, config) {
|
|
33
|
+
await t.putSetting(SKILLS_CONFIG_SETTING, JSON.stringify(TenantSkillsConfigSchema.parse(config)));
|
|
34
|
+
}
|
|
35
|
+
/** The Tenant's artifact, skill and disk version limits. */
|
|
36
|
+
export async function readTenantLimits(t) {
|
|
37
|
+
const runtime = await readRuntimeLimits(t);
|
|
38
|
+
return {
|
|
39
|
+
artifacts: await readArtifactLimits(t),
|
|
40
|
+
skills: await readSkillLimits(t),
|
|
41
|
+
disk: { versionBytes: runtime.diskVersionBytes, versionEntries: runtime.diskVersionEntries },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** The limits in force for an Environment whose manifest sets `set`: each within the Tenant's. */
|
|
45
|
+
export function limitsInForce(tenant, set) {
|
|
46
|
+
const within = (asked, ceiling) => Math.min(asked ?? ceiling, ceiling);
|
|
47
|
+
return {
|
|
48
|
+
artifacts: {
|
|
49
|
+
fileBytes: within(set?.artifacts?.fileBytes, tenant.artifacts.fileBytes),
|
|
50
|
+
totalBytes: within(set?.artifacts?.totalBytes, tenant.artifacts.totalBytes),
|
|
51
|
+
},
|
|
52
|
+
skills: { totalBytes: within(set?.skills?.totalBytes, tenant.skills.totalBytes) },
|
|
53
|
+
disk: {
|
|
54
|
+
versionBytes: within(set?.disk?.versionBytes, tenant.disk.versionBytes),
|
|
55
|
+
versionEntries: within(set?.disk?.versionEntries, tenant.disk.versionEntries),
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Why a manifest's `limits` cannot be made: each limit above the Tenant's. */
|
|
60
|
+
export function limitIssues(set, tenant) {
|
|
61
|
+
const issues = [];
|
|
62
|
+
const check = (path, asked, ceiling, unit = "bytes") => {
|
|
63
|
+
if (asked !== undefined && asked > ceiling)
|
|
64
|
+
issues.push(`limits.${path} asks for ${asked} ${unit}; this Tenant allows at most ${ceiling}.`);
|
|
65
|
+
};
|
|
66
|
+
check("artifacts.fileBytes", set?.artifacts?.fileBytes, tenant.artifacts.fileBytes);
|
|
67
|
+
check("artifacts.totalBytes", set?.artifacts?.totalBytes, tenant.artifacts.totalBytes);
|
|
68
|
+
check("skills.totalBytes", set?.skills?.totalBytes, tenant.skills.totalBytes);
|
|
69
|
+
check("disk.versionBytes", set?.disk?.versionBytes, tenant.disk.versionBytes);
|
|
70
|
+
check("disk.versionEntries", set?.disk?.versionEntries, tenant.disk.versionEntries, "entries");
|
|
71
|
+
return issues;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Why `labels` cannot be set: more than the Tenant's Runtime limit `count` names
|
|
75
|
+
* (`environmentLabels` or `artifactLabels`), or a key or a value longer than `labelKeyChars` or
|
|
76
|
+
* `labelValueChars`.
|
|
77
|
+
*/
|
|
78
|
+
export function labelIssues(labels, limits, count = "environmentLabels") {
|
|
79
|
+
if (labels === undefined)
|
|
80
|
+
return [];
|
|
81
|
+
const issues = [];
|
|
82
|
+
const keys = Object.keys(labels);
|
|
83
|
+
if (keys.length > limits[count])
|
|
84
|
+
issues.push(`labels has ${keys.length}; the Runtime limit ${count} allows at most ${limits[count]} (PUT /v1/tenant/limits).`);
|
|
85
|
+
for (const key of keys) {
|
|
86
|
+
if (key.length > limits.labelKeyChars)
|
|
87
|
+
issues.push(`Label key ${JSON.stringify(key.slice(0, limits.labelKeyChars))}… has ${key.length} characters; the Runtime limit labelKeyChars allows at most ${limits.labelKeyChars} (PUT /v1/tenant/limits).`);
|
|
88
|
+
const value = labels[key];
|
|
89
|
+
if (value.length > limits.labelValueChars)
|
|
90
|
+
issues.push(`Label ${JSON.stringify(key)}'s value has ${value.length} characters; the Runtime limit labelValueChars allows at most ${limits.labelValueChars} (PUT /v1/tenant/limits).`);
|
|
91
|
+
}
|
|
92
|
+
return issues;
|
|
93
|
+
}
|
|
94
|
+
/** `"{what} has {n} characters; the Runtime limit {name} allows at most {limit} …"`, when over. */
|
|
95
|
+
function longer(what, text, name, limits) {
|
|
96
|
+
return text !== undefined && text.length > limits[name]
|
|
97
|
+
? [`${what} has ${text.length} characters; the Runtime limit ${name} allows at most ${limits[name]} (PUT /v1/tenant/limits).`]
|
|
98
|
+
: [];
|
|
99
|
+
}
|
|
100
|
+
/** `"{what} has {n}; the Runtime limit {name} allows at most {limit} …"`, when over. */
|
|
101
|
+
function more(what, count, name, limits) {
|
|
102
|
+
return count > limits[name]
|
|
103
|
+
? [`${what} has ${count}; the Runtime limit ${name} allows at most ${limits[name]} (PUT /v1/tenant/limits).`]
|
|
104
|
+
: [];
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Why a secret write does not fit the Tenant's Runtime limits: an `environment_secret`'s or an
|
|
108
|
+
* `environment_variable`'s value past `secretValueChars`, or more hosts than `secretHosts`.
|
|
109
|
+
*/
|
|
110
|
+
export function secretRequestIssues(request, limits) {
|
|
111
|
+
const valued = request.type === "environment_secret" || request.type === "environment_variable";
|
|
112
|
+
return [
|
|
113
|
+
...(valued && typeof request.value === "string" ? longer("value", request.value, "secretValueChars", limits) : []),
|
|
114
|
+
...more("allowedHosts", request.allowedHosts?.length ?? 0, "secretHosts", limits),
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Why a tool policy does not fit the Tenant's Runtime limits: more rules than `toolRules`, a
|
|
119
|
+
* selector or a `when` path past `toolRuleFieldChars`, a `reason` or a `matches` past
|
|
120
|
+
* `toolRuleTextChars`.
|
|
121
|
+
*/
|
|
122
|
+
export function toolPolicyIssues(rules, limits) {
|
|
123
|
+
const issues = more("rules", rules.length, "toolRules", limits);
|
|
124
|
+
rules.forEach((rule, index) => {
|
|
125
|
+
const at = `rules[${index}]`;
|
|
126
|
+
issues.push(...longer(`${at}.tool`, rule.tool, "toolRuleFieldChars", limits), ...longer(`${at}.server`, rule.server, "toolRuleFieldChars", limits), ...longer(`${at}.agent`, rule.agent, "toolRuleFieldChars", limits), ...longer(`${at}.reason`, rule.reason, "toolRuleTextChars", limits));
|
|
127
|
+
for (const [path, condition] of Object.entries(rule.when ?? {}))
|
|
128
|
+
issues.push(...longer(`${at}.when path ${JSON.stringify(path.slice(0, limits.toolRuleFieldChars))}`, path, "toolRuleFieldChars", limits), ...longer(`${at}.when[${JSON.stringify(path)}].matches`, condition.matches, "toolRuleTextChars", limits));
|
|
129
|
+
});
|
|
130
|
+
return issues;
|
|
131
|
+
}
|
|
132
|
+
/** Why a set of model budgets does not fit: more than the Tenant's Runtime limit `modelBudgets`. */
|
|
133
|
+
export function budgetIssues(budgets, limits) {
|
|
134
|
+
return more("budgets", budgets.length, "modelBudgets", limits);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Why an agent's `requires` does not fit: more secrets than an Environment's manifest may declare
|
|
138
|
+
* slots for (the Runtime limit `environmentSecretSlots`).
|
|
139
|
+
*/
|
|
140
|
+
export function requiresIssues(requires, limits) {
|
|
141
|
+
return more("requires.secrets", requires?.secrets?.length ?? 0, "environmentSecretSlots", limits);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Why a manifest (inline, or saved) does not fit the Tenant's Runtime limits: its description,
|
|
145
|
+
* its setup commands, their lengths and timeouts, its input file entries and the files of each,
|
|
146
|
+
* and its secret slots, their descriptions and hosts.
|
|
147
|
+
*/
|
|
148
|
+
export function manifestShapeIssues(manifest, limits) {
|
|
149
|
+
const issues = [];
|
|
150
|
+
const over = (what, count, name) => {
|
|
151
|
+
if (count > limits[name])
|
|
152
|
+
issues.push(`${what} has ${count}; the Runtime limit ${name} allows at most ${limits[name]} (PUT /v1/tenant/limits).`);
|
|
153
|
+
};
|
|
154
|
+
over("setup", manifest.setup?.length ?? 0, "environmentSetupCommands");
|
|
155
|
+
over("files", manifest.files?.length ?? 0, "environmentFiles");
|
|
156
|
+
over("secrets", Object.keys(manifest.secrets ?? {}).length, "environmentSecretSlots");
|
|
157
|
+
issues.push(...longer("description", manifest.description, "manifestDescriptionChars", limits));
|
|
158
|
+
for (const [name, slot] of Object.entries(manifest.secrets ?? {})) {
|
|
159
|
+
issues.push(...longer(`secrets.${name}.description`, slot.description, "secretDescriptionChars", limits));
|
|
160
|
+
if ("allowedHosts" in slot && Array.isArray(slot.allowedHosts))
|
|
161
|
+
issues.push(...more(`secrets.${name}.allowedHosts`, slot.allowedHosts.length, "secretHosts", limits));
|
|
162
|
+
}
|
|
163
|
+
manifest.setup?.forEach((command, index) => {
|
|
164
|
+
if (command.command.length > limits.setupCommandChars)
|
|
165
|
+
issues.push(`setup[${index}].command has ${command.command.length} characters; the Runtime limit setupCommandChars allows at most ${limits.setupCommandChars} (PUT /v1/tenant/limits).`);
|
|
166
|
+
const ms = command.timeout === undefined ? undefined : parseSandboxDuration(command.timeout);
|
|
167
|
+
if (ms !== undefined && ms > limits.setupCommandMs)
|
|
168
|
+
issues.push(`setup[${index}].timeout asks for ${ms} ms; the Runtime limit setupCommandMs allows at most ${limits.setupCommandMs} (PUT /v1/tenant/limits).`);
|
|
169
|
+
});
|
|
170
|
+
manifest.files?.forEach((file, index) => {
|
|
171
|
+
if ("files" in file)
|
|
172
|
+
over(`files[${index}].files`, Object.keys(file.files).length, "environmentFilesPerEntry");
|
|
173
|
+
});
|
|
174
|
+
return issues;
|
|
175
|
+
}
|
package/dist/tenant/module.d.ts
CHANGED
|
@@ -3,7 +3,10 @@ export interface CreateTenantModuleOptions {
|
|
|
3
3
|
/** Opens the Host's Tenant (`store-pg.ts`). */
|
|
4
4
|
open: TenantOpener;
|
|
5
5
|
logger: Logger;
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Bound on opening the Tenant; a slower open fails with `open-timeout`. Default the Host
|
|
8
|
+
* tuning's `tenant.openTimeoutMs` (30 s).
|
|
9
|
+
*/
|
|
7
10
|
openTimeoutMs?: number;
|
|
8
11
|
/**
|
|
9
12
|
* Runs once the Tenant is open, at start or on a later retry: the Host starts what needs the
|
package/dist/tenant/module.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { openError, TenantOpenError } from "./cause.js";
|
|
2
2
|
import { TimeoutError, withTimeout } from "./pool.js";
|
|
3
|
+
import { hostTuning } from "../tuning.js";
|
|
3
4
|
import { TenantUnavailableError, } from "./types.js";
|
|
4
|
-
const OPEN_TIMEOUT_MS = 30_000;
|
|
5
|
-
/** Retries of an open that failed outside the Tenant: from 1 s, doubling, at most 30 s apart. */
|
|
6
|
-
const RETRY_FIRST_MS = 1_000;
|
|
7
|
-
const RETRY_MAX_MS = 30_000;
|
|
8
5
|
/**
|
|
9
6
|
* Deep Tenant module (§8): the Host's one Tenant over an injected opener (tenancy.md §5).
|
|
10
7
|
*
|
|
@@ -15,11 +12,15 @@ const RETRY_MAX_MS = 30_000;
|
|
|
15
12
|
* with backoff, and by the next request, which gets 503 meanwhile.
|
|
16
13
|
*/
|
|
17
14
|
export function createTenantModule(options) {
|
|
18
|
-
const
|
|
15
|
+
const tuning = hostTuning().tenant;
|
|
16
|
+
const { logger, openTimeoutMs = tuning.openTimeoutMs } = options;
|
|
17
|
+
// Retries of an open that failed outside the Tenant: from `openRetry.minMs`, doubling, at most
|
|
18
|
+
// `openRetry.maxMs` apart.
|
|
19
|
+
const { minMs: firstRetryMs, maxMs: maxRetryMs } = tuning.openRetry;
|
|
19
20
|
let state = { kind: "idle" };
|
|
20
21
|
let opening;
|
|
21
22
|
let retry;
|
|
22
|
-
let nextDelayMs =
|
|
23
|
+
let nextDelayMs = firstRetryMs;
|
|
23
24
|
const current = () => state;
|
|
24
25
|
async function openOnce() {
|
|
25
26
|
let handle;
|
|
@@ -64,7 +65,7 @@ export function createTenantModule(options) {
|
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
66
67
|
state = { kind: "open", handle };
|
|
67
|
-
nextDelayMs =
|
|
68
|
+
nextDelayMs = firstRetryMs;
|
|
68
69
|
logger.info("tenant opened", { tenantId: handle.envelope.id });
|
|
69
70
|
try {
|
|
70
71
|
await options.onOpen?.(handle);
|
|
@@ -92,7 +93,7 @@ export function createTenantModule(options) {
|
|
|
92
93
|
if (retry)
|
|
93
94
|
return;
|
|
94
95
|
const delayMs = nextDelayMs;
|
|
95
|
-
nextDelayMs = Math.min(delayMs * 2,
|
|
96
|
+
nextDelayMs = Math.min(delayMs * 2, maxRetryMs);
|
|
96
97
|
retry = setTimeout(() => {
|
|
97
98
|
retry = undefined;
|
|
98
99
|
void load();
|
|
@@ -30,21 +30,21 @@ export type OutboundResult = {
|
|
|
30
30
|
code: string;
|
|
31
31
|
message: string;
|
|
32
32
|
};
|
|
33
|
-
/** The largest answer read from an endpoint. */
|
|
34
|
-
export declare const MAX_RESPONSE_BYTES: number;
|
|
35
33
|
/** True when `address` is private, loopback, link-local or unspecified (IPv4-mapped included). */
|
|
36
34
|
export declare function isPrivateAddress(address: string): boolean;
|
|
37
35
|
/** Why the Host refuses `url`, or `undefined` when it may be called. */
|
|
38
36
|
export declare function refusal(url: URL, policy: OutboundPolicy): string | undefined;
|
|
39
37
|
/**
|
|
40
|
-
*
|
|
38
|
+
* Sends one request to `url` under `policy`: with `method` (default `POST`) and the JSON `body`,
|
|
39
|
+
* or with no body (and no `content-type`) when `body` is undefined, as for a `GET`. Never
|
|
41
40
|
* throws; `signal` aborts the request.
|
|
42
41
|
*/
|
|
43
|
-
export declare function
|
|
42
|
+
export declare function send(target: string, body: string | undefined, headers: Record<string, string>, options: {
|
|
44
43
|
policy: OutboundPolicy;
|
|
45
44
|
signal: AbortSignal;
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
/** The largest answer read: the Tenant's Runtime limit `toolAnswerBytes` for an HTTP tool. */
|
|
46
|
+
maxResponseBytes: number;
|
|
47
|
+
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
48
48
|
}): Promise<OutboundResult>;
|
|
49
49
|
/**
|
|
50
50
|
* A refusal of `guardedFetch`: the Host's policy forbids the URL or every address it resolves
|
|
@@ -59,34 +59,32 @@ export declare class OutboundRefused extends Error {
|
|
|
59
59
|
export declare class OutboundFailed extends Error {
|
|
60
60
|
readonly code: string;
|
|
61
61
|
/**
|
|
62
|
-
* The request was sent in full before it failed, as `
|
|
62
|
+
* The request was sent in full before it failed, as `send()`'s `lost` (R2b C7): the server
|
|
63
63
|
* may have acted on it. False for a failure to resolve or connect.
|
|
64
64
|
*/
|
|
65
65
|
readonly sent: boolean;
|
|
66
66
|
constructor(message: string, code: string,
|
|
67
67
|
/**
|
|
68
|
-
* The request was sent in full before it failed, as `
|
|
68
|
+
* The request was sent in full before it failed, as `send()`'s `lost` (R2b C7): the server
|
|
69
69
|
* may have acted on it. False for a failure to resolve or connect.
|
|
70
70
|
*/
|
|
71
71
|
sent?: boolean);
|
|
72
72
|
}
|
|
73
|
-
/** The largest answer `guardedFetch` reads. */
|
|
74
|
-
export declare const MAX_FETCH_RESPONSE_BYTES: number;
|
|
75
|
-
/** How long a `guardedFetch` request may take without a signal of its own. */
|
|
76
|
-
export declare const FETCH_TIMEOUT_MS = 30000;
|
|
77
73
|
/**
|
|
78
74
|
* A `fetch` under the Host's address policy (F9 C2): the literal-IP and DNS checks of `post`
|
|
79
75
|
* on the address actually connected to, `localhost` rewritten for the local stack, no
|
|
80
|
-
* redirects (`redirect: "error"`; a 3xx answer rejects),
|
|
81
|
-
* unless the caller passes a signal
|
|
82
|
-
*
|
|
76
|
+
* redirects (`redirect: "error"`; a 3xx answer rejects), an answer of at most `maxResponseBytes`
|
|
77
|
+
* and a `timeoutMs` timeout unless the caller passes a signal, both the caller's (a Tenant's
|
|
78
|
+
* Runtime limits). A refused URL or address rejects with `OutboundRefused`, any other failure
|
|
79
|
+
* with `OutboundFailed`.
|
|
83
80
|
*
|
|
84
81
|
* `stream`: the Response resolves once the headers arrive and its body streams, unbounded and
|
|
85
82
|
* with no timeout but the caller's signal: remote MCP servers, whose answers and event streams
|
|
86
83
|
* stay open (`mcp/connect.ts`).
|
|
87
84
|
*/
|
|
88
|
-
export declare function guardedFetch(policy: OutboundPolicy, options
|
|
89
|
-
maxResponseBytes
|
|
90
|
-
timeoutMs
|
|
91
|
-
|
|
85
|
+
export declare function guardedFetch(policy: OutboundPolicy, options: {
|
|
86
|
+
maxResponseBytes: number;
|
|
87
|
+
timeoutMs: number;
|
|
88
|
+
} | {
|
|
89
|
+
stream: true;
|
|
92
90
|
}): typeof fetch;
|
package/dist/tenant/outbound.js
CHANGED
|
@@ -13,8 +13,6 @@ import { request as httpRequest } from "node:http";
|
|
|
13
13
|
import { request as httpsRequest } from "node:https";
|
|
14
14
|
import { BlockList, isIP } from "node:net";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
|
-
/** The largest answer read from an endpoint. */
|
|
17
|
-
export const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
18
16
|
const PRIVATE = new BlockList();
|
|
19
17
|
for (const [network, prefix] of [
|
|
20
18
|
["0.0.0.0", 8],
|
|
@@ -76,12 +74,13 @@ function guardedLookup(policy) {
|
|
|
76
74
|
});
|
|
77
75
|
}
|
|
78
76
|
/**
|
|
79
|
-
*
|
|
77
|
+
* Sends one request to `url` under `policy`: with `method` (default `POST`) and the JSON `body`,
|
|
78
|
+
* or with no body (and no `content-type`) when `body` is undefined, as for a `GET`. Never
|
|
80
79
|
* throws; `signal` aborts the request.
|
|
81
80
|
*/
|
|
82
|
-
export async function
|
|
81
|
+
export async function send(target, body, headers, options) {
|
|
83
82
|
const { policy, signal } = options;
|
|
84
|
-
const limit = options.maxResponseBytes
|
|
83
|
+
const limit = options.maxResponseBytes;
|
|
85
84
|
const url = new URL(target);
|
|
86
85
|
const refused = refusal(url, policy);
|
|
87
86
|
if (refused)
|
|
@@ -108,8 +107,9 @@ export async function post(target, body, headers, options) {
|
|
|
108
107
|
headers: {
|
|
109
108
|
...headers,
|
|
110
109
|
host: originalHost,
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
...(body === undefined
|
|
111
|
+
? {}
|
|
112
|
+
: { "content-type": "application/json", "content-length": String(Buffer.byteLength(body)) }),
|
|
113
113
|
},
|
|
114
114
|
lookup: lookup,
|
|
115
115
|
signal,
|
|
@@ -156,7 +156,7 @@ export class OutboundFailed extends Error {
|
|
|
156
156
|
sent;
|
|
157
157
|
constructor(message, code,
|
|
158
158
|
/**
|
|
159
|
-
* The request was sent in full before it failed, as `
|
|
159
|
+
* The request was sent in full before it failed, as `send()`'s `lost` (R2b C7): the server
|
|
160
160
|
* may have acted on it. False for a failure to resolve or connect.
|
|
161
161
|
*/
|
|
162
162
|
sent = false) {
|
|
@@ -166,25 +166,22 @@ export class OutboundFailed extends Error {
|
|
|
166
166
|
this.name = "OutboundFailed";
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
-
/** The largest answer `guardedFetch` reads. */
|
|
170
|
-
export const MAX_FETCH_RESPONSE_BYTES = 1024 * 1024;
|
|
171
|
-
/** How long a `guardedFetch` request may take without a signal of its own. */
|
|
172
|
-
export const FETCH_TIMEOUT_MS = 30_000;
|
|
173
169
|
/**
|
|
174
170
|
* A `fetch` under the Host's address policy (F9 C2): the literal-IP and DNS checks of `post`
|
|
175
171
|
* on the address actually connected to, `localhost` rewritten for the local stack, no
|
|
176
|
-
* redirects (`redirect: "error"`; a 3xx answer rejects),
|
|
177
|
-
* unless the caller passes a signal
|
|
178
|
-
*
|
|
172
|
+
* redirects (`redirect: "error"`; a 3xx answer rejects), an answer of at most `maxResponseBytes`
|
|
173
|
+
* and a `timeoutMs` timeout unless the caller passes a signal, both the caller's (a Tenant's
|
|
174
|
+
* Runtime limits). A refused URL or address rejects with `OutboundRefused`, any other failure
|
|
175
|
+
* with `OutboundFailed`.
|
|
179
176
|
*
|
|
180
177
|
* `stream`: the Response resolves once the headers arrive and its body streams, unbounded and
|
|
181
178
|
* with no timeout but the caller's signal: remote MCP servers, whose answers and event streams
|
|
182
179
|
* stay open (`mcp/connect.ts`).
|
|
183
180
|
*/
|
|
184
|
-
export function guardedFetch(policy, options
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
const
|
|
181
|
+
export function guardedFetch(policy, options) {
|
|
182
|
+
const stream = "stream" in options;
|
|
183
|
+
const limit = "stream" in options ? Infinity : options.maxResponseBytes;
|
|
184
|
+
const timeoutMs = "stream" in options ? undefined : options.timeoutMs;
|
|
188
185
|
const lookup = guardedLookup(policy);
|
|
189
186
|
const guarded = async (input, init = {}) => {
|
|
190
187
|
const request = input instanceof Request ? input : undefined;
|
|
@@ -215,7 +212,7 @@ export function guardedFetch(policy, options = {}) {
|
|
|
215
212
|
}
|
|
216
213
|
const originalHost = url.host;
|
|
217
214
|
dockerHost(url, policy);
|
|
218
|
-
const signal = init.signal ?? request?.signal ?? (
|
|
215
|
+
const signal = init.signal ?? request?.signal ?? (timeoutMs === undefined ? undefined : AbortSignal.timeout(timeoutMs));
|
|
219
216
|
const outgoing = { host: originalHost };
|
|
220
217
|
for (const [name, value] of headers)
|
|
221
218
|
outgoing[name] = value;
|
|
@@ -223,7 +220,7 @@ export function guardedFetch(policy, options = {}) {
|
|
|
223
220
|
outgoing["content-length"] = String(body.byteLength);
|
|
224
221
|
return await new Promise((resolve, reject) => {
|
|
225
222
|
let settled = false;
|
|
226
|
-
// Set when the request is fully sent, as `
|
|
223
|
+
// Set when the request is fully sent, as `send()` does: a failure after it may follow
|
|
227
224
|
// the server acting on the request.
|
|
228
225
|
let delivered = false;
|
|
229
226
|
const settle = (fn) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { mintRunToken
|
|
1
|
+
import { mintRunToken } from "./run-token.js";
|
|
2
|
+
import { hostTuning } from "../tuning.js";
|
|
2
3
|
export function createRunGrants() {
|
|
3
4
|
const grants = new Map();
|
|
4
5
|
return {
|
|
@@ -50,7 +51,7 @@ export async function renewRunGrant(ctx, lease, run, active = () => true) {
|
|
|
50
51
|
const held = ctx.runGrants.get(lease.sessionId);
|
|
51
52
|
if (held && held.claims.epoch !== lease.epoch)
|
|
52
53
|
return;
|
|
53
|
-
if (held && held.claims.expiresAt - Date.now() >
|
|
54
|
+
if (held && held.claims.expiresAt - Date.now() > hostTuning().keys.runTokenRenewSeconds * 1000)
|
|
54
55
|
return;
|
|
55
56
|
const grant = await mintRunToken(ctx, lease, held ? { agentId: held.claims.agentId, activeTurnId: held.claims.turnId } : run);
|
|
56
57
|
if (active())
|
|
@@ -4,10 +4,6 @@ import type { SessionStore } from "../store/types.js";
|
|
|
4
4
|
import type { Lease } from "./context.js";
|
|
5
5
|
export declare const RUN_TOKEN_TYP = "nylorun-run+jwt";
|
|
6
6
|
export declare const RUN_TOKEN_AUD = "nylorun-gates";
|
|
7
|
-
/** How long a run token lives. */
|
|
8
|
-
export declare const RUN_TOKEN_TTL_SECONDS: number;
|
|
9
|
-
/** The lease heartbeat re-mints a token with less than this left. */
|
|
10
|
-
export declare const RUN_TOKEN_RENEW_SECONDS: number;
|
|
11
7
|
/** What a verified run token says: one lease of one session's turn. */
|
|
12
8
|
export interface RunClaims {
|
|
13
9
|
readonly tenantId: string;
|
package/dist/tenant/run-token.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Signed by the keys service with the Tenant's current signing key, like capability links,
|
|
8
8
|
* and told apart from them by `typ` and `aud`. Internal: not in `openapi.json`. A token lives
|
|
9
|
-
* `
|
|
9
|
+
* `keys.runTokenTtlSeconds` (Host tuning), no longer than `keys.tokenTtlMaxSeconds`, so a key rotation never
|
|
10
10
|
* revokes a live one (`signing-keys.ts`).
|
|
11
11
|
*
|
|
12
12
|
* ```text
|
|
@@ -18,15 +18,10 @@
|
|
|
18
18
|
import { randomUUID } from "node:crypto";
|
|
19
19
|
import { errors, importJWK, jwtVerify } from "jose";
|
|
20
20
|
import { tenantTokenIssuer } from "@nylorun/core/contracts";
|
|
21
|
-
import {
|
|
21
|
+
import { clockToleranceSeconds, tokenKeyId } from "./jwt.js";
|
|
22
|
+
import { hostTuning } from "../tuning.js";
|
|
22
23
|
export const RUN_TOKEN_TYP = "nylorun-run+jwt";
|
|
23
24
|
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
25
|
/**
|
|
31
26
|
* Mints the run token of `lease` over `session`'s active turn. Throws when the session has
|
|
32
27
|
* no active turn: there is no run to name.
|
|
@@ -37,7 +32,7 @@ export async function mintRunToken(signer, lease, session) {
|
|
|
37
32
|
throw new Error(`Session ${lease.sessionId} has no active turn to mint a run token for`);
|
|
38
33
|
const tenantId = signer.config.tenantId;
|
|
39
34
|
const iat = Math.floor(Date.now() / 1000);
|
|
40
|
-
const exp = iat +
|
|
35
|
+
const exp = iat + hostTuning().keys.runTokenTtlSeconds;
|
|
41
36
|
// Signed by the keys service (F4.2): this process never holds the private key.
|
|
42
37
|
const { token } = await signer.keys.sign({
|
|
43
38
|
typ: RUN_TOKEN_TYP,
|
|
@@ -92,7 +87,7 @@ export async function verifyRunToken(store, tenantId, raw, cache = new Map()) {
|
|
|
92
87
|
issuer: tenantTokenIssuer(tenantId),
|
|
93
88
|
audience: RUN_TOKEN_AUD,
|
|
94
89
|
typ: RUN_TOKEN_TYP,
|
|
95
|
-
clockTolerance:
|
|
90
|
+
clockTolerance: clockToleranceSeconds(),
|
|
96
91
|
requiredClaims: ["sub", "iat", "exp", "jti"],
|
|
97
92
|
});
|
|
98
93
|
payload = verified.payload;
|
|
@@ -112,7 +107,7 @@ export async function verifyRunToken(store, tenantId, raw, cache = new Map()) {
|
|
|
112
107
|
typeof epc !== "number" ||
|
|
113
108
|
!Number.isSafeInteger(epc) ||
|
|
114
109
|
epc < 1 ||
|
|
115
|
-
exp - iat >
|
|
110
|
+
exp - iat > hostTuning().keys.runTokenTtlSeconds + hostTuning().keys.tokenLifetimeSlackSeconds)
|
|
116
111
|
return refused("claims");
|
|
117
112
|
return {
|
|
118
113
|
ok: true,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Tenant's Runtime limits (harness boundary S0), stored as Tenant setting `limits.config`
|
|
3
|
+
* (`GET`/`PUT /v1/tenant/limits`): what one record entry, model call, model file, tool answer,
|
|
4
|
+
* custom harness state and Harness API chunk may hold. Unset limits take their defaults
|
|
5
|
+
* (`RUNTIME_LIMIT_DEFAULTS`); none may pass its ceiling (`runtimeLimitIssues`), which the route
|
|
6
|
+
* checks before it writes.
|
|
7
|
+
*
|
|
8
|
+
* Read from Postgres where a limit is checked, never kept: the Record seam, the store's event
|
|
9
|
+
* write, the gates and the segment's `turn.start` each read it in their own transaction.
|
|
10
|
+
*/
|
|
11
|
+
import { type RuntimeLimits, type TenantLimitsConfig } from "@nylorun/core/contracts";
|
|
12
|
+
export declare const LIMITS_CONFIG_SETTING = "limits.config";
|
|
13
|
+
interface SettingsReader {
|
|
14
|
+
getSetting(key: string): Promise<string | undefined>;
|
|
15
|
+
}
|
|
16
|
+
interface SettingsWriter extends SettingsReader {
|
|
17
|
+
putSetting(key: string, value: string): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
/** The Tenant's stored limits; a stored value that does not parse counts as none set. */
|
|
20
|
+
export declare function readLimitsConfig(t: SettingsReader): Promise<TenantLimitsConfig>;
|
|
21
|
+
/** The Runtime limits in force: the Tenant's, with the default for each it leaves out. */
|
|
22
|
+
export declare function readRuntimeLimits(t: SettingsReader): Promise<RuntimeLimits>;
|
|
23
|
+
export declare function writeLimitsConfig(t: SettingsWriter, config: TenantLimitsConfig): Promise<void>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Tenant's Runtime limits (harness boundary S0), stored as Tenant setting `limits.config`
|
|
3
|
+
* (`GET`/`PUT /v1/tenant/limits`): what one record entry, model call, model file, tool answer,
|
|
4
|
+
* custom harness state and Harness API chunk may hold. Unset limits take their defaults
|
|
5
|
+
* (`RUNTIME_LIMIT_DEFAULTS`); none may pass its ceiling (`runtimeLimitIssues`), which the route
|
|
6
|
+
* checks before it writes.
|
|
7
|
+
*
|
|
8
|
+
* Read from Postgres where a limit is checked, never kept: the Record seam, the store's event
|
|
9
|
+
* write, the gates and the segment's `turn.start` each read it in their own transaction.
|
|
10
|
+
*/
|
|
11
|
+
import { TenantLimitsConfigSchema, runtimeLimitsOf, } from "@nylorun/core/contracts";
|
|
12
|
+
export const LIMITS_CONFIG_SETTING = "limits.config";
|
|
13
|
+
/** The Tenant's stored limits; a stored value that does not parse counts as none set. */
|
|
14
|
+
export async function readLimitsConfig(t) {
|
|
15
|
+
const raw = await t.getSetting(LIMITS_CONFIG_SETTING);
|
|
16
|
+
if (raw === undefined)
|
|
17
|
+
return {};
|
|
18
|
+
const parsed = TenantLimitsConfigSchema.safeParse(JSON.parse(raw));
|
|
19
|
+
return parsed.success ? parsed.data : {};
|
|
20
|
+
}
|
|
21
|
+
/** The Runtime limits in force: the Tenant's, with the default for each it leaves out. */
|
|
22
|
+
export async function readRuntimeLimits(t) {
|
|
23
|
+
return runtimeLimitsOf((await readLimitsConfig(t)).limits);
|
|
24
|
+
}
|
|
25
|
+
export async function writeLimitsConfig(t, config) {
|
|
26
|
+
await t.putSetting(LIMITS_CONFIG_SETTING, JSON.stringify(TenantLimitsConfigSchema.parse(config)));
|
|
27
|
+
}
|