@sema-agent/server 7.137.0 → 7.139.0
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/MIGRATION.md +600 -142
- package/README.md +7 -3
- package/README.zh-CN.md +6 -3
- package/USAGE.md +132 -41
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/lib/lanes.json +3 -2
- package/dist/adoption/wire.d.ts +3 -3
- package/dist/approval-card.d.ts +23 -16
- package/dist/approval-card.js +1 -0
- package/dist/audit.js +1 -0
- package/dist/background-agent-name.d.ts +5 -0
- package/dist/background-agent-name.js +5 -0
- package/dist/bench/s1/arms.d.ts +1 -1
- package/dist/bench/s1/arms.js +1 -3
- package/dist/bench/s1/run-firm.js +2 -2
- package/dist/boot/budget-tracing.d.ts +1 -12
- package/dist/boot/deployment-mods.d.ts +46 -16
- package/dist/boot/deployment-mods.js +82 -6
- package/dist/boot/device-lane.d.ts +8 -3
- package/dist/boot/device-lane.js +7 -2
- package/dist/boot/execution-env.d.ts +4 -2
- package/dist/boot/execution-env.js +16 -24
- package/dist/boot/execution-lane.d.ts +1 -1
- package/dist/boot/governance-seams.d.ts +12 -18
- package/dist/boot/governance-seams.js +10 -24
- package/dist/boot/leader.d.ts +3 -0
- package/dist/boot/leader.js +1 -0
- package/dist/boot/memory-consolidation.js +2 -2
- package/dist/boot/org-delivery.js +15 -2
- package/dist/boot/resolve-spec.d.ts +4 -1
- package/dist/boot/resolve-spec.js +29 -19
- package/dist/boot/retention-lane.d.ts +14 -8
- package/dist/boot/retention-lane.js +2 -8
- package/dist/boot/runner-deps.d.ts +10 -4
- package/dist/boot/runner-deps.js +2 -0
- package/dist/boot/session-faces.d.ts +0 -3
- package/dist/boot/session-faces.js +15 -8
- package/dist/boot/stage-02-web-admin-pull.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -12
- package/dist/boot/stage-05-execution-env.d.ts +3 -12
- package/dist/boot/stage-05-execution-env.js +8 -5
- package/dist/boot/stage-06-runners.d.ts +3 -12
- package/dist/boot/stage-06-runners.js +5 -3
- package/dist/boot/stage-07-capability-layer.d.ts +3 -12
- package/dist/boot/stage-07-capability-layer.js +2 -2
- package/dist/boot/stage-08-reapers.d.ts +3 -12
- package/dist/boot/stage-09-leader.d.ts +3 -12
- package/dist/boot/stage-09-leader.js +2 -3
- package/dist/boot/stage-10-http-server.d.ts +3 -12
- package/dist/boot/stage-10-http-server.js +5 -3
- package/dist/boot/stores.js +4 -4
- package/dist/boot/web-admin.d.ts +5 -2
- package/dist/boot/web-admin.js +5 -5
- package/dist/brain.js +5 -5
- package/dist/budget.d.ts +17 -15
- package/dist/budget.js +2 -2
- package/dist/capabilities/mcp-probe.d.ts +36 -11
- package/dist/capabilities/mcp-probe.js +44 -5
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +21 -7
- package/dist/capability-facts.js +13 -3
- package/dist/checkpoint-identity.d.ts +13 -6
- package/dist/config-catalog.js +14 -8
- package/dist/config-invariants.d.ts +23 -3
- package/dist/config-invariants.js +42 -0
- package/dist/config-provider.d.ts +19 -24
- package/dist/config-provider.js +27 -12
- package/dist/config-types.d.ts +25 -12
- package/dist/config.d.ts +33 -0
- package/dist/config.js +52 -20
- package/dist/delegated-session-owner.d.ts +2 -2
- package/dist/deployment-shape.d.ts +7 -7
- package/dist/device-enrollment.d.ts +41 -2
- package/dist/device-enrollment.js +27 -12
- package/dist/device-store.d.ts +58 -85
- package/dist/device-store.js +19 -49
- package/dist/device-ws-hub.js +2 -1
- package/dist/env-name-allowlist-knobs.d.ts +5 -4
- package/dist/exec-cut.d.ts +60 -0
- package/dist/exec-cut.js +36 -0
- package/dist/execution-lane-caps.d.ts +19 -0
- package/dist/execution-lane-caps.js +10 -0
- package/dist/execution-placement.d.ts +259 -0
- package/dist/execution-placement.js +199 -0
- package/dist/field-conflict.d.ts +3 -2
- package/dist/field-conflict.js +1 -0
- package/dist/gate-settlement.d.ts +71 -0
- package/dist/gate-settlement.js +40 -0
- package/dist/hooks/classic-stations.d.ts +1 -0
- package/dist/hooks/classic-stations.js +1 -0
- package/dist/hooks/command-spawn.d.ts +2 -2
- package/dist/hooks/hook-runner.js +14 -14
- package/dist/hosted-posture.d.ts +2 -2
- package/dist/http/admission.js +10 -19
- package/dist/http/checkpoint-ctx.d.ts +21 -12
- package/dist/http/checkpoint-ctx.js +4 -2
- package/dist/http/claimed-run.d.ts +1 -1
- package/dist/http/dispatch.d.ts +4 -0
- package/dist/http/dispatch.js +3 -0
- package/dist/http/durable-run-leg.js +12 -6
- package/dist/http/execution-context.d.ts +218 -0
- package/dist/http/execution-context.js +330 -0
- package/dist/http/memory-compliance-failure-reply.js +3 -0
- package/dist/http/resume-legs.d.ts +5 -11
- package/dist/http/resume-legs.js +179 -104
- package/dist/http/route-ctx.d.ts +44 -59
- package/dist/http/route-ctx.js +2 -5
- package/dist/http/routes/a2a-serve.js +10 -4
- package/dist/http/routes/agents-roster.d.ts +1 -1
- package/dist/http/routes/agents-roster.js +5 -1
- package/dist/http/routes/approvals-assistant.js +6 -6
- package/dist/http/routes/background.d.ts +29 -1
- package/dist/http/routes/background.js +29 -2
- package/dist/http/routes/capabilities.js +12 -10
- package/dist/http/routes/devices.d.ts +13 -19
- package/dist/http/routes/devices.js +21 -26
- package/dist/http/routes/mcp-reconnect.d.ts +3 -2
- package/dist/http/routes/mcp-reconnect.js +7 -5
- package/dist/http/routes/memory-consolidation.d.ts +1 -1
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +3 -3
- package/dist/http/routes/runs.js +11 -7
- package/dist/http/routes/session-sync.js +110 -15
- package/dist/http/routes/sessions.d.ts +6 -0
- package/dist/http/routes/sessions.js +119 -13
- package/dist/http/routes/side-query.d.ts +12 -4
- package/dist/http/routes/side-query.js +6 -8
- package/dist/http/routes/tasks.js +800 -782
- package/dist/http/server.d.ts +61 -39
- package/dist/http/server.js +11 -0
- package/dist/http/session-inherit-env.d.ts +3 -4
- package/dist/http/session-inherit-env.js +1 -1
- package/dist/http/wire-types.d.ts +22 -18
- package/dist/identity-source.d.ts +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/leader/wire.d.ts +7 -0
- package/dist/leader/wire.js +5 -4
- package/dist/mcp-assertion-header.d.ts +51 -40
- package/dist/mcp-assertion-header.js +10 -11
- package/dist/mcp-authorization-face.d.ts +27 -0
- package/dist/mcp-authorization-face.js +15 -0
- package/dist/memory-operator-faces.d.ts +13 -4
- package/dist/memory-operator-faces.js +1 -17
- package/dist/memory-sync-client.d.ts +53 -14
- package/dist/memory-sync-client.js +187 -92
- package/dist/memory-sync.d.ts +1 -1
- package/dist/memory-sync.js +3 -2
- package/dist/model-route-gap.d.ts +6 -22
- package/dist/model-route-gap.js +7 -5
- package/dist/mods-declaration.d.ts +110 -0
- package/dist/mods-declaration.js +109 -0
- package/dist/observability/cost-taxonomy.d.ts +2 -2
- package/dist/observability/err-text.d.ts +5 -0
- package/dist/observability/err-text.js +9 -0
- package/dist/observability/fail-open.d.ts +17 -1
- package/dist/observability/fail-open.js +17 -1
- package/dist/observability/run-terminal.js +0 -1
- package/dist/observability/secret-env-scrub.d.ts +1 -1
- package/dist/observability/secret-env-scrub.js +1 -1
- package/dist/orchestration/workflow-notify-journal.js +4 -4
- package/dist/org-memory-admission.d.ts +4 -3
- package/dist/org-memory-admission.js +6 -6
- package/dist/parked-decide.d.ts +49 -11
- package/dist/parked-decide.js +25 -26
- package/dist/parked-revive-surface.d.ts +12 -15
- package/dist/parked-revive-surface.js +3 -6
- package/dist/peer-seat.d.ts +156 -0
- package/dist/peer-seat.js +273 -0
- package/dist/permission-rule-vocab.d.ts +7 -7
- package/dist/permission-rule-vocab.js +3 -3
- package/dist/plain-string-record.d.ts +38 -0
- package/dist/plain-string-record.js +38 -0
- package/dist/plugins/approval-ask-store-file.d.ts +7 -0
- package/dist/plugins/approval-ask-store-file.js +11 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
- package/dist/plugins/approval-ask-store-memory.js +12 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
- package/dist/plugins/approval-ask-store-sql.js +30 -12
- package/dist/plugins/background-agent-store-sql.js +6 -3
- package/dist/plugins/caching-session-store.d.ts +11 -2
- package/dist/plugins/caching-session-store.js +15 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +21 -25
- package/dist/plugins/checkpoint-store-sql.js +13 -15
- package/dist/plugins/device-store-sql.d.ts +25 -51
- package/dist/plugins/device-store-sql.js +49 -103
- package/dist/plugins/execution-placement-sql.d.ts +151 -0
- package/dist/plugins/execution-placement-sql.js +223 -0
- package/dist/plugins/file-history-store-sql.d.ts +7 -6
- package/dist/plugins/file-history-store-sql.js +8 -7
- package/dist/plugins/file-run-store.d.ts +6 -1
- package/dist/plugins/file-run-store.js +4 -0
- package/dist/plugins/fork-routing-session-store.d.ts +6 -0
- package/dist/plugins/fork-routing-session-store.js +16 -0
- package/dist/plugins/k8s-pod-reaper.d.ts +2 -8
- package/dist/plugins/k8s-pod-reaper.js +3 -3
- package/dist/plugins/local-checkpoint-store.d.ts +1 -11
- package/dist/plugins/local-checkpoint-store.js +0 -1
- package/dist/plugins/local-session-store.d.ts +183 -45
- package/dist/plugins/local-session-store.js +412 -105
- package/dist/plugins/mailbox-store-sql.d.ts +11 -3
- package/dist/plugins/mailbox-store-sql.js +103 -15
- package/dist/plugins/memory-key-guards.d.ts +4 -0
- package/dist/plugins/memory-key-guards.js +1 -0
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
- package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
- package/dist/plugins/one-time-migrations.d.ts +117 -6
- package/dist/plugins/one-time-migrations.js +383 -14
- package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +13 -15
- package/dist/plugins/permission-rule-store-sql.js +34 -29
- package/dist/plugins/pg-pool.d.ts +7 -3
- package/dist/plugins/pg-session-storage.d.ts +41 -22
- package/dist/plugins/pg-session-storage.js +136 -55
- package/dist/plugins/remote-env-adb.d.ts +22 -4
- package/dist/plugins/remote-env-adb.js +70 -62
- package/dist/plugins/remote-env-device.d.ts +32 -12
- package/dist/plugins/remote-env-device.js +18 -17
- package/dist/plugins/remote-env-e2b.d.ts +19 -3
- package/dist/plugins/remote-env-e2b.js +41 -42
- package/dist/plugins/remote-env-file-error.d.ts +2 -0
- package/dist/plugins/remote-env-file-error.js +1 -1
- package/dist/plugins/remote-env-host.d.ts +23 -8
- package/dist/plugins/remote-env-host.js +66 -70
- package/dist/plugins/remote-env-k8s.d.ts +13 -16
- package/dist/plugins/remote-env-k8s.js +61 -54
- package/dist/plugins/remote-env-local-docker.d.ts +30 -5
- package/dist/plugins/remote-env-local-docker.js +46 -59
- package/dist/plugins/remote-env-ssh.d.ts +18 -4
- package/dist/plugins/remote-env-ssh.js +72 -56
- package/dist/plugins/remote-shell.d.ts +100 -3
- package/dist/plugins/remote-shell.js +166 -2
- package/dist/plugins/retention-store-sql.d.ts +23 -8
- package/dist/plugins/retention-store-sql.js +13 -1
- package/dist/plugins/run-store-sql.d.ts +13 -3
- package/dist/plugins/run-store-sql.js +5 -0
- package/dist/plugins/schema-consistency.d.ts +15 -5
- package/dist/plugins/schema-consistency.js +14 -8
- package/dist/plugins/schema-drift-sentence.d.ts +27 -0
- package/dist/plugins/schema-drift-sentence.js +38 -0
- package/dist/plugins/session-fork.d.ts +63 -12
- package/dist/plugins/session-fork.js +38 -21
- package/dist/plugins/session-ownership.d.ts +7 -0
- package/dist/plugins/session-ownership.js +5 -0
- package/dist/plugins/session-row-sql.d.ts +22 -0
- package/dist/plugins/session-row-sql.js +81 -0
- package/dist/plugins/session-store.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.d.ts +17 -0
- package/dist/plugins/session-workspace-cell.js +38 -0
- package/dist/plugins/store-backend.d.ts +1 -1
- package/dist/plugins/store-backend.js +26 -14
- package/dist/plugins/store-contracts.d.ts +25 -5
- package/dist/plugins/tidb-pool.d.ts +12 -7
- package/dist/plugins/tidb-pool.js +15 -3
- package/dist/plugins/tidb-session-store.d.ts +74 -28
- package/dist/plugins/tidb-session-store.js +132 -53
- package/dist/plugins/tool-result-store-sql.d.ts +16 -8
- package/dist/plugins/tool-result-store-sql.js +26 -17
- package/dist/plugins/worktree-isolation.d.ts +3 -2
- package/dist/plugins/worktree-isolation.js +1 -7
- package/dist/project-identity.d.ts +1 -14
- package/dist/project-identity.js +0 -4
- package/dist/project-memory.d.ts +9 -15
- package/dist/project-memory.js +5 -4
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +6 -7
- package/dist/request-value-closure.js +1 -3
- package/dist/rules-consent.js +6 -4
- package/dist/run-local.d.ts +6 -0
- package/dist/run-local.js +36 -26
- package/dist/runs.d.ts +9 -2
- package/dist/runs.js +9 -2
- package/dist/sandbox-git-plane.d.ts +2 -2
- package/dist/security.d.ts +38 -12
- package/dist/security.js +8 -2
- package/dist/server-secret-env.d.ts +15 -8
- package/dist/server-secret-env.js +4 -2
- package/dist/session-children-retire.d.ts +27 -9
- package/dist/session-children-retire.js +9 -5
- package/dist/session-incarnation.d.ts +19 -10
- package/dist/session-incarnation.js +5 -3
- package/dist/session-row.d.ts +114 -5
- package/dist/session-row.js +55 -5
- package/dist/session-titler.d.ts +2 -2
- package/dist/session-workspace.d.ts +130 -0
- package/dist/session-workspace.js +83 -0
- package/dist/spec-fields.d.ts +1 -1
- package/dist/standing-answer-grant.d.ts +2 -13
- package/dist/standing-answer-grant.js +0 -7
- package/dist/task-a2a.d.ts +44 -14
- package/dist/task-a2a.js +46 -20
- package/dist/task-cwd.d.ts +6 -1
- package/dist/task-mcp.d.ts +33 -20
- package/dist/task-mcp.js +46 -22
- package/dist/tool-approval.d.ts +38 -8
- package/dist/tool-approval.js +57 -13
- package/dist/trace/core-keyset-guard.d.ts +33 -18
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.d.ts +20 -1
- package/dist/trace/exit-seat.js +3 -0
- package/dist/trace/extension-call-face.d.ts +33 -0
- package/dist/trace/extension-call-face.js +39 -0
- package/dist/trace/project.d.ts +26 -7
- package/dist/trace/project.js +30 -3
- package/dist/trace/redact.js +42 -13
- package/dist/trace/self-minted-wire.d.ts +240 -12
- package/dist/trace/self-minted-wire.js +100 -4
- package/dist/trace/wire-projection-faces.d.ts +6 -6
- package/dist/trace/wire-projection-faces.js +6 -5
- package/dist/url-refusal.d.ts +1 -1
- package/dist/user-authored-text.d.ts +2 -2
- package/dist/user-authored-text.js +16 -1
- package/dist/web-admin-client/apply-effective.d.ts +17 -3
- package/dist/web-admin-client/apply-effective.js +20 -10
- package/dist/web-admin-client/center-request.d.ts +3 -2
- package/dist/web-admin-client/center-request.js +2 -3
- package/dist/web-admin-client/skills-mcp.d.ts +8 -4
- package/dist/web-admin-client/skills-mcp.js +32 -18
- package/dist/web-admin-client/types.d.ts +11 -0
- package/package.json +3 -3
- package/wire-changes/server@7.138.0.json +446 -0
- package/wire-changes/server@7.139.0.json +348 -0
- package/dist/http/device-placement.d.ts +0 -175
- package/dist/http/device-placement.js +0 -145
- package/dist/http/mcp-remote-text.d.ts +0 -5
- package/dist/http/mcp-remote-text.js +0 -7
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { HttpError, createCodedHttpError, ssoVerifiedScope } from "../security.js";
|
|
2
|
+
import { gatedPrincipal } from "./principal-gate.js";
|
|
3
|
+
import { DEVICE_ERROR_CODES, assertDeviceIdShape } from "../device-store.js";
|
|
4
|
+
import { buildDeviceRejection } from "../device-enrollment.js";
|
|
5
|
+
import { sameOccupant } from "../execution-placement.js";
|
|
6
|
+
import { deployedExecutionLanes } from "../execution-lane-caps.js";
|
|
7
|
+
import { gateExecutionLane } from "../runtime-caps-resolver.js";
|
|
8
|
+
import { releaseClaimedRun } from "./claimed-run.js";
|
|
9
|
+
import { refusalBody } from "./refusal-body.js";
|
|
10
|
+
export async function resolveExecutionContext(deps, entry) {
|
|
11
|
+
const lanes = deployedExecutionLanes(deps.config);
|
|
12
|
+
switch (entry.kind) {
|
|
13
|
+
case "service":
|
|
14
|
+
return { kind: "service", lane: lanes[0], placement: "absent" };
|
|
15
|
+
case "fresh":
|
|
16
|
+
case "continuation": {
|
|
17
|
+
const seat = deps.sessionStorage?.executionPlacement;
|
|
18
|
+
const reading = seat === undefined ? undefined : await readRow(seat, entry.root, entry.kind === "fresh" ? entry.judged : undefined);
|
|
19
|
+
const { lane, placement } = laneOf(entry.root, reading?.cell ?? { role: "absent" }, lanes);
|
|
20
|
+
gateExecutionLane(await deps.executionRuling?.(entry.principal), lane, deps.logger);
|
|
21
|
+
const ctx = { kind: entry.kind, root: entry.root, occupant: reading?.occupant, placement, lane };
|
|
22
|
+
if (entry.kind === "continuation")
|
|
23
|
+
return ctx;
|
|
24
|
+
const device = await admitDevicePlacement(deps, entry, lane, seat, reading);
|
|
25
|
+
return device === undefined ? ctx : { ...ctx, device };
|
|
26
|
+
}
|
|
27
|
+
default: {
|
|
28
|
+
const unhandled = entry;
|
|
29
|
+
throw new Error(`resolveExecutionContext: unhandled entry ${JSON.stringify(unhandled)}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function readRow(seat, root, judged) {
|
|
34
|
+
const reading = await seat.read(root);
|
|
35
|
+
if (reading === undefined || (judged !== undefined && !sameOccupant(judged, reading.occupant)))
|
|
36
|
+
throw rootSessionGoneError();
|
|
37
|
+
if (reading.cell.role === "forking") {
|
|
38
|
+
throw createCodedHttpError(409, "conflict.fork_in_progress", "this session is the target of a fork that has not finished copying — retry that fork with the same forkId and forkNonce, or delete the session; nothing was started");
|
|
39
|
+
}
|
|
40
|
+
return reading;
|
|
41
|
+
}
|
|
42
|
+
function laneOf(root, cell, lanes) {
|
|
43
|
+
switch (cell.role) {
|
|
44
|
+
case "absent":
|
|
45
|
+
return { lane: lanes[0], placement: "absent" };
|
|
46
|
+
case "writer":
|
|
47
|
+
if (!lanes.includes(cell.lane)) {
|
|
48
|
+
throw createCodedHttpError(409, "execution_lane.not_served", "this session is placed where this deployment no longer runs work (see boundLane) — nothing was started", { boundLane: cell.lane });
|
|
49
|
+
}
|
|
50
|
+
return { lane: cell.lane, placement: "writer" };
|
|
51
|
+
case "mirror":
|
|
52
|
+
throw sessionMirrorError(root);
|
|
53
|
+
case "forking":
|
|
54
|
+
throw new Error(`resolveExecutionContext: session ${root} reads as forking after the read step refused it`);
|
|
55
|
+
default: {
|
|
56
|
+
const unknownRole = cell;
|
|
57
|
+
throw new Error(`resolveExecutionContext: no lane for ${JSON.stringify(unknownRole)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function sessionMirrorError(root) {
|
|
62
|
+
const answer = sessionMirrorAnswer(root);
|
|
63
|
+
return createCodedHttpError(answer.status, answer.body.errorCode, answer.body.error, { forkPath: forkPathOf(root) });
|
|
64
|
+
}
|
|
65
|
+
export function forkPathOf(root) {
|
|
66
|
+
return `/v1/sessions/${encodeURIComponent(root)}/fork`;
|
|
67
|
+
}
|
|
68
|
+
function sessionMirrorAnswer(root) {
|
|
69
|
+
return {
|
|
70
|
+
status: 409,
|
|
71
|
+
body: refusalBody({
|
|
72
|
+
errorCode: "conflict.session_mirror",
|
|
73
|
+
message: "this session is a read-only mirror of a session whose writer is elsewhere — it cannot run here; fork it (forkPath) to continue here; nothing was started",
|
|
74
|
+
facts: { forkPath: forkPathOf(root) },
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function rootSessionGoneError() {
|
|
79
|
+
const absent = rootSessionAbsentAnswer();
|
|
80
|
+
return createCodedHttpError(absent.status, absent.body.errorCode, absent.body.error);
|
|
81
|
+
}
|
|
82
|
+
export function isRootSessionGoneError(err) {
|
|
83
|
+
return err instanceof HttpError && err.code === rootSessionAbsentAnswer().body.errorCode;
|
|
84
|
+
}
|
|
85
|
+
export function deviceOwnerDomainOf(req, config) {
|
|
86
|
+
const subject = gatedPrincipal(req, config);
|
|
87
|
+
if (subject === undefined)
|
|
88
|
+
return undefined;
|
|
89
|
+
const tenant = ssoVerifiedScope(req);
|
|
90
|
+
if (tenant === undefined)
|
|
91
|
+
return undefined;
|
|
92
|
+
return { tenant, subject };
|
|
93
|
+
}
|
|
94
|
+
function asHttpError(rej) {
|
|
95
|
+
return createCodedHttpError(rej.status, rej.code, rej.message);
|
|
96
|
+
}
|
|
97
|
+
function registryUnavailable() {
|
|
98
|
+
return buildDeviceRejection(DEVICE_ERROR_CODES.NOT_BOUND, "the device binding for this session could not be resolved (the device registry is unavailable) — refusing rather than running on an unverified device");
|
|
99
|
+
}
|
|
100
|
+
async function admitDevicePlacement(deps, entry, lane, seat, reading) {
|
|
101
|
+
const requested = entry.deviceId;
|
|
102
|
+
if (lane !== "device") {
|
|
103
|
+
if (requested !== undefined) {
|
|
104
|
+
throw createCodedHttpError(400, "request.precondition_unmet", "deviceId is only accepted on a deployment that executes on the user's own device (REMOTE_EXEC=device) — this deployment does not, so binding this session to a device id would be a declaration that never took effect");
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
if (!deps.deviceStore || !deps.deviceEnrollment || seat === undefined || reading === undefined) {
|
|
109
|
+
throw createCodedHttpError(501, "capability.device_lane_required", "device execution is selected for this deployment but its stores are not wired on this replica — refusing to admit a submit that would execute nowhere");
|
|
110
|
+
}
|
|
111
|
+
if (entry.req === undefined) {
|
|
112
|
+
throw createCodedHttpError(403, "auth.forbidden", "device placement needs a verified caller domain and this leg carries no request context");
|
|
113
|
+
}
|
|
114
|
+
const owner = deviceOwnerDomainOf(entry.req, deps.config);
|
|
115
|
+
if (owner === undefined) {
|
|
116
|
+
throw createCodedHttpError(403, "auth.forbidden", "device placement needs a caller domain: this credential carries no verified tenant scope claim (registry auth-bridge JWT `scope`) — connect through the registry auth bridge");
|
|
117
|
+
}
|
|
118
|
+
if (requested !== undefined) {
|
|
119
|
+
if (typeof requested !== "string") {
|
|
120
|
+
throw createCodedHttpError(400, "request.field_invalid", "deviceId must be a string (the device id returned by POST /v1/devices/enroll)");
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
assertDeviceIdShape(requested);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
throw createCodedHttpError(400, "request.field_invalid", "deviceId is not a valid device id (byte-exact key: non-empty, no control characters or surrounding whitespace, at most 64 bytes)");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const requestedDeviceId = requested;
|
|
130
|
+
let admitted;
|
|
131
|
+
try {
|
|
132
|
+
admitted = await deps.deviceEnrollment.admitSession({ rootSessionId: entry.root, placement: reading, requester: owner, ...(requestedDeviceId === undefined ? {} : { requestedDeviceId }) });
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
throw asHttpError(registryUnavailable());
|
|
136
|
+
}
|
|
137
|
+
if (admitted.ok)
|
|
138
|
+
return { rootSessionId: entry.root, occupant: reading.occupant, owner, admittedDeviceId: admitted.value.deviceId, firstBind: false };
|
|
139
|
+
if (admitted.reject.code !== DEVICE_ERROR_CODES.NOT_BOUND || requestedDeviceId === undefined)
|
|
140
|
+
throw asHttpError(admitted.reject);
|
|
141
|
+
let view;
|
|
142
|
+
try {
|
|
143
|
+
view = await deps.deviceStore.readDeviceForAdmission(requestedDeviceId, owner);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
throw asHttpError(registryUnavailable());
|
|
147
|
+
}
|
|
148
|
+
if (!view)
|
|
149
|
+
throw asHttpError(buildDeviceRejection(DEVICE_ERROR_CODES.NOT_FOUND, "device not found"));
|
|
150
|
+
if (view.row.status === "revoked")
|
|
151
|
+
throw asHttpError(buildDeviceRejection(DEVICE_ERROR_CODES.REVOKED, "device has been revoked"));
|
|
152
|
+
if (!view.online)
|
|
153
|
+
throw asHttpError(buildDeviceRejection(DEVICE_ERROR_CODES.OFFLINE, "device is not connected"));
|
|
154
|
+
return { rootSessionId: entry.root, occupant: reading.occupant, owner, admittedDeviceId: requestedDeviceId, firstBind: true };
|
|
155
|
+
}
|
|
156
|
+
export function stampSpecRoot(spec, ctx) {
|
|
157
|
+
const named = spec.sessionId;
|
|
158
|
+
if (named !== undefined && named !== ctx.root) {
|
|
159
|
+
throw createCodedHttpError(400, "request.precondition_unmet", "the deployment's spec resolver named a session other than the one this submission was authorized for — a submission's root session comes only from the authorizer, so resolveSpec must not set sessionId (read opts.execution.root instead)");
|
|
160
|
+
}
|
|
161
|
+
return { ...spec, sessionId: ctx.root };
|
|
162
|
+
}
|
|
163
|
+
export function rootSessionAbsentAnswer() {
|
|
164
|
+
return {
|
|
165
|
+
status: 404,
|
|
166
|
+
body: refusalBody({
|
|
167
|
+
errorCode: "resume.session_not_found",
|
|
168
|
+
message: "the session this run was to start on no longer exists (it was deleted after the request was admitted) — nothing was started",
|
|
169
|
+
}),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export function placementWriteDeadlineMs(runStaleSec) {
|
|
173
|
+
const stale = typeof runStaleSec === "number" && Number.isFinite(runStaleSec) && runStaleSec > 0 ? runStaleSec : 120;
|
|
174
|
+
return Math.min(15_000, Math.max(1_000, Math.floor((stale * 1000) / 4)));
|
|
175
|
+
}
|
|
176
|
+
function deviceRefusal(rej) {
|
|
177
|
+
return { status: rej.status, body: devicePlacementErrorBody(rej) };
|
|
178
|
+
}
|
|
179
|
+
export function devicePlacementErrorBody(rej) {
|
|
180
|
+
return refusalBody({ errorCode: rej.code, message: rej.message });
|
|
181
|
+
}
|
|
182
|
+
function treeBoundAnswer(boundLane) {
|
|
183
|
+
return {
|
|
184
|
+
status: 409,
|
|
185
|
+
body: refusalBody({
|
|
186
|
+
errorCode: "execution_lane.tree_bound",
|
|
187
|
+
message: "this session is already placed elsewhere on this deployment (see boundLane) — this run cannot be placed somewhere else; nothing was started",
|
|
188
|
+
facts: { boundLane },
|
|
189
|
+
}),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
export async function commitExecutionContext(deps, claim, ctx) {
|
|
193
|
+
const seat = deps.sessionStorage?.executionPlacement;
|
|
194
|
+
if (ctx.occupant === undefined || seat === undefined)
|
|
195
|
+
return null;
|
|
196
|
+
const occupant = ctx.occupant;
|
|
197
|
+
const release = (why) => releaseClaim(deps, claim, why);
|
|
198
|
+
const deadlineMs = placementWriteDeadlineMs(deps.config?.runStaleSec);
|
|
199
|
+
const withDeadline = async (work) => {
|
|
200
|
+
let timer;
|
|
201
|
+
try {
|
|
202
|
+
return await Promise.race([
|
|
203
|
+
work,
|
|
204
|
+
new Promise((_resolve, reject) => {
|
|
205
|
+
timer = setTimeout(() => reject(new Error(`execution placement store call exceeded ${deadlineMs}ms`)), deadlineMs);
|
|
206
|
+
timer.unref?.();
|
|
207
|
+
}),
|
|
208
|
+
]);
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
if (timer)
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
const goneAnswer = () => (claim.kind === "parked" ? claim.goneAnswer : rootSessionAbsentAnswer());
|
|
216
|
+
let refusal;
|
|
217
|
+
try {
|
|
218
|
+
refusal =
|
|
219
|
+
ctx.device !== undefined
|
|
220
|
+
? await commitDevice(deps, seat, ctx.device, withDeadline, goneAnswer)
|
|
221
|
+
: ctx.placement === "absent"
|
|
222
|
+
? await commitPlace(seat, ctx, occupant, withDeadline, goneAnswer)
|
|
223
|
+
: await reassertWriter(seat, ctx, occupant, withDeadline, goneAnswer);
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
deps.logger?.warn("execution_placement_commit_failed", { root: ctx.root, lane: ctx.lane, placement: ctx.placement, deadlineMs, err: err instanceof Error ? err.message : String(err) });
|
|
227
|
+
await release(`execution placement could not be committed: ${err instanceof Error ? err.message : String(err)}`);
|
|
228
|
+
if (ctx.device !== undefined) {
|
|
229
|
+
refusal = deviceRefusal(registryUnavailable());
|
|
230
|
+
deps.metrics?.inc("execution_placement_rejects_total", { errorCode: refusal.body.errorCode });
|
|
231
|
+
return refusal;
|
|
232
|
+
}
|
|
233
|
+
throw err;
|
|
234
|
+
}
|
|
235
|
+
if (refusal === null)
|
|
236
|
+
return null;
|
|
237
|
+
deps.metrics?.inc("execution_placement_rejects_total", { errorCode: refusal.body.errorCode });
|
|
238
|
+
await release(`execution placement rejected: [${refusal.body.errorCode}] ${refusal.body.error}`);
|
|
239
|
+
return refusal;
|
|
240
|
+
}
|
|
241
|
+
async function releaseClaim(deps, claim, why) {
|
|
242
|
+
switch (claim.kind) {
|
|
243
|
+
case "run":
|
|
244
|
+
await releaseClaimedRun(claim.runStore, claim.taskId, why, (err, step) => {
|
|
245
|
+
deps.logger?.warn("execution_placement_run_release_failed", { taskId: claim.taskId, step, err: err instanceof Error ? err.message : String(err) });
|
|
246
|
+
deps.metrics?.inc("execution_placement_run_release_failed_total");
|
|
247
|
+
});
|
|
248
|
+
return;
|
|
249
|
+
case "parked":
|
|
250
|
+
await claim.rollback(why);
|
|
251
|
+
return;
|
|
252
|
+
default: {
|
|
253
|
+
const unhandled = claim;
|
|
254
|
+
throw new Error(`commitExecutionContext: unknown claim ${JSON.stringify(unhandled)}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function commitPlace(seat, ctx, occupant, withDeadline, goneAnswer) {
|
|
259
|
+
if (ctx.lane === "device") {
|
|
260
|
+
return deviceRefusal(buildDeviceRejection(DEVICE_ERROR_CODES.NOT_BOUND, "this session has no device binding — nothing was started"));
|
|
261
|
+
}
|
|
262
|
+
return placeOutcomeAnswer(ctx.root, await withDeadline(seat.place(ctx.root, occupant, { role: "writer", lane: ctx.lane })), goneAnswer);
|
|
263
|
+
}
|
|
264
|
+
function placeOutcomeAnswer(root, outcome, goneAnswer) {
|
|
265
|
+
switch (outcome.outcome) {
|
|
266
|
+
case "placed":
|
|
267
|
+
case "same":
|
|
268
|
+
return null;
|
|
269
|
+
case "tree_bound":
|
|
270
|
+
return treeBoundAnswer(outcome.boundLane);
|
|
271
|
+
case "mirror":
|
|
272
|
+
return sessionMirrorAnswer(root);
|
|
273
|
+
case "gone":
|
|
274
|
+
return goneAnswer();
|
|
275
|
+
default: {
|
|
276
|
+
const unhandled = outcome;
|
|
277
|
+
throw new Error(`commitExecutionContext: no answer for ${JSON.stringify(unhandled)}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async function reassertWriter(seat, ctx, occupant, withDeadline, goneAnswer) {
|
|
282
|
+
const now = await withDeadline(seat.read(ctx.root));
|
|
283
|
+
if (now === undefined || !sameOccupant(now.occupant, occupant))
|
|
284
|
+
return goneAnswer();
|
|
285
|
+
const cell = now.cell;
|
|
286
|
+
switch (cell.role) {
|
|
287
|
+
case "writer":
|
|
288
|
+
return cell.lane === ctx.lane ? null : treeBoundAnswer(cell.lane);
|
|
289
|
+
case "mirror":
|
|
290
|
+
return sessionMirrorAnswer(ctx.root);
|
|
291
|
+
case "absent":
|
|
292
|
+
case "forking":
|
|
293
|
+
throw new Error(`commitExecutionContext: session ${ctx.root} was resolved as a writer and now reads as ${cell.role} for the same occupant`);
|
|
294
|
+
default: {
|
|
295
|
+
const unknownRole = cell;
|
|
296
|
+
throw new Error(`commitExecutionContext: no arm for ${JSON.stringify(unknownRole)}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async function commitDevice(deps, seat, intent, withDeadline, goneAnswer) {
|
|
301
|
+
const enrollment = deps.deviceEnrollment;
|
|
302
|
+
if (!enrollment) {
|
|
303
|
+
throw new Error("commitExecutionContext: a device placement intent was minted without a DeviceEnrollment — assembly bug (both are read from the same ServiceDeps)");
|
|
304
|
+
}
|
|
305
|
+
if (intent.firstBind) {
|
|
306
|
+
const bound = await withDeadline(enrollment.bindPlacement({ rootSessionId: intent.rootSessionId, occupant: intent.occupant, deviceId: intent.admittedDeviceId, requester: intent.owner }));
|
|
307
|
+
if (bound.ok)
|
|
308
|
+
return null;
|
|
309
|
+
if ("reject" in bound)
|
|
310
|
+
return deviceRefusal(bound.reject);
|
|
311
|
+
switch (bound.placement) {
|
|
312
|
+
case "gone":
|
|
313
|
+
return goneAnswer();
|
|
314
|
+
case "mirror":
|
|
315
|
+
return sessionMirrorAnswer(intent.rootSessionId);
|
|
316
|
+
case "tree_bound":
|
|
317
|
+
return treeBoundAnswer(bound.boundLane);
|
|
318
|
+
default: {
|
|
319
|
+
const unhandled = bound;
|
|
320
|
+
throw new Error(`commitExecutionContext: no answer for ${JSON.stringify(unhandled)}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const placement = await withDeadline(seat.read(intent.rootSessionId));
|
|
325
|
+
if (placement === undefined || !sameOccupant(placement.occupant, intent.occupant))
|
|
326
|
+
return goneAnswer();
|
|
327
|
+
const readmitted = await withDeadline(enrollment.admitSession({ rootSessionId: intent.rootSessionId, placement, requester: intent.owner, requestedDeviceId: intent.admittedDeviceId }));
|
|
328
|
+
return readmitted.ok ? null : deviceRefusal(readmitted.reject);
|
|
329
|
+
}
|
|
330
|
+
//# sourceMappingURL=execution-context.js.map
|
|
@@ -8,6 +8,9 @@ function sentCoreCode(res, coreCode, coreMessage, audience) {
|
|
|
8
8
|
case "config.memory_erasure_request":
|
|
9
9
|
sendError(res, 400, "config.memory_erasure_request", textFor(audience, coreMessage, "session memory erasure refused — the erasure request is not valid; correct it and resend"));
|
|
10
10
|
return true;
|
|
11
|
+
case "config.memory_erasure_scope":
|
|
12
|
+
sendError(res, 400, "config.memory_erasure_scope", textFor(audience, coreMessage, "session memory erasure refused — the request reaches memory outside the scopes it may erase; nothing was erased"));
|
|
13
|
+
return true;
|
|
11
14
|
case "memory.erasure_evidence_unavailable":
|
|
12
15
|
sendError(res, 409, "memory.erasure_evidence_unavailable", textFor(audience, coreMessage, "session memory erasure refused — this deployment's memory store cannot produce erasure evidence; resend with allowUnevidenced: true to erase without evidence, or ask the operator"));
|
|
13
16
|
return true;
|
|
@@ -18,7 +18,7 @@ import type { CheckpointStoreFull } from "../plugins/store-backend.js";
|
|
|
18
18
|
import type { HostDecision } from "../host-decision.js";
|
|
19
19
|
import { type GateSighting } from "../approval-gate-currency.js";
|
|
20
20
|
import { type PlanApprovePermissionModeAfter } from "../task-settings.js";
|
|
21
|
-
import type { RouteCtxBase, DriveResumeArgs,
|
|
21
|
+
import type { RouteCtxBase, DriveResumeArgs, PreparedPlanReview, ResumeLegOutcome, WorkflowDecisionDelivery } from "./route-ctx.js";
|
|
22
22
|
import type { RunRegistry, RouteHelpers, RouteLegs } from "./route-ctx.js";
|
|
23
23
|
import type { createRequestGuards } from "./request-guards.js";
|
|
24
24
|
import type { FlatServiceDeps } from "./server.js";
|
|
@@ -59,12 +59,9 @@ export declare function createResumeLegs(ctx: {
|
|
|
59
59
|
resumePreempted: (sessionId: string, req: IncomingMessage | undefined, acceptEarly?: boolean, decider?: {
|
|
60
60
|
principal?: string;
|
|
61
61
|
explicitOperator: boolean;
|
|
62
|
-
}) => Promise<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}>;
|
|
66
|
-
startFreshHostTurn: (sessionId: string, objective: string, principal: string | undefined, req: IncomingMessage, workflowDecision: WorkflowDecisionDelivery, runId: string) => Promise<HostLegOutcome>;
|
|
67
|
-
resumeWake: (sessionId: string, message: string | undefined, caller: string | undefined, req: IncomingMessage | undefined, acceptEarly?: boolean, workflowDecision?: WorkflowDecisionDelivery, ownerLeaseAdmittedFor?: string) => Promise<HostLegOutcome>;
|
|
62
|
+
}) => Promise<ResumeLegOutcome>;
|
|
63
|
+
startFreshHostTurn: (sessionId: string, objective: string, principal: string | undefined, req: IncomingMessage, workflowDecision: WorkflowDecisionDelivery, runId: string) => Promise<ResumeLegOutcome>;
|
|
64
|
+
resumeWake: (sessionId: string, message: string | undefined, caller: string | undefined, req: IncomingMessage | undefined, acceptEarly?: boolean, workflowDecision?: WorkflowDecisionDelivery, ownerLeaseAdmittedFor?: string) => Promise<ResumeLegOutcome>;
|
|
68
65
|
preparePlanReview: (sessionId: string, log?: {
|
|
69
66
|
taskId?: string;
|
|
70
67
|
principalPresent?: boolean;
|
|
@@ -81,9 +78,6 @@ export declare function createResumeLegs(ctx: {
|
|
|
81
78
|
readonly ok: true;
|
|
82
79
|
readonly prepared: PreparedPlanReview;
|
|
83
80
|
}>;
|
|
84
|
-
drivePlanReview: (prepared: PreparedPlanReview, decision: "approve" | "edit" | "reject", editedPlan: string | undefined, reason: string | undefined, hostDecision: HostDecision, req: IncomingMessage | undefined, acceptEarly?: boolean, permissionModeAfter?: PlanApprovePermissionModeAfter) => Promise<
|
|
85
|
-
status: number;
|
|
86
|
-
body: object;
|
|
87
|
-
}>;
|
|
81
|
+
drivePlanReview: (prepared: PreparedPlanReview, decision: "approve" | "edit" | "reject", editedPlan: string | undefined, reason: string | undefined, hostDecision: HostDecision, req: IncomingMessage | undefined, acceptEarly?: boolean, permissionModeAfter?: PlanApprovePermissionModeAfter) => Promise<ResumeLegOutcome>;
|
|
88
82
|
};
|
|
89
83
|
//# sourceMappingURL=resume-legs.d.ts.map
|