@sema-agent/server 7.139.0 → 7.141.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 +471 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +94 -26
- package/dist/adoption/runner.js +1 -1
- package/dist/adoption/sql.js +1 -1
- package/dist/approval-batch-census.d.ts +27 -0
- package/dist/approval-batch-census.js +27 -0
- package/dist/approval-card.d.ts +37 -22
- package/dist/approval-card.js +6 -3
- 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/bench/s1/live-deps.js +3 -2
- package/dist/bench/s1/row.d.ts +5 -2
- package/dist/bench/s1/row.js +2 -22
- package/dist/boot/adoption.js +1 -1
- 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.js +18 -12
- package/dist/boot/execution-lane.d.ts +8 -0
- package/dist/boot/execution-lane.js +6 -1
- package/dist/boot/memory-boundary.d.ts +16 -53
- package/dist/boot/memory-boundary.js +10 -20
- package/dist/boot/memory-consolidation.d.ts +16 -12
- package/dist/boot/memory-consolidation.js +24 -13
- package/dist/boot/reapers.d.ts +2 -2
- package/dist/boot/resolve-spec.d.ts +3 -5
- package/dist/boot/resolve-spec.js +9 -8
- package/dist/boot/runner-deps.d.ts +3 -3
- package/dist/boot/runner-deps.js +1 -0
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +4 -3
- package/dist/boot/shutdown.js +5 -0
- package/dist/boot/stage-03-store-backend.d.ts +2 -6
- package/dist/boot/stage-03-store-backend.js +2 -3
- package/dist/boot/stage-04-stores.d.ts +2 -6
- package/dist/boot/stage-04-stores.js +5 -5
- package/dist/boot/stage-05-execution-env.d.ts +2 -6
- package/dist/boot/stage-05-execution-env.js +3 -1
- package/dist/boot/stage-06-runners.d.ts +2 -8
- package/dist/boot/stage-06-runners.js +3 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -7
- package/dist/boot/stage-07-capability-layer.js +8 -10
- package/dist/boot/stage-08-reapers.d.ts +2 -7
- package/dist/boot/stage-09-leader.d.ts +2 -7
- package/dist/boot/stage-10-http-server.d.ts +2 -7
- package/dist/boot/stage-10-http-server.js +6 -4
- package/dist/boot/stores.d.ts +3 -6
- package/dist/boot/stores.js +37 -44
- package/dist/boot/task-list-lane.d.ts +1 -1
- package/dist/boot/task-list-lane.js +1 -1
- package/dist/boot/web-admin.js +36 -9
- package/dist/capabilities/execution-lane.d.ts +2 -2
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +9 -0
- package/dist/capability-facts.js +9 -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 +18 -17
- package/dist/config-provider.js +28 -36
- package/dist/config-types.d.ts +36 -9
- package/dist/config.js +56 -46
- package/dist/delegated-session-owner.d.ts +23 -20
- package/dist/delegated-session-owner.js +2 -2
- package/dist/deployment-shape.d.ts +9 -8
- package/dist/deployment-shape.js +4 -4
- 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 +145 -107
- package/dist/execution-lane-caps.js +144 -83
- 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/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 +4 -0
- package/dist/http/live-frame.d.ts +24 -0
- package/dist/http/live-frame.js +16 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
- package/dist/http/memory-compliance-failure-reply.js +5 -0
- package/dist/http/memory-layer-reply.d.ts +30 -0
- package/dist/http/memory-layer-reply.js +24 -0
- package/dist/http/resume-legs.js +49 -35
- package/dist/http/retired-service-deps.d.ts +1 -1
- package/dist/http/retired-service-deps.js +1 -1
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/adoption.js +1 -1
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/capabilities.js +5 -4
- 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 +4 -1
- package/dist/http/routes/memory-compliance.js +9 -2
- package/dist/http/routes/memory-consolidation.js +12 -1
- package/dist/http/routes/memory-origin.js +14 -2
- package/dist/http/routes/memory-policy.js +7 -2
- 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/session-sync.d.ts +3 -6
- package/dist/http/routes/session-sync.js +7 -2
- package/dist/http/routes/sessions.js +33 -15
- package/dist/http/routes/tasks.js +67 -68
- package/dist/http/run-read-face.js +3 -3
- package/dist/http/send.d.ts +1 -1
- package/dist/http/send.js +2 -2
- package/dist/http/server.d.ts +14 -27
- package/dist/http/server.js +2 -2
- 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/identity-source.d.ts +1 -1
- package/dist/identity-source.js +1 -1
- package/dist/instruction-source-notice.d.ts +7 -0
- package/dist/instruction-source-notice.js +6 -6
- package/dist/lane-home-probe.d.ts +68 -42
- package/dist/lane-home-probe.js +53 -30
- package/dist/leader/wire.js +5 -4
- package/dist/mcp-assertion-header.d.ts +4 -0
- package/dist/mcp-assertion-header.js +5 -0
- package/dist/memory-bundle-engine.d.ts +16 -9
- package/dist/memory-bundle-engine.js +29 -6
- package/dist/memory-operator-faces.d.ts +85 -30
- package/dist/memory-operator-faces.js +136 -30
- package/dist/memory-posture.d.ts +1 -1
- package/dist/memory-project-layer.d.ts +3 -21
- package/dist/memory-project-layer.js +11 -8
- package/dist/memory-scope-stores.d.ts +73 -0
- package/dist/memory-scope-stores.js +86 -0
- package/dist/memory-scope.d.ts +33 -9
- package/dist/memory-scope.js +17 -5
- package/dist/memory-sync-client.d.ts +34 -9
- package/dist/memory-sync-client.js +108 -28
- package/dist/observability/fail-open.d.ts +30 -2
- package/dist/observability/fail-open.js +30 -2
- package/dist/observability/metrics.js +0 -1
- package/dist/observability/run-terminal.d.ts +6 -2
- package/dist/observability/run-terminal.js +25 -26
- 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/adoption-log-sql.js +4 -4
- package/dist/plugins/approval-ask-store-sql.js +14 -14
- package/dist/plugins/approval-nonce-store.js +4 -4
- package/dist/plugins/blob-backend.d.ts +4 -4
- package/dist/plugins/blob-backend.js +7 -7
- package/dist/plugins/breaker-state-sql.js +2 -2
- package/dist/plugins/caching-session-store.d.ts +6 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/checkpoint-index-columns.js +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
- package/dist/plugins/checkpoint-store-sql.js +90 -19
- package/dist/plugins/device-store-sql.d.ts +5 -2
- package/dist/plugins/device-store-sql.js +23 -20
- package/dist/plugins/ensure-index.d.ts +1 -2
- package/dist/plugins/execution-placement-sql.d.ts +19 -9
- package/dist/plugins/execution-placement-sql.js +20 -9
- package/dist/plugins/file-history-store-sql.d.ts +2 -2
- package/dist/plugins/file-history-store-sql.js +4 -4
- package/dist/plugins/file-run-store.d.ts +2 -2
- package/dist/plugins/file-run-store.js +1 -1
- package/dist/plugins/fork-routing-session-store.d.ts +10 -32
- package/dist/plugins/fork-routing-session-store.js +2 -61
- package/dist/plugins/image-bake-store-sql.js +9 -9
- package/dist/plugins/image-index-sql.js +11 -11
- package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
- package/dist/plugins/k8s-exec-protocol.js +6 -1
- package/dist/plugins/leader-run-store-sql.js +4 -4
- 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 +227 -35
- package/dist/plugins/local-session-store.js +518 -37
- package/dist/plugins/local-session-window.d.ts +17 -3
- package/dist/plugins/local-session-window.js +7 -13
- package/dist/plugins/mailbox-store-sql.d.ts +2 -1
- package/dist/plugins/mailbox-store-sql.js +4 -4
- package/dist/plugins/memory-engine-tidb.js +5 -5
- package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
- package/dist/plugins/memory-run-store.d.ts +2 -2
- package/dist/plugins/memory-run-store.js +1 -1
- package/dist/plugins/memory-sync-store-tidb.js +3 -3
- package/dist/plugins/one-time-migrations.d.ts +52 -11
- package/dist/plugins/one-time-migrations.js +101 -39
- package/dist/plugins/org-delivery-quota-store.js +2 -2
- package/dist/plugins/outcome-ledger-sql.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +4 -4
- package/dist/plugins/permission-rule-store-sql.js +16 -16
- 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-adb.d.ts +7 -24
- package/dist/plugins/remote-env-adb.js +22 -5
- package/dist/plugins/remote-env-device.d.ts +2 -28
- package/dist/plugins/remote-env-device.js +2 -1
- package/dist/plugins/remote-env-e2b.d.ts +40 -27
- package/dist/plugins/remote-env-e2b.js +180 -20
- package/dist/plugins/remote-env-file-error.d.ts +1 -1
- package/dist/plugins/remote-env-file-error.js +8 -2
- package/dist/plugins/remote-env-host.d.ts +19 -35
- package/dist/plugins/remote-env-host.js +26 -3
- package/dist/plugins/remote-env-k8s.d.ts +22 -14
- package/dist/plugins/remote-env-k8s.js +150 -26
- package/dist/plugins/remote-env-local-docker.d.ts +27 -24
- package/dist/plugins/remote-env-local-docker.js +91 -24
- package/dist/plugins/remote-env-ssh.d.ts +4 -24
- package/dist/plugins/remote-env-ssh.js +4 -3
- package/dist/plugins/resume-anchor-store-sql.js +2 -2
- package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
- package/dist/plugins/retention-lane-store-sql.js +2 -2
- package/dist/plugins/retention-store-sql.d.ts +1 -1
- package/dist/plugins/retention-store-sql.js +6 -6
- package/dist/plugins/run-store-sql.d.ts +3 -3
- package/dist/plugins/run-store-sql.js +19 -18
- package/dist/plugins/sandbox-read-floor.d.ts +273 -0
- package/dist/plugins/sandbox-read-floor.js +608 -0
- package/dist/plugins/schema-consistency.d.ts +6 -6
- package/dist/plugins/schema-consistency.js +1 -1
- package/dist/plugins/schema-ensure-legs.d.ts +2 -3
- package/dist/plugins/schema-ensure-legs.js +2 -2
- package/dist/plugins/schema-live-columns.d.ts +1 -1
- package/dist/plugins/session-capture-record-store-sql.js +1 -1
- 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-rules-column.d.ts +1 -1
- package/dist/plugins/session-rules-column.js +1 -1
- package/dist/plugins/session-store.d.ts +18 -20
- package/dist/plugins/session-store.js +4 -12
- package/dist/plugins/session-workspace-cell.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.js +1 -1
- package/dist/plugins/shared-memory-store-sql.js +2 -2
- package/dist/plugins/sql-driver.d.ts +10 -5
- package/dist/plugins/sql-driver.js +6 -6
- package/dist/plugins/sql-errors.js +1 -1
- package/dist/plugins/store-backend.d.ts +35 -6
- package/dist/plugins/store-backend.js +27 -10
- package/dist/plugins/store-contracts.d.ts +9 -2
- package/dist/plugins/task-list-store-sql.js +11 -11
- package/dist/plugins/tidb-pool.js +7 -6
- package/dist/plugins/tidb-session-storage.js +4 -4
- package/dist/plugins/tidb-session-store.d.ts +4 -1
- package/dist/plugins/tidb-session-store.js +16 -15
- package/dist/plugins/tool-result-store-sql.d.ts +2 -2
- package/dist/plugins/tool-result-store-sql.js +3 -3
- package/dist/plugins/user-input-ledger-sql.js +2 -2
- package/dist/plugins/workflow-journal-store-sql.js +5 -5
- package/dist/plugins/workflow-run-store-sql.js +19 -19
- package/dist/plugins/write-behind-counter.d.ts +2 -3
- package/dist/plugins/write-behind-counter.js +5 -5
- package/dist/resource-suspend.d.ts +14 -12
- package/dist/resource-suspend.js +1 -1
- package/dist/resource-window.d.ts +4 -3
- package/dist/run-cancel-context.d.ts +34 -0
- package/dist/run-cancel-context.js +7 -0
- package/dist/run-local.js +14 -19
- package/dist/runs.d.ts +47 -16
- package/dist/runs.js +112 -113
- package/dist/security.d.ts +2 -2
- package/dist/server-secret-env.d.ts +2 -1
- package/dist/server-secret-env.js +2 -4
- package/dist/session-error-wire.d.ts +22 -4
- package/dist/session-error-wire.js +16 -0
- package/dist/session-incarnation.d.ts +10 -4
- package/dist/session-incarnation.js +1 -1
- package/dist/session-policy-wire.d.ts +1 -1
- 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/sql-engine-posture.d.ts +1 -1
- package/dist/sql-engine-posture.js +1 -1
- 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/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
- package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
- package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
- package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
- package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-a2a.js +5 -6
- package/dist/task-mcp.d.ts +21 -8
- package/dist/task-mcp.js +17 -9
- package/dist/task-settings.d.ts +52 -25
- package/dist/task-settings.js +91 -28
- package/dist/terminal.d.ts +103 -19
- package/dist/terminal.js +72 -20
- package/dist/trace/core-keyset-guard.d.ts +79 -2
- package/dist/trace/exit-seat.d.ts +7 -0
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/model-segment.d.ts +2 -2
- package/dist/trace/redact.d.ts +3 -1
- package/dist/trace/redact.js +395 -110
- package/dist/trace/self-minted-wire.d.ts +226 -2
- package/dist/trace/self-minted-wire.js +79 -0
- 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 +2 -2
- package/dist/web-admin-client/candidate-gate.d.ts +11 -7
- package/dist/web-admin-client/candidate-gate.js +1 -1
- package/dist/web-admin-client/http-client.d.ts +5 -4
- package/dist/web-admin-client/http-client.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +7 -17
- package/dist/web-admin-client/read-warnings.js +5 -9
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
- package/package.json +2 -2
- package/wire-changes/server@7.140.0.json +184 -0
- package/wire-changes/server@7.141.0.json +367 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 在飞 run 的**权限模式切档口** —— 引擎动词 `TaskStream.setPermissionMode(word)` 的 wire 面:
|
|
3
|
+
*
|
|
4
|
+
* `POST /v1/runs/:id/permission-mode` body `{ mode }`(`mode` ∈ 引擎 `PERMISSION_MODES` 闭集)
|
|
5
|
+
* → 200 `{ taskId, sessionId, status: "running", permissionMode, admission?, persisted, persistMiss? }`
|
|
6
|
+
*
|
|
7
|
+
* 语义 = 引擎的切档:改写这条腿的请求词,部署的折叠(`RuntimeCaps` 拒位 / 分类器席缺席)由引擎重判,**下一次**工具调用按新档仲裁;
|
|
8
|
+
* 进 `auto` 当场武装分类器,武装失败 ⇒ 引擎回滚(被替换的词照旧)并拒。`permissionMode` 是引擎回执原样(`{ requested?, effective, foldedBy? }`,
|
|
9
|
+
* 与 `wiring_manifest.permissionMode` 同形):每次都回,动没动都回 —— 壳渲染引擎说的,不渲染它自己发的。
|
|
10
|
+
*
|
|
11
|
+
* ── 一条规则:切档口与提交口是同一只受理判官(`permission-mode-admission.ts`)───────────────────────────────
|
|
12
|
+
* 交给引擎的不是调用方的原词,是受理判官对它的答:本任务 settings 的 kill-switch 折叠(读这条 run 的**存储体**)→ 运维 `AUTONOMY` 入口地板钳
|
|
13
|
+
* → 托管写围栏拒。提交口在受理时拒过 / 折过的词,切档口同样拒 / 折(两口一张表的钉在 `test/run-permission-mode-admission.test.ts`);
|
|
14
|
+
* 引擎在根腿上没有这三问的拒位,所以它们必须在这里答。折了 ⇒ 回执旁带 `admission: { requested, admitted, foldedBy }`(present-iff:本判官改了词);
|
|
15
|
+
* 拒 ⇒ 422 `request.host_posture_refused`(与提交口同码同句)。
|
|
16
|
+
*
|
|
17
|
+
* ── 回写:准入的词写回存储体(run 模式词的唯一来源)────────────────────────────────────────────────────────
|
|
18
|
+
* 续跑腿(停驻后续跑 / 重启后续跑)按存储体顶层 `permissionMode` 重建 spec;不写回 = 下一腿回到切档前的词。写回走**认领栅栏**
|
|
19
|
+
* (checkpoint 店的 `rewriteCtxWhileClaimed`:只在**接下切档的那条腿**此刻仍持有这条 run 时写,只改既有行)—— 这条 run 终局放掉认领、同会话新 run
|
|
20
|
+
* 已写下它自己的存储体之后,或这条 run 停驻、续跑的新腿(同一个 taskId 回到 running、认领仍在)已写下它续跑用的体之后,迟到的回写零写,
|
|
21
|
+
* 不会拿旧腿的体盖掉新腿的。腿身份 = {@link switchLegHolds}(栅栏里问)。同一条腿上的切档在本进程按活流对象串行(引擎受理序 = 落盘序;活流只在本副本)。
|
|
22
|
+
* 写不进(`persisted:false` + `persistMiss` ∈ {@link PERMISSION_MODE_PERSIST_MISSES})⇒ 本腿的切档照样生效、下一腿读到的是切档前的词:
|
|
23
|
+
* 一行运维 warn + fail-open 计数(`server.run-permission-mode.write-back-unpersisted`),壳据 `persisted` 判要不要在续跑后重切。
|
|
24
|
+
*
|
|
25
|
+
* ── 门(与 steer 同一组)──────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
* 零模型工作(不进 billable);mutating ⇒ `rateLimited`。属主门与 steer / interrupt 同一组放行臂(行经属主存取器 `readOwnedRun` 取,`owner_or_operator`):
|
|
27
|
+
* 显式 operator 可切任一租户的 run(监督动作),普通调用方只切自己的(非属主 404 同串,不给存在性 oracle);`?session=` 的受理期断言同族。非 live 四臂一码 409 `steering.not_running`
|
|
28
|
+
* (停驻 / 终局 / 他副本 / 本副本无活流);坏词 422 `steering.invalid_content`(引擎同码,受理前判)。能力位 `permissionModeWrite`
|
|
29
|
+
* = 事实 `permissionModeWrite`(持久 run 账面 ∧ checkpoint 店),与两道 501 同一只。
|
|
30
|
+
*/
|
|
31
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
32
|
+
import type { PermissionMode, PermissionModeReceipt } from "@sema-agent/core";
|
|
33
|
+
import { type FencedRun } from "../../plugins/store-contracts.js";
|
|
34
|
+
import { type RouteCtx, type RouteMatch, type RouteIdsOf } from "../route-ctx.js";
|
|
35
|
+
import { type PermissionModeAdmissionFold } from "../../permission-mode-admission.js";
|
|
36
|
+
/** 回写没落定的成因(闭集;wire `persistMiss`):`claim_released` 接下切档的那条腿这一刻已不持有这条 run(终局 / 停驻 / 已由续跑的新腿接手);
|
|
37
|
+
* `context_absent` 持有但会话没有存储体行;`store_failed` 店写抛(库抖 / 一行装不下 / 封签失败)。 */
|
|
38
|
+
export declare const PERMISSION_MODE_PERSIST_MISSES: readonly ["claim_released", "context_absent", "store_failed"];
|
|
39
|
+
export type PermissionModePersistMiss = (typeof PERMISSION_MODE_PERSIST_MISSES)[number];
|
|
40
|
+
/**
|
|
41
|
+
* **接下切档的那条腿此刻是否仍持有这条 run** —— 回写的腿身份,在认领栅栏里问(`rewriteCtxWhileClaimed` 的 `legHolds`,锁住 / 同步段里那一行的 {@link FencedRun})。
|
|
42
|
+
* 认领与 `running` 只认 taskId,续跑腿让同一个 taskId 停驻之后回到 `running`、认领仍在(ABA);分开两条腿的是:
|
|
43
|
+
* · run 行仍在本副本(`runProvenElsewhere` 的反面 —— 别的副本续跑了它,续跑的 CAS 赢点把行上的副本改成它自己);
|
|
44
|
+
* · 本副本这条 run 此刻登记的活流仍是接下切档的那一只(本副本上续跑的新腿登记的是另一只流对象;腿收场时按对象身份摘掉自己的)。
|
|
45
|
+
*/
|
|
46
|
+
export declare function switchLegHolds<S extends object>(fenced: FencedRun, thisInstanceId: string | undefined, liveNow: S | undefined, switched: S): boolean;
|
|
47
|
+
/** 本判官改了词时回执旁的那一格(`admission`)。 */
|
|
48
|
+
interface PermissionModeAdmissionNote {
|
|
49
|
+
readonly requested: PermissionMode;
|
|
50
|
+
readonly admitted: PermissionMode;
|
|
51
|
+
readonly foldedBy: PermissionModeAdmissionFold;
|
|
52
|
+
}
|
|
53
|
+
/** 200 体(键集属主:`trace/self-minted-wire.ts` 的 `permissionModeVerbBodyKey` 按它的键 `satisfies`)。`permissionMode` = 引擎回执原样。 */
|
|
54
|
+
export interface PermissionModeVerbReply {
|
|
55
|
+
readonly taskId: string;
|
|
56
|
+
readonly sessionId: string;
|
|
57
|
+
readonly status: "running";
|
|
58
|
+
readonly permissionMode: PermissionModeReceipt;
|
|
59
|
+
/** present-iff 受理判官改了词。 */
|
|
60
|
+
readonly admission?: PermissionModeAdmissionNote;
|
|
61
|
+
readonly persisted: boolean;
|
|
62
|
+
/** present-iff `persisted: false`。 */
|
|
63
|
+
readonly persistMiss?: PermissionModePersistMiss;
|
|
64
|
+
}
|
|
65
|
+
export declare function handlePermissionMode(req: IncomingMessage, res: ServerResponse, match: RouteMatch<RunPermissionModeRouteId>, ctx: RouteCtx): Promise<void>;
|
|
66
|
+
export declare const RUN_PERMISSION_MODE_ROUTES: readonly [{
|
|
67
|
+
readonly id: "run-permission-mode";
|
|
68
|
+
readonly pattern: RegExp;
|
|
69
|
+
readonly label: "/v1/runs/:id/permission-mode";
|
|
70
|
+
readonly methods: readonly ["POST"];
|
|
71
|
+
}];
|
|
72
|
+
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
73
|
+
export type RunPermissionModeRouteId = RouteIdsOf<typeof RUN_PERMISSION_MODE_ROUTES>;
|
|
74
|
+
export {};
|
|
75
|
+
//# sourceMappingURL=run-permission-mode.d.ts.map
|
|
@@ -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
|
|
@@ -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") {
|
|
@@ -7,6 +7,8 @@ import { seatText } from "../../trace/exit-seat.js";
|
|
|
7
7
|
import { recordFailOpen } from "../../observability/fail-open.js";
|
|
8
8
|
import { MCP_DIAL_DEADLINE_MS, raceDeadline } from "../mcp-dial-deadline.js";
|
|
9
9
|
import { sentMemoryComplianceFailure } from "../memory-compliance-failure-reply.js";
|
|
10
|
+
import { readLayerSeat, sentMemoryLayerRefusal } from "../memory-layer-reply.js";
|
|
11
|
+
import { foldPlaneStatuses } from "../../memory-operator-faces.js";
|
|
10
12
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
|
|
11
13
|
import { readWritableSessionPermissionRules } from "../../session-policy-wire.js";
|
|
12
14
|
import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders, httpErrorBody } from "../send.js";
|
|
@@ -524,14 +526,14 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
524
526
|
}
|
|
525
527
|
const auditFact = facts.sessionAuditView(deps);
|
|
526
528
|
if (!auditFact.available) {
|
|
527
|
-
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)");
|
|
528
530
|
return;
|
|
529
531
|
}
|
|
530
532
|
if ((await readOwnedSessionView(auditFact.sessionAudit, sessionId)) === undefined)
|
|
531
533
|
return;
|
|
532
534
|
const faces = deps.memoryCompliance;
|
|
533
535
|
if (faces === undefined) {
|
|
534
|
-
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)");
|
|
535
537
|
return;
|
|
536
538
|
}
|
|
537
539
|
const raw = (await readJson(req));
|
|
@@ -551,11 +553,17 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
551
553
|
sendError(res, 400, "request.field_invalid", "allowUnevidenced must be a boolean when present");
|
|
552
554
|
return;
|
|
553
555
|
}
|
|
556
|
+
const at = readLayerSeat(res, Object.prototype.hasOwnProperty.call(raw, "scope"), raw.scope);
|
|
557
|
+
if (at === null)
|
|
558
|
+
return;
|
|
559
|
+
const withheld = (coreMessage) => deps.logger?.warn?.("session_memory_erase_refused", { sessionId, err: coreMessage });
|
|
554
560
|
try {
|
|
555
|
-
|
|
561
|
+
const input = { requestId: raw.requestId, select: { sessionId }, ...(raw.allowUnevidenced !== undefined ? { allowUnevidenced: raw.allowUnevidenced } : {}) };
|
|
562
|
+
sendJson(res, 200, await (at !== undefined ? faces.erase(input, at) : faces.erase(input)));
|
|
556
563
|
}
|
|
557
564
|
catch (err) {
|
|
558
|
-
|
|
565
|
+
if (sentMemoryLayerRefusal(res, err, { kind: "session_owner", withheld }))
|
|
566
|
+
return;
|
|
559
567
|
if (sentMemoryComplianceFailure(res, err, { kind: "session_owner", withheld }, deps.logger))
|
|
560
568
|
return;
|
|
561
569
|
deps.logger?.warn?.("session_memory_erase_failed", { sessionId, err: String(err) });
|
|
@@ -577,33 +585,41 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
577
585
|
}
|
|
578
586
|
const auditFact = facts.sessionAuditView(deps);
|
|
579
587
|
if (!auditFact.available) {
|
|
580
|
-
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)");
|
|
581
589
|
return;
|
|
582
590
|
}
|
|
583
591
|
if ((await readOwnedSessionView(auditFact.sessionAudit, sessionId)) === undefined)
|
|
584
592
|
return;
|
|
585
593
|
const face = deps.sessionMemoryStatus;
|
|
586
594
|
if (face === undefined) {
|
|
587
|
-
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)");
|
|
588
596
|
return;
|
|
589
597
|
}
|
|
598
|
+
const query = new URLSearchParams(match.search);
|
|
599
|
+
const at = readLayerSeat(res, query.has("scope"), query.get("scope"));
|
|
600
|
+
if (at === null)
|
|
601
|
+
return;
|
|
590
602
|
try {
|
|
591
603
|
const { memoryStatusCache } = ctx.local.sessions;
|
|
592
|
-
|
|
593
|
-
|
|
604
|
+
const statusOf = (store) => {
|
|
605
|
+
const cacheKey = JSON.stringify([sessionId, store.controlPlane]);
|
|
606
|
+
const cached = memoryStatusCache.get(cacheKey);
|
|
607
|
+
if (cached && Date.now() - cached.at <= MEMORY_STATUS_TTL_MS)
|
|
608
|
+
return cached.status;
|
|
594
609
|
if (memoryStatusCache.size > 1024)
|
|
595
610
|
memoryStatusCache.clear();
|
|
596
|
-
const status =
|
|
611
|
+
const status = store.status(sessionId);
|
|
597
612
|
const fresh = { at: Date.now(), status };
|
|
598
|
-
memoryStatusCache.set(
|
|
599
|
-
status.catch(() => { if (memoryStatusCache.get(
|
|
600
|
-
memoryStatusCache.delete(
|
|
601
|
-
|
|
602
|
-
}
|
|
603
|
-
const s = await
|
|
613
|
+
memoryStatusCache.set(cacheKey, fresh);
|
|
614
|
+
status.catch(() => { if (memoryStatusCache.get(cacheKey) === fresh)
|
|
615
|
+
memoryStatusCache.delete(cacheKey); });
|
|
616
|
+
return status;
|
|
617
|
+
};
|
|
618
|
+
const s = foldPlaneStatuses(await Promise.all(face.storesAt(at).map(statusOf)));
|
|
604
619
|
sendJson(res, 200, {
|
|
605
620
|
sessionId,
|
|
606
621
|
autoConsolidationArmed: deps.memoryAutoConsolidationArmed,
|
|
622
|
+
...(deps.memorySyncLeg !== undefined ? { memorySync: { parked: deps.memorySyncLeg.parked().map((p) => ({ scope: p.scope, wayOut: p.wayOut })) } } : {}),
|
|
607
623
|
...(s.captureOptedOut !== undefined ? { captureOptedOut: s.captureOptedOut } : {}),
|
|
608
624
|
...(s.committedCount !== undefined ? { committedCount: s.committedCount } : {}),
|
|
609
625
|
...(s.foldedCount !== undefined ? { foldedCount: s.foldedCount } : {}),
|
|
@@ -612,6 +628,8 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
612
628
|
});
|
|
613
629
|
}
|
|
614
630
|
catch (err) {
|
|
631
|
+
if (sentMemoryLayerRefusal(res, err, { kind: "session_owner", withheld: (message) => deps.logger?.warn?.("session_memory_status_refused", { sessionId, err: message }) }))
|
|
632
|
+
return;
|
|
615
633
|
deps.logger?.warn?.("session_memory_status_failed", { sessionId, err: String(err) });
|
|
616
634
|
sendError(res, 500, "internal.error", "session memory status read failed");
|
|
617
635
|
}
|