@nylorun/runtime 0.15.2-beta → 0.17.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 +134 -0
- package/README.md +68 -29
- package/dist/adapters/execution/restate.d.ts +9 -1
- package/dist/adapters/execution/restate.js +64 -5
- package/dist/adapters/sandbox/local.d.ts +10 -0
- package/dist/adapters/sandbox/local.js +195 -0
- package/dist/adapters/sandbox/virtual.js +36 -1
- package/dist/admin-openapi.json +341 -7
- package/dist/api/a2a/tasks.js +6 -1
- package/dist/api/ag-ui/endpoint.js +1 -2
- package/dist/api/ag-ui/history.js +6 -1
- package/dist/api/ag-ui/routes.d.ts +1 -1
- package/dist/api/ag-ui/routes.js +15 -13
- package/dist/api/components.d.ts +2667 -711
- package/dist/api/components.js +24 -11
- package/dist/api/gate/keys.d.ts +4 -3
- package/dist/api/gate/routes.d.ts +19 -3
- package/dist/api/gate/routes.js +162 -53
- package/dist/api/http/app.js +6 -0
- package/dist/api/http/define.d.ts +19 -12
- package/dist/api/http/define.js +13 -27
- package/dist/api/http/routes/access.d.ts +3 -19
- package/dist/api/http/routes/access.js +12 -172
- package/dist/api/http/routes/actions.js +0 -1
- package/dist/api/http/routes/artifacts.d.ts +11 -0
- package/dist/api/http/routes/artifacts.js +516 -0
- package/dist/api/http/routes/me.d.ts +13 -0
- package/dist/api/http/routes/me.js +45 -0
- package/dist/api/http/routes/sandboxes.d.ts +9 -0
- package/dist/api/http/routes/sandboxes.js +150 -0
- package/dist/api/http/routes/sessions.js +9 -13
- package/dist/api/http/routes/tenant.js +36 -2
- package/dist/api/http/routes/vaults.d.ts +12 -3
- package/dist/api/http/routes/vaults.js +122 -54
- package/dist/api/openapi.js +4 -10
- package/dist/artifacts/config.d.ts +23 -0
- package/dist/artifacts/config.js +27 -0
- package/dist/artifacts/export.d.ts +8 -0
- package/dist/artifacts/export.js +205 -0
- package/dist/artifacts/files.d.ts +36 -0
- package/dist/artifacts/files.js +45 -0
- package/dist/artifacts/folders.d.ts +60 -0
- package/dist/artifacts/folders.js +209 -0
- package/dist/artifacts/links.d.ts +28 -0
- package/dist/artifacts/links.js +92 -0
- package/dist/artifacts/media-types.d.ts +13 -0
- package/dist/artifacts/media-types.js +80 -0
- package/dist/artifacts/parts.d.ts +34 -0
- package/dist/artifacts/parts.js +38 -0
- package/dist/artifacts/service.d.ts +74 -0
- package/dist/artifacts/service.js +301 -0
- package/dist/artifacts/workspace.d.ts +60 -0
- package/dist/artifacts/workspace.js +57 -0
- package/dist/artifacts/zip.d.ts +10 -0
- package/dist/artifacts/zip.js +126 -0
- package/dist/blob/fs.d.ts +13 -0
- package/dist/blob/fs.js +194 -0
- package/dist/blob/index.d.ts +9 -0
- package/dist/blob/index.js +9 -0
- package/dist/blob/s3.d.ts +42 -0
- package/dist/blob/s3.js +366 -0
- package/dist/blob/types.d.ts +108 -0
- package/dist/blob/types.js +85 -0
- package/dist/core/sandbox-routes.d.ts +2 -2
- package/dist/core/sandbox-routes.js +4 -3
- package/dist/execution/memory.d.ts +11 -1
- package/dist/execution/memory.js +77 -3
- package/dist/execution/types.d.ts +54 -1
- package/dist/execution/types.js +31 -0
- package/dist/gates/contract.d.ts +14 -13
- package/dist/gates/contract.js +13 -12
- package/dist/gates/egress.d.ts +72 -0
- package/dist/gates/egress.js +319 -0
- package/dist/gates/handler.d.ts +3 -0
- package/dist/gates/handler.js +3 -2
- package/dist/gates/http-client.d.ts +3 -2
- package/dist/gates/http-client.js +17 -6
- package/dist/gates/in-process.d.ts +13 -1
- package/dist/gates/in-process.js +11 -0
- package/dist/gates/inflight.d.ts +23 -4
- package/dist/gates/inflight.js +18 -4
- package/dist/gates/mcp-handler.js +5 -1
- package/dist/gates/model-gate.d.ts +6 -2
- package/dist/gates/tenant-vaults.d.ts +18 -9
- package/dist/gates/tenant-vaults.js +17 -12
- package/dist/gates/tool-calls.d.ts +9 -5
- package/dist/gates/tool-calls.js +6 -6
- package/dist/gates/tool-client.d.ts +9 -2
- package/dist/gates/tool-client.js +56 -22
- package/dist/gates/tool-contract.d.ts +12 -7
- package/dist/gates/tool-contract.js +10 -4
- package/dist/gates/tool-gate.d.ts +1 -0
- package/dist/harness/calls.d.ts +52 -0
- package/dist/harness/calls.js +113 -0
- package/dist/harness/executors.d.ts +22 -0
- package/dist/harness/executors.js +96 -0
- package/dist/harness/main.d.ts +6 -0
- package/dist/harness/main.js +103 -0
- package/dist/harness/pod.d.ts +21 -0
- package/dist/harness/pod.js +119 -0
- package/dist/harness/run-tokens.d.ts +18 -0
- package/dist/harness/run-tokens.js +15 -0
- package/dist/harness/service.d.ts +46 -0
- package/dist/harness/service.js +179 -0
- package/dist/harness/ws-client.d.ts +43 -0
- package/dist/harness/ws-client.js +122 -0
- package/dist/harness/ws-port.d.ts +22 -0
- package/dist/harness/ws-port.js +78 -0
- package/dist/harness-api/in-process.d.ts +24 -0
- package/dist/harness-api/in-process.js +119 -0
- package/dist/harness-api/record.d.ts +44 -0
- package/dist/harness-api/record.js +250 -0
- package/dist/harness-api/renew.d.ts +37 -0
- package/dist/harness-api/renew.js +77 -0
- package/dist/harness-api/server.d.ts +104 -0
- package/dist/harness-api/server.js +408 -0
- package/dist/harness-api/start.d.ts +36 -0
- package/dist/harness-api/start.js +118 -0
- package/dist/harness-api/workspace.d.ts +53 -0
- package/dist/harness-api/workspace.js +201 -0
- package/dist/harness-api/ws-server.d.ts +34 -0
- package/dist/harness-api/ws-server.js +227 -0
- package/dist/host/admin-api.d.ts +12 -5
- package/dist/host/admin-api.js +72 -6
- package/dist/host/app.d.ts +0 -1
- package/dist/host/app.js +35 -24
- package/dist/host/config-for.d.ts +4 -0
- package/dist/host/config-for.js +2 -0
- package/dist/host/config.d.ts +0 -6
- package/dist/host/create-host.d.ts +3 -10
- package/dist/host/create-host.js +4 -1
- package/dist/host/gates.d.ts +27 -2
- package/dist/host/gates.js +52 -2
- package/dist/host/http.d.ts +4 -1
- package/dist/host/http.js +6 -1
- package/dist/host/main.js +167 -37
- package/dist/host/stack-config.d.ts +112 -9
- package/dist/host/stack-config.js +222 -20
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/keys/client.js +9 -1
- package/dist/keys/keys.d.ts +12 -2
- package/dist/keys/keys.js +8 -0
- package/dist/mcp/connect.js +2 -1
- package/dist/mcp/pool.d.ts +1 -0
- package/dist/mcp/pool.js +1 -0
- package/dist/mcp/snapshot.d.ts +8 -1
- package/dist/mcp/snapshot.js +28 -0
- package/dist/media.d.ts +5 -24
- package/dist/media.js +5 -0
- package/dist/model/pi-model.d.ts +6 -2
- package/dist/model/pi-model.js +39 -22
- package/dist/node/index.d.ts +1 -1
- package/dist/node/index.js +1 -1
- package/dist/openapi.json +14084 -9426
- package/dist/record/index.d.ts +1 -0
- package/dist/record/index.js +1 -0
- package/dist/record/sandbox.d.ts +25 -0
- package/dist/record/sandbox.js +43 -0
- package/dist/sandbox/capability.d.ts +3 -0
- package/dist/sandbox/capability.js +10 -0
- package/dist/sandbox/egress-token.d.ts +26 -0
- package/dist/sandbox/egress-token.js +8 -0
- package/dist/sandbox/join.d.ts +37 -0
- package/dist/sandbox/join.js +165 -0
- package/dist/sandbox/manager.d.ts +99 -34
- package/dist/sandbox/manager.js +216 -84
- package/dist/sandbox/placement.d.ts +27 -0
- package/dist/sandbox/placement.js +44 -0
- package/dist/sandbox/pods/client.d.ts +90 -0
- package/dist/sandbox/pods/client.js +79 -0
- package/dist/sandbox/pods/lifecycle.d.ts +91 -0
- package/dist/sandbox/pods/lifecycle.js +178 -0
- package/dist/sandbox/pods/name.d.ts +1 -0
- package/dist/sandbox/pods/name.js +28 -0
- package/dist/sandbox/pods/network-gate.d.ts +25 -0
- package/dist/sandbox/pods/network-gate.js +71 -0
- package/dist/sandbox/pods/reconcile.d.ts +11 -0
- package/dist/sandbox/pods/reconcile.js +152 -0
- package/dist/sandbox/pods/spec.d.ts +45 -0
- package/dist/sandbox/pods/spec.js +62 -0
- package/dist/sandbox/records.d.ts +29 -0
- package/dist/sandbox/records.js +79 -0
- package/dist/sandbox/resolve.d.ts +5 -0
- package/dist/sandbox/resolve.js +3 -2
- package/dist/sandbox/session-sandbox.js +7 -5
- package/dist/sandbox/share.d.ts +11 -0
- package/dist/sandbox/share.js +10 -0
- package/dist/sandbox/tenant-config.d.ts +16 -1
- package/dist/sandbox/tenant-config.js +9 -0
- package/dist/sandbox/types.d.ts +29 -1
- package/dist/sandbox/types.js +13 -1
- package/dist/store/postgres/drizzle/0004_sandbox_resources.sql +20 -0
- package/dist/store/postgres/drizzle/0005_artifacts.sql +27 -0
- package/dist/store/postgres/drizzle/0006_folder_artifacts.sql +12 -0
- package/dist/store/postgres/drizzle/0007_sandbox_pods.sql +14 -0
- package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
- package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
- package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
- package/dist/store/postgres/record-writer.d.ts +6 -1
- package/dist/store/postgres/record-writer.js +20 -2
- package/dist/store/postgres/schema.d.ts +1168 -250
- package/dist/store/postgres/schema.js +174 -28
- package/dist/store/postgres/store.js +339 -103
- package/dist/store/postgres/tenant.d.ts +1 -1
- package/dist/store/postgres/tenant.js +2 -2
- package/dist/store/types.d.ts +163 -32
- package/dist/streams/control.d.ts +2 -5
- package/dist/streams/control.js +5 -8
- package/dist/streams/types.d.ts +8 -8
- package/dist/streams/types.js +1 -1
- package/dist/tenant/advance.js +218 -160
- package/dist/tenant/artifact-tool.d.ts +5 -0
- package/dist/tenant/artifact-tool.js +119 -0
- package/dist/tenant/auth.d.ts +13 -9
- package/dist/tenant/auth.js +25 -32
- package/dist/tenant/commands.js +31 -10
- package/dist/tenant/context.d.ts +83 -26
- package/dist/tenant/delivery-token.js +4 -4
- package/dist/tenant/effects.d.ts +24 -14
- package/dist/tenant/effects.js +10 -398
- package/dist/tenant/ephemeral.d.ts +14 -2
- package/dist/tenant/ephemeral.js +8 -1
- package/dist/tenant/history.d.ts +13 -23
- package/dist/tenant/history.js +14 -62
- package/dist/tenant/host-token.d.ts +92 -0
- package/dist/tenant/host-token.js +179 -0
- package/dist/tenant/identity-file.d.ts +75 -0
- package/dist/tenant/identity-file.js +202 -0
- package/dist/tenant/issuers.d.ts +48 -0
- package/dist/tenant/issuers.js +311 -0
- package/dist/tenant/jwt.js +3 -3
- package/dist/tenant/module.js +1 -0
- package/dist/tenant/operator-keys.d.ts +18 -0
- package/dist/tenant/operator-keys.js +55 -0
- package/dist/tenant/outbound.d.ts +30 -0
- package/dist/tenant/outbound.js +142 -10
- package/dist/tenant/paths.d.ts +2 -1
- package/dist/tenant/paths.js +3 -1
- package/dist/tenant/principals.d.ts +2 -11
- package/dist/tenant/principals.js +10 -30
- package/dist/tenant/reset.d.ts +5 -2
- package/dist/tenant/reset.js +22 -2
- package/dist/tenant/run-grants.d.ts +49 -0
- package/dist/tenant/run-grants.js +62 -0
- package/dist/tenant/run-token.d.ts +70 -0
- package/dist/tenant/run-token.js +138 -0
- package/dist/tenant/runtime.d.ts +42 -0
- package/dist/tenant/runtime.js +134 -33
- package/dist/tenant/sandboxes.d.ts +119 -0
- package/dist/tenant/sandboxes.js +551 -0
- package/dist/tenant/session-streams.d.ts +6 -10
- package/dist/tenant/session-streams.js +9 -27
- package/dist/tenant/session.d.ts +2 -13
- package/dist/tenant/session.js +4 -28
- package/dist/tenant/sessions.d.ts +6 -1
- package/dist/tenant/sessions.js +29 -2
- package/dist/tenant/signing-keys.d.ts +2 -1
- package/dist/tenant/signing-keys.js +2 -2
- package/dist/tenant/status.d.ts +8 -3
- package/dist/tenant/status.js +5 -0
- package/dist/tenant/streams.d.ts +8 -2
- package/dist/tenant/streams.js +25 -6
- package/dist/tenant/sweep.js +37 -4
- package/dist/tenant/types.d.ts +39 -1
- package/dist/tenant/worker.d.ts +11 -5
- package/dist/tenant/worker.js +12 -6
- package/dist/vault/crypto.d.ts +7 -0
- package/dist/vault/crypto.js +16 -0
- package/dist/vault/oauth.d.ts +55 -0
- package/dist/vault/oauth.js +186 -0
- package/dist/vault/service.d.ts +54 -7
- package/dist/vault/service.js +261 -17
- package/dist/vault/sources.d.ts +54 -0
- package/dist/vault/sources.js +185 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +14 -10
- package/dist/adapters/media.d.ts +0 -24
- package/dist/adapters/media.js +0 -111
- package/dist/host/cors.d.ts +0 -27
- package/dist/host/cors.js +0 -64
- package/dist/tenant/access-policy.d.ts +0 -30
- package/dist/tenant/access-policy.js +0 -52
- package/dist/tenant/browser.d.ts +0 -15
- package/dist/tenant/browser.js +0 -39
- package/dist/tenant/subject-limits.d.ts +0 -16
- package/dist/tenant/subject-limits.js +0 -45
- package/dist/tenant/tokens.d.ts +0 -14
- package/dist/tenant/tokens.js +0 -180
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,139 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.0-beta
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- bd478ee: **Subject tokens, browser keys and derived principals leave open source (F9 I3, protocol 7).** Upgrade these with the Runtime: they speak protocol 7, which this Runtime serves beside 4, 5 and 6; protocol 6 clients keep working on every route that remains. Browsers and apps present a trusted issuer's token (F9 I2), servers an operator key (F9 I1), and a person's own credentials come from the operator's credential resolver (F9 C1). See MIGRATION.md.
|
|
8
|
+
|
|
9
|
+
- **Breaking (`@nylorun/runtime`, `@nylorun/core`): subject tokens are gone.** `POST /v1/tokens`, `POST /v1/access/revocations`, subject-token verification, revocation epochs and the `subject.revoked` stream signal are removed; the routes answer `404`, and a JWT no trusted issuer signed is the opaque `404`. A stream opened with a token now ends only at its expiry (`event: nylorun.closed`, reason `token_expired`; `StreamClosedFrame` loses `revoked`). Migration `0010_oss_auth_removals` drops the `subject_epochs`, `subject_usage` and `publishable_keys` tables. The `token` caller is a trusted issuer's token only: it loses `role`, `limits` and `epoch`, and `issuer` is always set; `GET /v1/me` no longer reports `via: token`. Core drops `SUBJECT_TOKEN_TYPE`, `SUBJECT_TOKEN_AUDIENCE`, `subjectTokenIssuer` (the Runtime's own tokens use `tenantTokenIssuer`, same value), `CreateTokenRequest`/`Response`, `RevokeSubject*`, `SubjectTokenClaims`, `TOKEN_TTL_MIN_SECONDS` and `TOKEN_TTL_DEFAULT_SECONDS`; `TOKEN_SCOPES` is `agents:read`, `sessions:own`, `sandboxes:write`.
|
|
10
|
+
- **Breaking (`@nylorun/runtime`, `@nylorun/core`): the access policy and role limits are gone.** `GET`/`PUT /v1/access/policy` answer `404`, a turn is never refused with `429 limit_exceeded` for a role (limit requests at your proxy), and an existing `access.policy` setting is ignored. Core drops `AccessPolicy*`, `AccessRole*`, `RoleLimits*`, `DEFAULT_ACCESS_POLICY`, `PutAccessPolicyRequest` and `ROLE_NAME_PATTERN`.
|
|
11
|
+
- **Breaking (`@nylorun/runtime`, `@nylorun/core`): publishable keys and the Runtime's CORS are gone.** `/v1/access/publishable-keys*` answer `404`, `Nylorun-Key` is ignored, `NYLORUN_BROWSER_ACCESS` and `browserAccess` (host.json, `createHost`, `startEphemeralRuntime`) are removed, and the Runtime sends no CORS headers. A request with `Origin` reaches the Tenant routes of the public listener with no toggle: a trusted issuer's token is served, an application key or delivery token is `403 origin_rejected`; admin routes, `/health`, `/ready` and the operator listener still refuse `Origin`. Any `OPTIONS` request is `204` with `Allow` and no CORS header: the operator's proxy answers preflights. Route declarations lose `browser` (`x-nylorun-browser` leaves the OpenAPI document) and the `publishable` credential; the token security scheme is `issuerToken`. Core drops `PUBLISHABLE_KEY_HEADER`, `PUBLISHABLE_KEY_PATTERN`, `PUBLISHABLE_KEY_ID_PATTERN`, `tenantOfPublishableKey`, `newPublishableKey`, `newPublishableKeyId`, `PublishableKey*`, `OriginEntrySchema`, `originAllowed` and `LOOPBACK_ORIGIN_WILDCARDS`.
|
|
12
|
+
- **Breaking (`@nylorun/runtime`, `@nylorun/core`, `@nylorun/agents`): vault routes are application-only, and `vaults:own` is retired.** Every `/v1/vaults` route takes an application key acting for no one (operator keys, Studio); a request acting for a subject or a trusted issuer's token gets `403 scope_required`. `vaults:own` leaves `SUBJECT_SCOPES`, `TOKEN_SCOPES` and the issuer scopes; `Nylorun-Scopes` may still name it and it grants nothing. Existing user vaults stay attachable to their own owner's sessions. `client.createVault` takes `{ scope: "installation" }` or `ownerUserId`, and `client.listVaults()` takes an optional owner.
|
|
13
|
+
- **Breaking (`@nylorun/runtime`, `@nylorun/core`, `@nylorun/admin`, `nylorun`): derived principals are gone.** Only Studio's key is derived from the admin key. `NYLORUN_DERIVED_PRINCIPALS` is ignored and no longer written, `hostPrincipals` takes no `derived`, `startEphemeralRuntime` no `derivedPrincipals`, and `@nylorun/admin` drops `deriveTenantKey`, `PROJECT_PRINCIPAL_ID` and `admin.deriveTenantKey`. Keys an earlier Host derived stay in the database and keep working as ordinary keys; replace them with operator keys. Core renames `DERIVED_PRINCIPAL_ID_PATTERN` to `APPLICATION_KEY_ID_PATTERN`.
|
|
14
|
+
- **Breaking (`@nylorun/agents`):** `createTokenEndpoint`, `createBrowserClient` and the `@nylorun/agents/browser` entry, `client.tokens`, `TokensClient`, `PublishableKeysClient`, `client.access.getPolicy`/`putPolicy`/`revokeSubject`/`publishableKeys` and `Destination.publishableKey` are removed. `client.access.signingKeys` and `client.access.jwks()` stay, and `Destination.token` takes a trusted issuer's tokens.
|
|
15
|
+
- **Breaking (`@nylorun/cli`):** `nylo access policy|keys|revoke|token` are removed and name what replaces them; `nylo access signing-keys list|rotate|revoke` stays.
|
|
16
|
+
- **Breaking (`@nylorun/core`, `@nylorun/agents`, `@nylorun/studio`): the Studio embed message `open.babai` is now `open.session`** (`{ sessionId }`): it asks the embedding app to open that session in its own UI. Studio sends no such message itself.
|
|
17
|
+
- **Breaking (`nylorun`): Studio embedding is opt-in.** No origin may frame Studio by default; list your app's origins with `nylorun start --studio-embed-origin <origin>`. A Tenant's `.env` from an earlier nylorun keeps the origins it had until `--studio-embed-origin-reset`.
|
|
18
|
+
- `PROTOCOL_VERSION` is 7 and `HOST_PROTOCOL` 4–7; the Host features `subject-tokens`, `browser-access` and `derived-principals` are removed. `@nylorun/studio` and `@nylorun/create-agent` speak protocol 7.
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- 8773586: **Installation vaults and a credential resolver (F9 C1).** A session's MCP credential now comes from its attached vaults, then from the operator's own credential resolver; nothing changes for existing vaults, and the protocol stays at 6.
|
|
23
|
+
|
|
24
|
+
- Installation vaults: `POST /v1/vaults` takes `scope: "installation"` (no `ownerUserId`) from an application key acting for no one; a request acting for a subject gets `403`. The vault is owned by `installation`, now a reserved subject like `host`. Any session may attach one and select its credentials. `GET /v1/vaults` from an application key lists them after the named person's vaults, and lists only them without `ownerUserId`; a request acting for a subject never sees one (the opaque `404`). The host model vault stays hidden and unattachable. Migration 0008 adds the scope.
|
|
25
|
+
- The credential resolver: `NYLORUN_RESOLVER_URL` and `NYLORUN_RESOLVER_TOKEN` on the gateway (`TenantConfig.resolver` and `startEphemeralRuntime({ resolver })` in process). When the session's vaults hold nothing for a remote MCP server's URL, the Runtime POSTs `{ owner, session, turn, target: { kind: "mcp", server, agent, url } }` with the resolver's bearer: `200 { headers, expiresAt? }` is used, `404` goes without a credential, and anything else or no answer within 5 s refuses the server with `credential_unavailable`. Owner and turn come from the session row. Answers are cached per owner and URL until `expiresAt`, at most 5 minutes (60 s without one), and concurrent misses share one request. See DEPLOYMENT.md, Credentials.
|
|
26
|
+
- `nylorun`: the gateway's Compose service passes `NYLORUN_RESOLVER_URL` and `NYLORUN_RESOLVER_TOKEN` from the shell that runs `nylorun start` (unset by default).
|
|
27
|
+
- Studio: the Vault page is now **Connections**, and creates installation vaults.
|
|
28
|
+
|
|
29
|
+
- e1bfb4b: **MCP OAuth connect for installation vaults (F9 C2).** The installation can sign in once to a remote MCP server that uses OAuth, and every session that attaches the vault uses the credential. Additive; the protocol stays at 6.
|
|
30
|
+
|
|
31
|
+
- `POST /v1/vaults/{vaultId}/oauth/start` (application keys acting for no one, installation vaults only) takes `{ url, server, clientId? }` and answers `{ authorizeUrl, expiresAt }`. The Runtime discovers the server's authorization server (RFC 9728, then RFC 8414), registers itself (RFC 7591) unless `clientId` names a registered client, and starts an S256 PKCE sign-in whose `state` works once, for ten minutes. A server without registration and no `clientId` is `400 oauth_client_required`.
|
|
32
|
+
- `GET /v1/oauth/callback` takes the browser back: anonymous and unversioned like an artifact link, it exchanges the code and stores an `oauth` credential bound to the URL, named after `server` (connecting again rotates it), and answers a small HTML page. An unknown, used or expired `state` is `oauth_state_invalid`; the authorization server's refusal is `oauth_failed`. The callback's base is `NYLORUN_PUBLIC_URL` (`TenantConfig.publicUrl`), else the start request's origin.
|
|
33
|
+
- Every OAuth step runs in the gateway's keys module (F9-D14): the runtime container never sees a token, the PKCE verifier or a client secret, and makes no outbound call. Migration 0009 adds `oauth_pending`, with the verifier and secret sealed under the vault key. `Keys` gains `startOAuth` and `finishOAuth`.
|
|
34
|
+
- OAuth requests, including refresh of every OAuth vault credential, now go through `guardedFetch` (`tenant/outbound.ts`): the `NYLORUN_ENDPOINT_*` address policy checked on the address connected to, no redirects, a bounded answer. With `NYLORUN_ENDPOINT_PRIVATE=refuse` a token endpoint on a private address is refused, where refresh used to call it.
|
|
35
|
+
- `@nylorun/core`: `ERROR_CODES` adds `oauth_client_required`, `oauth_state_invalid` and `oauth_failed`; `StartOAuthRequestSchema` and `StartOAuthResponseSchema`.
|
|
36
|
+
- `nylorun mcp connect <url> --server <name> [--vault <id>] [--client-id <id>]`: creates the installation vault `mcp` if needed, opens the sign-in page and waits up to 10 minutes for the credential. See DEPLOYMENT.md, "Connecting a remote MCP server with OAuth".
|
|
37
|
+
|
|
38
|
+
- 5cfaed9: **Operator keys, and project links on them (F9 I1).** The Admin API manages the Tenant's application keys by name: `PUT /v1/admin/keys/{id}` creates a key or rotates it and returns it once, `GET /v1/admin/keys` lists every key's id, role and issue time (never the keys), and `DELETE /v1/admin/keys/{id}` removes one. A rotated or deleted key stops authenticating on its next request. Ids follow `^[a-z][a-z0-9-]{0,31}$`; `studio` (derived from the admin key) is refused. Keys keep today's format (64 hex) and only their SHA-256 is stored, in the existing principals table (no migration). Host feature `operator-keys` (additive, protocol unchanged); core adds the `OperatorKey`, `ListOperatorKeysResponse`, `PutOperatorKeyResponse` and `DeleteOperatorKeyResponse` schemas.
|
|
39
|
+
|
|
40
|
+
- `@nylorun/admin`: `admin.keys.put(id)`, `admin.keys.list()` and `admin.keys.delete(id)`; they refuse with `incompatible_host` when the Host lacks `operator-keys`.
|
|
41
|
+
- `nylorun key put|list|rm <id>` manages a running local Tenant's keys; `put` prints the key once on stdout.
|
|
42
|
+
- `nylorun start` no longer derives the project's key. It keeps `.nylorun/credentials.json` while its key still reaches the Tenant (one authenticated read); otherwise it gives the project the operator key `project`, which the Host root keeps in `project-credentials.json` (0600) so every checkout linked to the Tenant shares it. An existing derived project key keeps working and is adopted.
|
|
43
|
+
- `nylorun sandbox` uses the linked project's key, or the operator key `cli` it puts once and keeps in `<Host root>/cli-credentials.json` (0600).
|
|
44
|
+
- The ephemeral Runtime (`startEphemeralRuntime`) no longer registers the derived `project` principal; pass `derivedPrincipals: ["project"]` to keep it. Derived principals (`NYLORUN_DERIVED_PRINCIPALS`, `deriveTenantKey`) still work on a Host.
|
|
45
|
+
|
|
46
|
+
- c0b604e: **Trusted issuers (F9 I2).** The Tenant API accepts JWTs from the operator's own identity provider as bearers, configured in an identity file: `NYLORUN_IDENTITY_FILE` names a YAML file listing each issuer's `name`, `issuer`, `audience`, `jwks` URL or static PEM `keys`, a `subject` template over scalar claims (`u:{sub}`), `scopes` from a claim or a fixed list, `allowedScopes`, an optional `agents` allowlist, optional `sandboxes` grant templates (`{org_id}/*`) and `maxLifetime`. A malformed file stops the boot, naming the issuer and the field; a subject template without a claim is malformed. A bearer whose unverified `iss` names an issuer is verified with RS256, ES256 or EdDSA only, up to 16 KiB, `aud` matching, `exp` and `iat` required and `exp − iat` within `maxLifetime`, and becomes a token caller (`role: issuer:<name>`) with the issuer's scopes, agents and rendered sandbox grants; subject revocation does not reach it. JWKS keys are fetched only from the configured URL (no redirects), cached by `kid`, refetched at most once a minute for an unknown `kid`; while a JWKS is unreachable cached keys keep working and a new `kid` is `401 issuer_unavailable` (new error code). A browser request with an issuer token needs no publishable key (CORS comes from the operator's proxy). New: `GET /v1/me` reports the subject, scopes, agents, sandbox grants and `via` (`application:<id>`, `subject`, `token` or `issuer:<name>`) of any credential; the issuer-only scope `studio`; `ISSUER_SCOPES`, `CALLER_SCOPES` and `MeResponseSchema` in `@nylorun/core/contracts`; `parseIdentityFile` and `createTrustedIssuers` in `@nylorun/runtime`, and an `issuers` option on `startEphemeralRuntime`; Host feature `trusted-issuers` (additive, protocol unchanged). `nylorun start` sets `NYLORUN_IDENTITY_FILE=/nylorun/identity.yaml` on the runtime container when `<Host root>/identity.yaml` exists.
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- Pin core to the tested release.
|
|
51
|
+
- Pin harness to the tested release.
|
|
52
|
+
- Updated dependencies [8773586]
|
|
53
|
+
- Updated dependencies [e1bfb4b]
|
|
54
|
+
- Updated dependencies [5cfaed9]
|
|
55
|
+
- Updated dependencies [bd478ee]
|
|
56
|
+
- Updated dependencies [c0b604e]
|
|
57
|
+
- Updated dependencies
|
|
58
|
+
- @nylorun/core@0.13.0-beta
|
|
59
|
+
- @nylorun/harness@0.22.1-beta
|
|
60
|
+
|
|
61
|
+
## 0.16.0-beta
|
|
62
|
+
|
|
63
|
+
### Major Changes
|
|
64
|
+
|
|
65
|
+
- b352feb: **File artifacts, message parts and capability links (protocol 6).** Upgrade these with the Runtime: they speak protocol 6, which this Runtime serves beside 4 and 5. See MIGRATION.md.
|
|
66
|
+
|
|
67
|
+
- **`@nylorun/runtime`: file artifacts.** A file is an artifact: an `af_` id, a name and numbered immutable versions, with rows in the new `artifacts` and `artifact_versions` tables (migration `0004_artifacts`) and bytes in the Object store (`BlobStore`). `POST /v1/artifacts?name=&sessionId=` uploads a file in one streamed request and `POST /v1/artifacts/{id}/versions` adds a version, within the Tenant's limits (`GET`/`PUT /v1/tenant/artifacts`: 100 MiB per file and 10 GiB in all by default); a body past either is `413 limit_exceeded`, refused mid-stream with nothing stored. `GET /v1/artifacts` lists them (by session), `GET /v1/artifacts/{id}` reads one with its versions, `GET /v1/artifacts/{id}/versions/{n|latest}/content` downloads through the Runtime with HTTP Range (`206`, `Content-Range`, `416`), and `DELETE /v1/artifacts/{id}` deletes it with its bytes. A session's artifacts go with it on a sessions reset, and appear in its history as `artifact.created`, `artifact.version.created` and `artifact.deleted` (new in the event catalog). A subject reaches only the artifacts of their own sessions.
|
|
68
|
+
- **`@nylorun/runtime`: capability links.** `POST /v1/artifacts/{id}/links` mints a short-lived path, `/v1/artifact-links/<token>`, that downloads one version (with Range) with no credential and no `Nylorun-Protocol`: an ES256 JWT (`typ: nylorun-artifact+jwt`) signed with the Tenant's signing key, at most 15 minutes, and dead once the artifact is deleted. The Host logs its path without the token.
|
|
69
|
+
- **Breaking (`@nylorun/core`, `@nylorun/runtime`): message `parts`.** A user message may carry `parts`: `text`, and `file` by `artifactId` (and `version`, else the latest, pinned when the message is accepted). Model-gate reads the file from the Object store: an image goes to the model as image input, a text file as text, and any other file fails the call (`invalid_request`). The record and the transcript hold only the reference. The gateway builds its `BlobStore` from `NYLORUN_OBJECT_STORE_*` (the Tenant's `fs` store without it). `PROTOCOL_VERSION` is 6; `HOST_PROTOCOL` is 4–6 and protocol 6 clients require the feature `artifacts`.
|
|
70
|
+
- **`@nylorun/runtime`: `save_artifact`.** A session with a sandbox gets the `nylorun.artifacts` capability, whose `save_artifact` tool saves a sandbox file (`path`) or text (`content`) as an artifact of the session, carrying the turn and tool call on its event.
|
|
71
|
+
- **Breaking (`@nylorun/runtime`): `MediaStore` is removed.** `MediaStore` and `localMedia` leave `@nylorun/runtime/node`, and `piModel` takes `files` (a resolver from an artifact reference to its bytes) instead of `media`; images are file artifacts. The image checks (`decodeImageBase64`, `validateImageBytes`, `IMAGE_MEDIA_TYPES`, `MAX_IMAGE_BYTES`) stay.
|
|
72
|
+
- **`@nylorun/agents`:** `client.artifacts` (`upload`, `uploadVersion`, `list`, `get`, `download` with a range, `link`, `delete`), also on `BrowserClient`, and `session.inputParts(parts)`. A request with a body of its own type keeps it.
|
|
73
|
+
- `@nylorun/admin`, `@nylorun/cli`, `nylorun`, `@nylorun/studio`: speak protocol 6.
|
|
74
|
+
|
|
75
|
+
### Minor Changes
|
|
76
|
+
|
|
77
|
+
- e58e6ec: **egress-gate: pod sandboxes reach only the hosts their spec allows (F7.2).** A new `egress` service runs in the gateway's process (`--service gates,keys,egress`) on `NYLORUN_EGRESS_LISTEN_PORT` (default 4200). It is a CONNECT proxy: each tunnel needs an egress token (`Proxy-Authorization`, Basic with the token as password or Bearer) for a live sandbox at its current host epoch, a host name (no IP literal) that the sandbox's `network.allow` names exactly or by `*.suffix`, port 443 or 80, and a name that resolves to a public address; the gate connects to that checked address and pipes bytes, with no TLS interception and no credential injection. Plain HTTP is answered `405`; a sandbox holds at most 64 tunnels; idle tunnels close after 5 minutes; refusals are logged, never emitted as events. Egress tokens (`typ: nylorun-egress+jwt`), minted with each host token at a pod's join, are accepted nowhere else; egress-gate also refuses a token of a sandbox that is deleted, lost or expired, or of a pod other than the one that joined. Sandbox spec resolution lets `pod` sandboxes allow `*.suffix` hosts within the Tenant's ceiling (virtual ones keep exact names). With sandboxes enabled, `nylorun` runs the gateway with `egress` and publishes it on `NYLORUN_SANDBOX_BIND` at `NYLORUN_SANDBOX_EGRESS_PORT`, next to the gates.
|
|
78
|
+
- 186ec3f: **A local Tenant has an Object store: RustFS, behind the Runtime's new `BlobStore` seam.** `nylorun start` adds a `rustfs` container (RustFS 1.0.1, single node and single drive, pinned by digest) on the `nylorun-<tenant>-rustfs` volume, unpublished and without its console. Its secret key is generated once into `docker/.env` (`NYLORUN_OBJECT_STORE_SECRET_KEY`), and only the `runtime` and `gateway` containers receive the credential (`NYLORUN_OBJECT_STORE_ENDPOINT`, `_ACCESS_KEY`, `_SECRET_KEY`); the runtime creates the bucket at boot. The Runtime reaches the store through `BlobStore` (put with a streamed body and a size cap, get with a byte range, head, delete, list by prefix) with an `s3` adapter over the plain S3 API and an `fs` adapter, which a Runtime without `NYLORUN_OBJECT_STORE_ENDPOINT` (embedded, ephemeral, tests) uses under the Tenant directory's `blobs/`. Nothing stores files there yet; file artifacts build on it.
|
|
79
|
+
- 6077272: **Folder artifacts and the turn-end outputs export (F8.2).** Part of protocol 6, with file artifacts.
|
|
80
|
+
|
|
81
|
+
- **`@nylorun/runtime`: the turn-end export.** When an agent's turn completes, the Runtime reads `/workspace/outputs` of the session's sandbox and keeps it as a version of the session's folder artifact `outputs`: the first export creates it, and each later turn whose outputs changed adds a version (`artifact.created` / `artifact.version.created` with `kind: "folder"`, `source: "export"`, `fileCount` and `claimed: true`, since the listing and bytes are what the sandbox supplied). Nothing is exported without a sandbox or without outputs. An export past 10,000 files, 1 GiB, the per-file limit or the Tenant total stores nothing and records `artifact.export.skipped` with its reason; a failure records `artifact.export.failed`. Neither fails the turn. Core reads the workspace through one seam, `WorkspaceReader`, which the Harness API and pod sandboxes will implement later.
|
|
82
|
+
- **`@nylorun/runtime`: folder artifacts.** A folder version is a manifest of paths to content-addressed files: each file's bytes are stored once at `blobs/sha256/<hex>`, so an unchanged file is never stored again, and the Tenant total (`GET /v1/tenant/artifacts` `usedBytes`) counts it once. New routes: `GET /v1/artifacts/{id}/versions/{n|latest}/tree` (the manifest), `…/files/{path}` (one file by its percent-encoded path, with Range), `…/diff?from=n` (files added, removed and changed) and `…/zip` (a streamed zip). `POST /v1/artifacts/{id}/links` takes `file` to link one file of a folder; a folder's link without it opens the zip. A folder's `/content`, a new version uploaded to a folder, and a message part naming a folder are `400`. Deleting a folder removes the files no other version names. Migration `0006_folder_artifacts` allows the `folder` kind and adds the `artifact_content` table.
|
|
83
|
+
- **`@nylorun/core`:** `ArtifactKindSchema` (`file`, `folder`), the `export` source, `FolderEntrySchema`, `FolderManifestSchema`, `ArtifactTreeSchema`, `ArtifactDiffSchema`, the `artifact.export.skipped` and `artifact.export.failed` events, and `fileCount` and `claimed` on `artifact.created` / `artifact.version.created`.
|
|
84
|
+
- **`@nylorun/agents`:** `client.artifacts.tree()`, `file()`, `diff()` and `zip()`, and `link(id, { file })`.
|
|
85
|
+
|
|
86
|
+
- a5f9cf5: **Gate trust (F5): run tokens and two gate credentials.** A model or MCP call through the gates service reaches only its own run, and the gate takes the call's scope from a token instead of believing the request body.
|
|
87
|
+
|
|
88
|
+
- **Run tokens.** Each advance that takes a session's lease mints a run token (`tenant/run-token.ts`): an ES256 JWT of `typ` `nylorun-run+jwt` and `aud` `nylorun-gates`, signed by the keys service with the Tenant's current signing key, naming the session (`sub`), its active turn (`trn`), its root agent (`agt`) and the lease epoch (`epc`), for 15 minutes. The lease heartbeat re-mints it when less than 5 minutes remain, and it is dropped when the advance ends or loses the lease. The tokens are internal: not in `openapi.json`, and the JWKS is unchanged.
|
|
89
|
+
- **Two credentials at the gateway.** Model calls and their cancels accept only a run token. Tool calls, their cancels and the MCP connect, list and close routes accept a run token, or `NYLORUN_GATES_TOKEN` (now core's credential) for requests made outside a run. Keys, `/deliveries` and endpoint pings accept only core's credential.
|
|
90
|
+
- **Scope from the token.** The model-call body no longer carries `sessionId`, `turnId` or `agentId` (a body naming them is refused with `400`), and MCP requests under a run token leave out `server.sessionId`: ledger rows, turn and agent caps, and MCP manifest and vault-grant lookup use the token's claims.
|
|
91
|
+
- **Stale tokens die.** Every call under a run token checks that the session's epoch and active turn are still the token's and that it is not cancelled, and answers `409 run_stale` otherwise. A keyed call joins a call already running at the gate only for the same session at the same or a newer epoch, so the new owner's re-send after a takeover keeps working (P1.2, F4.1) and the old owner's is refused. A cancel stops only its own session's call (`403 gate_forbidden` otherwise).
|
|
92
|
+
- In process nothing changes: embedding, `startEphemeralRuntime` and Runtimes without the gates service mint no token. No protocol bump: the public API, protocol 5, the Action endpoint wire and `docker/.env` are unchanged. See `DEPLOYMENT.md`.
|
|
93
|
+
|
|
94
|
+
- b6bf1f5: **Harness API v1, in process (F6.1).** Every segment now runs in a harness: the advance takes the session's lease and offers the segment as a run, and the Tenant's own harness, in the same process, runs the engine and reports how it ended. Core settles it exactly as before. Nothing changes on the wire: protocol 5, durable checkpoint 1, engine `hosted-3` and the Action endpoint wire are the same.
|
|
95
|
+
|
|
96
|
+
- `@nylorun/core/harness-api`: the protocol (messages, Zod schemas, the effect request hash, transcript edits, an RPC channel with an in-process memory transport).
|
|
97
|
+
- `@nylorun/harness/api`: `createHarness({ channel, executors })`, a harness that leases runs, renews their leases, replays a run's recorded outcomes without asking, keeps transcripts by record cursor, and runs model, MCP and sandbox calls through the executors it is given.
|
|
98
|
+
- `@nylorun/runtime`: the Harness API server per Tenant (`TenantHandle.attachHarness`), the in-process harness, and the journal as the Record seam. A model call's journal row now stores the request's hash without its prompt, so a replay never sends a prompt twice. `NYLORUN_HARNESS_API=0` runs the engine in the advance as before, until F6.2 removes it. A Runtime older than this one may fail a turn that was in flight across a downgrade with drift.
|
|
99
|
+
|
|
100
|
+
- 8ed4ea6: **Harness service over WebSocket, with the workspace capability (F6.2).** A Runtime started with `NYLORUN_HARNESS=remote` runs no harness of its own: it opens the Harness API listener (`NYLORUN_HARNESS_LISTEN_HOST`/`_PORT`, default port 4200, `NYLORUN_HARNESS_ALLOWED_HOSTS`), which accepts only the harness credential (`NYLORUN_HARNESS_TOKEN`) on `/nylorun/harness/v1`. The runtime image's `--service harness` connects to it (`NYLORUN_HARNESS_URL`, `NYLORUN_HARNESS_TOKEN`, `NYLORUN_GATES_URL`, `NYLORUN_HARNESS_ROOT`) and runs the Tenant's segments, MCP servers and sandboxes with no store; it refuses to start with a database, the gates' or keys' credential, or Restate settings, and presents only run tokens at the gates. The in-process harness stays the default.
|
|
101
|
+
|
|
102
|
+
- `@nylorun/core/harness-api`: the `workspace.*` requests core sends to a harness that serves workspaces, `tenantId` in the `hello` answer, a workspace record on `sandbox.state` claims, and `TurnStart.options.holdMs`. Tenant and admin status report the Tenant's harnesses (`harness`).
|
|
103
|
+
- `@nylorun/harness/api`: `createHarness` declares capabilities, reports grants (`onGrant`) and the `hello` answer, readies MCP through `executors.prepare` (`session.mcp`), and holds a run while its Action is pending until core sends the outcome (`effect.resolved`).
|
|
104
|
+
- `@nylorun/runtime`: the WebSocket listener and client, `--service harness`, the workspace capability (`ctx.sandbox` is a `WorkspacePort`; sandbox tool routes, `save_artifact`, sweep and reset reach the harness's workspaces), the SandboxManager's records port, and held runs (`actionHoldMs`, default 5 minutes). `save_artifact` runs in core. `NYLORUN_HARNESS_API` and the engine run in the advance are removed; tests run with `NYLORUN_TEST_HARNESS=memory|json|ws`.
|
|
105
|
+
|
|
106
|
+
- 678e085: **Pod sandboxes (F7.2, second part).** With `nylorun sandbox enable`, a sandbox resource of kind `pod` (`PUT /v1/sandboxes/{id}` with `kind: "pod"`, an `image`, `*.suffix` hosts, `storage` and `lifecycle.ttl`) is an agent-sandbox pod on the Tenant's cluster, created at once. The turns of the sessions attached to it run in the pod: the engine is copied from the Runtime image into the pod (the Runtime image now carries tini for it), waits until the pod's NetworkPolicy is in force, exchanges its join token for a host token at the Harness API listener (`POST /nylorun/harness/v1/host/join`, published for pods on the Docker host's address, with the gates), and serves its sandbox alone. New: `POST /v1/sandboxes/{id}/stop` and `/reset`; the Tenant's `limits.ttl`, `lifecycle.onExpiry`, `lifecycle.stopGrace` and `placement`; idle stop; lifecycle events `sandbox.running`, `.suspended`, `.expired`, `.relaunched`, `.lost`, `.reset` and `.failed`; error codes `placement_refused`, `sandbox_lost` and `sandbox_expired`; `cluster` in `GET /v1/tenant/sandbox`; Host feature `sandbox-pods` (additive, protocol unchanged). Without a cluster, kind `pod` is `409 sandbox_unavailable` (it was 400). Migration `0007_sandbox_pods` adds the pod lifecycle columns to `sandbox_resources`.
|
|
107
|
+
- 926711b: **Sandboxes are a resource (F7.1, blueprint D39; Host feature `sandboxes`).** A sandbox has its own id, a kind, a spec and labels, and outlives the sessions attached to it. Additive: protocol 5 is unchanged.
|
|
108
|
+
|
|
109
|
+
- `PUT /v1/sandboxes/{id}` creates a sandbox or finds the one with that id (get-or-create in one call); `GET /v1/sandboxes/{id}`, `GET /v1/sandboxes?label=key=value` (repeatable), `GET /v1/sandboxes/{id}/events` and `DELETE /v1/sandboxes/{id}`. Ids are `/`-separated segments (`team-a/proj-42`), sent percent-encoded as one path segment. Only kind `virtual` runs; `pod` is refused with `sandbox_unavailable`. The spec is resolved against the Tenant's limits and fixed once the sandbox exists; labels can change.
|
|
110
|
+
- A session attaches with `sandbox: { id }` and shares the sandbox's `/workspace` with every other session attached to it. Turns are serial per sandbox: a second session's turn is refused with `409 sandbox_busy` while another runs. Deleting a session (a sessions reset) only detaches it. Deleting a sandbox is refused while a turn runs in it; afterwards an attached session's next turn is refused with `sandbox_unavailable` until a sandbox with that id exists again.
|
|
111
|
+
- Subject tokens carry an `sbx` claim: `POST /v1/tokens` takes `sandboxes`, exact ids or prefixes ending in `/*` (at most 16). A token reaches only the sandboxes they match, checked when a session attaches and at every turn start (`403 sandbox_not_granted`); any other sandbox is the 404 of a missing one. The new scope `sandboxes:write` lets a role create and delete the sandboxes its grants reach. Application keys reach every sandbox.
|
|
112
|
+
- The Tenant holds at most `limits.sandboxes` sandboxes (`PUT /v1/tenant/sandbox`, default 100); one more is `409 limit_exceeded`.
|
|
113
|
+
- Lifecycle events (`sandbox.created`, `sandbox.attached`, `sandbox.detached`, `sandbox.deleted`) go to the sandbox's own stream in the record, through the record module; the session's log records `sandbox.attached`. The sandbox stream is not relayed to S2.
|
|
114
|
+
- New error codes `sandbox_not_granted`, `sandbox_busy` and `sandbox_unavailable`; the session view gains `sandboxId` and `sandboxSource: "sandbox"`. Migration `0004_sandbox_resources` adds `sandbox_resources`, `nylorun_streams.sandbox_events` and the sessions' `sandbox_id` column.
|
|
115
|
+
- `@nylorun/agents`: `client.sandboxes` with `ensure(id, spec)`, `get`, `list({ labels })`, `delete`, `events`, and `forSession({ session, spec })`, which creates a sandbox for one session, opens the session on it, and deletes it with `release()`. It replaces sharing through another session (`sandbox: { session }` and the view's `sandboxOwnerId`, now deprecated). `client.tokens.create` takes `sandboxes`.
|
|
116
|
+
- `nylorun sandbox ls [--label key=value]... [--json]` and `nylorun sandbox rm <id>` list and delete the running local Tenant's sandboxes.
|
|
117
|
+
|
|
118
|
+
- db045cd: **The sandboxes service and `nylorun sandbox enable` (F7.2, first part).** A new image, `ghcr.io/nylorun/sandboxes`, versioned with the Runtime, drives agent-sandbox v1.0.5 Sandboxes in one namespace per Tenant: `PUT`, `GET ?wait=` and `DELETE /v1/pods/{name}` with an operation id, `/ready` and `/v1/info`, behind a bearer token only the runtime container holds. `nylorun sandbox enable --context <name>` installs into that kubeconfig context only (the pinned controller when absent, the namespace `nylorun-sbx-<tenant>`, a ServiceAccount whose Role covers Sandbox lifecycle and join Secrets, no NetworkPolicy or exec rights), refuses a cluster whose NetworkPolicy it cannot prove enforced, records `<Host root>/sandboxes/cluster.json` and the token, and adds the `sandboxes` service to the Tenant; `nylorun sandbox disable` and `nylorun sandbox status` remove and report it. Sessions do not run in pods yet: that comes with the Harness API and egress-gate.
|
|
119
|
+
|
|
120
|
+
### Patch Changes
|
|
121
|
+
|
|
122
|
+
- 3aaf640: **Closing a Tenant no longer hangs on work that ignores its abort.** `MemoryExecution.stop()` and `RestateExecution.stop()` now abort running handlers and wait for them only up to a stop grace (`stopGraceMs`, default `DEFAULT_STOP_GRACE_MS`, 30 s), then abandon the ones still running. Before, `stop()` waited without a limit, so `TenantRuntime.close()` hung in its first step (`detach`) before its bounded `idle` step could run. Shutdown semantics are unchanged: work is aborted as a `shutdown`, and an abandoned advance's lease lapses so the next advance takes the session over. A Tenant that owns its execution uses its advance grace period as the stop grace; the Restate Worker logs a warning when it abandons handlers and drops their connections, so Restate retries them.
|
|
123
|
+
- 167d01a: **A run core stops while it asks about a pending Action is no longer held.** When core cancelled a run, or stopped it for a shutdown, while the harness waited for core's answer to an Action's `effect.intent`, the harness then held the run for the whole `holdMs` (5 minutes by default): it listened for an abort that had already happened. The run kept its lease, and closing the Tenant waited for it. The harness now gives such a run back at once.
|
|
124
|
+
- Pin core to the tested release.
|
|
125
|
+
- Pin harness to the tested release.
|
|
126
|
+
- Updated dependencies [b352feb]
|
|
127
|
+
- Updated dependencies [6077272]
|
|
128
|
+
- Updated dependencies [b6bf1f5]
|
|
129
|
+
- Updated dependencies [8ed4ea6]
|
|
130
|
+
- Updated dependencies [167d01a]
|
|
131
|
+
- Updated dependencies [678e085]
|
|
132
|
+
- Updated dependencies [926711b]
|
|
133
|
+
- Updated dependencies
|
|
134
|
+
- @nylorun/core@0.12.0-beta
|
|
135
|
+
- @nylorun/harness@0.22.0-beta
|
|
136
|
+
|
|
3
137
|
## 0.15.2-beta
|
|
4
138
|
|
|
5
139
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The independent OSS **Runtime Host** consumes `@nylorun/harness/run` and
|
|
4
4
|
`@nylorun/core/contracts`. Cloud installs published `@nylorun/harness` from npm
|
|
5
5
|
and does not import this package. Client authoring and sessions belong to
|
|
6
|
-
`@nylorun/agents`; the Host's status and
|
|
6
|
+
`@nylorun/agents`; the Host's status and operator keys belong to `@nylorun/admin`.
|
|
7
7
|
Vocabulary: [src/CONTEXT.md](./src/CONTEXT.md).
|
|
8
8
|
|
|
9
9
|
Requires Node 24+. Build from the repository root:
|
|
@@ -34,9 +34,9 @@ database, in the fixed schemas `nylorun` (its state: the Session Store) and
|
|
|
34
34
|
transaction under an advisory lock, records them in `nylorun.__drizzle_migrations`, and
|
|
35
35
|
refuses a database holding a migration it does not ship (`schema-too-new`); then, on
|
|
36
36
|
first start, it creates the Tenant there: its id (`NYLORUN_TENANT_ID`, default a
|
|
37
|
-
new one), its name (`NYLORUN_TENANT_NAME`, default `default`)
|
|
38
|
-
the
|
|
39
|
-
|
|
37
|
+
new one), its name (`NYLORUN_TENANT_NAME`, default `default`) and its Studio principal,
|
|
38
|
+
whose key the admin key derives (the only derived key). Operator keys (`/v1/admin/keys`) add, rotate and delete the Tenant's
|
|
39
|
+
application keys after that; a rotated or deleted key stops authenticating at once. A database written by a Runtime that kept several Tenants in one
|
|
40
40
|
database (`tenant_<id>` schemas), or by a pre-release build of one Tenant per database
|
|
41
41
|
(`schema_version` tables), is refused: this release starts fresh on a new database. Restate runs one advance of a session at a time and holds the Tenant's sweep
|
|
42
42
|
timer (Durable Session Execution); every session's events are relayed from the record to
|
|
@@ -50,25 +50,54 @@ them. A local Tenant packs `core,loop` into the `runtime` container and
|
|
|
50
50
|
api|worker|all` is the deprecated name of `--service core`, `loop` and
|
|
51
51
|
`core,loop`.
|
|
52
52
|
|
|
53
|
+
`harness` runs alone: agent turns, stdio MCP servers and workspaces, apart from
|
|
54
|
+
core. With `NYLORUN_HARNESS=remote`, core runs no turn itself and opens the
|
|
55
|
+
Harness API listener (`NYLORUN_HARNESS_LISTEN_HOST`, `NYLORUN_HARNESS_LISTEN_PORT`,
|
|
56
|
+
default 4200, `NYLORUN_HARNESS_ALLOWED_HOSTS`), which accepts only
|
|
57
|
+
`NYLORUN_HARNESS_TOKEN`; `in-process` (the default outside Compose) runs turns in
|
|
58
|
+
core's process. A `harness` process needs `NYLORUN_HARNESS_URL`,
|
|
59
|
+
`NYLORUN_HARNESS_TOKEN`, `NYLORUN_GATES_URL` and `NYLORUN_HARNESS_ROOT` (default
|
|
60
|
+
`/harness`), answers `/health` on `127.0.0.1:4300`, and refuses to start with a
|
|
61
|
+
database, Restate, keys or gates credential in its environment. A local Tenant
|
|
62
|
+
runs it in the `harness` container, on a network with only the runtime and the
|
|
63
|
+
gateway ([DEPLOYMENT.md](../DEPLOYMENT.md#the-harness-agent-turns-mcp-servers-and-workspaces)).
|
|
64
|
+
|
|
53
65
|
The container is configured by its environment, which a local Tenant's Compose file
|
|
54
66
|
sets: `NYLORUN_DATABASE_URL` (required), `NYLORUN_RESTATE_INGRESS_URL`,
|
|
55
67
|
`NYLORUN_RESTATE_ADMIN_URL`, `NYLORUN_WORKER_URL` and
|
|
56
68
|
`NYLORUN_RESTATE_IDENTITY_KEY` (Restate), `NYLORUN_S2_ENDPOINT` and
|
|
57
69
|
`NYLORUN_S2_TOKEN`, and in container mode `NYLORUN_LISTEN_HOST`,
|
|
58
70
|
`NYLORUN_LISTEN_PORT`, `NYLORUN_ALLOWED_HOSTS` and `NYLORUN_PUBLIC_URL`, plus
|
|
59
|
-
|
|
71
|
+
the operator listener
|
|
60
72
|
(`NYLORUN_ADMIN_LISTEN_PORT`, `NYLORUN_ADMIN_LISTEN_HOST`,
|
|
61
|
-
`NYLORUN_ADMIN_ALLOWED_HOSTS`).
|
|
62
|
-
|
|
63
|
-
|
|
73
|
+
`NYLORUN_ADMIN_ALLOWED_HOSTS`). `NYLORUN_IDENTITY_FILE` names the identity
|
|
74
|
+
file, a YAML list of the trusted issuers whose JWTs the Tenant API accepts
|
|
75
|
+
(Host feature `trusted-issuers`), read once at boot; a malformed file stops the
|
|
76
|
+
boot ([DEPLOYMENT.md](../DEPLOYMENT.md#trusted-issuers)). A process that runs `loop` sends its model
|
|
77
|
+
calls to the gate at `NYLORUN_GATES_URL`; in a container it refuses to start
|
|
78
|
+
without it and `NYLORUN_GATES_TOKEN`. That token is core's credential: the gate
|
|
79
|
+
accepts only it for vault writes, token signing and Action deliveries. Model and
|
|
80
|
+
remote MCP calls carry a run token instead, which the loop mints for each
|
|
81
|
+
session it advances, and the gate takes the call's session, turn and agent from
|
|
82
|
+
it; a token whose turn was cancelled or whose session another process took over
|
|
83
|
+
is refused with `409 run_stale`. A `gates` process needs only
|
|
64
84
|
`NYLORUN_DATABASE_URL`, `NYLORUN_GATES_TOKEN`, its listener
|
|
65
85
|
(`NYLORUN_GATES_LISTEN_HOST`, `NYLORUN_GATES_LISTEN_PORT`, default 4100, and
|
|
66
86
|
`NYLORUN_GATES_ALLOWED_HOSTS`) and the Host's `tenant/` directory, which it
|
|
67
|
-
never writes; it serves the database's one Tenant. `
|
|
87
|
+
never writes; it serves the database's one Tenant. `NYLORUN_RESOLVER_URL` and
|
|
88
|
+
`NYLORUN_RESOLVER_TOKEN` (both or neither, gates only) name the operator's
|
|
89
|
+
credential resolver, which the gate asks for a person's MCP credential when the
|
|
90
|
+
session's vaults hold none ([DEPLOYMENT.md](../DEPLOYMENT.md#credentials)); an
|
|
91
|
+
embedding or `startEphemeralRuntime` takes `resolver: { url, token }` instead. With `egress`
|
|
92
|
+
(`--service gates,keys,egress`, for pod sandboxes) the same process runs
|
|
93
|
+
egress-gate on `NYLORUN_EGRESS_LISTEN_HOST`:`NYLORUN_EGRESS_LISTEN_PORT`
|
|
94
|
+
(default `0.0.0.0:4200`): a CONNECT proxy that admits a pod's egress token and
|
|
95
|
+
tunnels only to the hosts its sandbox spec allows, on 443 or 80, never to a
|
|
96
|
+
private address. `NYLORUN_PACKING` (`combined` or `split`) is logged at startup.
|
|
68
97
|
|
|
69
98
|
With an operator listener (a local Tenant's default: container port 4001), the Host
|
|
70
|
-
serves two ports. The public one serves the Tenant API, to browsers too
|
|
71
|
-
|
|
99
|
+
serves two ports. The public one serves the Tenant API, to browsers too (with a
|
|
100
|
+
trusted issuer's token), and answers admin routes with the opaque `404`. The
|
|
72
101
|
operator one serves the Admin API, Host shutdown and the Tenant API, never to
|
|
73
102
|
browsers; keep it on loopback or a private network and never proxy it. Outside
|
|
74
103
|
a container, `adminPort` in `host.json` does the same on loopback.
|
|
@@ -82,6 +111,7 @@ The Host root is `NYLORUN_HOME` or `~/.nylorun` (for a local Tenant,
|
|
|
82
111
|
<host root>/
|
|
83
112
|
host.json # format 1: hostId, host, port, adminPort?, runtimeVersion, …
|
|
84
113
|
host-credentials.json # adminKey (0600)
|
|
114
|
+
identity.yaml # optional: trusted issuers (NYLORUN_IDENTITY_FILE)
|
|
85
115
|
docker/ # compose.yaml, .env (0600), Restate identity key
|
|
86
116
|
tenant/ # vault-kek, plugin-data, logs, home, tmp, sandboxes
|
|
87
117
|
```
|
|
@@ -95,20 +125,30 @@ The Host root is `NYLORUN_HOME` or `~/.nylorun` (for a local Tenant,
|
|
|
95
125
|
| `GET /openapi.json` | none | The Tenant API's OpenAPI 3.2 document (below); refuses an `Origin` |
|
|
96
126
|
| `GET /v1/admin/status` | admin key | `AdminStatusSchema`: the Host, its protocol and its Tenant (`tenant`: id, name, `open` or `unavailable`, and the cause when it could not be opened); alias `GET /v1/admin/host`. On the operator listener when there is one |
|
|
97
127
|
| `POST /v1/admin/host/shutdown` | admin key | Host-private; not in `@nylorun/admin` |
|
|
128
|
+
| `PUT /v1/admin/keys/{id}` | admin key | Operator keys (Host feature `operator-keys`): creates key `id` or rotates it, and returns it once (`PutOperatorKeyResponse`); `studio` is refused |
|
|
129
|
+
| `GET /v1/admin/keys` | admin key | Every key of the Tenant: id, role, when issued (`ListOperatorKeysResponse`), never the keys |
|
|
130
|
+
| `DELETE /v1/admin/keys/{id}` | admin key | The key stops authenticating at once; `404` when there is none |
|
|
98
131
|
| `GET /v1/admin/openapi.json` | admin key | The Admin API's OpenAPI 3.2 document |
|
|
99
|
-
| `/v1/*` Tenant routes | application key,
|
|
132
|
+
| `/v1/*` Tenant routes | application key, trusted issuer's token or delivery token | Require `Nylorun-Protocol`; nothing names the Tenant |
|
|
133
|
+
| `POST /v1/vaults/{vaultId}/oauth/start` | application key | MCP OAuth connect into an installation vault (F9 C2): `{url, server, clientId?}` → `{authorizeUrl, expiresAt}`; the gateway's keys module does discovery, registration and the exchange. `oauth_client_required` without DCR or a `clientId` |
|
|
134
|
+
| `GET /v1/oauth/callback` | the `state` itself | Where the authorization server sends the browser back: no credential, no `Nylorun-Protocol`; finishes the connect once (`oauth_state_invalid` after) and answers a small HTML page. Its base is `NYLORUN_PUBLIC_URL`, else the start request's origin |
|
|
135
|
+
| `GET /v1/artifact-links/{token}` | the link itself | A capability link to one artifact version (protocol 6): no credential, no `Nylorun-Protocol`, Range supported; a folder's link opens its zip, or one of its files |
|
|
100
136
|
|
|
101
137
|
Every route checks `Host` first (`421 host_rejected`) and rejects non-JSON bodies
|
|
102
|
-
with `415 unsupported_media_type
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
from
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
unsupported protocol → `426` before authentication (the Host serves protocols 4
|
|
110
|
-
|
|
111
|
-
|
|
138
|
+
with `415 unsupported_media_type`, except an artifact upload (`POST /v1/artifacts`,
|
|
139
|
+
`POST /v1/artifacts/{id}/versions`), whose body is the file in any media type. An `Origin` is `403 origin_rejected` on
|
|
140
|
+
`/health`, `/ready`, admin routes and the operator listener. On Tenant routes a browser
|
|
141
|
+
presents a trusted issuer's token; application keys and delivery tokens are refused
|
|
142
|
+
from browsers before they are looked up. The Runtime sends no CORS headers, and answers
|
|
143
|
+
`OPTIONS` with `204` and `Allow` only: the operator's reverse proxy answers preflights
|
|
144
|
+
([DEPLOYMENT.md](../DEPLOYMENT.md#calling-the-runtime-from-browsers-and-apps)). Missing or
|
|
145
|
+
unsupported protocol → `426` before authentication (the Host serves protocols 4 to 7;
|
|
146
|
+
protocol 7 removes subject tokens, the access policy, revocations, browser keys and
|
|
147
|
+
derived principals, whose routes answer `404`; protocol 6 adds file and folder artifacts and message `parts`; at each turn's end the
|
|
148
|
+
Runtime exports `/workspace/outputs` of the session's sandbox as a version of its
|
|
149
|
+
`outputs` folder, read at `/v1/artifacts/{id}/versions/{n|latest}/tree`, `/files/{path}`,
|
|
150
|
+
`/diff?from=` and `/zip`). Protocol 5 and later clients name no Tenant. A protocol 4 client's `Nylorun-Tenant` naming
|
|
151
|
+
another Tenant (or malformed), a Tenant that
|
|
112
152
|
could not be opened and rejected credentials → opaque `404` with identical body. A path or method no route serves is `404 Route not found`
|
|
113
153
|
once the caller is known.
|
|
114
154
|
|
|
@@ -129,18 +169,17 @@ tool at one of them; for [Scalar](https://scalar.com), the package file of a ver
|
|
|
129
169
|
https://cdn.jsdelivr.net/npm/@nylorun/runtime@<version>/dist/openapi.json
|
|
130
170
|
```
|
|
131
171
|
|
|
132
|
-
Each operation's `security` says which credentials it takes (application key,
|
|
133
|
-
|
|
134
|
-
`x-nylorun-scopes` (the subject scopes that reach it)
|
|
135
|
-
may call it. Event streams are `text/event-stream` with an `itemSchema`. `runtime/openapi/` holds the committed snapshots: a change to a route changes
|
|
172
|
+
Each operation's `security` says which credentials it takes (application key, trusted
|
|
173
|
+
issuer's token, delivery token, admin key), and its `x-nylorun-credentials` and
|
|
174
|
+
`x-nylorun-scopes` (the subject scopes that reach it) fields say who may call it. Event streams are `text/event-stream` with an `itemSchema`. `runtime/openapi/` holds the committed snapshots: a change to a route changes
|
|
136
175
|
them (`node scripts/build-openapi.mjs --write`), and `check-package` fails until they are
|
|
137
176
|
updated.
|
|
138
177
|
|
|
139
178
|
## HTTP layer
|
|
140
179
|
|
|
141
180
|
Routes are Hono routes (`@hono/zod-openapi`), each declared once with who may call it
|
|
142
|
-
(`api/http/define.ts`): that declaration serves the route, checks subject scopes
|
|
143
|
-
|
|
181
|
+
(`api/http/define.ts`): that declaration serves the route, checks subject scopes and makes
|
|
182
|
+
the OpenAPI document. `host/` holds the listeners, the `Host`
|
|
144
183
|
check (in Node, before Hono) and the Host pipeline (`host/app.ts`); `api/` the Tenant and
|
|
145
184
|
Admin routes (`api/http/routes/`, `api/ag-ui/`, `api/a2a/`, `host/admin-api.ts`). Only `host/`
|
|
146
185
|
and `api/` import Hono.
|
|
@@ -212,7 +251,7 @@ the Project link (outside a project it runs the Tenant `default`); the project's
|
|
|
212
251
|
| `schema-too-new` | Run a Runtime at least as new as the one that migrated the database |
|
|
213
252
|
| `database-layout-old` | The database holds `tenant_<id>` schemas of an older Runtime, or the `schema_version` tables of a pre-release one: point the Runtime at a new database (locally, a new Tenant: `nylorun start --tenant <new name>`); the old one is left as it is |
|
|
214
253
|
| `426 protocol_unsupported` | Upgrade clients or Host to a compatible set |
|
|
215
|
-
| `421 host_rejected` / `403 origin_rejected` | In a container, list the `Host` in `NYLORUN_ALLOWED_HOSTS`. From a browser,
|
|
254
|
+
| `421 host_rejected` / `403 origin_rejected` | In a container, list the `Host` in `NYLORUN_ALLOWED_HOSTS`. From a browser, send a trusted issuer's token, never a Tenant key |
|
|
216
255
|
| `503` for a Tenant | Postgres or Restate is unreachable; `GET /ready` names which |
|
|
217
256
|
| Port in use | Change `NYLORUN_PORT` in `~/.nylorun/tenants/<name>/docker/.env` and run `nylorun start` |
|
|
218
257
|
| Logs | `nylorun logs runtime` |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type DurableExecution, type StuckInvocation, type Wake, type WorkerHandlers } from "../../execution/types.js";
|
|
1
|
+
import { type DurableExecution, type SandboxSignal, type StuckInvocation, type Wake, type WorkerHandlers } from "../../execution/types.js";
|
|
2
2
|
export interface RestateTimeouts {
|
|
3
3
|
/**
|
|
4
4
|
* How long an invocation may run without journal progress before Restate
|
|
@@ -66,6 +66,12 @@ export interface RestateExecutionOptions {
|
|
|
66
66
|
forceRegistration?: boolean;
|
|
67
67
|
/** How long `start` keeps retrying registration while Restate comes up. Default 60000. */
|
|
68
68
|
registrationTimeoutMs?: number;
|
|
69
|
+
/**
|
|
70
|
+
* How long `stop` waits for running handlers after aborting them; the ones still running
|
|
71
|
+
* then are abandoned and their attempts end with the dropped connections, so Restate
|
|
72
|
+
* retries them. Default `DEFAULT_STOP_GRACE_MS`.
|
|
73
|
+
*/
|
|
74
|
+
stopGraceMs?: number;
|
|
69
75
|
/** Replaces the SDK's console logging. */
|
|
70
76
|
logger?: (level: string, message: string) => void;
|
|
71
77
|
}
|
|
@@ -91,9 +97,11 @@ export declare class RestateExecution implements DurableExecution {
|
|
|
91
97
|
tenant: string;
|
|
92
98
|
timer: string;
|
|
93
99
|
action: string;
|
|
100
|
+
sandbox: string;
|
|
94
101
|
};
|
|
95
102
|
wake(tenantId: string, sessionId: string, wake: Wake): Promise<void>;
|
|
96
103
|
deliver(tenantId: string, actionId: string): Promise<void>;
|
|
104
|
+
sandbox(tenantId: string, sandboxId: string, signal: SandboxSignal): Promise<void>;
|
|
97
105
|
timer(tenantId: string, key: string, at: Date): Promise<void>;
|
|
98
106
|
armSweep(tenantId: string): Promise<void>;
|
|
99
107
|
disarmSweep(tenantId: string): Promise<void>;
|
|
@@ -22,13 +22,19 @@
|
|
|
22
22
|
* no state and makes no `ctx.run`: its only journal entry is the delayed
|
|
23
23
|
* self-send after a `retry` result, so an endpoint that is down never spends
|
|
24
24
|
* the retry budget, which is for infrastructure errors.
|
|
25
|
+
* - `<prefix>NylorunSandbox`, key `<tenantId>:<sandboxId>` (F7.2). Its
|
|
26
|
+
* exclusive handlers serialize a pod sandbox's reconciles and timers:
|
|
27
|
+
* `reconcile` calls `WorkerHandlers.sandbox`; `arm` records a timer's latest
|
|
28
|
+
* time (`idle`, `ttl`, or the reconcile's own `retry`) and sends a delayed
|
|
29
|
+
* `fire`; a `fire` for an older time is a no-op, as the timer object's.
|
|
30
|
+
* What a run answers arms its timers and its retry.
|
|
25
31
|
*
|
|
26
32
|
* Wakes, deliveries, timers and sweep arming go through the ingress as one-way
|
|
27
33
|
* sends, so an API node can call them without ever calling `start`.
|
|
28
34
|
*/
|
|
29
35
|
import { createServer } from "node:http2";
|
|
30
36
|
import * as restate from "@restatedev/restate-sdk";
|
|
31
|
-
import { WAKE_REASONS, parseSessionKey, sessionKey, } from "../../execution/types.js";
|
|
37
|
+
import { DEFAULT_STOP_GRACE_MS, WAKE_REASONS, parseSessionKey, sessionKey, settleWithin, } from "../../execution/types.js";
|
|
32
38
|
const HOUR_MS = 60 * 60 * 1000;
|
|
33
39
|
export function createRestateExecution(options) {
|
|
34
40
|
return new RestateExecution(options);
|
|
@@ -55,6 +61,7 @@ export class RestateExecution {
|
|
|
55
61
|
tenant: `${prefix}NylorunTenant`,
|
|
56
62
|
timer: `${prefix}NylorunTimer`,
|
|
57
63
|
action: `${prefix}NylorunAction`,
|
|
64
|
+
sandbox: `${prefix}NylorunSandbox`,
|
|
58
65
|
};
|
|
59
66
|
this.ingressUrl = options.ingressUrl.replace(/\/+$/, "");
|
|
60
67
|
this.adminUrl = options.adminUrl.replace(/\/+$/, "");
|
|
@@ -72,6 +79,16 @@ export class RestateExecution {
|
|
|
72
79
|
async deliver(tenantId, actionId) {
|
|
73
80
|
await this.send(this.names.action, sessionKey(tenantId, actionId), "deliver", {});
|
|
74
81
|
}
|
|
82
|
+
async sandbox(tenantId, sandboxId, signal) {
|
|
83
|
+
const key = sessionKey(tenantId, sandboxId);
|
|
84
|
+
if (signal.kind === "reconcile")
|
|
85
|
+
await this.send(this.names.sandbox, key, "reconcile", {});
|
|
86
|
+
else
|
|
87
|
+
await this.send(this.names.sandbox, key, "arm", {
|
|
88
|
+
timer: signal.timer,
|
|
89
|
+
at: signal.at,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
75
92
|
async timer(tenantId, key, at) {
|
|
76
93
|
await this.send(this.names.timer, sessionKey(tenantId, key), "set", {
|
|
77
94
|
at: at.getTime(),
|
|
@@ -143,8 +160,9 @@ export class RestateExecution {
|
|
|
143
160
|
this.stopping = true;
|
|
144
161
|
for (const controller of this.controllers)
|
|
145
162
|
controller.abort(new Error("Worker stopping"));
|
|
146
|
-
|
|
147
|
-
|
|
163
|
+
const abandoned = await settleWithin(this.inflight, this.options.stopGraceMs ?? DEFAULT_STOP_GRACE_MS);
|
|
164
|
+
if (abandoned > 0)
|
|
165
|
+
this.options.logger?.("warn", `Worker stopped with ${abandoned} handler(s) still running; abandoned`);
|
|
148
166
|
this.handlers = undefined;
|
|
149
167
|
const server = this.server;
|
|
150
168
|
this.server = undefined;
|
|
@@ -259,7 +277,48 @@ export class RestateExecution {
|
|
|
259
277
|
},
|
|
260
278
|
options: serviceOptions,
|
|
261
279
|
});
|
|
262
|
-
|
|
280
|
+
/** Sets a sandbox timer: the latest time wins; `fire` checks it. */
|
|
281
|
+
const armSandbox = async (ctx, input) => {
|
|
282
|
+
ctx.set(`at:${input.timer}`, input.at);
|
|
283
|
+
const now = await ctx.date.now();
|
|
284
|
+
ctx
|
|
285
|
+
.objectSendClient({ name: names.sandbox }, ctx.key)
|
|
286
|
+
.fire(input, restate.rpc.sendOpts({ delay: Math.max(0, input.at - now) }));
|
|
287
|
+
};
|
|
288
|
+
const runSandbox = async (ctx, trigger) => {
|
|
289
|
+
const { tenantId, sessionId: sandboxId } = parseSessionKey(ctx.key);
|
|
290
|
+
const result = await this.runAborted(ctx.request().attemptCompletedSignal, (handlers, signal) => {
|
|
291
|
+
if (!handlers.sandbox)
|
|
292
|
+
throw new Error("WorkerHandlers.sandbox is required for sandbox reconciles");
|
|
293
|
+
return handlers.sandbox(tenantId, sandboxId, trigger, signal);
|
|
294
|
+
});
|
|
295
|
+
for (const item of result.arm ?? [])
|
|
296
|
+
await armSandbox(ctx, item);
|
|
297
|
+
if (result.retryAfterMs !== undefined)
|
|
298
|
+
await armSandbox(ctx, {
|
|
299
|
+
timer: "retry",
|
|
300
|
+
at: (await ctx.date.now()) + Math.max(0, result.retryAfterMs),
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
const sandbox = restate.object({
|
|
304
|
+
name: names.sandbox,
|
|
305
|
+
handlers: {
|
|
306
|
+
reconcile: async (ctx) => {
|
|
307
|
+
await runSandbox(ctx, "reconcile");
|
|
308
|
+
},
|
|
309
|
+
arm: async (ctx, input) => {
|
|
310
|
+
await armSandbox(ctx, input);
|
|
311
|
+
},
|
|
312
|
+
fire: async (ctx, input) => {
|
|
313
|
+
if ((await ctx.get(`at:${input.timer}`)) !== input.at)
|
|
314
|
+
return;
|
|
315
|
+
ctx.clear(`at:${input.timer}`);
|
|
316
|
+
await runSandbox(ctx, input.timer === "retry" ? "reconcile" : input.timer);
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
options: serviceOptions,
|
|
320
|
+
});
|
|
321
|
+
return [session, tenant, timer, action, sandbox];
|
|
263
322
|
}
|
|
264
323
|
/**
|
|
265
324
|
* Runs a Worker handler with a signal aborted by `stop` or by the end of the Restate
|
|
@@ -365,7 +424,7 @@ export async function listStuckInvocations(options) {
|
|
|
365
424
|
const prefix = options.servicePrefix ?? "";
|
|
366
425
|
if (!/^[A-Za-z0-9_]*$/.test(prefix))
|
|
367
426
|
throw new Error(`Invalid Restate service prefix: ${prefix}`);
|
|
368
|
-
const services = ["NylorunSession", "NylorunTenant", "NylorunTimer", "NylorunAction"].map((name) => `'${prefix}${name}'`);
|
|
427
|
+
const services = ["NylorunSession", "NylorunTenant", "NylorunTimer", "NylorunAction", "NylorunSandbox"].map((name) => `'${prefix}${name}'`);
|
|
369
428
|
const clauses = [
|
|
370
429
|
`target_service_name IN (${services.join(", ")})`,
|
|
371
430
|
`status IN ('paused', 'backing-off')`,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SandboxBackend } from "../../sandbox/types.js";
|
|
2
|
+
export interface LocalBackendOptions {
|
|
3
|
+
/** Default `/workspace`. */
|
|
4
|
+
readonly workspace?: string;
|
|
5
|
+
/** The proxy variables of the moment (the egress token rotates); default none. */
|
|
6
|
+
readonly proxyEnv?: () => Readonly<Record<string, string>>;
|
|
7
|
+
/** The base environment of commands (the engine's allowlisted one); `NYLORUN_*` is dropped. */
|
|
8
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
9
|
+
}
|
|
10
|
+
export declare function localBackend(options: LocalBackendOptions): SandboxBackend;
|