@nylorun/runtime 0.13.0-beta → 0.15.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 +434 -0
- package/README.md +137 -41
- package/dist/adapters/execution/restate.d.ts +7 -2
- package/dist/adapters/execution/restate.js +40 -10
- package/dist/adapters/replication/pgoutput.d.ts +21 -0
- package/dist/adapters/replication/pgoutput.js +170 -0
- package/dist/adapters/streams/s2.js +37 -20
- package/dist/admin-openapi.json +570 -0
- package/dist/api/a2a/card.d.ts +15 -0
- package/dist/api/a2a/card.js +24 -0
- package/dist/api/a2a/endpoint.d.ts +8 -0
- package/dist/api/a2a/endpoint.js +81 -0
- package/dist/api/a2a/protocol.d.ts +144 -0
- package/dist/api/a2a/protocol.js +188 -0
- package/dist/api/a2a/routes.d.ts +19 -0
- package/dist/api/a2a/routes.js +289 -0
- package/dist/api/a2a/tasks.d.ts +50 -0
- package/dist/api/a2a/tasks.js +186 -0
- package/dist/api/ag-ui/endpoint.d.ts +8 -0
- package/dist/api/ag-ui/endpoint.js +126 -0
- package/dist/api/ag-ui/history.d.ts +7 -0
- package/dist/api/ag-ui/history.js +60 -0
- package/dist/api/ag-ui/routes.d.ts +32 -0
- package/dist/api/ag-ui/routes.js +305 -0
- package/dist/api/ag-ui/session-id.d.ts +1 -0
- package/dist/api/ag-ui/session-id.js +12 -0
- package/dist/api/ag-ui/sse.d.ts +9 -0
- package/dist/api/ag-ui/sse.js +7 -0
- package/dist/api/ag-ui/translate.d.ts +38 -0
- package/dist/api/ag-ui/translate.js +222 -0
- package/dist/api/components.d.ts +8065 -0
- package/dist/api/components.js +82 -0
- package/dist/api/gate/keys.d.ts +14 -0
- package/dist/api/gate/keys.js +59 -0
- package/dist/api/gate/routes.d.ts +34 -0
- package/dist/api/gate/routes.js +235 -0
- package/dist/api/http/app.d.ts +18 -0
- package/dist/api/http/app.js +58 -0
- package/dist/api/http/body.d.ts +4 -0
- package/dist/api/http/body.js +37 -0
- package/dist/api/http/define.d.ts +39 -0
- package/dist/api/http/define.js +122 -0
- package/dist/api/http/respond.d.ts +9 -0
- package/dist/api/http/respond.js +44 -0
- package/dist/api/http/routes/access.d.ts +24 -0
- package/dist/api/http/routes/access.js +266 -0
- package/dist/api/http/routes/actions.d.ts +8 -0
- package/dist/api/http/routes/actions.js +76 -0
- package/dist/api/http/routes/endpoints.d.ts +7 -0
- package/dist/api/http/routes/endpoints.js +100 -0
- package/dist/api/http/routes/sessions.d.ts +3 -0
- package/dist/api/http/routes/sessions.js +205 -0
- package/dist/api/http/routes/tenant.d.ts +11 -0
- package/dist/api/http/routes/tenant.js +244 -0
- package/dist/api/http/routes/vaults.d.ts +8 -0
- package/dist/api/http/routes/vaults.js +167 -0
- package/dist/api/openapi.d.ts +19 -0
- package/dist/api/openapi.js +137 -0
- package/dist/api/route.d.ts +12 -0
- package/dist/api/route.js +4 -0
- package/dist/core/bearer.d.ts +8 -0
- package/dist/core/bearer.js +11 -0
- package/dist/core/flow-host.d.ts +7 -15
- package/dist/core/flow-host.js +16 -41
- package/dist/core/sandbox-routes.d.ts +12 -7
- package/dist/core/sandbox-routes.js +24 -25
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +33 -13
- package/dist/execution/types.d.ts +38 -6
- package/dist/execution/types.js +12 -3
- package/dist/gates/contract.d.ts +51 -0
- package/dist/gates/contract.js +37 -0
- package/dist/gates/handler.d.ts +21 -0
- package/dist/gates/handler.js +55 -0
- package/dist/gates/http-client.d.ts +12 -0
- package/dist/gates/http-client.js +160 -0
- package/dist/gates/in-process.d.ts +32 -0
- package/dist/gates/in-process.js +42 -0
- package/dist/gates/inflight.d.ts +39 -0
- package/dist/gates/inflight.js +70 -0
- package/dist/gates/mcp-handler.d.ts +35 -0
- package/dist/gates/mcp-handler.js +142 -0
- package/dist/gates/meter.d.ts +24 -0
- package/dist/gates/meter.js +165 -0
- package/dist/gates/model-gate.d.ts +45 -0
- package/dist/gates/model-gate.js +1 -0
- package/dist/gates/tenant-vaults.d.ts +64 -0
- package/dist/gates/tenant-vaults.js +94 -0
- package/dist/gates/tool-calls.d.ts +51 -0
- package/dist/gates/tool-calls.js +64 -0
- package/dist/gates/tool-client.d.ts +12 -0
- package/dist/gates/tool-client.js +201 -0
- package/dist/gates/tool-contract.d.ts +116 -0
- package/dist/gates/tool-contract.js +64 -0
- package/dist/gates/tool-gate.d.ts +45 -0
- package/dist/gates/tool-gate.js +10 -0
- package/dist/host/admin-api.d.ts +23 -0
- package/dist/host/admin-api.js +74 -0
- package/dist/host/app.d.ts +34 -0
- package/dist/host/app.js +181 -0
- package/dist/host/config-for.d.ts +3 -1
- package/dist/host/config-for.js +3 -2
- package/dist/host/config.d.ts +11 -0
- package/dist/host/cors.d.ts +27 -0
- package/dist/host/cors.js +64 -0
- package/dist/host/create-host.d.ts +36 -8
- package/dist/host/create-host.js +161 -330
- package/dist/host/execution.d.ts +20 -18
- package/dist/host/execution.js +2 -2
- package/dist/host/gates.d.ts +58 -0
- package/dist/host/gates.js +127 -0
- package/dist/host/http.d.ts +20 -4
- package/dist/host/http.js +81 -23
- package/dist/host/index.d.ts +1 -1
- package/dist/host/main.js +189 -58
- package/dist/host/stack-config.d.ts +91 -19
- package/dist/host/stack-config.js +246 -17
- package/dist/infra/database.d.ts +2 -2
- package/dist/infra/execution.d.ts +5 -5
- package/dist/infra/execution.js +8 -6
- package/dist/infra/index.d.ts +1 -1
- package/dist/infra/workers.d.ts +17 -17
- package/dist/infra/workers.js +9 -9
- package/dist/keys/client.d.ts +9 -0
- package/dist/keys/client.js +70 -0
- package/dist/keys/contract.d.ts +24 -0
- package/dist/keys/contract.js +12 -0
- package/dist/keys/keys.d.ts +41 -0
- package/dist/keys/keys.js +50 -0
- package/dist/mcp/connect.d.ts +43 -3
- package/dist/mcp/connect.js +12 -5
- package/dist/mcp/pool.d.ts +39 -3
- package/dist/mcp/pool.js +74 -11
- package/dist/model/catalog.d.ts +2 -10
- package/dist/model/classify.d.ts +13 -0
- package/dist/model/classify.js +61 -0
- package/dist/model/models.d.ts +10 -1
- package/dist/model/models.js +14 -6
- package/dist/model/pi-model.d.ts +17 -2
- package/dist/model/pi-model.js +366 -222
- package/dist/openapi.json +25889 -0
- package/dist/record/append.d.ts +51 -0
- package/dist/record/append.js +20 -0
- package/dist/record/envelope.d.ts +19 -0
- package/dist/record/envelope.js +49 -0
- package/dist/record/index.d.ts +11 -0
- package/dist/record/index.js +11 -0
- package/dist/sandbox/manager.d.ts +13 -1
- package/dist/sandbox/manager.js +8 -11
- package/dist/sandbox/resolve.d.ts +33 -0
- package/dist/sandbox/resolve.js +81 -0
- package/dist/sandbox/session-sandbox.d.ts +11 -0
- package/dist/sandbox/session-sandbox.js +51 -0
- package/dist/sandbox/share.d.ts +14 -2
- package/dist/sandbox/share.js +33 -3
- package/dist/sandbox/tenant-config.d.ts +37 -0
- package/dist/sandbox/tenant-config.js +51 -0
- package/dist/sandbox/tools.d.ts +7 -9
- package/dist/sandbox/tools.js +11 -3
- package/dist/sandbox/types.d.ts +8 -2
- package/dist/store/postgres/connect.d.ts +25 -9
- package/dist/store/postgres/connect.js +27 -10
- package/dist/store/postgres/db.d.ts +16 -0
- package/dist/store/postgres/db.js +28 -0
- package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
- package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
- package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
- package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
- package/dist/store/postgres/migrate.d.ts +68 -0
- package/dist/store/postgres/migrate.js +183 -0
- package/dist/store/postgres/record-writer.d.ts +4 -0
- package/dist/store/postgres/record-writer.js +34 -0
- package/dist/store/postgres/record.d.ts +5 -0
- package/dist/store/postgres/record.js +60 -0
- package/dist/store/postgres/schema.d.ts +3096 -0
- package/dist/store/postgres/schema.js +363 -0
- package/dist/store/postgres/store.d.ts +2 -4
- package/dist/store/postgres/store.js +590 -434
- package/dist/store/postgres/tenant.d.ts +56 -0
- package/dist/store/postgres/tenant.js +154 -0
- package/dist/store/types.d.ts +183 -132
- package/dist/store/types.js +0 -1
- package/dist/streams/basin.d.ts +18 -1
- package/dist/streams/basin.js +34 -2
- package/dist/streams/control.d.ts +18 -0
- package/dist/streams/control.js +33 -0
- package/dist/streams/memory.d.ts +3 -1
- package/dist/streams/memory.js +14 -8
- package/dist/streams/relay/core.d.ts +35 -0
- package/dist/streams/relay/core.js +366 -0
- package/dist/streams/relay/types.d.ts +78 -0
- package/dist/streams/relay/types.js +8 -0
- package/dist/streams/types.d.ts +71 -36
- package/dist/streams/types.js +18 -53
- package/dist/tenant/access-policy.d.ts +30 -0
- package/dist/tenant/access-policy.js +52 -0
- package/dist/tenant/actions.d.ts +10 -149
- package/dist/tenant/actions.js +5 -167
- package/dist/tenant/advance.d.ts +7 -2
- package/dist/tenant/advance.js +125 -14
- package/dist/tenant/auth.d.ts +19 -17
- package/dist/tenant/auth.js +90 -93
- package/dist/tenant/browser.d.ts +15 -0
- package/dist/tenant/browser.js +39 -0
- package/dist/tenant/cause.d.ts +25 -0
- package/dist/tenant/cause.js +45 -0
- package/dist/tenant/commands.d.ts +18 -2
- package/dist/tenant/commands.js +111 -115
- package/dist/tenant/context.d.ts +101 -21
- package/dist/tenant/context.js +14 -10
- package/dist/tenant/delivery-token.d.ts +41 -0
- package/dist/tenant/delivery-token.js +104 -0
- package/dist/tenant/delivery.d.ts +62 -0
- package/dist/tenant/delivery.js +481 -0
- package/dist/tenant/effects.d.ts +20 -1
- package/dist/tenant/effects.js +159 -42
- package/dist/tenant/endpoints.d.ts +67 -0
- package/dist/tenant/endpoints.js +90 -0
- package/dist/tenant/ephemeral.d.ts +22 -7
- package/dist/tenant/ephemeral.js +83 -125
- package/dist/tenant/history.d.ts +64 -0
- package/dist/tenant/history.js +120 -0
- package/dist/tenant/http.d.ts +8 -9
- package/dist/tenant/http.js +13 -27
- package/dist/tenant/jwt.d.ts +16 -0
- package/dist/tenant/jwt.js +50 -0
- package/dist/tenant/model-setting.d.ts +2 -2
- package/dist/tenant/module.d.ts +17 -28
- package/dist/tenant/module.js +133 -279
- package/dist/tenant/outbound.d.ts +44 -0
- package/dist/tenant/outbound.js +127 -0
- package/dist/tenant/paths.d.ts +11 -16
- package/dist/tenant/paths.js +24 -68
- package/dist/tenant/pool.d.ts +1 -3
- package/dist/tenant/pool.js +1 -19
- package/dist/tenant/principals.d.ts +23 -11
- package/dist/tenant/principals.js +61 -49
- package/dist/tenant/reset.d.ts +0 -4
- package/dist/tenant/reset.js +0 -5
- package/dist/tenant/runtime.d.ts +28 -20
- package/dist/tenant/runtime.js +95 -64
- package/dist/tenant/scheduler.d.ts +10 -3
- package/dist/tenant/scheduler.js +9 -5
- package/dist/tenant/session-streams.d.ts +127 -0
- package/dist/tenant/session-streams.js +404 -0
- package/dist/tenant/sessions.d.ts +28 -4
- package/dist/tenant/sessions.js +118 -24
- package/dist/tenant/signing-keys.d.ts +55 -0
- package/dist/tenant/signing-keys.js +172 -0
- package/dist/tenant/slim.d.ts +8 -0
- package/dist/tenant/slim.js +21 -0
- package/dist/tenant/status.d.ts +3 -3
- package/dist/tenant/status.js +20 -13
- package/dist/tenant/store-pg.d.ts +9 -6
- package/dist/tenant/store-pg.js +60 -125
- package/dist/tenant/streams.d.ts +62 -85
- package/dist/tenant/streams.js +143 -226
- package/dist/tenant/subject-limits.d.ts +16 -0
- package/dist/tenant/subject-limits.js +45 -0
- package/dist/tenant/sweep.d.ts +1 -10
- package/dist/tenant/sweep.js +29 -69
- package/dist/tenant/tokens.d.ts +14 -0
- package/dist/tenant/tokens.js +180 -0
- package/dist/tenant/transcript.d.ts +48 -5
- package/dist/tenant/transcript.js +58 -1
- package/dist/tenant/types.d.ts +69 -98
- package/dist/tenant/types.js +4 -15
- package/dist/tenant/worker.d.ts +3 -1
- package/dist/tenant/worker.js +13 -7
- package/dist/vault/host-model.d.ts +31 -0
- package/dist/vault/host-model.js +57 -0
- package/dist/vault/service.d.ts +44 -17
- package/dist/vault/service.js +126 -121
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +25 -9
- package/dist/core/executors.d.ts +0 -44
- package/dist/core/executors.js +0 -95
- package/dist/store/memory.d.ts +0 -57
- package/dist/store/memory.js +0 -645
- package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
- package/dist/store/postgres/migrations/001_initial.js +0 -196
- package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
- package/dist/store/postgres/migrations/002_session_owner.js +0 -13
- package/dist/store/postgres/migrations/index.d.ts +0 -49
- package/dist/store/postgres/migrations/index.js +0 -76
- package/dist/store/postgres/names.d.ts +0 -11
- package/dist/store/postgres/names.js +0 -42
- package/dist/store/postgres/tenants.d.ts +0 -90
- package/dist/store/postgres/tenants.js +0 -214
- package/dist/streams/relay.d.ts +0 -37
- package/dist/streams/relay.js +0 -254
- package/dist/tenant/envelope.d.ts +0 -7
- package/dist/tenant/envelope.js +0 -22
- package/dist/tenant/live.d.ts +0 -92
- package/dist/tenant/live.js +0 -290
- package/dist/tenant/quarantine-error.d.ts +0 -6
- package/dist/tenant/quarantine-error.js +0 -9
- package/dist/tenant/quarantine.d.ts +0 -27
- package/dist/tenant/quarantine.js +0 -72
- package/dist/tenant/routes-tenant.d.ts +0 -12
- package/dist/tenant/routes-tenant.js +0 -137
- package/dist/tenant/routes.d.ts +0 -9
- package/dist/tenant/routes.js +0 -151
- package/dist/tenant/store-memory.d.ts +0 -16
- package/dist/tenant/store-memory.js +0 -111
- /package/dist/{store → record}/cursor.d.ts +0 -0
- /package/dist/{store → record}/cursor.js +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Thrown by migrations, the Tenant bootstrap and the Tenant Runtime when the Tenant cannot open. */
|
|
2
|
+
export class TenantOpenError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
repair;
|
|
5
|
+
/** The Tenant, when it was read before opening failed (`/v1/admin/status` names it). */
|
|
6
|
+
envelope;
|
|
7
|
+
constructor(fields) {
|
|
8
|
+
super(fields.message);
|
|
9
|
+
this.name = "TenantOpenError";
|
|
10
|
+
this.code = fields.code;
|
|
11
|
+
this.repair = fields.repair;
|
|
12
|
+
}
|
|
13
|
+
toCause() {
|
|
14
|
+
return { code: this.code, message: this.message, repair: this.repair };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** A `TenantOpenError` with the standard repair for `code`. */
|
|
18
|
+
export function openError(code, message) {
|
|
19
|
+
return new TenantOpenError({ code, message, repair: repairFor(code) });
|
|
20
|
+
}
|
|
21
|
+
/** The cause an error carries, when it is a `TenantOpenError`. */
|
|
22
|
+
export function causeOf(error) {
|
|
23
|
+
return error instanceof TenantOpenError ? error.toCause() : undefined;
|
|
24
|
+
}
|
|
25
|
+
/** What an operator does about each cause. */
|
|
26
|
+
export function repairFor(code) {
|
|
27
|
+
switch (code) {
|
|
28
|
+
case "kek-missing":
|
|
29
|
+
return "restore the vault key (vault-kek in the Host root's keys/ directory) from a backup, then restart the Runtime and its gateway: the Tenant's ciphertext cannot be opened without it";
|
|
30
|
+
case "corrupt":
|
|
31
|
+
return "restore the database from a backup, then restart the Runtime";
|
|
32
|
+
case "schema-too-new":
|
|
33
|
+
return "the database was migrated by a newer Runtime: run that version or newer";
|
|
34
|
+
case "migration-failed":
|
|
35
|
+
return "inspect the Runtime log for the failed migration, fix it, then restart the Runtime";
|
|
36
|
+
case "envelope-invalid":
|
|
37
|
+
return "the nylorun.tenant row is missing or invalid: restore the database from a backup";
|
|
38
|
+
case "open-timeout":
|
|
39
|
+
return "opening the Tenant timed out: inspect the Runtime log and Postgres, then restart the Runtime";
|
|
40
|
+
case "open-failed":
|
|
41
|
+
return "inspect the Runtime log, repair what it names, then restart the Runtime";
|
|
42
|
+
case "database-layout-old":
|
|
43
|
+
return "this release starts fresh: point the Runtime at a new database (a new stack: `nylorun start`); the old database is left as it is";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
-
import type { SessionCommand } from "@nylorun/core/contracts";
|
|
2
|
-
import
|
|
1
|
+
import type { Action, ActionOutcome, LiveEvent, SessionCommand } from "@nylorun/core/contracts";
|
|
2
|
+
import type { Tx } from "../store/types.js";
|
|
3
|
+
import { type AuthScope, type Session, type TenantContext } from "./context.js";
|
|
4
|
+
/** `outcome`, or a failed one when a tool's output does not match its stored output schema. */
|
|
5
|
+
export declare function acceptedOutcome(action: Action, outcome: ActionOutcome): ActionOutcome;
|
|
3
6
|
export declare function command(ctx: TenantContext, id: string, input: SessionCommand, scope: AuthScope): Promise<unknown>;
|
|
7
|
+
/**
|
|
8
|
+
* Completes a delivered Action with `outcome`, in the caller's transaction, which
|
|
9
|
+
* holds the lock of the Action's session `s`: the Action and its effect, the
|
|
10
|
+
* `action.completed` event (and `loop.verified` or `loop.decided`), the wake that resumes the
|
|
11
|
+
* turn, and queued workflow effects. The Action deliverer and the background-result callback
|
|
12
|
+
* both record outcomes here. It sets `s.status`; the caller writes `s`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function recordActionOutcome(t: Tx, ctx: TenantContext, s: Session, action: Action, received: ActionOutcome, options?: {
|
|
15
|
+
requestId?: string;
|
|
16
|
+
}): Promise<{
|
|
17
|
+
event: LiveEvent;
|
|
18
|
+
receipt: Record<string, unknown>;
|
|
19
|
+
}>;
|
package/dist/tenant/commands.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The session command service: `message`, `approve`/`respond
|
|
2
|
+
* The session command service: `message`, `approve`/`respond` and `cancel`,
|
|
3
3
|
* with per-session idempotency keys. A command locks its session and commits its state
|
|
4
4
|
* change, events and wakes in one transaction (the store publishes the events and runs the
|
|
5
5
|
* `afterCommit` wakes), then aborts a cancelled advance and cascades workflow cancels to the
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Wakes (`ctx.wake`, architecture §12.3) carry the command type as the reason and a dedupe
|
|
9
9
|
* key naming the cause: `<type>:<turnId>:<segment>` for `message`, `approve` and `respond`
|
|
10
|
-
* (every accepted one writes a new checkpoint segment)
|
|
11
|
-
* `
|
|
10
|
+
* (every accepted one writes a new checkpoint segment). An Action's outcome
|
|
11
|
+
* (`recordActionOutcome`, from the deliverer) wakes with
|
|
12
|
+
* `action_result:<turnId>:<actionId>:<generation>`.
|
|
12
13
|
*
|
|
13
14
|
* Cancel commits `cancelled` first; the engine host sees it before its next effect and before
|
|
14
15
|
* settlement on any Worker. It then aborts an advance running on this process
|
|
@@ -22,19 +23,21 @@ import { resolveMessageManifest } from "../core/turn-manifest.js";
|
|
|
22
23
|
import { canonical } from "../store/canonical.js";
|
|
23
24
|
import { lockedSession, } from "./context.js";
|
|
24
25
|
import { fail } from "./http.js";
|
|
25
|
-
import {
|
|
26
|
+
import { accessOf } from "./auth.js";
|
|
27
|
+
import { chargeTurn } from "./subject-limits.js";
|
|
26
28
|
import { actionTarget, rebaseSessionState, turnManifestOf, variantStore, } from "./session.js";
|
|
27
29
|
import { signalSessionCancel } from "./streams.js";
|
|
28
30
|
import { toolIds } from "./transcript.js";
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
import { slimModelEffects } from "./slim.js";
|
|
32
|
+
/** `outcome`, or a failed one when a tool's output does not match its stored output schema. */
|
|
33
|
+
export function acceptedOutcome(action, outcome) {
|
|
31
34
|
if (action.kind !== "tool" || !action.outputSchema)
|
|
32
|
-
return
|
|
33
|
-
const value =
|
|
35
|
+
return outcome;
|
|
36
|
+
const value = outcome.value;
|
|
34
37
|
// Only a result carries output: failures, denials, interactions and deferrals pass as sent.
|
|
35
38
|
if (!isResultToolValue(value))
|
|
36
|
-
return
|
|
37
|
-
//
|
|
39
|
+
return outcome;
|
|
40
|
+
// Endpoints wrap successful tool output as `{ kind: "completed", output }`.
|
|
38
41
|
// Validate the tool payload, not the outcome envelope.
|
|
39
42
|
const candidate = completedToolOutput(value);
|
|
40
43
|
let matches = false;
|
|
@@ -45,16 +48,13 @@ function acceptedToolResult(action, command) {
|
|
|
45
48
|
matches = false;
|
|
46
49
|
}
|
|
47
50
|
if (matches)
|
|
48
|
-
return
|
|
51
|
+
return outcome;
|
|
49
52
|
return {
|
|
50
|
-
...
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
code: "tool.invalid-output",
|
|
56
|
-
message: "Tool result does not match the output schema stored on the action",
|
|
57
|
-
},
|
|
53
|
+
...outcome,
|
|
54
|
+
value: {
|
|
55
|
+
kind: "failed",
|
|
56
|
+
code: "tool.invalid-output",
|
|
57
|
+
message: "Tool result does not match the output schema stored on the action",
|
|
58
58
|
},
|
|
59
59
|
};
|
|
60
60
|
}
|
|
@@ -89,18 +89,15 @@ export async function command(ctx, id, input, scope) {
|
|
|
89
89
|
const { store } = ctx;
|
|
90
90
|
const cascadeCancelIds = [];
|
|
91
91
|
const response = await store.tx(async (t) => {
|
|
92
|
-
const s = await lockedSession(t, id,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
else if (scope.kind === "executor")
|
|
103
|
-
fail(403, "Application credential required");
|
|
92
|
+
const s = await lockedSession(t, id, accessOf(scope));
|
|
93
|
+
const command = input;
|
|
94
|
+
// A person's token may not replace the instructions of a turn.
|
|
95
|
+
if (scope.kind === "token" &&
|
|
96
|
+
command.type === "message" &&
|
|
97
|
+
command.manifest !== undefined)
|
|
98
|
+
fail(403, "A subject token cannot send message.manifest", {
|
|
99
|
+
code: "scope_required",
|
|
100
|
+
});
|
|
104
101
|
const key = commandKey(id, command.idempotencyKey);
|
|
105
102
|
const existing = await t.get("commands", key);
|
|
106
103
|
if (existing) {
|
|
@@ -111,85 +108,7 @@ export async function command(ctx, id, input, scope) {
|
|
|
111
108
|
let event;
|
|
112
109
|
// The turn a `cancel` ended, if one was active.
|
|
113
110
|
let cancelledTurnId = null;
|
|
114
|
-
if (command.type === "
|
|
115
|
-
const action = (await t.get("actions", command.actionId));
|
|
116
|
-
const prior = await t.get("effects", command.actionId);
|
|
117
|
-
if (action.status === "completed") {
|
|
118
|
-
if (action.claimId !== command.claimId ||
|
|
119
|
-
action.generation !== command.generation ||
|
|
120
|
-
canonical(prior.outcome) !== canonical(command.outcome))
|
|
121
|
-
fail(409, "Conflicting action result");
|
|
122
|
-
await t.put("commands", key, { command, response: prior.receipt });
|
|
123
|
-
return prior.receipt;
|
|
124
|
-
}
|
|
125
|
-
if (s.status === "cancelled" ||
|
|
126
|
-
s.activeTurnId !== action.turnId ||
|
|
127
|
-
action.status !== "claimed" ||
|
|
128
|
-
action.claimId !== command.claimId ||
|
|
129
|
-
action.generation !== command.generation ||
|
|
130
|
-
Date.parse(action.leaseExpiresAt) <= Date.now())
|
|
131
|
-
fail(409, "Stale, expired, or cancelled claim");
|
|
132
|
-
action.status = "completed";
|
|
133
|
-
await t.put("actions", action.actionId, action);
|
|
134
|
-
prior.status = "completed";
|
|
135
|
-
prior.outcome = command.outcome;
|
|
136
|
-
s.status = "runnable";
|
|
137
|
-
const resultWake = {
|
|
138
|
-
reason: "action_result",
|
|
139
|
-
dedupeKey: `action_result:${action.turnId}:${action.actionId}:${action.generation}`,
|
|
140
|
-
};
|
|
141
|
-
t.afterCommit(() => ctx.wake(id, resultWake));
|
|
142
|
-
event = await t.event(id, s.activeTurnId, "action.completed", {
|
|
143
|
-
actionId: action.actionId,
|
|
144
|
-
...actionTarget(action),
|
|
145
|
-
kind: action.kind,
|
|
146
|
-
...(action.kind === "tool" ? toolIds(action.context) : {}),
|
|
147
|
-
result: command.outcome.value,
|
|
148
|
-
});
|
|
149
|
-
if (action.kind === "verify") {
|
|
150
|
-
const verdict = command.outcome.value;
|
|
151
|
-
if (verdict?.kind !== "failed") {
|
|
152
|
-
await t.event(id, s.activeTurnId, "loop.verified", {
|
|
153
|
-
path: String(action.context?.loopPath ?? action.path ?? ""),
|
|
154
|
-
n: Number(action.context?.n ?? 1),
|
|
155
|
-
pass: Boolean(verdict?.pass),
|
|
156
|
-
...(verdict?.feedback !== undefined
|
|
157
|
-
? { feedback: verdict.feedback }
|
|
158
|
-
: {}),
|
|
159
|
-
...(verdict?.data !== undefined ? { data: verdict.data } : {}),
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
else if (action.kind === "fn" && action.context?.role === "decide") {
|
|
164
|
-
const decision = command.outcome.value;
|
|
165
|
-
await t.event(id, s.activeTurnId, "loop.decided", {
|
|
166
|
-
path: String(action.context?.loopPath ?? action.path ?? ""),
|
|
167
|
-
n: Number(action.context?.n ?? 1),
|
|
168
|
-
next: "output" in decision && !("input" in decision)
|
|
169
|
-
? "output"
|
|
170
|
-
: "input",
|
|
171
|
-
patched: Boolean(decision.agent),
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
const receipt = {
|
|
175
|
-
status: "accepted",
|
|
176
|
-
turnId: s.activeTurnId,
|
|
177
|
-
cursor: event.cursor,
|
|
178
|
-
requestId: command.requestId,
|
|
179
|
-
};
|
|
180
|
-
prior.receipt = receipt;
|
|
181
|
-
await t.put("effects", action.actionId, prior);
|
|
182
|
-
if (isWorkflowManifest(s.manifest) && s.activeTurnId) {
|
|
183
|
-
await wakeForQueuedEffects({
|
|
184
|
-
t,
|
|
185
|
-
workflowSessionId: id,
|
|
186
|
-
turnId: s.activeTurnId,
|
|
187
|
-
limits: ctx.flowLimits,
|
|
188
|
-
schedule: ctx.wake,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
else if (command.type === "cancel") {
|
|
111
|
+
if (command.type === "cancel") {
|
|
193
112
|
cancelledTurnId = s.activeTurnId;
|
|
194
113
|
const workflowCancel = isWorkflowManifest(s.manifest);
|
|
195
114
|
// Linked sessions this one started: a workflow's agents, or flow agents an agent
|
|
@@ -210,10 +129,10 @@ export async function command(ctx, id, input, scope) {
|
|
|
210
129
|
else if (cancelledTurnId !== null) {
|
|
211
130
|
for (const a of await t.actionsForSession(id, {
|
|
212
131
|
turnId: cancelledTurnId,
|
|
213
|
-
statuses: ["pending", "
|
|
132
|
+
statuses: ["pending", "delivering"],
|
|
214
133
|
})) {
|
|
215
|
-
//
|
|
216
|
-
a.status = a.status === "
|
|
134
|
+
// Delivered work may already have an external effect. Preserve it for reconciliation.
|
|
135
|
+
a.status = a.status === "pending" ? "cancelled" : "uncertain";
|
|
217
136
|
await t.put("actions", a.actionId, a);
|
|
218
137
|
const effect = await t.get("effects", a.actionId);
|
|
219
138
|
if (effect) {
|
|
@@ -232,8 +151,9 @@ export async function command(ctx, id, input, scope) {
|
|
|
232
151
|
event = await t.event(id, cancelledTurnId, "turn.cancelled", {
|
|
233
152
|
reason: command.reason,
|
|
234
153
|
});
|
|
154
|
+
await slimModelEffects(t, id, cancelledTurnId);
|
|
235
155
|
// The process running the advance aborts it on `session.cancel` (tenant/control).
|
|
236
|
-
t.afterCommit(() => signalSessionCancel(ctx, id));
|
|
156
|
+
t.afterCommit(() => signalSessionCancel(ctx, id, cancelledTurnId));
|
|
237
157
|
s.activeTurnId = null;
|
|
238
158
|
if (cancelledTurnId !== null)
|
|
239
159
|
s.lastTurnId = cancelledTurnId;
|
|
@@ -258,7 +178,13 @@ export async function command(ctx, id, input, scope) {
|
|
|
258
178
|
if (command.type === "message") {
|
|
259
179
|
if (!["idle", "completed", "failed", "cancelled"].includes(s.status))
|
|
260
180
|
fail(409, "Session has active or unresolved work");
|
|
181
|
+
// After the replay check above, so a retried message is never charged twice.
|
|
182
|
+
if (scope.kind === "token" && scope.limits)
|
|
183
|
+
await chargeTurn(t, scope.subject, scope.limits);
|
|
261
184
|
s.turnStartState = s.state;
|
|
185
|
+
// A new turn folds from the latest snapshot: a cancel or failure only reverts this turn.
|
|
186
|
+
if (s.history?.snapshot !== undefined)
|
|
187
|
+
s.history = { ...s.history, from: s.history.snapshot };
|
|
262
188
|
s.activeTurnId = randomUUID();
|
|
263
189
|
if (isWorkflowManifest(s.manifest)) {
|
|
264
190
|
const input = "content" in command ? command.content : command.data;
|
|
@@ -340,7 +266,6 @@ export async function command(ctx, id, input, scope) {
|
|
|
340
266
|
segment: (s.checkpoint?.segment ?? 0) + 1,
|
|
341
267
|
});
|
|
342
268
|
}
|
|
343
|
-
await t.put("checkpoints", JSON.stringify([id, s.activeTurnId, s.checkpoint.segment]), { checkpoint: s.checkpoint, status: "runnable" });
|
|
344
269
|
s.status = "runnable";
|
|
345
270
|
s.waits = undefined;
|
|
346
271
|
const commandWake = {
|
|
@@ -389,3 +314,74 @@ export async function command(ctx, id, input, scope) {
|
|
|
389
314
|
}
|
|
390
315
|
return response;
|
|
391
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* Completes a delivered Action with `outcome`, in the caller's transaction, which
|
|
319
|
+
* holds the lock of the Action's session `s`: the Action and its effect, the
|
|
320
|
+
* `action.completed` event (and `loop.verified` or `loop.decided`), the wake that resumes the
|
|
321
|
+
* turn, and queued workflow effects. The Action deliverer and the background-result callback
|
|
322
|
+
* both record outcomes here. It sets `s.status`; the caller writes `s`.
|
|
323
|
+
*/
|
|
324
|
+
export async function recordActionOutcome(t, ctx, s, action, received, options = {}) {
|
|
325
|
+
const outcome = acceptedOutcome(action, received);
|
|
326
|
+
const prior = await t.get("effects", action.actionId);
|
|
327
|
+
action.status = "completed";
|
|
328
|
+
await t.put("actions", action.actionId, action);
|
|
329
|
+
prior.status = "completed";
|
|
330
|
+
prior.outcome = outcome;
|
|
331
|
+
s.status = "runnable";
|
|
332
|
+
const resultWake = {
|
|
333
|
+
reason: "action_result",
|
|
334
|
+
dedupeKey: `action_result:${action.turnId}:${action.actionId}:${action.generation}`,
|
|
335
|
+
};
|
|
336
|
+
t.afterCommit(() => ctx.wake(s.id, resultWake));
|
|
337
|
+
const event = await t.event(s.id, s.activeTurnId, "action.completed", {
|
|
338
|
+
actionId: action.actionId,
|
|
339
|
+
...actionTarget(action),
|
|
340
|
+
kind: action.kind,
|
|
341
|
+
...(action.kind === "tool" ? toolIds(action.context) : {}),
|
|
342
|
+
result: outcome.value,
|
|
343
|
+
});
|
|
344
|
+
if (action.kind === "verify") {
|
|
345
|
+
const verdict = outcome.value;
|
|
346
|
+
if (verdict?.kind !== "failed") {
|
|
347
|
+
await t.event(s.id, s.activeTurnId, "loop.verified", {
|
|
348
|
+
path: String(action.context?.loopPath ?? action.path ?? ""),
|
|
349
|
+
n: Number(action.context?.n ?? 1),
|
|
350
|
+
pass: Boolean(verdict?.pass),
|
|
351
|
+
...(verdict?.feedback !== undefined
|
|
352
|
+
? { feedback: verdict.feedback }
|
|
353
|
+
: {}),
|
|
354
|
+
...(verdict?.data !== undefined ? { data: verdict.data } : {}),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
else if (action.kind === "fn" && action.context?.role === "decide") {
|
|
359
|
+
const decision = outcome.value;
|
|
360
|
+
await t.event(s.id, s.activeTurnId, "loop.decided", {
|
|
361
|
+
path: String(action.context?.loopPath ?? action.path ?? ""),
|
|
362
|
+
n: Number(action.context?.n ?? 1),
|
|
363
|
+
next: "output" in decision && !("input" in decision)
|
|
364
|
+
? "output"
|
|
365
|
+
: "input",
|
|
366
|
+
patched: Boolean(decision.agent),
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
const receipt = {
|
|
370
|
+
status: "accepted",
|
|
371
|
+
turnId: s.activeTurnId,
|
|
372
|
+
cursor: event.cursor,
|
|
373
|
+
...(options.requestId === undefined ? {} : { requestId: options.requestId }),
|
|
374
|
+
};
|
|
375
|
+
prior.receipt = receipt;
|
|
376
|
+
await t.put("effects", action.actionId, prior);
|
|
377
|
+
if (isWorkflowManifest(s.manifest) && s.activeTurnId) {
|
|
378
|
+
await wakeForQueuedEffects({
|
|
379
|
+
t,
|
|
380
|
+
workflowSessionId: s.id,
|
|
381
|
+
turnId: s.activeTurnId,
|
|
382
|
+
limits: ctx.flowLimits,
|
|
383
|
+
schedule: ctx.wake,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return { event, receipt };
|
|
387
|
+
}
|
package/dist/tenant/context.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Business code changes state only inside `ctx.store.tx(async (t) => …)` and follows the
|
|
7
7
|
* seam rules: events through `t.event(...)` (the relay appends them to Durable Streams after
|
|
8
|
-
* commit),
|
|
8
|
+
* commit), deliveries through `t.afterCommit(() => ctx.deliver(actionId))`, and advances through
|
|
9
9
|
* `t.afterCommit(() => ctx.wake(id, { reason, dedupeKey }))`. It never publishes or notifies
|
|
10
10
|
* itself; `runtime.ts` wires the streams with `wireStreams()`. No external I/O runs inside a
|
|
11
11
|
* tx.
|
|
@@ -13,22 +13,26 @@
|
|
|
13
13
|
* `wake` goes to `DurableExecution.wake`, which calls the Tenant's `advance` (`advance.ts`)
|
|
14
14
|
* under ownership (§10.6); `abortLocal` aborts an advance running on this process.
|
|
15
15
|
*/
|
|
16
|
-
import type {
|
|
16
|
+
import type { SessionHistory } from "./history.js";
|
|
17
|
+
import type { RoleLimits, SubjectScope, TenantEnvelope } from "@nylorun/core/contracts";
|
|
17
18
|
import type { DurableCheckpoint, FlowCheckpoint } from "@nylorun/harness/run";
|
|
18
|
-
import type { AgentManifest, JsonValue } from "@nylorun/core/define";
|
|
19
|
+
import type { AgentManifest, JsonValue, SandboxManifest } from "@nylorun/core/define";
|
|
19
20
|
import type { CredentialSelection } from "@nylorun/core/contracts";
|
|
20
21
|
import type { SessionStore, Tx } from "../store/types.js";
|
|
21
|
-
import type { ExecutorRecord, ExecutorRegistry } from "../core/executors.js";
|
|
22
22
|
import type { FlowLimits } from "../core/limits.js";
|
|
23
23
|
import type { ModelProvider } from "../core/provider.js";
|
|
24
|
+
import type { ModelGate } from "../gates/model-gate.js";
|
|
25
|
+
import type { ToolGate } from "../gates/tool-gate.js";
|
|
26
|
+
import type { Keys } from "../keys/keys.js";
|
|
24
27
|
import type { VaultService } from "../vault/service.js";
|
|
25
28
|
import type { McpPool } from "../mcp/pool.js";
|
|
26
29
|
import type { McpDiagnostic, McpSnapshot } from "../mcp/snapshot.js";
|
|
27
30
|
import type { SandboxManager } from "../sandbox/manager.js";
|
|
28
31
|
import type { TenantConfig } from "./types.js";
|
|
29
|
-
import type {
|
|
32
|
+
import type { SessionStreams } from "./session-streams.js";
|
|
30
33
|
import type { StuckInvocation, Wake } from "../execution/types.js";
|
|
31
34
|
import type { WorkState } from "./scheduler.js";
|
|
35
|
+
import type { SigningKeys } from "./signing-keys.js";
|
|
32
36
|
export interface Session {
|
|
33
37
|
id: string;
|
|
34
38
|
agentId: string;
|
|
@@ -43,8 +47,11 @@ export interface Session {
|
|
|
43
47
|
status: string;
|
|
44
48
|
activeTurnId: string | null;
|
|
45
49
|
checkpoint?: DurableCheckpoint | FlowCheckpoint;
|
|
50
|
+
/** The engine state; its transcript is folded from the record (`history.ts`), not stored. */
|
|
46
51
|
state?: any;
|
|
47
52
|
turnStartState?: any;
|
|
53
|
+
/** Where the transcript's fold starts in the session's record (`history.ts`). */
|
|
54
|
+
history?: SessionHistory;
|
|
48
55
|
waits?: unknown;
|
|
49
56
|
error?: string;
|
|
50
57
|
/** Last completed turn output (for linked agent → workflow settle). */
|
|
@@ -64,45 +71,102 @@ export interface Session {
|
|
|
64
71
|
/** Session id that keys the shared sandbox; absent means this session owns it. */
|
|
65
72
|
sandboxOwnerId?: string;
|
|
66
73
|
/**
|
|
67
|
-
* The
|
|
68
|
-
*
|
|
74
|
+
* The sandbox chosen when the session was opened, resolved against the Tenant's limits, or
|
|
75
|
+
* inherited from the session it shares with. Absent when the session has no sandbox or its
|
|
76
|
+
* definition declares one (`.sandbox()`).
|
|
69
77
|
*/
|
|
70
|
-
|
|
78
|
+
sandbox?: SandboxManifest;
|
|
79
|
+
/** Where `sandbox` came from. */
|
|
80
|
+
sandboxSource?: "default" | "inline" | "shared";
|
|
71
81
|
}
|
|
72
82
|
export type AuthScope = {
|
|
73
83
|
kind: "application";
|
|
74
84
|
principalId: string;
|
|
75
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* A subject token (Host feature `subject-tokens`): one subject, the role's scopes and agents
|
|
88
|
+
* narrowed by the token, until `expiresAt` (ms) or the subject's epoch moves past `epoch`.
|
|
89
|
+
*/
|
|
90
|
+
| {
|
|
91
|
+
kind: "token";
|
|
92
|
+
subject: string;
|
|
93
|
+
scopes: ReadonlySet<SubjectScope>;
|
|
94
|
+
agents: ReadonlySet<string> | "*";
|
|
95
|
+
role: string;
|
|
96
|
+
limits?: RoleLimits;
|
|
97
|
+
epoch: number;
|
|
98
|
+
expiresAt: number;
|
|
99
|
+
tokenId: string;
|
|
100
|
+
keyId: string;
|
|
101
|
+
}
|
|
76
102
|
/** An application principal acting for `subject` (`Nylorun-Subject`), narrowed to `scopes`. */
|
|
77
103
|
| {
|
|
78
104
|
kind: "subject";
|
|
79
105
|
principalId: string;
|
|
80
106
|
subject: string;
|
|
81
107
|
scopes: ReadonlySet<SubjectScope>;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
108
|
+
}
|
|
109
|
+
/** No credential, on a route that serves public data (`RouteAccess.anonymous`). */
|
|
110
|
+
| {
|
|
111
|
+
kind: "anonymous";
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* A delivery token (Action endpoints): the Runtime's own token for one delivery of one
|
|
115
|
+
* Action, presented back by the Action endpoint on that Action's callbacks only.
|
|
116
|
+
*/
|
|
117
|
+
| {
|
|
118
|
+
kind: "delivery";
|
|
119
|
+
actionId: string;
|
|
120
|
+
agentId: string;
|
|
121
|
+
generation: number;
|
|
122
|
+
expiresAt: number;
|
|
123
|
+
tokenId: string;
|
|
124
|
+
keyId: string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* A publishable key with no bearer (Host feature `browser-access`): what the policy grants
|
|
128
|
+
* `anon`, at most `agents:read`. It owns no session or vault.
|
|
129
|
+
*/
|
|
130
|
+
| {
|
|
131
|
+
kind: "publishable";
|
|
132
|
+
keyId: string;
|
|
133
|
+
scopes: ReadonlySet<SubjectScope>;
|
|
134
|
+
agents: ReadonlySet<string> | "*";
|
|
85
135
|
};
|
|
86
136
|
export interface TenantContext {
|
|
87
137
|
readonly config: TenantConfig;
|
|
88
138
|
readonly envelope: TenantEnvelope;
|
|
89
139
|
readonly store: SessionStore;
|
|
90
140
|
readonly vault: VaultService;
|
|
91
|
-
readonly registry: ExecutorRegistry;
|
|
92
141
|
readonly mcp: McpPool;
|
|
93
142
|
readonly sandbox: SandboxManager;
|
|
94
143
|
readonly flowLimits: FlowLimits;
|
|
95
144
|
readonly modelProvider: ModelProvider;
|
|
96
|
-
/** True when the model comes from the Tenant vault selection
|
|
145
|
+
/** True when the model comes from the Tenant vault selection, served by `modelGate`. */
|
|
97
146
|
readonly useVaultModel: boolean;
|
|
147
|
+
/** Serves vault-backed model calls (blueprint §15): in this process, or the gates service. */
|
|
148
|
+
readonly modelGate: ModelGate;
|
|
149
|
+
/**
|
|
150
|
+
* Serves remote MCP servers and Action deliveries (blueprint §12, F4.1): in this process, or
|
|
151
|
+
* the gates service.
|
|
152
|
+
*/
|
|
153
|
+
readonly toolGate: ToolGate;
|
|
154
|
+
/**
|
|
155
|
+
* Vault writes that touch a secret, and all token signing (blueprint §14, F4.2): in this
|
|
156
|
+
* process, or the gateway's keys service. Call it instead of `vault`'s sealing methods and
|
|
157
|
+
* `signingKeys`' private key.
|
|
158
|
+
*/
|
|
159
|
+
readonly keys: Keys;
|
|
98
160
|
/** Set by drain/close; reset clears it again. Stops new advances. */
|
|
99
161
|
closing: boolean;
|
|
100
162
|
/** Set once close has finished releasing resources. */
|
|
101
163
|
closed: boolean;
|
|
102
164
|
/** Advances running on this process, for `abortLocal`, drain and close. */
|
|
103
165
|
readonly work: WorkState;
|
|
104
|
-
/** Live delivery over Durable Streams:
|
|
105
|
-
readonly
|
|
166
|
+
/** Live delivery over Durable Streams: one `SessionStream` per observed session, and the streams wiring. */
|
|
167
|
+
readonly sessionStreams: SessionStreams;
|
|
168
|
+
/** The Tenant's signing keys for subject tokens. */
|
|
169
|
+
readonly signingKeys: SigningKeys;
|
|
106
170
|
/** The Worker id this process writes as session `owner` (§10.6). */
|
|
107
171
|
readonly workerId: string;
|
|
108
172
|
/** How long an advance's ownership lease lasts; the heartbeat renews it. */
|
|
@@ -113,19 +177,35 @@ export interface TenantContext {
|
|
|
113
177
|
* re-wakes anything left runnable.
|
|
114
178
|
*/
|
|
115
179
|
wake(sessionId: string, wake: Wake): Promise<void>;
|
|
116
|
-
/**
|
|
117
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Seam: abort the advance of a session running in this process, if any. With `turnId`, only
|
|
182
|
+
* an advance of that turn (a cancel signal names the turn it cancelled).
|
|
183
|
+
*/
|
|
184
|
+
abortLocal(sessionId: string, turnId?: string): void;
|
|
185
|
+
/**
|
|
186
|
+
* Seam: deliver an Action to its agent's Action endpoint (`DurableExecution.deliver`). Call it
|
|
187
|
+
* from `t.afterCommit`. Dropped while the Tenant is closing; the sweep re-sends it.
|
|
188
|
+
*/
|
|
189
|
+
deliver(actionId: string): Promise<void>;
|
|
118
190
|
/** This Tenant's execution invocations that need an operator, for Tenant status. */
|
|
119
191
|
readonly stuckInvocations?: () => Promise<StuckInvocation[]>;
|
|
120
192
|
/** Callbacks the Tenant sweep runs after its own steps (`sweep.ts`). */
|
|
121
193
|
readonly sweepHooks: ReadonlySet<() => Promise<void>>;
|
|
122
|
-
/** Adds a sweep callback
|
|
194
|
+
/** Adds a sweep callback. Returns a function that removes it. */
|
|
123
195
|
onSweep(hook: () => Promise<void>): () => void;
|
|
124
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* What a request acting for a person may reach: that person's sessions and vaults, and, for a
|
|
199
|
+
* subject token, only sessions of the agents its role allows. Undefined means the whole Tenant.
|
|
200
|
+
*/
|
|
201
|
+
export interface SessionAccess {
|
|
202
|
+
readonly owner: string;
|
|
203
|
+
readonly agents?: ReadonlySet<string>;
|
|
204
|
+
}
|
|
125
205
|
/** The session, or a 404. Reads without locking. */
|
|
126
|
-
export declare function sessionOf(t: Tx, id: string,
|
|
206
|
+
export declare function sessionOf(t: Tx, id: string, access?: SessionAccess): Promise<Session>;
|
|
127
207
|
/** Locks the session for the rest of the transaction and returns it, or a 404. */
|
|
128
|
-
export declare function lockedSession(t: Tx, id: string,
|
|
208
|
+
export declare function lockedSession(t: Tx, id: string, access?: SessionAccess): Promise<Session>;
|
|
129
209
|
/** An advance's hold on its session (§10.6): every write the advance makes presents `epoch`. */
|
|
130
210
|
export interface Lease {
|
|
131
211
|
readonly sessionId: string;
|
|
@@ -139,7 +219,7 @@ export interface Lease {
|
|
|
139
219
|
*/
|
|
140
220
|
export declare function ownedSession(t: Tx, lease: Lease, id: string): Promise<Session>;
|
|
141
221
|
/** The session read in its own transaction, or a 404. */
|
|
142
|
-
export declare function loadSession(ctx: TenantContext, id: string,
|
|
222
|
+
export declare function loadSession(ctx: TenantContext, id: string, access?: SessionAccess): Promise<Session>;
|
|
143
223
|
/**
|
|
144
224
|
* A synchronous lookup over `start` and every session its `sandboxOwnerId` chain names, for
|
|
145
225
|
* the sandbox-sharing helpers, which walk that chain synchronously.
|
package/dist/tenant/context.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import { fail } from "./http.js";
|
|
2
2
|
/**
|
|
3
|
-
* `
|
|
4
|
-
* 404 as a missing
|
|
3
|
+
* `access`, when set, limits the request to the owner's sessions of the allowed agents:
|
|
4
|
+
* anything else is the same 404 as a missing session, so a subject cannot learn which session
|
|
5
|
+
* ids exist.
|
|
5
6
|
*/
|
|
6
|
-
function owned(session,
|
|
7
|
-
if (!session ||
|
|
7
|
+
function owned(session, access) {
|
|
8
|
+
if (!session ||
|
|
9
|
+
(access !== undefined &&
|
|
10
|
+
(session.ownerUserId !== access.owner ||
|
|
11
|
+
(access.agents !== undefined && !access.agents.has(session.agentId)))))
|
|
8
12
|
return fail(404, "Session not found");
|
|
9
13
|
return session;
|
|
10
14
|
}
|
|
11
15
|
/** The session, or a 404. Reads without locking. */
|
|
12
|
-
export async function sessionOf(t, id,
|
|
13
|
-
return owned(await t.get("sessions", id),
|
|
16
|
+
export async function sessionOf(t, id, access) {
|
|
17
|
+
return owned(await t.get("sessions", id), access);
|
|
14
18
|
}
|
|
15
19
|
/** Locks the session for the rest of the transaction and returns it, or a 404. */
|
|
16
|
-
export async function lockedSession(t, id,
|
|
17
|
-
return owned(await t.lockSession(id),
|
|
20
|
+
export async function lockedSession(t, id, access) {
|
|
21
|
+
return owned(await t.lockSession(id), access);
|
|
18
22
|
}
|
|
19
23
|
/**
|
|
20
24
|
* Locks `id` for the advance holding `lease`. The advance's own session is epoch-checked: a
|
|
@@ -27,8 +31,8 @@ export async function ownedSession(t, lease, id) {
|
|
|
27
31
|
return lockedSession(t, id);
|
|
28
32
|
}
|
|
29
33
|
/** The session read in its own transaction, or a 404. */
|
|
30
|
-
export function loadSession(ctx, id,
|
|
31
|
-
return ctx.store.tx((t) => sessionOf(t, id,
|
|
34
|
+
export function loadSession(ctx, id, access) {
|
|
35
|
+
return ctx.store.tx((t) => sessionOf(t, id, access));
|
|
32
36
|
}
|
|
33
37
|
/**
|
|
34
38
|
* A synchronous lookup over `start` and every session its `sandboxOwnerId` chain names, for
|