@sema-agent/server 7.140.0 → 7.142.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 +505 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +142 -25
- package/dist/approval-ask-audit-store.d.ts +54 -38
- package/dist/approval-ask-audit-store.js +58 -39
- package/dist/approval-batch-census.d.ts +27 -0
- package/dist/approval-batch-census.js +27 -0
- package/dist/approval-card.d.ts +39 -24
- package/dist/approval-card.js +6 -3
- package/dist/approval-crash-convergence.d.ts +17 -7
- package/dist/approval-crash-convergence.js +94 -22
- package/dist/approval-deny-reasons.d.ts +34 -30
- package/dist/approval-deny-reasons.js +21 -6
- package/dist/approval-policy-settlement.d.ts +1 -1
- package/dist/approval-reconciler.d.ts +33 -1
- package/dist/approval-reconciler.js +43 -40
- package/dist/approval.d.ts +15 -0
- package/dist/approval.js +10 -0
- package/dist/audit.d.ts +4 -2
- package/dist/audit.js +2 -2
- package/dist/auth-bridge.d.ts +9 -6
- package/dist/auth-bridge.js +9 -3
- package/dist/boot/adoption.js +2 -1
- package/dist/boot/budget-tracing.d.ts +16 -0
- package/dist/boot/budget-tracing.js +10 -6
- package/dist/boot/device-lane.d.ts +11 -0
- package/dist/boot/device-lane.js +16 -0
- package/dist/boot/engine-lease.d.ts +24 -3
- package/dist/boot/engine-lease.js +9 -2
- package/dist/boot/execution-env.d.ts +24 -4
- package/dist/boot/execution-env.js +34 -27
- package/dist/boot/execution-lane.js +2 -1
- package/dist/boot/exit-boundary.js +5 -4
- package/dist/boot/memory-boundary.d.ts +2 -2
- package/dist/boot/memory-consolidation.d.ts +1 -1
- package/dist/boot/memory-consolidation.js +3 -2
- package/dist/boot/parked-revive-gate.js +2 -1
- package/dist/boot/permission-rules-audit.d.ts +0 -28
- package/dist/boot/permission-rules-audit.js +2 -1
- package/dist/boot/read-deny-anchor-audit.js +2 -1
- package/dist/boot/reapers.d.ts +2 -2
- package/dist/boot/reapers.js +4 -3
- package/dist/boot/resolve-spec.js +18 -6
- package/dist/boot/retention-lane.js +5 -3
- package/dist/boot/runner-deps.js +16 -13
- package/dist/boot/runtime-caps.js +3 -2
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +8 -6
- package/dist/boot/shutdown.js +8 -2
- package/dist/boot/stage-04-stores.js +3 -2
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.js +11 -7
- package/dist/boot/stage-06-runners.d.ts +1 -3
- package/dist/boot/stage-06-runners.js +3 -2
- package/dist/boot/stage-07-capability-layer.d.ts +1 -2
- package/dist/boot/stage-07-capability-layer.js +10 -11
- package/dist/boot/stage-08-reapers.d.ts +1 -2
- package/dist/boot/stage-08-reapers.js +3 -2
- package/dist/boot/stage-09-leader.d.ts +1 -2
- package/dist/boot/stage-10-http-server.d.ts +1 -2
- package/dist/boot/stage-10-http-server.js +6 -4
- package/dist/boot/stores.js +13 -6
- package/dist/boot/web-admin.js +18 -17
- package/dist/boot/webfetch-summarize-lane.js +2 -1
- package/dist/boot/workflow-orchestration.js +3 -2
- package/dist/capabilities/builtin-tools.d.ts +2 -1
- package/dist/capabilities/builtin-tools.js +2 -4
- package/dist/capabilities/composite-members.d.ts +2 -1
- package/dist/capabilities/composite-members.js +2 -1
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +33 -0
- package/dist/capability-facts.js +32 -0
- package/dist/card-void.d.ts +38 -0
- package/dist/card-void.js +23 -0
- package/dist/config-catalog.js +12 -8
- package/dist/config-provider.d.ts +13 -13
- package/dist/config-provider.js +19 -33
- package/dist/config-types.d.ts +39 -10
- package/dist/config.d.ts +6 -0
- package/dist/config.js +72 -49
- package/dist/delegated-session-owner.d.ts +21 -19
- package/dist/delegated-session-owner.js +1 -1
- package/dist/device-store.d.ts +7 -0
- package/dist/device-store.js +3 -0
- package/dist/execution-config-root.d.ts +22 -0
- package/dist/execution-config-root.js +35 -0
- package/dist/execution-lane-caps.d.ts +50 -2
- package/dist/execution-lane-caps.js +90 -13
- package/dist/execution-placement.d.ts +3 -0
- package/dist/execution-placement.js +7 -0
- package/dist/fleet/drain-entry.d.ts +3 -0
- package/dist/fleet/fleet-bus.d.ts +6 -3
- package/dist/hooks/hook-runner.d.ts +6 -0
- package/dist/hooks/hook-runner.js +30 -1
- package/dist/hosted-posture.d.ts +8 -0
- package/dist/hosted-posture.js +4 -0
- package/dist/http/admission.js +3 -3
- package/dist/http/caller-trust.d.ts +3 -2
- package/dist/http/caller-trust.js +2 -1
- package/dist/http/dispatch.js +8 -2
- package/dist/http/execution-context.d.ts +2 -2
- package/dist/http/idempotency.d.ts +1 -1
- package/dist/http/live-frame.d.ts +24 -0
- package/dist/http/live-frame.js +16 -0
- package/dist/http/principal-gate.d.ts +3 -1
- package/dist/http/principal-gate.js +17 -8
- package/dist/http/resume-legs.js +42 -29
- package/dist/http/route-table.js +4 -0
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/attachments.js +18 -0
- package/dist/http/routes/capabilities.js +10 -6
- package/dist/http/routes/diagnostics.d.ts +0 -19
- package/dist/http/routes/diagnostics.js +16 -2
- package/dist/http/routes/memory-bundle.d.ts +2 -2
- package/dist/http/routes/memory-bundle.js +1 -1
- package/dist/http/routes/memory-compliance.js +1 -1
- package/dist/http/routes/memory-policy.js +27 -7
- package/dist/http/routes/run-permission-mode.d.ts +75 -0
- package/dist/http/routes/run-permission-mode.js +170 -0
- package/dist/http/routes/runs.js +3 -3
- package/dist/http/routes/session-memory-sync.d.ts +63 -0
- package/dist/http/routes/session-memory-sync.js +157 -0
- package/dist/http/routes/session-sync.d.ts +3 -6
- package/dist/http/routes/session-sync.js +7 -2
- package/dist/http/routes/sessions-list.d.ts +2 -1
- package/dist/http/routes/sessions.js +5 -4
- package/dist/http/routes/tasks.js +70 -71
- package/dist/http/run-read-face.d.ts +7 -0
- package/dist/http/run-read-face.js +10 -4
- package/dist/http/send.d.ts +6 -1
- package/dist/http/send.js +10 -3
- package/dist/http/server.d.ts +20 -7
- package/dist/http/server.js +2 -4
- package/dist/http/sse-log.d.ts +3 -8
- package/dist/http/stored-body-replay.d.ts +3 -3
- package/dist/http/stored-body-replay.js +3 -1
- package/dist/http/submit-replay.d.ts +4 -3
- package/dist/http/submit-replay.js +2 -2
- package/dist/http/sync-submit-body.d.ts +21 -4
- package/dist/http/sync-submit-body.js +13 -0
- package/dist/identity-source.d.ts +21 -2
- package/dist/identity-source.js +6 -0
- package/dist/instruction-source-notice.d.ts +7 -0
- package/dist/instruction-source-notice.js +6 -6
- package/dist/mcp-assertion-header.d.ts +4 -0
- package/dist/mcp-assertion-header.js +5 -0
- package/dist/memory-incident-sink.d.ts +19 -6
- package/dist/memory-incident-sink.js +8 -4
- package/dist/memory-posture.d.ts +1 -1
- package/dist/memory-scope.d.ts +4 -4
- package/dist/memory-scope.js +1 -1
- package/dist/memory-sync-client.d.ts +35 -23
- package/dist/memory-sync-client.js +122 -52
- package/dist/memory-sync-identity.d.ts +24 -0
- package/dist/memory-sync-identity.js +57 -0
- package/dist/memory-sync.d.ts +7 -1
- package/dist/memory-sync.js +6 -3
- package/dist/observability/fail-open.d.ts +57 -12
- package/dist/observability/fail-open.js +50 -9
- package/dist/observability/metrics.js +0 -1
- package/dist/observability/run-terminal.d.ts +93 -31
- package/dist/observability/run-terminal.js +175 -171
- package/dist/observability/secret-env-scrub.d.ts +20 -1
- package/dist/observability/secret-env-scrub.js +14 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
- package/dist/orchestration/workflow-completion-inbox.js +13 -5
- package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
- package/dist/orchestration/workflow-notify-journal.js +2 -1
- package/dist/permission-mode-admission.d.ts +68 -0
- package/dist/permission-mode-admission.js +23 -0
- package/dist/permission-rule-list.d.ts +55 -6
- package/dist/permission-rule-list.js +52 -5
- package/dist/plugins/approval-ask-store-file.d.ts +14 -4
- package/dist/plugins/approval-ask-store-file.js +48 -7
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
- package/dist/plugins/approval-ask-store-memory.js +9 -8
- package/dist/plugins/approval-ask-store-sql.d.ts +42 -10
- package/dist/plugins/approval-ask-store-sql.js +49 -19
- package/dist/plugins/caching-session-store.d.ts +6 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
- package/dist/plugins/checkpoint-store-sql.js +74 -3
- package/dist/plugins/device-store-sql.d.ts +5 -2
- package/dist/plugins/device-store-sql.js +4 -1
- package/dist/plugins/execution-placement-sql.d.ts +11 -1
- package/dist/plugins/execution-placement-sql.js +12 -1
- package/dist/plugins/file-run-store.d.ts +7 -2
- package/dist/plugins/file-run-store.js +12 -2
- package/dist/plugins/fork-routing-session-store.d.ts +10 -32
- package/dist/plugins/fork-routing-session-store.js +2 -61
- package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
- package/dist/plugins/k8s-exec-protocol.js +6 -1
- package/dist/plugins/local-checkpoint-store.d.ts +10 -1
- package/dist/plugins/local-checkpoint-store.js +22 -0
- package/dist/plugins/local-session-store.d.ts +260 -39
- package/dist/plugins/local-session-store.js +582 -57
- package/dist/plugins/local-session-window.d.ts +17 -3
- package/dist/plugins/local-session-window.js +7 -13
- package/dist/plugins/local-task-attachment-store.d.ts +12 -2
- package/dist/plugins/local-task-attachment-store.js +67 -43
- package/dist/plugins/memory-key-guards.d.ts +3 -3
- package/dist/plugins/memory-run-store.d.ts +7 -2
- package/dist/plugins/memory-run-store.js +12 -2
- package/dist/plugins/one-time-migrations.d.ts +68 -2
- package/dist/plugins/one-time-migrations.js +120 -4
- package/dist/plugins/pg-pool.js +3 -2
- package/dist/plugins/pg-session-storage.d.ts +2 -0
- package/dist/plugins/pg-session-storage.js +3 -2
- package/dist/plugins/remote-env-e2b.d.ts +47 -7
- package/dist/plugins/remote-env-e2b.js +242 -46
- package/dist/plugins/remote-env-k8s.d.ts +51 -6
- package/dist/plugins/remote-env-k8s.js +234 -44
- package/dist/plugins/remote-env-local-docker.d.ts +40 -14
- package/dist/plugins/remote-env-local-docker.js +127 -33
- package/dist/plugins/run-store-sql.d.ts +11 -3
- package/dist/plugins/run-store-sql.js +13 -4
- package/dist/plugins/sandbox-read-floor.d.ts +273 -0
- package/dist/plugins/sandbox-read-floor.js +608 -0
- package/dist/plugins/session-ownership.d.ts +2 -1
- package/dist/plugins/session-placement.d.ts +17 -4
- package/dist/plugins/session-placement.js +2 -1
- package/dist/plugins/session-store.d.ts +18 -20
- package/dist/plugins/session-store.js +4 -12
- package/dist/plugins/store-backend.d.ts +41 -7
- package/dist/plugins/store-backend.js +22 -4
- package/dist/plugins/store-contracts.d.ts +35 -3
- package/dist/plugins/store-contracts.js +3 -0
- package/dist/plugins/task-attachment-store.d.ts +112 -14
- package/dist/plugins/task-attachment-store.js +221 -28
- package/dist/plugins/tidb-pool.js +2 -1
- package/dist/plugins/tidb-session-store.d.ts +4 -1
- package/dist/plugins/tidb-session-store.js +3 -2
- package/dist/plugins/workspace-fill.d.ts +60 -0
- package/dist/plugins/workspace-fill.js +106 -0
- package/dist/project-bundle.d.ts +38 -0
- package/dist/project-bundle.js +609 -0
- package/dist/run-cancel-context.d.ts +34 -0
- package/dist/run-cancel-context.js +7 -0
- package/dist/run-local.js +2 -8
- package/dist/runs.d.ts +39 -12
- package/dist/runs.js +69 -55
- package/dist/sandbox-egress-policy.d.ts +4 -1
- package/dist/sandbox-egress-policy.js +3 -1
- package/dist/sandbox-git-plane.d.ts +31 -12
- package/dist/sandbox-git-plane.js +24 -4
- package/dist/security.d.ts +11 -2
- package/dist/security.js +8 -0
- package/dist/server-secret-env.d.ts +2 -1
- package/dist/server-secret-env.js +2 -4
- package/dist/session-error-wire.d.ts +46 -4
- package/dist/session-error-wire.js +23 -0
- package/dist/session-incarnation.d.ts +9 -3
- package/dist/session-sync-kernel.d.ts +42 -1
- package/dist/session-sync-kernel.js +17 -0
- package/dist/session-sync.js +16 -4
- package/dist/sql-engine-coordinates.d.ts +49 -0
- package/dist/sql-engine-coordinates.js +27 -0
- package/dist/standing-answer-grant.d.ts +31 -2
- package/dist/standing-answer-grant.js +14 -2
- package/dist/store-posture.d.ts +1 -1
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-mcp.d.ts +1 -1
- package/dist/task-settings.d.ts +62 -27
- package/dist/task-settings.js +105 -29
- package/dist/terminal.d.ts +97 -20
- package/dist/terminal.js +107 -18
- package/dist/tool-approval.d.ts +4 -3
- package/dist/tool-approval.js +12 -14
- package/dist/trace/core-keyset-guard.d.ts +196 -2
- package/dist/trace/engine-notice-wire.d.ts +8 -2
- package/dist/trace/engine-notice-wire.js +4 -2
- package/dist/trace/exit-seat.d.ts +9 -1
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/ledger-sink.d.ts +8 -4
- package/dist/trace/ledger-sink.js +2 -2
- package/dist/trace/model-segment.d.ts +2 -2
- package/dist/trace/project.d.ts +18 -1
- package/dist/trace/project.js +16 -0
- package/dist/trace/redact.js +323 -90
- package/dist/trace/self-minted-wire.d.ts +419 -4
- package/dist/trace/self-minted-wire.js +145 -0
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/verifier-pause-card.d.ts +8 -0
- package/dist/verifier-pause-card.js +19 -0
- package/dist/web-admin-client/apply-effective.d.ts +5 -7
- package/dist/web-admin-client/apply-effective.js +22 -2
- package/dist/web-admin-client/candidate-gate.d.ts +1 -1
- package/dist/web-admin-client/effective-keys.js +2 -0
- package/dist/web-admin-client/read-warnings.d.ts +2 -2
- package/dist/web-admin-client/types.d.ts +4 -0
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
- package/package.json +2 -2
- package/wire-changes/server@7.141.0.json +367 -0
- package/wire-changes/server@7.142.0.json +639 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isParkedRunStatus, isTerminalRunStatus, runProvenElsewhere } from "../../plugins/store-contracts.js";
|
|
3
|
+
import { sendJson, sendError } from "../send.js";
|
|
4
|
+
import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
|
|
5
|
+
import { readOwnedRun } from "../owner-access.js";
|
|
6
|
+
import { runNotFoundMessage } from "../route-ctx.js";
|
|
7
|
+
import { assertNever } from "../route-ctx.js";
|
|
8
|
+
import * as facts from "../../capability-facts.js";
|
|
9
|
+
import { PERMISSION_MODE_WORDS, coercePermissionModeStrict, parseTaskSettings } from "../../task-settings.js";
|
|
10
|
+
import { admitPermissionModeWord } from "../../permission-mode-admission.js";
|
|
11
|
+
import { readResumeCtx, resumeCtxCorruptAnswer, resumeCtxUnsealableAnswer } from "../resume-ctx-read.js";
|
|
12
|
+
import { sealCheckpointCtx, withPermissionModeRewritten } from "../checkpoint-ctx.js";
|
|
13
|
+
import { serviceVersion } from "../../service-version.js";
|
|
14
|
+
import { recordFailOpen } from "../../observability/fail-open.js";
|
|
15
|
+
export const PERMISSION_MODE_PERSIST_MISSES = ["claim_released", "context_absent", "store_failed"];
|
|
16
|
+
const BodySchema = z.object({ mode: z.string() }).strict();
|
|
17
|
+
const switchChains = new WeakMap();
|
|
18
|
+
function serialOnStream(stream, work) {
|
|
19
|
+
const prior = switchChains.get(stream) ?? Promise.resolve();
|
|
20
|
+
const job = prior.then(work, work);
|
|
21
|
+
switchChains.set(stream, job.catch(() => undefined));
|
|
22
|
+
return job;
|
|
23
|
+
}
|
|
24
|
+
export function switchLegHolds(fenced, thisInstanceId, liveNow, switched) {
|
|
25
|
+
return !runProvenElsewhere(fenced, thisInstanceId) && liveNow === switched;
|
|
26
|
+
}
|
|
27
|
+
export async function handlePermissionMode(req, res, match, ctx) {
|
|
28
|
+
const { deps } = ctx;
|
|
29
|
+
switch (match.decl.id) {
|
|
30
|
+
case "run-permission-mode": {
|
|
31
|
+
if (ctx.helpers.rateLimited(req, res))
|
|
32
|
+
return;
|
|
33
|
+
const seat = facts.permissionModeWrite(deps);
|
|
34
|
+
if (!seat.available) {
|
|
35
|
+
switch (seat.why) {
|
|
36
|
+
case "run_store_absent":
|
|
37
|
+
sendError(res, 501, "capability.run_store_required", "changing a running run's permission mode requires a durable run store (DB_BACKEND=mysql|pg|local)");
|
|
38
|
+
return;
|
|
39
|
+
case "checkpoint_store_absent":
|
|
40
|
+
sendError(res, 501, "capability.checkpoint_store_required", "changing a running run's permission mode requires the checkpoint store (DURABLE_APPROVAL): the run's stored request is where its permission mode lives and where the task's own settings are read");
|
|
41
|
+
return;
|
|
42
|
+
default:
|
|
43
|
+
return assertNever(seat.why);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const { runStore, checkpointStore } = seat;
|
|
47
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
48
|
+
if (deps.config.requirePrincipal && principal === undefined) {
|
|
49
|
+
sendPrincipalRefusal(res, req, deps.config);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const taskId = match.params.taskId;
|
|
53
|
+
const parsed = BodySchema.safeParse((await ctx.helpers.readJson(req)) ?? {});
|
|
54
|
+
if (!parsed.success) {
|
|
55
|
+
sendError(res, 400, "request.body_shape", `body must be { mode: ${PERMISSION_MODE_WORDS} } — the permission-mode verb takes no other keys`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const word = coercePermissionModeStrict(parsed.data.mode);
|
|
59
|
+
if (word === undefined) {
|
|
60
|
+
sendError(res, 422, "steering.invalid_content", `mode must be one of ${PERMISSION_MODE_WORDS} (got ${JSON.stringify(parsed.data.mode.slice(0, 64))})`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const run = await readOwnedRun(deps.config, runStore, principal, taskId, "owner_or_operator");
|
|
64
|
+
if (!run) {
|
|
65
|
+
sendError(res, 404, "not_found.run", runNotFoundMessage(taskId));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!ctx.helpers.runSessionAcceptOk(req, res, run, "run.permission_mode"))
|
|
69
|
+
return;
|
|
70
|
+
const live = ctx.registry.steerableRuns.get(taskId);
|
|
71
|
+
if (!live) {
|
|
72
|
+
if (isParkedRunStatus(run.status)) {
|
|
73
|
+
sendError(res, 409, "steering.not_running", "run is waiting on a pending decision — the permission-mode verb changes a running run; when it resumes, its mode is rebuilt from the run's stored request (a plan review's approval names the mode it continues in)");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (isTerminalRunStatus(run.status)) {
|
|
77
|
+
sendError(res, 409, "steering.not_running", "run already ended — the permission-mode verb changes a running run; submit the next turn of this session with the permissionMode you want");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (runProvenElsewhere(run, deps.instanceId)) {
|
|
81
|
+
sendError(res, 409, "steering.not_running", "run is live on another replica — the permission-mode verb must reach the replica holding the stream; resend it there");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
sendError(res, 409, "steering.not_running", "no live steerable face for this run on this replica — a verify or cascade submission exposes none, so the permission-mode verb has nothing to switch");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const sessionId = run.sessionId;
|
|
88
|
+
const out = await serialOnStream(live, async () => {
|
|
89
|
+
const read = await readResumeCtx(checkpointStore.getCtx(sessionId), { sessionId, opener: deps.checkpointCtxSealing });
|
|
90
|
+
if (read.kind === "corrupt" || read.kind === "unsealable") {
|
|
91
|
+
const { status, body: { error, errorCode, ...facts } } = read.kind === "corrupt" ? resumeCtxCorruptAnswer(read.where) : resumeCtxUnsealableAnswer(read);
|
|
92
|
+
sendError(res, status, errorCode, error, facts);
|
|
93
|
+
return { kind: "answered" };
|
|
94
|
+
}
|
|
95
|
+
if (read.kind === "absent") {
|
|
96
|
+
sendError(res, 409, "conflict.resume_context_unavailable", "this run's stored request is missing — its permission mode cannot be changed (the task's settings are read from it and the new mode is written back to it)");
|
|
97
|
+
return { kind: "answered" };
|
|
98
|
+
}
|
|
99
|
+
const stored = read.ctx;
|
|
100
|
+
const admission = admitPermissionModeWord(word, { deployment: deps.config, settings: parseTaskSettings(stored.body.settings).settings });
|
|
101
|
+
if (admission.kind === "refused") {
|
|
102
|
+
sendError(res, admission.status, admission.code, admission.message);
|
|
103
|
+
return { kind: "answered" };
|
|
104
|
+
}
|
|
105
|
+
const admitted = admission.admitted ?? word;
|
|
106
|
+
let receipt;
|
|
107
|
+
try {
|
|
108
|
+
receipt = await live.setPermissionMode(admitted);
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
const code = e.code;
|
|
112
|
+
if (code === "steering.invalid_content") {
|
|
113
|
+
sendError(res, 422, "steering.invalid_content", e instanceof Error ? e.message : String(e));
|
|
114
|
+
return { kind: "answered" };
|
|
115
|
+
}
|
|
116
|
+
if (code === "steering.not_running") {
|
|
117
|
+
sendError(res, 409, "steering.not_running", "run just finished — the permission-mode verb needs a running run; submit the next turn of this session with the permissionMode you want");
|
|
118
|
+
return { kind: "answered" };
|
|
119
|
+
}
|
|
120
|
+
throw e;
|
|
121
|
+
}
|
|
122
|
+
let persistMiss;
|
|
123
|
+
try {
|
|
124
|
+
const next = sealCheckpointCtx({ ...withPermissionModeRewritten(stored, { ...stored.body, permissionMode: admitted }), writerVersion: serviceVersion() }, deps.checkpointCtxSealing, sessionId);
|
|
125
|
+
const rewrite = await checkpointStore.rewriteCtxWhileClaimed(sessionId, taskId, next, (fenced) => switchLegHolds(fenced, deps.instanceId, ctx.registry.steerableRuns.get(taskId), live));
|
|
126
|
+
persistMiss = rewrite === "written" ? undefined : rewrite;
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
persistMiss = "store_failed";
|
|
130
|
+
deps.logger?.warn?.("run_permission_mode_write_back_failed", { taskId, sessionId, err: e instanceof Error ? e.message : String(e) });
|
|
131
|
+
}
|
|
132
|
+
if (persistMiss !== undefined) {
|
|
133
|
+
recordFailOpen("server.run-permission-mode.write-back-unpersisted", `miss=${persistMiss} mode=${admitted}`);
|
|
134
|
+
deps.logger?.warn?.("run_permission_mode_unpersisted", { taskId, sessionId, mode: admitted, miss: persistMiss });
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
kind: "switched",
|
|
138
|
+
receipt,
|
|
139
|
+
...(admission.foldedBy !== undefined ? { admission: { requested: word, admitted, foldedBy: admission.foldedBy } } : {}),
|
|
140
|
+
...(persistMiss !== undefined ? { persistMiss } : {}),
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
switch (out.kind) {
|
|
144
|
+
case "answered":
|
|
145
|
+
return;
|
|
146
|
+
case "switched": {
|
|
147
|
+
const reply = {
|
|
148
|
+
taskId,
|
|
149
|
+
sessionId,
|
|
150
|
+
status: "running",
|
|
151
|
+
permissionMode: out.receipt,
|
|
152
|
+
...(out.admission !== undefined ? { admission: out.admission } : {}),
|
|
153
|
+
persisted: out.persistMiss === undefined,
|
|
154
|
+
...(out.persistMiss !== undefined ? { persistMiss: out.persistMiss } : {}),
|
|
155
|
+
};
|
|
156
|
+
sendJson(res, 200, reply);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
default:
|
|
160
|
+
return assertNever(out);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
default:
|
|
164
|
+
return assertNever(match.decl.id);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export const RUN_PERMISSION_MODE_ROUTES = [
|
|
168
|
+
{ id: "run-permission-mode", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/permission-mode$/, label: "/v1/runs/:id/permission-mode", methods: ["POST"] },
|
|
169
|
+
];
|
|
170
|
+
//# sourceMappingURL=run-permission-mode.js.map
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -16,7 +16,7 @@ import { buildApprovalPreamble, buildApprovalPreambleSseFrames, readClosedWordSe
|
|
|
16
16
|
import { heldAskOf, liveSettledBy, readDecidedBy, retiredDecisionKeyFacts, DECIDED_BY_NOT_IN_PROOF, DECIDED_BY_OUTSIDE_CLOSED_SET, POLICY_CANNOT_ALLOW_PERSON_ASK } from "../../host-decision.js";
|
|
17
17
|
import { ASK_DECISION_CONSUMER_ABSENT, approvalPreambleReplays, outcomeOfDecidedRow, presentedApprovalFrameFamily, resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
18
18
|
import { MAX_DECISION_NOTE_CHARS, noteLandedOnRow, noteRecordedReceipt } from "../../approval-ask-machine.js";
|
|
19
|
-
import {
|
|
19
|
+
import { askRowEndingCause } from "../../approval-deny-reasons.js";
|
|
20
20
|
import { normalizeRunEventType, screenedStoredEventData } from "../../trace/project.js";
|
|
21
21
|
import { invalidJsonError, sendJson, sendError, sendHttpError, sseHeaders } from "../send.js";
|
|
22
22
|
import { mintCancelledResult } from "../../run-cancel-context.js";
|
|
@@ -1429,10 +1429,10 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1429
1429
|
return;
|
|
1430
1430
|
case "DENIED":
|
|
1431
1431
|
case "VOID": {
|
|
1432
|
-
const
|
|
1432
|
+
const endingCause = askRowEndingCause(row);
|
|
1433
1433
|
sendError(res, 410, "gone.ask", "this ask reached a terminal state without a human decision (cause says why: a routing failure, a cancellation or batch withdrawal or abort, or the process that held it ended) and can no longer be decided", {
|
|
1434
1434
|
state: row.state,
|
|
1435
|
-
...(
|
|
1435
|
+
...(endingCause !== undefined ? { cause: endingCause } : {}),
|
|
1436
1436
|
});
|
|
1437
1437
|
return;
|
|
1438
1438
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [ref] —— **会话记忆同步触发口**:`POST /v1/sessions/:id/memory/sync` body `{ target: string }`
|
|
3
|
+
* → 200 引擎的同步报告(core `memorySyncReportOf` 的产物,原样)
|
|
4
|
+
*
|
|
5
|
+
* 一口一件事:让本机的记忆同步腿**现在**对会话要去的那台引擎同步一轮,只回报这个会话读的那几层。壳在把会话推上云(fork)之前调它。
|
|
6
|
+
*
|
|
7
|
+
* ── 规则(规则账只减)────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
* R1 一轮 = 同步腿的一轮(`MemorySyncRunner.syncOnce`):交回每层每次调用如它落定的那样,从不整轮 reject —— 部分层失败落在报告的逐层结局里。
|
|
9
|
+
* R2 同步身份一只铸点(`memory-sync-identity.ts`):`target` 必须**就是**目标引擎能力面公开的 `memorySyncServe.identity`(规范形、不带凭据),
|
|
10
|
+
* 与本机腿的 `peer` 按串相等比。壳不拼、本口不规范化。
|
|
11
|
+
* R4 门之后的一切答复都是 core 报告:200 带 `layers`(每层恰一个结局)或整轮 `refused`(目标不符 / 会话有在飞的 run)。
|
|
12
|
+
* HTTP 错误只留给报告产生之前的门(能力 / 身份 / id / 属主 / 体)与「读集不可取得」。本口不铸汇总键、不加第五种结局。
|
|
13
|
+
* R5 可用性 = 能力事实 `sessionMemorySync`(本机同步腿 ∧ 持久 run 账面 ∧ 会话头读),与能力位同读一只;不从后端名推。
|
|
14
|
+
*
|
|
15
|
+
* ── 读集(这个会话读哪几层)────────────────────────────────────────────────────────────────────
|
|
16
|
+
* = 会话**最近一条已结腿**结果里引擎铸的记忆观测(run 店 `latestMemoryObservationForSession`;规则属主 `store-contracts.ts` 的
|
|
17
|
+
* `SettledLegMemoryObservation`):`mounted` ⇒ `scopes[].scope` 按服务序;`memoryless` / `none` ⇒ `[]`(那一腿没挂上记忆 —— 是值)。
|
|
18
|
+
* 那一腿没有观测(prepare 没走完)/ 会话还没有已结腿 / 会话没有叶子 ⇒ 409 `conflict.session_memory_unobserved`(有在飞 run 时带 `activeTaskId`)。
|
|
19
|
+
* 路由不重铸 spec:spec 只在受理时铸、不落会话行,重铸 = 重启前后两种答案。
|
|
20
|
+
*
|
|
21
|
+
* ── 门序(编号即执行序)──────────────────────────────────────────────────────────────────────────
|
|
22
|
+
* 1. 事实 `sessionMemorySync` 为假 ⇒ 501(码按 why)。先于身份:「这台部署有没有这一口」本由能力位公开。
|
|
23
|
+
* 2. 多租户部署上没有验过的身份 ⇒ 401/403(`sendPrincipalRefusal`,会话面同一只)。
|
|
24
|
+
* 3. 限流(本口会向远端发请求)。
|
|
25
|
+
* 4. 会话 id 段解码不了 ⇒ 400 `request.path_malformed`。
|
|
26
|
+
* 5. 一次头读:没有这只会话 / 不是你的 ⇒ 404 `not_found.session`(同码同串,无存在性 oracle;属主判据 = 会话面唯一谓词 `sessionOwnerOk`);
|
|
27
|
+
* 同一次头读的叶子 = `sourceRevision`(与 `GET /v1/sessions/:id/head` 的 `leafId` 同一读法)。
|
|
28
|
+
* 6. 体:闭集一键 `{ target }`,否则 400 `request.body_shape`;`target` 不是声明的同步身份 ⇒ 400 `request.field_invalid`(`field` / `reason`,不回显值)。
|
|
29
|
+
* 7. 读集(上一节)。
|
|
30
|
+
* 8. 目标比对:`target` ≠ 本机腿的 `peer` ⇒ 报告 `refused: config.memory_sync_target_mismatch`(句由 core 铸;零请求零本地写)。先于空读集出口。
|
|
31
|
+
* 9. 收割栅栏(前检):会话**不静** ⇒ 报告 `refused: session_active`(句由本服务铸)。零请求。
|
|
32
|
+
* **静** = 一只谓词,前检与后检同读,三格合取:会话没有认领 ∧ 最近受理的腿**就是**观测所在的那一条(读完观测之后没有新 run 被受理)∧ 会话叶子仍是 `sourceRevision`
|
|
33
|
+
* (会话的记录没有前进)。次序 = 认领 → 最近受理的腿 → 叶子:run 行在受理时同步落库、消息在那之后才追加,所以认领读之后才受理的 run 在第二只读里看得见;
|
|
34
|
+
* 第三格不靠建行时刻 —— 「最近受理」按建行时刻排序,副本之间钟偏 / 时钟回拨时一条后受理的 run 可以排在观测腿之前,而它只要跑过就挪动了叶子。
|
|
35
|
+
* 10. 一轮:读集为空 ⇒ 空报告(同一只铸点);否则 `syncOnce("session-trigger", { reading })`,只挑读集里的层的调用交给引擎出报告。
|
|
36
|
+
* 11. 收割栅栏(后检):轮落定后会话不静 ⇒ 第 9 步的报告 —— 这一轮的请求与写已经发生(幂等,下次触发重放),报告只是不再为这份读集作保。
|
|
37
|
+
* 报告之后、fork 之前的源前进不由本口封闭:壳拿 `sourceRevision` 与它推上云的会话头比。
|
|
38
|
+
* 12. 引擎的报告铸点自己抛 `TypeError`(名字带凭据 / 空串)⇒ 500 + 运维 warn(拒句不引原值)。同步层名起服时已过同一只凭据判官(`config.ts`),这一臂按构造不可达。
|
|
39
|
+
*
|
|
40
|
+
* ── 并发 ───────────────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
* 同一会话、同一 `sourceRevision`、同一观测腿、同一 `target` 的请求各自走完 1–9,再**并入**在飞的那一轮,拿同一份报告(只减少重复工作,不延长等待)。
|
|
42
|
+
* 别的会话 / 后台轮经腿的同一条串行链(`exclusive`):会话触发的轮起跑晚于本请求到达。请求方断开不取消轮。本口不设自己的截止(= 各调用的传输超时之和)。
|
|
43
|
+
*
|
|
44
|
+
* ── 计费 / 门 ──────────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
* 零模型工作(不 billable、不吃 drain 503);写的是调用方自己的记忆到它自己配置的中心 ⇒ 属主门兜底,不进无凭据改写门(会话自助抹除同判)。
|
|
46
|
+
*/
|
|
47
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
48
|
+
import { type RouteCtx, type RouteMatch, type RouteIdsOf } from "../route-ctx.js";
|
|
49
|
+
import type { EffectiveMemoryScopes } from "../../plugins/store-contracts.js";
|
|
50
|
+
/** 收割栅栏的句(本服务铸;码 `session_active` 是引擎报告闭集的成员)。 */
|
|
51
|
+
export declare const SESSION_ACTIVE_SYNC_MESSAGE = "a run in this session is in flight, or the session moved on after the memory it reads was observed (a run was admitted or its transcript advanced) \u2014 wait for it to finish on its own, then sync again (do not cancel it to sync sooner)";
|
|
52
|
+
/** 读集 = 最近一条已结腿的记忆观测(三态穷举;引擎加第四态 ⇒ 编译红)。 */
|
|
53
|
+
export declare function sessionReadSetOf(observation: EffectiveMemoryScopes): readonly string[];
|
|
54
|
+
export declare function handleSessionMemorySync(req: IncomingMessage, res: ServerResponse, match: RouteMatch<SessionMemorySyncRouteId>, ctx: RouteCtx): Promise<void>;
|
|
55
|
+
export declare const SESSION_MEMORY_SYNC_ROUTES: readonly [{
|
|
56
|
+
readonly id: "session-memory-sync";
|
|
57
|
+
readonly pattern: RegExp;
|
|
58
|
+
readonly label: "/v1/sessions/:id/memory/sync";
|
|
59
|
+
readonly methods: readonly ["POST"];
|
|
60
|
+
}];
|
|
61
|
+
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
62
|
+
export type SessionMemorySyncRouteId = RouteIdsOf<typeof SESSION_MEMORY_SYNC_ROUTES>;
|
|
63
|
+
//# sourceMappingURL=session-memory-sync.d.ts.map
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { memorySyncReportOf } from "@sema-agent/core";
|
|
2
|
+
import { sendJson, sendError } from "../send.js";
|
|
3
|
+
import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
|
|
4
|
+
import {} from "../route-ctx.js";
|
|
5
|
+
import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
|
|
6
|
+
import * as facts from "../../capability-facts.js";
|
|
7
|
+
import { sessionOwnerCellOf } from "../../security.js";
|
|
8
|
+
import { leafOfAdmittedHead } from "../../session-incarnation.js";
|
|
9
|
+
import { declaredMemorySyncIdentityFlaw, memorySyncIdentityRule } from "../../memory-sync-identity.js";
|
|
10
|
+
export const SESSION_ACTIVE_SYNC_MESSAGE = "a run in this session is in flight, or the session moved on after the memory it reads was observed (a run was admitted or its transcript advanced) — wait for it to finish on its own, then sync again (do not cancel it to sync sooner)";
|
|
11
|
+
export function sessionReadSetOf(observation) {
|
|
12
|
+
switch (observation.state) {
|
|
13
|
+
case "mounted":
|
|
14
|
+
return observation.scopes.map((s) => s.scope);
|
|
15
|
+
case "memoryless":
|
|
16
|
+
case "none":
|
|
17
|
+
return [];
|
|
18
|
+
default:
|
|
19
|
+
return assertNever(observation);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
class ReportMintRefused extends Error {
|
|
23
|
+
}
|
|
24
|
+
function reportOf(round) {
|
|
25
|
+
try {
|
|
26
|
+
return memorySyncReportOf(round);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
if (err instanceof TypeError)
|
|
30
|
+
throw new ReportMintRefused(err.message);
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const inflightByLeg = new WeakMap();
|
|
35
|
+
function mergedRound(leg, key, run) {
|
|
36
|
+
let table = inflightByLeg.get(leg);
|
|
37
|
+
if (table === undefined) {
|
|
38
|
+
table = new Map();
|
|
39
|
+
inflightByLeg.set(leg, table);
|
|
40
|
+
}
|
|
41
|
+
const inflight = table.get(key);
|
|
42
|
+
if (inflight !== undefined)
|
|
43
|
+
return inflight;
|
|
44
|
+
const owner = table;
|
|
45
|
+
const round = run().finally(() => {
|
|
46
|
+
if (owner.get(key) === round)
|
|
47
|
+
owner.delete(key);
|
|
48
|
+
});
|
|
49
|
+
table.set(key, round);
|
|
50
|
+
return round;
|
|
51
|
+
}
|
|
52
|
+
export async function handleSessionMemorySync(req, res, match, ctx) {
|
|
53
|
+
const { deps } = ctx;
|
|
54
|
+
switch (match.decl.id) {
|
|
55
|
+
case "session-memory-sync": {
|
|
56
|
+
const fact = facts.sessionMemorySync(deps);
|
|
57
|
+
if (!fact.available) {
|
|
58
|
+
switch (fact.why) {
|
|
59
|
+
case "memory_sync_leg_absent":
|
|
60
|
+
sendError(res, 501, "feature.memory_sync_disabled", "session memory sync is not available on this deployment — it runs only where a file memory engine syncs to a center (MEMORY_SYNC_URL)");
|
|
61
|
+
return;
|
|
62
|
+
case "run_store_absent":
|
|
63
|
+
sendError(res, 501, "capability.run_store_required", "session memory sync requires a durable run store (DB_BACKEND=mysql|pg|local) — the session's read set is the memory its latest settled run observed");
|
|
64
|
+
return;
|
|
65
|
+
case "session_head_face_absent":
|
|
66
|
+
sendError(res, 501, "capability.session_ownership_required", "session memory sync requires the session-ownership face (sessionStorage.headOf)");
|
|
67
|
+
return;
|
|
68
|
+
default:
|
|
69
|
+
return assertNever(fact.why);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const { leg, runStore, sessionStorage } = fact;
|
|
73
|
+
if (deps.config.requirePrincipal && gatedPrincipal(req, deps.config) === undefined) {
|
|
74
|
+
sendPrincipalRefusal(res, req, deps.config);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (ctx.helpers.rateLimited(req, res))
|
|
78
|
+
return;
|
|
79
|
+
const sessionId = ctx.helpers.safeDecode(match.params.sessionId);
|
|
80
|
+
if (sessionId === null) {
|
|
81
|
+
sendError(res, 400, "request.path_malformed", "session id segment is not valid percent-encoding");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const readHead = () => sessionStorage.headOf(sessionId);
|
|
85
|
+
const head = await readHead();
|
|
86
|
+
if (head === undefined) {
|
|
87
|
+
sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!ctx.helpers.sessionOwnerOk(req, res, await sessionOwnerCellOf(sessionStorage, sessionId, head.owner)))
|
|
91
|
+
return;
|
|
92
|
+
const sourceRevision = leafOfAdmittedHead(head);
|
|
93
|
+
const body = await ctx.helpers.readJson(req);
|
|
94
|
+
if (body === null || typeof body !== "object" || Array.isArray(body) || Object.keys(body).some((k) => k !== "target") || typeof body.target !== "string") {
|
|
95
|
+
sendError(res, 400, "request.body_shape", "body must be { target: string } — the sync identity the target engine publishes (GET /v1/capabilities → memorySyncServe.identity), verbatim; no other key");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const target = body.target;
|
|
99
|
+
const flaw = declaredMemorySyncIdentityFlaw(target);
|
|
100
|
+
if (flaw !== undefined) {
|
|
101
|
+
sendError(res, 400, "request.field_invalid", `target ${memorySyncIdentityRule(flaw)} — send the target engine's memorySyncServe.identity exactly as GET /v1/capabilities publishes it`, { field: "target", reason: flaw });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const observed = await runStore.latestMemoryObservationForSession(sessionId);
|
|
105
|
+
if (observed?.effectiveMemoryScopes === undefined || sourceRevision === null) {
|
|
106
|
+
const activeTaskId = await runStore.getActiveTaskId(sessionId);
|
|
107
|
+
sendError(res, 409, "conflict.session_memory_unobserved", "this session has no settled run with a memory observation yet, so there is nothing to say about which memory it reads — run a task in it (or let the run in flight finish), then sync", activeTaskId !== undefined ? { activeTaskId } : undefined);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const scopes = sessionReadSetOf(observed.effectiveMemoryScopes);
|
|
111
|
+
const quiet = async () => {
|
|
112
|
+
if ((await runStore.getActiveTaskId(sessionId)) !== undefined)
|
|
113
|
+
return false;
|
|
114
|
+
if ((await runStore.latestRunForSession(sessionId))?.taskId !== observed.taskId)
|
|
115
|
+
return false;
|
|
116
|
+
const now = await readHead();
|
|
117
|
+
return now !== undefined && leafOfAdmittedHead(now) === sourceRevision;
|
|
118
|
+
};
|
|
119
|
+
const fenced = () => reportOf({ target, sourceRevision, scopes, refused: { code: "session_active", message: SESSION_ACTIVE_SYNC_MESSAGE } });
|
|
120
|
+
let report;
|
|
121
|
+
try {
|
|
122
|
+
if (target !== leg.peer) {
|
|
123
|
+
report = reportOf({ target, sourceRevision, scopes, refused: { code: "config.memory_sync_target_mismatch", reaches: leg.peer } });
|
|
124
|
+
}
|
|
125
|
+
else if (!(await quiet())) {
|
|
126
|
+
report = fenced();
|
|
127
|
+
}
|
|
128
|
+
else if (scopes.length === 0) {
|
|
129
|
+
report = reportOf({ target, sourceRevision, scopes, layers: [] });
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
report = await mergedRound(leg, JSON.stringify([sessionId, sourceRevision, observed.taskId, target]), async () => {
|
|
133
|
+
const round = await leg.syncOnce("session-trigger", { reading: scopes });
|
|
134
|
+
if (!(await quiet()))
|
|
135
|
+
return fenced();
|
|
136
|
+
return reportOf({ target, sourceRevision, scopes, layers: round.calls.filter((c) => scopes.includes(c.scope)) });
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
if (!(err instanceof ReportMintRefused))
|
|
142
|
+
throw err;
|
|
143
|
+
deps.logger?.warn?.("session_memory_sync_report_refused", { sessionId, err: err.message });
|
|
144
|
+
sendError(res, 500, "internal.error", "memory sync failed");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
sendJson(res, 200, report);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
default:
|
|
151
|
+
return assertNever(match.decl.id);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export const SESSION_MEMORY_SYNC_ROUTES = [
|
|
155
|
+
{ id: "session-memory-sync", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/memory\/sync$/, label: "/v1/sessions/:id/memory/sync", methods: ["POST"] },
|
|
156
|
+
];
|
|
157
|
+
//# sourceMappingURL=session-memory-sync.js.map
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
11
11
|
import { type SessionRulesRecord, type FileHistoryExport } from "@sema-agent/core";
|
|
12
|
-
import { type StagingHandle } from "../../session-sync-kernel.js";
|
|
12
|
+
import { type ImportAnchor, type StagingHandle } from "../../session-sync-kernel.js";
|
|
13
13
|
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
14
14
|
import { type SessionRowPatch } from "../../session-row.js";
|
|
15
15
|
export type PendingStaging = {
|
|
@@ -21,11 +21,8 @@ export type PendingStaging = {
|
|
|
21
21
|
/** [ref]: the session's file-history envelope (or null — no history travelled). Replaces the E19 per-key manifests. */
|
|
22
22
|
fileHistory: FileHistoryExport | null;
|
|
23
23
|
policy: SessionRulesRecord[];
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
entryId: string;
|
|
27
|
-
owner: string | null;
|
|
28
|
-
}>;
|
|
24
|
+
/** 已在 Phase A 入口判过的锚(`readImportAnchors`:每条 `entryId` 都在 `declaredEntryIds` 里)—— 提交后的重放腿只迭代它,不再判;owner 不带(重钉到 `owner`)。 */
|
|
25
|
+
anchors: ImportAnchor[];
|
|
29
26
|
/** 会话行的两位可写位(Phase A 体里带了哪几键就重放哪几键;老客户端不带 ⇒ `{}`,目的端两位不动)。提交后经会话店唯一写口落。 */
|
|
30
27
|
row: SessionRowPatch;
|
|
31
28
|
/** 交叉复查 A-3(HIGH):Phase A's DECLARED entry-id set — Phase B verifies the streamed NDJSON matches it
|
|
@@ -4,7 +4,7 @@ import { sessionLogDigest, sessionLogDigestsComparable, StreamingImportValidator
|
|
|
4
4
|
import { StringDecoder } from "node:string_decoder";
|
|
5
5
|
import { FileHistoryUnreadableError, blobGetterOf, exportSessionManifest, exportedFileHistory, fileHistoryImportFace, replayFileHistory, replayPolicyRecords, wipeSessionAttendants } from "../../session-sync.js";
|
|
6
6
|
import * as facts from "../../capability-facts.js";
|
|
7
|
-
import { classifySyncRelationshipByIds, SyncConflictError } from "../../session-sync-kernel.js";
|
|
7
|
+
import { classifySyncRelationshipByIds, readImportAnchors, SyncConflictError } from "../../session-sync-kernel.js";
|
|
8
8
|
import { isUuidShape, legacyUnownedSessionOk, sessionOwnerCellOf } from "../../security.js";
|
|
9
9
|
import { sendJson, sendError, sendSessionError, sendSessionPolicyError, logRequestError, msg } from "../send.js";
|
|
10
10
|
import { buildDisplaySafeUrl } from "../../trace/redact.js";
|
|
@@ -307,6 +307,11 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
307
307
|
sendError(res, 400, "request.body_shape", "malformed import metadata");
|
|
308
308
|
return;
|
|
309
309
|
}
|
|
310
|
+
const anchorsRead = readImportAnchors(body.anchors, body.entryIds);
|
|
311
|
+
if ("problem" in anchorsRead) {
|
|
312
|
+
sendError(res, 400, "request.field_invalid", anchorsRead.problem);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
310
315
|
const policyRead = readSessionRulesRecords(body.policy);
|
|
311
316
|
if (policyRead.problem !== undefined) {
|
|
312
317
|
if (policyRead.problem.kind === "rules") {
|
|
@@ -350,7 +355,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
350
355
|
const srcLogDigest = typeof body.logDigest === "string" && body.logDigest.length > 0 ? body.logDigest : undefined;
|
|
351
356
|
const entryIds = body.entryIds;
|
|
352
357
|
const policy = policyRead.records;
|
|
353
|
-
const anchors =
|
|
358
|
+
const anchors = anchorsRead.anchors;
|
|
354
359
|
const dstIds = (await sessionStorage.listEntryIds?.(sessionId)) ?? null;
|
|
355
360
|
const relA = classifySyncRelationshipByIds(entryIds, dstIds);
|
|
356
361
|
if ((relA.relation === "fork" || relA.relation === "stale") && resolution !== "overwrite-dst") {
|
|
@@ -16,7 +16,8 @@ export declare function handleSessionsList(req: IncomingMessage, res: ServerResp
|
|
|
16
16
|
* 会话列表行的 **wire 投影** —— `GET /v1/sessions` 的每一行与 `PATCH /v1/sessions/:id` 的 200 回执是**同一只**(店行 → wire 行只有这一处)。
|
|
17
17
|
* · 预览:店行读的是 run 行预览列的事实位(`""` = 有记录、剥完没有用户正文;NULL = 没记录);本面只说「有没有可显示的预览」⇒ 两者都是 `null`。
|
|
18
18
|
* · `tempo`:见上方 [ref] 注(由 `lastStatus` 经唯一一张表派生;omit-when-absent)。
|
|
19
|
-
* · 其余键(含 `title` / `pinned`)零投影透传 ——
|
|
19
|
+
* · 其余键(含 `title` / `pinned` / `rowTag` / `unreadable`)零投影透传 —— 店行是唯一铸点(`unreadable` 的码出自 `session-error-wire.ts` 的那一张表,
|
|
20
|
+
* 与全局兜底对同一只会话答的码同读;SQL 孪生的叶子是行上一列 ⇒ 恒缺席)。
|
|
20
21
|
*/
|
|
21
22
|
export declare function projectSessionListRow(r: SessionListItem): SessionListItem & {
|
|
22
23
|
tempo?: ReturnType<typeof sessionTempoOfRunStatus>;
|
|
@@ -526,14 +526,14 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
526
526
|
}
|
|
527
527
|
const auditFact = facts.sessionAuditView(deps);
|
|
528
528
|
if (!auditFact.available) {
|
|
529
|
-
sendError(res, 501, "capability.memory_engine_required", "memory provenance and erasure are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/
|
|
529
|
+
sendError(res, 501, "capability.memory_engine_required", "memory provenance and erasure are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/mysql memory backends do not (their control plane would land on a replica's local disk)");
|
|
530
530
|
return;
|
|
531
531
|
}
|
|
532
532
|
if ((await readOwnedSessionView(auditFact.sessionAudit, sessionId)) === undefined)
|
|
533
533
|
return;
|
|
534
534
|
const faces = deps.memoryCompliance;
|
|
535
535
|
if (faces === undefined) {
|
|
536
|
-
sendError(res, 501, "capability.memory_engine_required", "memory provenance and erasure are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/
|
|
536
|
+
sendError(res, 501, "capability.memory_engine_required", "memory provenance and erasure are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/mysql memory backends do not (their control plane would land on a replica's local disk)");
|
|
537
537
|
return;
|
|
538
538
|
}
|
|
539
539
|
const raw = (await readJson(req));
|
|
@@ -585,14 +585,14 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
585
585
|
}
|
|
586
586
|
const auditFact = facts.sessionAuditView(deps);
|
|
587
587
|
if (!auditFact.available) {
|
|
588
|
-
sendError(res, 501, "capability.memory_engine_required", "session memory status is not available on this deployment — it requires a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/
|
|
588
|
+
sendError(res, 501, "capability.memory_engine_required", "session memory status is not available on this deployment — it requires a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/mysql memory backends do not (their control plane would land on a replica's local disk)");
|
|
589
589
|
return;
|
|
590
590
|
}
|
|
591
591
|
if ((await readOwnedSessionView(auditFact.sessionAudit, sessionId)) === undefined)
|
|
592
592
|
return;
|
|
593
593
|
const face = deps.sessionMemoryStatus;
|
|
594
594
|
if (face === undefined) {
|
|
595
|
-
sendError(res, 501, "capability.memory_engine_required", "session memory status is not available on this deployment — it requires a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/
|
|
595
|
+
sendError(res, 501, "capability.memory_engine_required", "session memory status is not available on this deployment — it requires a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/mysql memory backends do not (their control plane would land on a replica's local disk)");
|
|
596
596
|
return;
|
|
597
597
|
}
|
|
598
598
|
const query = new URLSearchParams(match.search);
|
|
@@ -619,6 +619,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
619
619
|
sendJson(res, 200, {
|
|
620
620
|
sessionId,
|
|
621
621
|
autoConsolidationArmed: deps.memoryAutoConsolidationArmed,
|
|
622
|
+
...(deps.memorySyncLeg !== undefined ? { memorySync: { parked: deps.memorySyncLeg.parked().map((p) => ({ scope: p.scope, wayOut: p.wayOut })) } } : {}),
|
|
622
623
|
...(s.captureOptedOut !== undefined ? { captureOptedOut: s.captureOptedOut } : {}),
|
|
623
624
|
...(s.committedCount !== undefined ? { committedCount: s.committedCount } : {}),
|
|
624
625
|
...(s.foldedCount !== undefined ? { foldedCount: s.foldedCount } : {}),
|