@sema-agent/server 7.136.0 → 7.137.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 +173 -0
- package/USAGE.md +84 -16
- package/deploy/sema-up/README.md +14 -0
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
- package/dist/approval-content-kind.d.ts +6 -0
- package/dist/approval-content-kind.js +7 -0
- package/dist/approval-gate-currency.d.ts +48 -6
- package/dist/approval-gate-currency.js +16 -9
- package/dist/approval-hmac.d.ts +13 -1
- package/dist/approval-hmac.js +1 -1
- package/dist/approval.js +3 -3
- package/dist/auth-bridge.js +2 -2
- package/dist/boot/execution-env.d.ts +24 -1
- package/dist/boot/execution-env.js +69 -6
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +15 -1
- package/dist/boot/resolve-spec.d.ts +4 -0
- package/dist/boot/resolve-spec.js +16 -17
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +3 -1
- package/dist/boot/stage-05-execution-env.js +11 -2
- package/dist/boot/stage-06-runners.d.ts +3 -1
- package/dist/boot/stage-06-runners.js +5 -1
- package/dist/boot/stage-07-capability-layer.d.ts +4 -1
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-08-reapers.d.ts +4 -1
- package/dist/boot/stage-08-reapers.js +2 -1
- package/dist/boot/stage-09-leader.d.ts +4 -1
- package/dist/boot/stage-10-http-server.d.ts +4 -1
- package/dist/boot/stage-10-http-server.js +3 -2
- package/dist/boot/stores.d.ts +1 -1
- package/dist/broker/broker.d.ts +89 -0
- package/dist/broker/broker.js +287 -0
- package/dist/broker/constants.d.ts +39 -0
- package/dist/broker/constants.js +15 -0
- package/dist/broker/http1.d.ts +111 -0
- package/dist/broker/http1.js +256 -0
- package/dist/broker/lease-book.d.ts +87 -0
- package/dist/broker/lease-book.js +142 -0
- package/dist/broker/lease-file.d.ts +60 -0
- package/dist/broker/lease-file.js +127 -0
- package/dist/broker/main.d.ts +2 -0
- package/dist/broker/main.js +20 -0
- package/dist/capabilities/composite-members.d.ts +2 -1
- package/dist/capabilities/composite-members.js +2 -1
- package/dist/capabilities/engine-capabilities.js +1 -0
- package/dist/capabilities/scenarios.d.ts +23 -8
- package/dist/capabilities/scenarios.js +13 -8
- package/dist/capabilities/skills.d.ts +28 -5
- package/dist/capabilities/skills.js +8 -0
- package/dist/center-credential-renewer.js +5 -1
- package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
- package/dist/checkpoint-ctx-seal-m28.js +1 -1
- package/dist/checkpoint-identity.d.ts +3 -3
- package/dist/config-catalog.js +9 -6
- package/dist/config-types.d.ts +9 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +34 -8
- package/dist/deployment-governance.d.ts +2 -2
- package/dist/deployment-governance.js +7 -9
- package/dist/device-ws-protocol.d.ts +1 -0
- package/dist/device-ws-protocol.js +2 -1
- package/dist/file-history-disclosure.d.ts +4 -2
- package/dist/file-history-disclosure.js +2 -0
- package/dist/hooks/classic-stations.d.ts +11 -54
- package/dist/http/admission.js +10 -7
- package/dist/http/approval-sweeps.js +4 -3
- package/dist/http/caller-trust.d.ts +2 -1
- package/dist/http/caller-trust.js +1 -3
- package/dist/http/checkpoint-ctx.d.ts +48 -7
- package/dist/http/checkpoint-ctx.js +40 -0
- package/dist/http/dispatch.js +15 -10
- package/dist/http/durable-run-leg.d.ts +5 -1
- package/dist/http/durable-run-leg.js +1 -1
- package/dist/http/health-self-report.d.ts +2 -2
- package/dist/http/mcp-remote-text.d.ts +5 -0
- package/dist/http/mcp-remote-text.js +7 -0
- package/dist/http/principal-gate.d.ts +15 -10
- package/dist/http/principal-gate.js +48 -9
- package/dist/http/resume-legs.d.ts +2 -20
- package/dist/http/resume-legs.js +52 -38
- package/dist/http/route-ctx.d.ts +8 -2
- package/dist/http/routes/approvals-assistant.js +18 -4
- package/dist/http/routes/capabilities.js +11 -1
- package/dist/http/routes/mcp-reconnect.d.ts +1 -1
- package/dist/http/routes/mcp-reconnect.js +5 -7
- package/dist/http/routes/runs.js +2 -1
- package/dist/http/routes/session-sync.js +42 -6
- package/dist/http/routes/sessions.js +2 -1
- package/dist/http/routes/tasks.js +8 -6
- package/dist/http/server.d.ts +4 -0
- package/dist/http/stored-body-replay.d.ts +3 -3
- package/dist/http/stored-body-replay.js +6 -1
- package/dist/http/wire-types.d.ts +52 -27
- package/dist/identity-source.d.ts +65 -0
- package/dist/identity-source.js +45 -0
- package/dist/memory-store-refusal.d.ts +3 -3
- package/dist/model-entry-refusal.d.ts +2 -0
- package/dist/model-entry-refusal.js +11 -0
- package/dist/model-select.d.ts +6 -17
- package/dist/model-select.js +0 -3
- package/dist/namespaced-rule-name.d.ts +1 -1
- package/dist/observability/fail-open.d.ts +13 -1
- package/dist/observability/fail-open.js +13 -1
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal.js +1 -0
- package/dist/operator-ask.d.ts +2 -2
- package/dist/org-delivery-gate.js +1 -1
- package/dist/org-memory-admission.d.ts +2 -2
- package/dist/org-memory-admission.js +39 -2
- package/dist/parked-decide.js +3 -2
- package/dist/peer-directory.js +2 -0
- package/dist/permission-rule-list.d.ts +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +19 -9
- package/dist/plugins/checkpoint-store-sql.js +6 -6
- package/dist/plugins/file-history-store-sql.js +7 -14
- package/dist/plugins/file-run-store.js +8 -2
- package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
- package/dist/plugins/k8s-exec-protocol.js +116 -28
- package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
- package/dist/plugins/k8s-pod-delete-failure.js +17 -0
- package/dist/plugins/k8s-pod-reaper.d.ts +112 -0
- package/dist/plugins/k8s-pod-reaper.js +122 -0
- package/dist/plugins/local-checkpoint-store.d.ts +3 -6
- package/dist/plugins/local-checkpoint-store.js +3 -3
- package/dist/plugins/memory-run-store.js +2 -0
- package/dist/plugins/one-time-migrations.d.ts +28 -9
- package/dist/plugins/one-time-migrations.js +48 -5
- package/dist/plugins/permission-rule-store-file.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
- package/dist/plugins/permission-rule-store-sql.js +16 -17
- package/dist/plugins/pg-pool.js +4 -0
- package/dist/plugins/pg-session-storage.js +2 -2
- package/dist/plugins/remote-env-adb.d.ts +3 -0
- package/dist/plugins/remote-env-adb.js +1 -0
- package/dist/plugins/remote-env-device.d.ts +3 -0
- package/dist/plugins/remote-env-device.js +4 -0
- package/dist/plugins/remote-env-e2b.d.ts +3 -0
- package/dist/plugins/remote-env-e2b.js +1 -0
- package/dist/plugins/remote-env-file-error.d.ts +8 -1
- package/dist/plugins/remote-env-file-error.js +7 -1
- package/dist/plugins/remote-env-host.d.ts +7 -1
- package/dist/plugins/remote-env-host.js +7 -1
- package/dist/plugins/remote-env-k8s.d.ts +137 -10
- package/dist/plugins/remote-env-k8s.js +276 -14
- package/dist/plugins/remote-env-local-docker.d.ts +3 -0
- package/dist/plugins/remote-env-local-docker.js +1 -0
- package/dist/plugins/remote-env-ssh.d.ts +3 -0
- package/dist/plugins/remote-env-ssh.js +1 -0
- package/dist/plugins/run-store-sql.js +10 -4
- package/dist/plugins/session-ownership.d.ts +17 -3
- package/dist/plugins/session-ownership.js +19 -5
- package/dist/plugins/sql-errors.d.ts +23 -2
- package/dist/plugins/sql-errors.js +15 -0
- package/dist/plugins/store-contracts.d.ts +10 -1
- package/dist/plugins/store-fault.d.ts +2 -2
- package/dist/plugins/store-fault.js +4 -2
- package/dist/plugins/tidb-pool.js +7 -0
- package/dist/plugins/tidb-session-store.js +2 -2
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +31 -2
- package/dist/request-value-closure.js +7 -1
- package/dist/rule-entry-list.d.ts +3 -2
- package/dist/rule-entry-list.js +2 -2
- package/dist/rules-consent.js +7 -4
- package/dist/run-local.d.ts +2 -2
- package/dist/runs.js +2 -1
- package/dist/runtime-governance.d.ts +5 -5
- package/dist/sandbox-git-lease.d.ts +49 -0
- package/dist/sandbox-git-lease.js +60 -0
- package/dist/sandbox-git-plane.d.ts +84 -0
- package/dist/sandbox-git-plane.js +105 -0
- package/dist/security.d.ts +16 -4
- package/dist/security.js +8 -3
- package/dist/server-secret-env.d.ts +6 -0
- package/dist/server-secret-env.js +14 -0
- package/dist/session-error-wire.d.ts +14 -6
- package/dist/session-error-wire.js +2 -1
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-policy-wire.js +2 -2
- package/dist/session-sync.d.ts +1 -1
- package/dist/spec-fields.d.ts +43 -13
- package/dist/spec-fields.js +33 -12
- package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
- package/dist/stored-request-prompt-profile-m30.js +25 -0
- package/dist/task-mcp.d.ts +1 -1
- package/dist/task-settings.d.ts +2 -2
- package/dist/tool-approval.d.ts +12 -2
- package/dist/tool-approval.js +8 -3
- package/dist/tool-rule-names.d.ts +5 -0
- package/dist/trace/core-keyset-guard.d.ts +4 -4
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.js +2 -2
- package/dist/trace/ledger-sink.d.ts +4 -5
- package/dist/trace/ledger-sink.js +3 -7
- package/dist/trace/project.d.ts +37 -3
- package/dist/trace/project.js +20 -6
- package/dist/trace/redact.d.ts +18 -6
- package/dist/trace/redact.js +118 -53
- package/dist/trace/self-minted-wire.d.ts +377 -3
- package/dist/trace/self-minted-wire.js +122 -0
- package/dist/trace/wire-projection-faces.d.ts +5 -3
- package/dist/trace/wire-projection-faces.js +7 -3
- package/dist/web-admin-client/apply-effective.js +0 -1
- package/dist/web-admin-client/center-request.d.ts +20 -2
- package/dist/web-admin-client/center-request.js +40 -5
- package/dist/web-admin-client/http-client.js +6 -6
- package/dist/web-admin-client/types.d.ts +5 -5
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
- package/package.json +2 -2
- package/wire-changes/server@7.137.0.json +825 -0
|
@@ -193,7 +193,7 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
193
193
|
};
|
|
194
194
|
readonly "server.approvals.durable-row-checkpoint-id-missing": {
|
|
195
195
|
readonly cls: "F";
|
|
196
|
-
readonly note: "7.133.0(`checkpoint-identity.ts` 的 `durableRowIdentity`;两只店的 `listPending` + `/v1/assistant/inbox` handler):三处 durable 读面(运维队列行 `GET /v1/approvals` 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的行)读到一行停驻门**没有可用的 `checkpointId`** ⇒ 这一行**照列、`checkpointId` 键缺席**(消费方按「这一行没有可 join 的身份」处理)。detail = `face=<approvals-queue|assistant-inbox> why=<absent|untrusted>`:`absent` = 行上没有这一格(core 5.42.0 之前停驻的老行 —— core 明写不回填);`untrusted` = 在场却没过信任边界(非串 / 空串 / 超长 / 含 resume token —— 数据污染;没过边界的值不上 wire)。放行(列、键缺席)的最坏后果 = 消费方对这一行做不了跨面 join(三面对账少一行)—— 行本身、决断口(`/v1/approvals/:sessionId/decide`,按会话)、到期收割都照常。不放行有两种形:整只拒的代价是一行老 / 坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧);把这一行藏起来的代价更糟 —— 人看不到要批的东西(审批面上的 fail-open,方向反了)。故 F 类;必须留痕(两种成因从此可分)。计数非零 = 有老 / 坏行在飞(列着、没身份);`/stream` 每 3s 一拍重读,同一行每拍计一次(量值 = 行 × 拍数,在场才是信号)。";
|
|
196
|
+
readonly note: "7.133.0(`checkpoint-identity.ts` 的 `durableRowIdentity`;两只店的 `listPending` + `/v1/assistant/inbox` handler):三处 durable 读面(运维队列行 `GET /v1/approvals` 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的行)读到一行停驻门**没有可用的 `checkpointId`** ⇒ 这一行**照列、`checkpointId` 键缺席**(消费方按「这一行没有可 join 的身份」处理)。detail = `face=<approvals-queue|assistant-inbox> why=<absent|untrusted>`:`absent` = 行上没有这一格(core 5.42.0 之前停驻的老行 —— core 明写不回填);`untrusted` = 在场却没过信任边界(非串 / 空串 / 超长 / 含 resume token —— 数据污染;没过边界的值不上 wire)。放行(列、键缺席)的最坏后果 = 消费方对这一行做不了跨面 join(三面对账少一行)—— 行本身、决断口(`/v1/approvals/:sessionId/decide`,按会话)、到期收割都照常。不放行有两种形:整只拒的代价是一行老 / 坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧);把这一行藏起来的代价更糟 —— 人看不到要批的东西(审批面上的 fail-open,方向反了)。故 F 类;必须留痕(两种成因从此可分)。计数非零 = 有老 / 坏行在飞(列着、没身份);`/stream` 每 3s 一拍重读,同一行每拍计一次(量值 = 行 × 拍数,在场才是信号)。 7.137.0 起第四面:park 账本行(`GET /v1/runs/:id/events` 的 `suspended` / `needs_review`;唯一构造器 `trace/project.ts` 的 `parkEventData`,写时判一次,detail `face=run-events-park`)—— 暂停因由上的门身份没过信任边界 ⇒ 行照写、`checkpointId` 键缺席(决断方回显不了这道门,退回不带键的决断或从 run 结果取原值;行本身、门、待批调用身份照常)。";
|
|
197
197
|
};
|
|
198
198
|
readonly "server.approvals.queue-row-gate-kind-missing": {
|
|
199
199
|
readonly cls: "F";
|
|
@@ -499,6 +499,18 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
499
499
|
readonly cls: "F";
|
|
500
500
|
readonly note: "k8s 车道工作区空间结束时对**快照对象**的回收(`plugins/remote-env-k8s.ts` `createSpaceReclaimer` 的对象部分;与上一条同一只回收座、两条腿:会话删除协调器与托管留存 sweep):按空间的 `<前缀>/<化身>/space/` 分区(快照 + `latest`)和该会话停驻车道里名字带被结束住客归属记号的 `<前缀>/<会话 id>/*.tar.gz` 分别 LIST、逐个预签 DELETE;LIST 失败、DELETE 失败(HTTP 非 2xx / 非 404、网络)LIST 超过 1000 个对象被截断,或回收座一次调用的绝对截止(= 快照时限;卷回收也在内)到点〔切断在途请求、停掉余下的;这次调用里还没轮到的空间零请求、各记一次;没删的归生命周期规则〕,各记一次(detail `lane=space|park stage=list|delete|budget`;会话删除口把回收座脱离应答路径,座若抛了——契约禁止——记 `stage=seat_threw`)⇒ 这些对象留在桶里,会话照删(不挡删除)。放行的最坏后果 = 一份已无人能读的工作区快照留在桶里(会话已删,经 API 不可达;内容是用户工作区,所以不是「只占盘」—— 留到桶的生命周期规则把它过期掉为止);不碰任何别的空间、不是门不是凭据;反方向(清不掉对象就拒删会话)会让一个对象存储抖动挡住用户的删除权。故 F 类,但必须留痕:静默吞掉之后「这个会话删了、它的工作区内容还在不在桶里」在遥测里没有任何痕迹。同拍一行 `workspace_space_reclaim_failed`(`space` 前缀 / `sessionId` / `lane` / `stage` / `count` / `error`;一次调用里同类〔分区 × 阶段〕的失败合成一行,`count` = 撞上它的空间数,其余键点名调用里排第一的那个,计数仍逐空间;回收座同一时刻最多 4 个空间在收,sweep 与会话删除共用这一只门)。计数非零 = 有对象没回收掉,运维按行里的空间前缀 / 会话 id 去桶里手清,并核对象存储的可达性与 DELETE 权限。回收与同一空间的尚在途的 run 末提交不是互斥的:会话删除时没有活 run(409 挡),但上一条 run 的提交若恰在删除之后才落对象,这批对象不在本次回收射程内(同样归桶的生命周期规则)。停驻分区按会话 id 分区,所以只删名字带被结束住客归属记号的对象(不读任何钟)—— 会话 id 删后被别人重新登记,新住客停驻出的快照带它自己的记号、不碰;不带记号的旧形与 `none` 留给桶的生命周期规则。";
|
|
501
501
|
};
|
|
502
|
+
readonly "server.sandbox-secret.seat-unverified": {
|
|
503
|
+
readonly cls: "F";
|
|
504
|
+
readonly note: "k8s 车道沙箱秘密座的起服核(`boot/execution-env.ts` `ensureSandboxSecretSeat`,第 5 段 await;只在 `K8S_SANDBOX_ENV` 点到了本进程环境里有的键时问):对沙箱命名空间 secrets 的 env 座那一行动词(create —— 适配器为 env Secret 真发的调用)各发一次 SelfSubjectAccessReview,**问不到**(请求失败 / 审查本身被拒 / 答复没有布尔 `status.allowed`)而没有一个动词答「不许」⇒ 照常起服(答了「不许」⇒ 拒启,不走本 tag)。放行的最坏后果 = RBAC 真缺时第一个要建 Secret 的 run 在连接时被拒(`auth_failed`)、它的 pod 被删 —— 名单值仍然只经该 pod 自己的 Secret,不回落成 pod spec 里的明文;不是门不是凭据的放宽,只是把「缺 RBAC」从起服挪到了第一次连接。detail = `namespace=<ns>` + 每个没问到的动词与原因(不带值)。同拍一行 `sandbox_secret_seat_unverified`。计数非零 = 这台部署起服时没能确认 Secret 动词,按 detail 核 API 可达性与该服务账号能不能建 SelfSubjectAccessReview。";
|
|
505
|
+
};
|
|
506
|
+
readonly "server.sandbox-pod-reaper.list-failed": {
|
|
507
|
+
readonly cls: "F";
|
|
508
|
+
readonly note: "k8s 车道沙箱 Pod 回收器(`plugins/k8s-pod-reaper.ts` `reapOnce`,维护 tick 每副本每 5 分钟一轮)按身份标签列沙箱命名空间的 Pod **失败**(API 不可达 / 403 / 非 2xx)⇒ 这一轮一只都不删、不抛,下一轮一个周期后照常再列。最坏后果 = 已结束的沙箱 Pod 与已终 run 的 Pod(连同它们以 `ownerReferences` 挂着的 Secret —— 部署名单值 / 成员凭据租约)在 etcd 里多留至少一个周期:是**静态数据**多留,不是凭据被用 —— 成员凭据的使用界是代理的租约(到点 destroy,不随回收器);run 收尾的正常删除路不经本腿。不是门 / 凭据的放宽 ⇒ F。计数逐轮一次 + 探针行(`namespace=` + 原因)+ 同拍一行 warn `sandbox_pod_reap_list_failed`;修前只一行 info 轮次行(`listed:false`),不计数、不响。计数持续非零 = 回收器连续列不到 Pod,按原因核 API 可达性与 Role 的 pods `list`。";
|
|
509
|
+
};
|
|
510
|
+
readonly "server.sandbox-pod.delete-failed": {
|
|
511
|
+
readonly cls: "F";
|
|
512
|
+
readonly note: "k8s 车道**删一只沙箱 pod 却没删成**,两个座一只上报口(`plugins/k8s-pod-delete-failure.ts` `reportPodDeleteFailed`;detail = `namespace=<ns> pod=<名> why=<原因词> cause=<http_<状态>|request_failed>`):①连接清理座(`plugins/remote-env-k8s.ts` `discardRefusedPod`,`why` = `env_secret_unsealed|lease_secret_unsealed|broker_image_pull_failed`):env Secret / 租约 Secret 建不成、或代理镜像拉不下 ⇒ 连接被拒、该 pod 要删,而这一删本身失败(请求抛 / API 回 403、5xx 等非 2xx 非 404)⇒ 拒答不变(仍是原来那一条),pod 与挂在它名下的 Secret(部署名单值 / 租约占位)留在命名空间里,由回收器兜(`plugins/k8s-pod-reaper.ts`:带 run 标签的按 run 终态删,不带的等容器全部结束后删);②回收器座(`plugins/k8s-pod-reaper.ts` `reapOnce` 的删一步,`why` = `reaper_containers_ended|reaper_run_ended_past_hold`):兜底本身删失败 ⇒ 这一只留到下一轮(一个周期后)再判再删,一只失败不挡同轮其余。最坏后果 = **静态数据**多留至少一个回收周期(与 `server.sandbox-pod-reaper.list-failed` 同一件事的另一半):没有值进 pod spec、没有连接被放过、成员凭据的使用界是代理的租约 ⇒ 不是门 / 凭据的放宽 ⇒ F。计数逐次 + 探针行 + 同拍一行 error `sandbox_pod_delete_failed`(pod / 命名空间 / 原因词 / 原因码,不带值、不带 API 文本;运维行座缺席或只有 warn 的座上行不发、计数照记);修前连接清理三处 `.catch(() => {})` 吞,零日志零计数,回收器的删失败只落一行 warn(不计数)。计数持续非零 = 删 pod 的 Role 动词(pods `delete`)或 API 可达性有问题,按 `cause` 核;`why` 以 `reaper_` 起头的计数持续非零 = 兜底本身删不动,残留会随轮次累积。";
|
|
513
|
+
};
|
|
502
514
|
readonly "server.sandbox-egress.policy-gap": {
|
|
503
515
|
readonly cls: "P-DEBT";
|
|
504
516
|
readonly note: "k8s 车道沙箱出网的起服自检(`plugins/remote-env-k8s.ts` `checkSandboxEgressPolicy` 读节点表与沙箱 ns 的 NetworkPolicy,判法 `sandbox-egress-policy.ts` `sandboxEgressGaps`;起服一次、不挡起服)读出一个缺口 —— 某节点的 InternalIP / ExternalIP 落在一条放行 ipBlock 内(`kind=node_address_open`)、没有策略以 Egress 选中沙箱 pod(`kind=policy_absent`)、pod 保留 IPv6 而策略层担保不了 v6(`kind=ipv6_enabled`)、节点表或策略表读不到(`kind=unreadable what=nodes|networkpolicies`)⇒ 车道**照常起**,每个缺口一行 `sandbox_egress_policy_gap`(带后果句与去哪改)。归 P 类:出网策略是保护面,缺口意味着沙箱里的代码可能够到节点上的服务;归 DEBT 的理由:修法都在集群配置里(节点地址点名、kubelet 放行 v6 sysctl、CNI 的 v6 执行、RBAC),本服务改不了,而拒起车道会让缺省单节点装法(恒有 `ipv6_enabled`)升级即停摆沙箱执行 —— 车道自己的隔离(VM 边界、零 SA token)仍在。放行的最坏后果 = 读出缺口的那一面照旧可达,直到运维按行里的后果句改配置。计数非零 = 这台部署的沙箱出网面有读得出的缺口。读不出的面(宿主 DNAT 发布端口、「pod 所在节点恒放行」的实现差异、CNI 根本不执行策略)不在本 tag 的射程里。";
|
|
@@ -174,7 +174,7 @@ export const FAIL_OPEN_TAGS = {
|
|
|
174
174
|
},
|
|
175
175
|
"server.approvals.durable-row-checkpoint-id-missing": {
|
|
176
176
|
cls: "F",
|
|
177
|
-
note: "7.133.0(`checkpoint-identity.ts` 的 `durableRowIdentity`;两只店的 `listPending` + `/v1/assistant/inbox` handler):三处 durable 读面(运维队列行 `GET /v1/approvals` 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的行)读到一行停驻门**没有可用的 `checkpointId`** ⇒ 这一行**照列、`checkpointId` 键缺席**(消费方按「这一行没有可 join 的身份」处理)。detail = `face=<approvals-queue|assistant-inbox> why=<absent|untrusted>`:`absent` = 行上没有这一格(core 5.42.0 之前停驻的老行 —— core 明写不回填);`untrusted` = 在场却没过信任边界(非串 / 空串 / 超长 / 含 resume token —— 数据污染;没过边界的值不上 wire)。放行(列、键缺席)的最坏后果 = 消费方对这一行做不了跨面 join(三面对账少一行)—— 行本身、决断口(`/v1/approvals/:sessionId/decide`,按会话)、到期收割都照常。不放行有两种形:整只拒的代价是一行老 / 坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧);把这一行藏起来的代价更糟 —— 人看不到要批的东西(审批面上的 fail-open,方向反了)。故 F 类;必须留痕(两种成因从此可分)。计数非零 = 有老 / 坏行在飞(列着、没身份);`/stream` 每 3s 一拍重读,同一行每拍计一次(量值 = 行 × 拍数,在场才是信号)。",
|
|
177
|
+
note: "7.133.0(`checkpoint-identity.ts` 的 `durableRowIdentity`;两只店的 `listPending` + `/v1/assistant/inbox` handler):三处 durable 读面(运维队列行 `GET /v1/approvals` 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的行)读到一行停驻门**没有可用的 `checkpointId`** ⇒ 这一行**照列、`checkpointId` 键缺席**(消费方按「这一行没有可 join 的身份」处理)。detail = `face=<approvals-queue|assistant-inbox> why=<absent|untrusted>`:`absent` = 行上没有这一格(core 5.42.0 之前停驻的老行 —— core 明写不回填);`untrusted` = 在场却没过信任边界(非串 / 空串 / 超长 / 含 resume token —— 数据污染;没过边界的值不上 wire)。放行(列、键缺席)的最坏后果 = 消费方对这一行做不了跨面 join(三面对账少一行)—— 行本身、决断口(`/v1/approvals/:sessionId/decide`,按会话)、到期收割都照常。不放行有两种形:整只拒的代价是一行老 / 坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧);把这一行藏起来的代价更糟 —— 人看不到要批的东西(审批面上的 fail-open,方向反了)。故 F 类;必须留痕(两种成因从此可分)。计数非零 = 有老 / 坏行在飞(列着、没身份);`/stream` 每 3s 一拍重读,同一行每拍计一次(量值 = 行 × 拍数,在场才是信号)。 7.137.0 起第四面:park 账本行(`GET /v1/runs/:id/events` 的 `suspended` / `needs_review`;唯一构造器 `trace/project.ts` 的 `parkEventData`,写时判一次,detail `face=run-events-park`)—— 暂停因由上的门身份没过信任边界 ⇒ 行照写、`checkpointId` 键缺席(决断方回显不了这道门,退回不带键的决断或从 run 结果取原值;行本身、门、待批调用身份照常)。",
|
|
178
178
|
},
|
|
179
179
|
"server.approvals.queue-row-gate-kind-missing": {
|
|
180
180
|
cls: "F",
|
|
@@ -459,6 +459,18 @@ export const FAIL_OPEN_TAGS = {
|
|
|
459
459
|
cls: "F",
|
|
460
460
|
note: "k8s 车道工作区空间结束时对**快照对象**的回收(`plugins/remote-env-k8s.ts` `createSpaceReclaimer` 的对象部分;与上一条同一只回收座、两条腿:会话删除协调器与托管留存 sweep):按空间的 `<前缀>/<化身>/space/` 分区(快照 + `latest`)和该会话停驻车道里名字带被结束住客归属记号的 `<前缀>/<会话 id>/*.tar.gz` 分别 LIST、逐个预签 DELETE;LIST 失败、DELETE 失败(HTTP 非 2xx / 非 404、网络)LIST 超过 1000 个对象被截断,或回收座一次调用的绝对截止(= 快照时限;卷回收也在内)到点〔切断在途请求、停掉余下的;这次调用里还没轮到的空间零请求、各记一次;没删的归生命周期规则〕,各记一次(detail `lane=space|park stage=list|delete|budget`;会话删除口把回收座脱离应答路径,座若抛了——契约禁止——记 `stage=seat_threw`)⇒ 这些对象留在桶里,会话照删(不挡删除)。放行的最坏后果 = 一份已无人能读的工作区快照留在桶里(会话已删,经 API 不可达;内容是用户工作区,所以不是「只占盘」—— 留到桶的生命周期规则把它过期掉为止);不碰任何别的空间、不是门不是凭据;反方向(清不掉对象就拒删会话)会让一个对象存储抖动挡住用户的删除权。故 F 类,但必须留痕:静默吞掉之后「这个会话删了、它的工作区内容还在不在桶里」在遥测里没有任何痕迹。同拍一行 `workspace_space_reclaim_failed`(`space` 前缀 / `sessionId` / `lane` / `stage` / `count` / `error`;一次调用里同类〔分区 × 阶段〕的失败合成一行,`count` = 撞上它的空间数,其余键点名调用里排第一的那个,计数仍逐空间;回收座同一时刻最多 4 个空间在收,sweep 与会话删除共用这一只门)。计数非零 = 有对象没回收掉,运维按行里的空间前缀 / 会话 id 去桶里手清,并核对象存储的可达性与 DELETE 权限。回收与同一空间的尚在途的 run 末提交不是互斥的:会话删除时没有活 run(409 挡),但上一条 run 的提交若恰在删除之后才落对象,这批对象不在本次回收射程内(同样归桶的生命周期规则)。停驻分区按会话 id 分区,所以只删名字带被结束住客归属记号的对象(不读任何钟)—— 会话 id 删后被别人重新登记,新住客停驻出的快照带它自己的记号、不碰;不带记号的旧形与 `none` 留给桶的生命周期规则。",
|
|
461
461
|
},
|
|
462
|
+
"server.sandbox-secret.seat-unverified": {
|
|
463
|
+
cls: "F",
|
|
464
|
+
note: "k8s 车道沙箱秘密座的起服核(`boot/execution-env.ts` `ensureSandboxSecretSeat`,第 5 段 await;只在 `K8S_SANDBOX_ENV` 点到了本进程环境里有的键时问):对沙箱命名空间 secrets 的 env 座那一行动词(create —— 适配器为 env Secret 真发的调用)各发一次 SelfSubjectAccessReview,**问不到**(请求失败 / 审查本身被拒 / 答复没有布尔 `status.allowed`)而没有一个动词答「不许」⇒ 照常起服(答了「不许」⇒ 拒启,不走本 tag)。放行的最坏后果 = RBAC 真缺时第一个要建 Secret 的 run 在连接时被拒(`auth_failed`)、它的 pod 被删 —— 名单值仍然只经该 pod 自己的 Secret,不回落成 pod spec 里的明文;不是门不是凭据的放宽,只是把「缺 RBAC」从起服挪到了第一次连接。detail = `namespace=<ns>` + 每个没问到的动词与原因(不带值)。同拍一行 `sandbox_secret_seat_unverified`。计数非零 = 这台部署起服时没能确认 Secret 动词,按 detail 核 API 可达性与该服务账号能不能建 SelfSubjectAccessReview。",
|
|
465
|
+
},
|
|
466
|
+
"server.sandbox-pod-reaper.list-failed": {
|
|
467
|
+
cls: "F",
|
|
468
|
+
note: "k8s 车道沙箱 Pod 回收器(`plugins/k8s-pod-reaper.ts` `reapOnce`,维护 tick 每副本每 5 分钟一轮)按身份标签列沙箱命名空间的 Pod **失败**(API 不可达 / 403 / 非 2xx)⇒ 这一轮一只都不删、不抛,下一轮一个周期后照常再列。最坏后果 = 已结束的沙箱 Pod 与已终 run 的 Pod(连同它们以 `ownerReferences` 挂着的 Secret —— 部署名单值 / 成员凭据租约)在 etcd 里多留至少一个周期:是**静态数据**多留,不是凭据被用 —— 成员凭据的使用界是代理的租约(到点 destroy,不随回收器);run 收尾的正常删除路不经本腿。不是门 / 凭据的放宽 ⇒ F。计数逐轮一次 + 探针行(`namespace=` + 原因)+ 同拍一行 warn `sandbox_pod_reap_list_failed`;修前只一行 info 轮次行(`listed:false`),不计数、不响。计数持续非零 = 回收器连续列不到 Pod,按原因核 API 可达性与 Role 的 pods `list`。",
|
|
469
|
+
},
|
|
470
|
+
"server.sandbox-pod.delete-failed": {
|
|
471
|
+
cls: "F",
|
|
472
|
+
note: "k8s 车道**删一只沙箱 pod 却没删成**,两个座一只上报口(`plugins/k8s-pod-delete-failure.ts` `reportPodDeleteFailed`;detail = `namespace=<ns> pod=<名> why=<原因词> cause=<http_<状态>|request_failed>`):①连接清理座(`plugins/remote-env-k8s.ts` `discardRefusedPod`,`why` = `env_secret_unsealed|lease_secret_unsealed|broker_image_pull_failed`):env Secret / 租约 Secret 建不成、或代理镜像拉不下 ⇒ 连接被拒、该 pod 要删,而这一删本身失败(请求抛 / API 回 403、5xx 等非 2xx 非 404)⇒ 拒答不变(仍是原来那一条),pod 与挂在它名下的 Secret(部署名单值 / 租约占位)留在命名空间里,由回收器兜(`plugins/k8s-pod-reaper.ts`:带 run 标签的按 run 终态删,不带的等容器全部结束后删);②回收器座(`plugins/k8s-pod-reaper.ts` `reapOnce` 的删一步,`why` = `reaper_containers_ended|reaper_run_ended_past_hold`):兜底本身删失败 ⇒ 这一只留到下一轮(一个周期后)再判再删,一只失败不挡同轮其余。最坏后果 = **静态数据**多留至少一个回收周期(与 `server.sandbox-pod-reaper.list-failed` 同一件事的另一半):没有值进 pod spec、没有连接被放过、成员凭据的使用界是代理的租约 ⇒ 不是门 / 凭据的放宽 ⇒ F。计数逐次 + 探针行 + 同拍一行 error `sandbox_pod_delete_failed`(pod / 命名空间 / 原因词 / 原因码,不带值、不带 API 文本;运维行座缺席或只有 warn 的座上行不发、计数照记);修前连接清理三处 `.catch(() => {})` 吞,零日志零计数,回收器的删失败只落一行 warn(不计数)。计数持续非零 = 删 pod 的 Role 动词(pods `delete`)或 API 可达性有问题,按 `cause` 核;`why` 以 `reaper_` 起头的计数持续非零 = 兜底本身删不动,残留会随轮次累积。",
|
|
473
|
+
},
|
|
462
474
|
"server.sandbox-egress.policy-gap": {
|
|
463
475
|
cls: "P-DEBT",
|
|
464
476
|
note: "k8s 车道沙箱出网的起服自检(`plugins/remote-env-k8s.ts` `checkSandboxEgressPolicy` 读节点表与沙箱 ns 的 NetworkPolicy,判法 `sandbox-egress-policy.ts` `sandboxEgressGaps`;起服一次、不挡起服)读出一个缺口 —— 某节点的 InternalIP / ExternalIP 落在一条放行 ipBlock 内(`kind=node_address_open`)、没有策略以 Egress 选中沙箱 pod(`kind=policy_absent`)、pod 保留 IPv6 而策略层担保不了 v6(`kind=ipv6_enabled`)、节点表或策略表读不到(`kind=unreadable what=nodes|networkpolicies`)⇒ 车道**照常起**,每个缺口一行 `sandbox_egress_policy_gap`(带后果句与去哪改)。归 P 类:出网策略是保护面,缺口意味着沙箱里的代码可能够到节点上的服务;归 DEBT 的理由:修法都在集群配置里(节点地址点名、kubelet 放行 v6 sysctl、CNI 的 v6 执行、RBAC),本服务改不了,而拒起车道会让缺省单节点装法(恒有 `ipv6_enabled`)升级即停摆沙箱执行 —— 车道自己的隔离(VM 边界、零 SA token)仍在。放行的最坏后果 = 读出缺口的那一面照旧可达,直到运维按行里的后果句改配置。计数非零 = 这台部署的沙箱出网面有读得出的缺口。读不出的面(宿主 DNAT 发布端口、「pod 所在节点恒放行」的实现差异、CNI 根本不执行策略)不在本 tag 的射程里。",
|
|
@@ -262,7 +262,6 @@ export function createMetrics() {
|
|
|
262
262
|
m.counter("resume_anchor_capture_failed", "E18 resume-at per-turn anchor captures that failed (best-effort, run unaffected)");
|
|
263
263
|
m.counter("brain_retry_total", "Brain-layer status events (S4), by phase (rate_limited/retrying/reconnecting/circuit_open/waiting_first_token/recovered/gave_up)");
|
|
264
264
|
m.gauge("store_backend_degraded", "1 when the auto DB probe failed at boot and this replica degraded to in-memory (S5)");
|
|
265
|
-
m.counter("images_omitted_total", "Tasks whose images will be degraded to text placeholders (no-vision model), by model (S6)");
|
|
266
265
|
m.counter("runs_reaped_total", "Run rows flipped by the background reapers (S7), by kind (stale/suspended/expired_checkpoint)");
|
|
267
266
|
m.counter("checkpoints_reaped_total", "Durable checkpoints expired by the backstop reaper (S7)");
|
|
268
267
|
m.counter("user_input_converged_total", "User-input acceptances (S-928 ledger) converged to crashed by the maintenance tick (the boot pass reports its count on the user_input_ledger_crash_converged warn line instead): steers accepted whose writer process ended before the run settled");
|
|
@@ -303,6 +302,7 @@ export function createMetrics() {
|
|
|
303
302
|
m.counter("remote_env_exec_idle_timeout_total", "Remote-env exec idle timeouts, by provider");
|
|
304
303
|
m.counter("remote_env_file_timeout_total", "Remote-env file-op timeouts, by provider");
|
|
305
304
|
m.counter("remote_env_rpc_timeout_total", "Remote-env RPC timeouts, by provider");
|
|
305
|
+
m.counter("sandbox_pods_reaped_total", "Sandbox pods deleted by the k8s lane's pod reaper, by reason (containers_ended: every container finished; run_ended_past_hold: the pod's run ended longer ago than the hold)");
|
|
306
306
|
m.counter("task_outcomes_total", "Task outcome-ledger rows recorded, by status and oracle green flag");
|
|
307
307
|
m.counter("workflow_runs_total", "Self-orchestration workflow runs, by status");
|
|
308
308
|
m.histogram("auth_bridge_verify_seconds", "Registry SSO JWT verify latency (hot path sub-ms; cold path includes a JWKS fetch)", [0.001, 0.005, 0.02, 0.1, 0.5, 2, 10]);
|
package/dist/operator-ask.d.ts
CHANGED
|
@@ -82,8 +82,8 @@ export declare function requestAlwaysHumanAsk(rule: string, message: string): Op
|
|
|
82
82
|
* 只加一位、从不放宽)。同步叶保持同步返回;叶抛照抛(fail-closed 的属主仍是叶)。
|
|
83
83
|
*
|
|
84
84
|
* **只收叶**:治理层(`runtime-governance.ts` `applyRuntimeGovernance`)对每一片叶各盖一次,再交 core `combinePolicies` 折**一次**
|
|
85
|
-
* (折叠是最外层,其输出原样交回 —— core 把落选 ask 的类(含本位带出的 `operator_forced`)
|
|
86
|
-
* 把一只折叠交给本函数 =
|
|
85
|
+
* (折叠是最外层,其输出原样交回 —— core 把落选 ask 的类(含本位带出的 `operator_forced`)记进它铸的认证记录 `attestation.foldedClasses`)。
|
|
86
|
+
* 把一只折叠交给本函数 = 在折叠的输出上再盖一次出身(只改幸存者自己的类)—— [ref] 收成「逐叶盖、折一次」,治理层只有一个折叠点。
|
|
87
87
|
*/
|
|
88
88
|
export declare function createDeploymentAuthoredPolicy(leaf: ToolPolicy): ToolPolicy;
|
|
89
89
|
//# sourceMappingURL=operator-ask.d.ts.map
|
|
@@ -16,7 +16,7 @@ async function shareAnOrg(directory, a, b) {
|
|
|
16
16
|
return false;
|
|
17
17
|
return Object.keys(la.scopes).some((org) => Object.hasOwn(lb.scopes, org));
|
|
18
18
|
}
|
|
19
|
-
const principalShaped = (s) =>
|
|
19
|
+
const principalShaped = (s) => principalShapeProblem(s) === undefined;
|
|
20
20
|
const fitsInboxKey = (recipient) => mailboxKeyProblem("scope", recipient) === undefined;
|
|
21
21
|
export function createOrgDeliveryGate(deps) {
|
|
22
22
|
const nowMs = deps.nowMs ?? Date.now;
|
|
@@ -19,8 +19,8 @@ import type { MemoryScopeAdmission } from "@sema-agent/core";
|
|
|
19
19
|
/**
|
|
20
20
|
* 单机腿:装载 `MEMORY_ORG_DIRECTORY_JSON`(`Record<principal, Record<orgScope, {write?}>>`)。启动期调用,
|
|
21
21
|
* **fail-loud**——任何非法直接 throw 且消息点名坏在哪个键(响亮拒是正确方向:一张半坏的授权表比拒绝
|
|
22
|
-
* 启动更危险)。principal 键复用 {@link assertPrincipalShape}(
|
|
23
|
-
* principal
|
|
22
|
+
* 启动更危险)。principal 键复用 {@link assertPrincipalShape}(拒不是名字的串 / 保留哨兵 / 超长租户名,与所有其它
|
|
23
|
+
* principal 入口同一道闸;拒句按键在 JSON 文本里的出现序点名,不回显不是名字的键 —— 对象的枚举序会把整数形的键排到最前,不能拿它编号);scope 键必须是 `org:` 前缀、值必须是仅含可选 `write: boolean` 的 plain object。
|
|
24
24
|
*/
|
|
25
25
|
export declare function parseOrgDirectoryStatic(json: string): Map<string, Record<string, {
|
|
26
26
|
write?: boolean;
|
|
@@ -2,6 +2,37 @@ import { PrincipalOrgMemoryWire } from "@sema-agent/settings-schema";
|
|
|
2
2
|
import { assertPrincipalShape } from "./security.js";
|
|
3
3
|
const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
4
4
|
const ORG_SCOPE_RE = /^org:\S+$/;
|
|
5
|
+
function topLevelKeysInTextOrder(json) {
|
|
6
|
+
const keys = [];
|
|
7
|
+
let depth = 0;
|
|
8
|
+
let expectKey = false;
|
|
9
|
+
for (let i = 0; i < json.length; i++) {
|
|
10
|
+
const c = json[i];
|
|
11
|
+
if (c === '"') {
|
|
12
|
+
let j = i + 1;
|
|
13
|
+
while (json[j] !== '"')
|
|
14
|
+
j += json[j] === "\\" ? 2 : 1;
|
|
15
|
+
if (depth === 1 && expectKey) {
|
|
16
|
+
const key = JSON.parse(json.slice(i, j + 1));
|
|
17
|
+
if (typeof key !== "string")
|
|
18
|
+
throw new Error("MEMORY_ORG_DIRECTORY_JSON: a top-level key did not decode as a string (scanner out of step with the JSON text)");
|
|
19
|
+
keys.push(key);
|
|
20
|
+
expectKey = false;
|
|
21
|
+
}
|
|
22
|
+
i = j;
|
|
23
|
+
}
|
|
24
|
+
else if (c === "{" || c === "[") {
|
|
25
|
+
depth++;
|
|
26
|
+
if (depth === 1)
|
|
27
|
+
expectKey = c === "{";
|
|
28
|
+
}
|
|
29
|
+
else if (c === "}" || c === "]")
|
|
30
|
+
depth--;
|
|
31
|
+
else if (c === "," && depth === 1)
|
|
32
|
+
expectKey = true;
|
|
33
|
+
}
|
|
34
|
+
return keys;
|
|
35
|
+
}
|
|
5
36
|
export function parseOrgDirectoryStatic(json) {
|
|
6
37
|
let parsed;
|
|
7
38
|
try {
|
|
@@ -14,8 +45,14 @@ export function parseOrgDirectoryStatic(json) {
|
|
|
14
45
|
throw new Error("MEMORY_ORG_DIRECTORY_JSON must be a JSON object: Record<principal, Record<orgScope, {write?: boolean}>>");
|
|
15
46
|
}
|
|
16
47
|
const table = new Map();
|
|
17
|
-
for (const [
|
|
18
|
-
|
|
48
|
+
for (const [n, principal] of topLevelKeysInTextOrder(json).entries()) {
|
|
49
|
+
try {
|
|
50
|
+
assertPrincipalShape(principal);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
throw new Error(`MEMORY_ORG_DIRECTORY_JSON principal key #${n + 1} (in text order): ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
54
|
+
}
|
|
55
|
+
const scopesRaw = parsed[principal];
|
|
19
56
|
if (!isPlainObject(scopesRaw)) {
|
|
20
57
|
throw new Error(`MEMORY_ORG_DIRECTORY_JSON[${JSON.stringify(principal)}] must be an object of org scopes`);
|
|
21
58
|
}
|
package/dist/parked-decide.js
CHANGED
|
@@ -4,7 +4,7 @@ import { routeWorkflowPark } from "./orchestration/workflow-agent-session-index.
|
|
|
4
4
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
5
5
|
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
6
6
|
import { backgroundScopesForCheckpointScope, decodeCheckpointScope, ownerOfCheckpointScope } from "./security.js";
|
|
7
|
-
import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
|
|
7
|
+
import { ASK_USER_QUESTION_TOOL_NAME, isEngineQuestionRow } from "./approval-content-kind.js";
|
|
8
8
|
import { sessionFaceOfHead } from "./session-incarnation.js";
|
|
9
9
|
import { refusalBody, resumeRefusalFacts } from "./http/refusal-body.js";
|
|
10
10
|
export async function findParkOrigin(sources, cp) {
|
|
@@ -120,12 +120,13 @@ export function buildParkedResumeOutcome(req) {
|
|
|
120
120
|
const pa = req.pendingAction;
|
|
121
121
|
const persistedCallId = pa?.kind === "tool_approval" ? (pa.toolCallId ?? "") : "";
|
|
122
122
|
const persistedHash = pa?.kind === "tool_approval" ? (pa.boundInputHash ?? "") : "";
|
|
123
|
+
const engineQuestion = isEngineQuestionRow(req.pendingAction);
|
|
123
124
|
return {
|
|
124
125
|
gate: "policy_ask",
|
|
125
126
|
boundCallId: req.binding?.boundCallId ?? persistedCallId,
|
|
126
127
|
boundInputHash: req.binding?.boundInputHash ?? persistedHash,
|
|
127
128
|
decision: req.decision === "approve" ? "allow" : "deny",
|
|
128
|
-
...(req.decision === "approve" && req.binding?.updatedInput !== undefined ? { updatedInput: req.binding.updatedInput } : {}),
|
|
129
|
+
...(req.decision === "approve" && !engineQuestion && req.binding?.updatedInput !== undefined ? { updatedInput: req.binding.updatedInput } : {}),
|
|
129
130
|
...(req.binding?.feedback !== undefined ? { feedback: req.binding.feedback } : {}),
|
|
130
131
|
...(req.answer !== undefined ? { answer: req.answer } : {}),
|
|
131
132
|
...(req.reason !== undefined ? { reason: req.reason } : {}),
|
package/dist/peer-directory.js
CHANGED
|
@@ -85,6 +85,7 @@ export function createPeerDirectory(ctx) {
|
|
|
85
85
|
startedAt: Number.isFinite(started) ? started : 0,
|
|
86
86
|
updatedAt: Number.isFinite(updated) ? updated : 0,
|
|
87
87
|
liveness: "live",
|
|
88
|
+
where: "hosted",
|
|
88
89
|
...(tempo !== undefined ? { tempo } : {}),
|
|
89
90
|
inboundPosture: posture,
|
|
90
91
|
});
|
|
@@ -125,6 +126,7 @@ export function createPeerDirectory(ctx) {
|
|
|
125
126
|
startedAt: t.retiredAt,
|
|
126
127
|
updatedAt: t.retiredAt,
|
|
127
128
|
liveness: "deleted",
|
|
129
|
+
where: "hosted",
|
|
128
130
|
diedAt: t.retiredAt,
|
|
129
131
|
inboundPosture: posture,
|
|
130
132
|
});
|
|
@@ -40,7 +40,7 @@ export declare function buildRuleListJudgeOptions(pathBases: PathRuleBases | und
|
|
|
40
40
|
* `namespacedCoveringEntries/Hit`。同族分家,差的只是解析器。
|
|
41
41
|
*
|
|
42
42
|
* ⇒ **规则表的 policy 面现在同源**:两种态度都由 `createPermissionRulePolicy` 的 covering 车道解,底下是
|
|
43
|
-
* core 的**同一只** `
|
|
43
|
+
* core 的**同一只** `ruleNameCovers`(协议无关:`mcp__`/`a2a__` 同解;peer 段精确比;工具段 `*`/glob 覆盖)。
|
|
44
44
|
* 本仓**不自写第二套 glob** —— 那正是当年分家的成因。
|
|
45
45
|
*
|
|
46
46
|
* ⚠️ 「同源」的**射程**说准了免得下一个人当成全仓结论(codex 交叉轮 finding-1 追出来的边):
|
|
@@ -51,7 +51,7 @@ export declare function buildRuleListJudgeOptions(pathBases: PathRuleBases | und
|
|
|
51
51
|
* 所以各表收 `mcp__figma__*`(等价、且被推荐)、拒 `mcp__figma`。
|
|
52
52
|
* · **同源的射程已扩到 operator 名单腿**([ref] 孪生修,[ref]):`createDurableAskPolicy`(`src/approval.ts`)的
|
|
53
53
|
* deny/neverAuto/require 三集合过去是逐字 `Set.has`,自 core 5.60.1 谓词根导出后改成「精确 ∪ covering」,用的是
|
|
54
|
-
* **同一只** `
|
|
54
|
+
* **同一只** `ruleNameCovers`。⇒ env `APPROVAL_*` 腿 / 中心下发 `runtime.approvalRequire` 腿 / 规则表三家
|
|
55
55
|
* 现在对 covering 拼法的解读逐字一致,`mcp__figma__*` 在哪条腿上都是真判。
|
|
56
56
|
* · **两段形为什么仍不放开**(理由已换,别再引「approval.ts 还不认」):它一名两义——「整台服务器」与「三段名写漏一段」
|
|
57
57
|
* 在 boot 期不可分辨,所以各条腿一律响亮拒 + 指引给无歧义的 `mcp__figma__*`。放宽是各腿同时变宽的 behavior-facing
|
|
@@ -4,14 +4,27 @@ import { type ExecutionOutcomeRecordWord, type GateOutcome, type Checkpoint, typ
|
|
|
4
4
|
import { type ParkRowFacts, type RuleOfferProjection, type SkillCardFacts } from "../approval-card.js";
|
|
5
5
|
import { type SqlDialect, type SqlDriver } from "./sql-driver.js";
|
|
6
6
|
import type { SealedCheckpointCtx } from "../http/checkpoint-ctx.js";
|
|
7
|
-
/**
|
|
8
|
-
export interface
|
|
7
|
+
/** ctx 行改写店腿的读数(两孪生同形):扫到的可读行 · 回写成的行 · 回写成的行里改写函数报的改动数 · 读不出的行 · 改写之后一行装不下的行。 */
|
|
8
|
+
export interface CtxRowsRewrite {
|
|
9
9
|
readonly scanned: number;
|
|
10
10
|
readonly rewritten: number;
|
|
11
11
|
readonly valuesRewritten: number;
|
|
12
12
|
readonly unreadable: number;
|
|
13
13
|
readonly tooLarge: number;
|
|
14
14
|
}
|
|
15
|
+
/** 一行的改写:交回新的落盘形(只有封口模块的铸点产得出 —— 品牌型)与这一行的改动数;`undefined` = 这一行不写。 */
|
|
16
|
+
export type CtxRowRewrite = (sessionId: string, ctx: object) => {
|
|
17
|
+
readonly ctx: SealedCheckpointCtx;
|
|
18
|
+
readonly values: number;
|
|
19
|
+
} | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* 🔴 **一次性迁移的 ctx 行改写店腿**(两孪生:`SqlCheckpointStore` / `LocalCheckpointStore` 的 `rewriteCtxRows`;逐行判词两孪生同):扫全部 `checkpoint_ctx` 行、
|
|
22
|
+
* 交给 `rewrite`、交回新体的行原地回写(续跑时钟不动)。店不知道改写是什么 —— 用它的迁移各自带行改写:M28(凭据位明文封好)、M30(退役请求键改写)。
|
|
23
|
+
* 窗与删法逐条在 `plugins/one-time-migrations.ts` 登记表;登记表里最后一条用它的迁移删码时,本腿、读数型与扫描页一起删。
|
|
24
|
+
*/
|
|
25
|
+
export interface CheckpointCtxRowsRewriter {
|
|
26
|
+
rewriteCtxRows(rewrite: CtxRowRewrite): Promise<CtxRowsRewrite>;
|
|
27
|
+
}
|
|
15
28
|
export { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
|
|
16
29
|
/**
|
|
17
30
|
* [ref] D-1 (§3 invariant #3 — crash-safe reaper backstop): an ABSOLUTE upper bound on a pending
|
|
@@ -832,17 +845,14 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
832
845
|
putCtx(sessionId: string, ctx: SealedCheckpointCtx): Promise<void>;
|
|
833
846
|
getCtx<T extends object = object>(sessionId: string): Promise<T | null>;
|
|
834
847
|
/**
|
|
835
|
-
* 🔴
|
|
836
|
-
*
|
|
848
|
+
* 🔴 **一次性迁移的 ctx 行改写店腿**({@link CheckpointCtxRowsRewriter};用它的迁移与各自的行改写见那里,窗与删法见 `plugins/one-time-migrations.ts` 登记表;
|
|
849
|
+
* 本店不知道改写是什么,只负责扫与回写):按 `session_id` 键集分页扫 `checkpoint_ctx`
|
|
837
850
|
* (每页 {@link CTX_REWRITE_PAGE} 行,只取会话 id 与 ctx 文本),行体经读腿**同一只**判形({@link decodeCtxColumn})—— 读不出 ⇒ 计数、跳过、不动;
|
|
838
851
|
* `rewrite` 交回新体 ⇒ 经写腿同一只编码与形闸,**按原文 CAS** 回写(MySQL 协议两边 `CAST(… AS BINARY)`,与 M17 / M18 同一条理由:连接排序规则大小写
|
|
839
852
|
* 不敏感;PG `text` 等值)。`updated_at_ms` **不动**(收割按它判,迁移不许延长或缩短一行的寿命)。CAS 打空(扫描与回写之间这一行被续跑腿 / 别的副本写过)
|
|
840
|
-
* ⇒ 不计、不重试(
|
|
853
|
+
* ⇒ 不计、不重试(新写者是本版写下的、已是新形;是旧版副本就由之后的起服再扫到)。一行装不下(如封签比明文长)⇒ 计 `tooLarge`、那一行留原样,其余照扫。
|
|
841
854
|
*/
|
|
842
|
-
|
|
843
|
-
readonly ctx: SealedCheckpointCtx;
|
|
844
|
-
readonly values: number;
|
|
845
|
-
} | undefined): Promise<CtxRowsRewriteM28>;
|
|
855
|
+
rewriteCtxRows(rewrite: CtxRowRewrite): Promise<CtxRowsRewrite>;
|
|
846
856
|
/**
|
|
847
857
|
* GC ctx rows whose lifecycle is over (bound to the checkpoint, don't leak):
|
|
848
858
|
* a session with NO pending checkpoint, NO active run, AND not touched since `cutoff`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parkedApprovalCard } from "../approval-content-kind.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { CHECKPOINT_FACET_SQL, CHECKPOINT_SWEEP_ORDER_SQL, CheckpointIndexRefusedError, judgeCheckpointIndexCells } from "./checkpoint-index-columns.js";
|
|
4
|
-
import { CheckpointError, isGateKind, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, MAX_SUPPORTED_CHECKPOINT_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, } from "@sema-agent/core";
|
|
4
|
+
import { CheckpointError, isGateKind, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, checkpointRevOf, MAX_SUPPORTED_CHECKPOINT_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, } from "@sema-agent/core";
|
|
5
5
|
import { redactDeep, redactSecrets } from "../trace/redact.js";
|
|
6
6
|
import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, parkRowCarriesSkillCardFacts, parkRowFacts, readRuleOfferList, skillCardFacts } from "../approval-card.js";
|
|
7
7
|
import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readJsonArrayColumn, readJsonObjectColumn, readNonNullJsonValueColumn, } from "./sql-json-column.js";
|
|
@@ -412,7 +412,7 @@ export class SqlCheckpointStore {
|
|
|
412
412
|
cells.risk_descriptor === null ? null : this.json(cells.risk_descriptor, "risk descriptor"),
|
|
413
413
|
ruleOffers === undefined ? null : this.json(ruleOffers, "rule offers"),
|
|
414
414
|
cells.has_bidi_controls,
|
|
415
|
-
cp
|
|
415
|
+
checkpointRevOf(cp),
|
|
416
416
|
cp.reopenReason ?? null,
|
|
417
417
|
executionOutcome,
|
|
418
418
|
executionAtMs,
|
|
@@ -738,7 +738,7 @@ export class SqlCheckpointStore {
|
|
|
738
738
|
const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "ctx")} FROM checkpoint_ctx WHERE session_id=?`, `SELECT ${jsonColumnText("pg", "ctx")} FROM checkpoint_ctx WHERE session_id=$1`), [sessionId]);
|
|
739
739
|
return rows[0] ? refusing("a resume context nobody can read", () => decodeCtxColumn(rows[0].ctx, `for session ${JSON.stringify(sessionId)}`)) : null;
|
|
740
740
|
}
|
|
741
|
-
async
|
|
741
|
+
async rewriteCtxRows(rewrite) {
|
|
742
742
|
const out = { scanned: 0, rewritten: 0, valuesRewritten: 0, unreadable: 0, tooLarge: 0 };
|
|
743
743
|
let after = "";
|
|
744
744
|
for (;;) {
|
|
@@ -748,7 +748,7 @@ export class SqlCheckpointStore {
|
|
|
748
748
|
after = sessionId;
|
|
749
749
|
let ctx;
|
|
750
750
|
try {
|
|
751
|
-
ctx = decodeCtxColumn(row.ctx, `for session ${JSON.stringify(sessionId)} (
|
|
751
|
+
ctx = decodeCtxColumn(row.ctx, `for session ${JSON.stringify(sessionId)} (ctx row rewrite scan)`);
|
|
752
752
|
}
|
|
753
753
|
catch {
|
|
754
754
|
out.unreadable += 1;
|
|
@@ -759,13 +759,13 @@ export class SqlCheckpointStore {
|
|
|
759
759
|
if (next === undefined)
|
|
760
760
|
continue;
|
|
761
761
|
const text = this.json(next.ctx, "checkpoint ctx");
|
|
762
|
-
assertJsonColumnWritable(text, createObjectColumnReader(), `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (
|
|
762
|
+
assertJsonColumnWritable(text, createObjectColumnReader(), `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (ctx row rewrite)`);
|
|
763
763
|
let affected;
|
|
764
764
|
try {
|
|
765
765
|
({ affected } = await this.db.query(this.q("UPDATE checkpoint_ctx SET ctx = ? WHERE session_id = ? AND CAST(CAST(ctx AS CHAR) AS BINARY) = CAST(? AS BINARY)", "UPDATE checkpoint_ctx SET ctx = $1::jsonb WHERE session_id = $2 AND ctx::text = $3"), [text, sessionId, String(row.ctx)]));
|
|
766
766
|
}
|
|
767
767
|
catch (e) {
|
|
768
|
-
const folded = rowTooLargeOrSelf(this.db.dialect, e, `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (
|
|
768
|
+
const folded = rowTooLargeOrSelf(this.db.dialect, e, `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (ctx row rewrite)`);
|
|
769
769
|
if (!(folded instanceof StoreRowTooLargeError))
|
|
770
770
|
throw folded;
|
|
771
771
|
out.tooLarge += 1;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { captureFileBackupViaEnv, applyFileRestoreViaEnv, fileHistoryModeProvenDifferent, worldStillMatchesMint, boundaryPublishVerdict, fileHistoryDescribeTrackKey, fileHistoryTrackKeyOf, fileHistoryResolveTrackKey, fileHistoryExportDigest, validateFileHistoryExport, resolveFileHistoryRetention, fileHistoryBoundariesToKeep, } from "@sema-agent/core";
|
|
2
|
+
import { captureFileBackupViaEnv, applyFileRestoreViaEnv, fileHistoryModeProvenDifferent, worldStillMatchesMint, annulProofFailure, boundaryPublishVerdict, fileHistoryDescribeTrackKey, fileHistoryTrackKeyOf, fileHistoryResolveTrackKey, fileHistoryExportDigest, validateFileHistoryExport, resolveFileHistoryRetention, fileHistoryBoundariesToKeep, } from "@sema-agent/core";
|
|
3
3
|
import { BlobTooLargeError, SqlBlobBackend } from "./blob-backend.js";
|
|
4
4
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
5
5
|
import { isMissingColumnError } from "./sql-errors.js";
|
|
@@ -149,15 +149,8 @@ export class SqlFileHistoryStore {
|
|
|
149
149
|
const baseAvailable = truthy(t.rows[0].base_available);
|
|
150
150
|
const mintedBlobHash = v1.rows[0]?.blob_hash != null ? String(v1.rows[0].blob_hash) : null;
|
|
151
151
|
const proof = await worldStillMatchesMint(verify.env, absPath, root, baseAvailable, mintedBlobHash, verify.signal);
|
|
152
|
-
if (
|
|
153
|
-
return
|
|
154
|
-
ok: false,
|
|
155
|
-
error: {
|
|
156
|
-
code: "conflict",
|
|
157
|
-
message: `first-touch record for ${absPath} is NOT retractable: ${proof.reason} — the write this retraction compensates for may have changed the file, and deleting the only baseline of a modified file is worse than keeping a stale record`,
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
}
|
|
152
|
+
if (proof.verdict !== "unchanged")
|
|
153
|
+
return annulProofFailure(proof, absPath);
|
|
161
154
|
}
|
|
162
155
|
const conn = await this.db.connect();
|
|
163
156
|
try {
|
|
@@ -441,7 +434,7 @@ export class SqlFileHistoryStore {
|
|
|
441
434
|
let targetVersion = boundary.get(t.keyHash);
|
|
442
435
|
if (targetVersion === undefined) {
|
|
443
436
|
if (!t.baseAvailable) {
|
|
444
|
-
ledger.refused.push({ path: t.trackKey, reason: "base_unavailable" });
|
|
437
|
+
ledger.refused.push({ path: t.trackKey, reason: "base_unavailable", class: "base_unavailable" });
|
|
445
438
|
continue;
|
|
446
439
|
}
|
|
447
440
|
targetVersion = 1;
|
|
@@ -452,12 +445,12 @@ export class SqlFileHistoryStore {
|
|
|
452
445
|
continue;
|
|
453
446
|
}
|
|
454
447
|
if (rec.mode !== undefined && !(Number.isInteger(rec.mode) && rec.mode >= 0 && rec.mode <= 0o7777)) {
|
|
455
|
-
ledger.
|
|
448
|
+
ledger.failed.push({ path: t.trackKey, reason: `recorded mode ${String(rec.mode)} is not a valid POSIX permission value (store integrity) — refusing to apply a corrupted permission record` });
|
|
456
449
|
continue;
|
|
457
450
|
}
|
|
458
451
|
const resolved = fileHistoryResolveTrackKey(root, t.trackKey, t.keyDesc);
|
|
459
452
|
if (!resolved.ok) {
|
|
460
|
-
ledger.refused.push({ path: t.trackKey, reason: resolved.reason });
|
|
453
|
+
ledger.refused.push({ path: t.trackKey, reason: resolved.reason, class: resolved.class });
|
|
461
454
|
continue;
|
|
462
455
|
}
|
|
463
456
|
let applied;
|
|
@@ -488,7 +481,7 @@ export class SqlFileHistoryStore {
|
|
|
488
481
|
else if (applied.outcome === "identical")
|
|
489
482
|
ledger.identicalSkipped.push(t.trackKey);
|
|
490
483
|
else if (applied.outcome === "refused")
|
|
491
|
-
ledger.refused.push({ path: t.trackKey, reason: applied.reason });
|
|
484
|
+
ledger.refused.push({ path: t.trackKey, reason: applied.reason, class: applied.class });
|
|
492
485
|
else
|
|
493
486
|
ledger.failed.push({ path: t.trackKey, reason: applied.reason });
|
|
494
487
|
if ((applied.outcome === "applied" || applied.outcome === "identical") && applied.modeNotPreserved === true)
|
|
@@ -9,6 +9,7 @@ import { recordFailOpen } from "../observability/fail-open.js";
|
|
|
9
9
|
import { replayJsonlSkipping } from "../observability/corrupt-read-seat.js";
|
|
10
10
|
import { RUN_HOLDER_DEATH_CODES, RUN_ORPHANED_ERROR, RUN_STALLED_ERROR, StoreBootRefusalError, buildRunOrphanedError, readLegacyHolderDeathCode } from "./store-contracts.js";
|
|
11
11
|
import { notifyRunTerminal } from "../observability/run-terminal.js";
|
|
12
|
+
import { readRunIdentityCells, runIdentityCells } from "../identity-source.js";
|
|
12
13
|
import { createInProcessSpaceCommitTable, fenceSpaceCommitAtTerminal, SPACE_COMMIT_RECORD_SCHEMA } from "../workspace-space-commit.js";
|
|
13
14
|
const ownerEq = (a, b) => (a ?? null) === (b ?? null);
|
|
14
15
|
const isParseableDate = (v) => typeof v === "string" && !Number.isNaN(Date.parse(v));
|
|
@@ -140,16 +141,19 @@ export class FileRunStore {
|
|
|
140
141
|
return this.spaceCommits.abandonSpaceCommit(spaceKey, taskId);
|
|
141
142
|
}
|
|
142
143
|
toRow(d) {
|
|
144
|
+
const { identitySource, identityScope, identityPrincipalInc, ...rest } = d;
|
|
143
145
|
return {
|
|
144
|
-
...
|
|
146
|
+
...rest,
|
|
145
147
|
idemKey: typeof d.idemKey === "string" ? d.idemKey : null,
|
|
146
148
|
idemBodyFingerprint: typeof d.idemBodyFingerprint === "string" ? d.idemBodyFingerprint : null,
|
|
149
|
+
identity: readRunIdentityCells(identitySource, identityScope, identityPrincipalInc),
|
|
147
150
|
createdAt: new Date(d.createdAt),
|
|
148
151
|
updatedAt: new Date(d.updatedAt),
|
|
149
152
|
};
|
|
150
153
|
}
|
|
151
154
|
toDisk(r) {
|
|
152
|
-
|
|
155
|
+
const { identity, ...rest } = r;
|
|
156
|
+
return { ...rest, ...runIdentityCells(identity), createdAt: r.createdAt.toISOString(), updatedAt: r.updatedAt.toISOString() };
|
|
153
157
|
}
|
|
154
158
|
toRecord(r) {
|
|
155
159
|
return {
|
|
@@ -162,6 +166,7 @@ export class FileRunStore {
|
|
|
162
166
|
errorCode: r.errorCode,
|
|
163
167
|
jobId: r.jobId,
|
|
164
168
|
source: r.source,
|
|
169
|
+
identity: r.identity,
|
|
165
170
|
instanceId: r.instanceId,
|
|
166
171
|
cancelRequested: r.cancelRequested,
|
|
167
172
|
objectivePreview: r.objectivePreview,
|
|
@@ -376,6 +381,7 @@ export class FileRunStore {
|
|
|
376
381
|
objectivePreview: meta.objectivePreview ?? null,
|
|
377
382
|
idemKey,
|
|
378
383
|
idemBodyFingerprint: meta.idem?.bodyFingerprint ?? null,
|
|
384
|
+
identity: meta.identity ?? null,
|
|
379
385
|
createdAt: now,
|
|
380
386
|
updatedAt: now,
|
|
381
387
|
cancelRequested: false,
|
|
@@ -1,9 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
* k8s adapter's protocol/patch layer, independent of pod lifecycle. Split out of `remote-env-k8s.ts` ([ref]
|
|
5
|
-
* A13, internal-lossless — `remote-env-k8s.ts` re-exports every symbol below unchanged).
|
|
2
|
+
* The identity labels every sandbox pod carries — the ONE spelling (RBAC selectors, the reaper's listing and the egress self-check key on
|
|
3
|
+
* them; a pod-spec patch can add labels, never set or replace one of these).
|
|
6
4
|
*/
|
|
5
|
+
export declare const SANDBOX_POD_LABELS: {
|
|
6
|
+
readonly app: "sema-sandbox";
|
|
7
|
+
readonly "managed-by": "sema-server";
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The pod's owning run: label key, value = the server run id (the task id) of the TOP-LEVEL run whose env created the pod. Minted only by the
|
|
11
|
+
* adapter from `K8sEnvConfig.runId` (the per-run binding decides who has one); absent ⇒ the pod names no run, and the reaper removes it only once
|
|
12
|
+
* its containers have ended. An identity label: a patch can neither set nor replace it.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SANDBOX_RUN_LABEL = "sema.io/run";
|
|
15
|
+
/**
|
|
16
|
+
* The pod's own reaper hold: annotation key, value = the decimal milliseconds the reaper must wait after the owning run turned terminal before it
|
|
17
|
+
* removes the pod (`k8s-pod-reaper.ts` `sandboxRunEndHoldMs` of the CREATING replica's data bound). Written once, by the adapter, on every pod that
|
|
18
|
+
* carries {@link SANDBOX_RUN_LABEL} (the hold is only read for those); the reaper reads this and nothing else — never its own replica's config, so a
|
|
19
|
+
* rolling change of the snapshot bound never shortens another replica's pod. A pod-spec patch reaches labels only, never annotations.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SANDBOX_RUN_END_HOLD_ANNOTATION = "sema.io/run-end-hold-ms";
|
|
22
|
+
/** The two per-pod Secret seats: `env` = the pod's env Secret (the values `K8S_SANDBOX_ENV` names); `lease` = the member-credential broker's lease Secret. */
|
|
23
|
+
export type SandboxSecretSeatKind = "env" | "lease";
|
|
24
|
+
/**
|
|
25
|
+
* Per seat, the secrets verbs the adapter sends for it in the sandbox namespace — the ONE table, and each row is exactly that seat's calls:
|
|
26
|
+
* `env` = `create` (the env Secret, POSTed after its pod, never touched again); `lease` = `create` (the lease Secret) + `patch` (the lease rewrite,
|
|
27
|
+
* a merge-patch of `broker.json`). Nothing else: the adapter never reads a Secret back (no `get` — which would also let the service account read
|
|
28
|
+
* every Secret in the namespace) and never deletes one (no `delete`) — each Secret is owned by its pod (`ownerReferences`, `blockOwnerDeletion:
|
|
29
|
+
* false`), so the cluster's garbage collector removes it with the pod under its own identity. A seat in play asks only its own row: the boot gate
|
|
30
|
+
* (`K8S_SANDBOX_ENV` non-empty) asks `env`, the member-credential plane judge asks `lease` — an env-only deployment is never asked for `patch`.
|
|
31
|
+
* Readers: the SelfSubjectAccessReview (`readSandboxSecretSeat`), the refusal sentences that name the Role rule, the plane's off-reason text, the
|
|
32
|
+
* catalog sentence. `test/k8s-sandbox-secret.test.ts` pins row ≡ calls per seat, both directions.
|
|
33
|
+
*/
|
|
34
|
+
export declare const SANDBOX_SECRET_SEAT_VERBS: {
|
|
35
|
+
readonly env: readonly ["create"];
|
|
36
|
+
readonly lease: readonly ["create", "patch"];
|
|
37
|
+
};
|
|
38
|
+
export type SandboxSecretVerb = (typeof SANDBOX_SECRET_SEAT_VERBS)[SandboxSecretSeatKind][number];
|
|
39
|
+
export declare function isLabelValue(v: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The volume names the adapter itself mints on a sandbox pod — the ONE table (a patch volume / volumeMount must not reuse one: it would collide
|
|
42
|
+
* with, or mount, the adapter's own volume). Today: the space's build-cache claim and the member-credential broker's lease volume. A volume the
|
|
43
|
+
* adapter adds later is added HERE, and the patch judge below refuses it with no second guard.
|
|
44
|
+
*/
|
|
45
|
+
export declare const ADAPTER_VOLUME_NAMES: {
|
|
46
|
+
readonly cache: "ws-cache";
|
|
47
|
+
readonly brokerLease: "sema-broker-lease";
|
|
48
|
+
};
|
|
7
49
|
/**
|
|
8
50
|
* The confined, ADDITIVE pod-spec patch. Every field is purely additive —
|
|
9
51
|
* none can override the hard isolation invariants. This is the seam the RSI L8 immutable-mount probe + the
|
|
@@ -22,19 +64,32 @@ export interface PodSpecPatch {
|
|
|
22
64
|
/** Extra labels merged into metadata.labels (e.g. a grader/probe tag). */
|
|
23
65
|
labels?: Record<string, string>;
|
|
24
66
|
}
|
|
25
|
-
|
|
26
|
-
* Apply a {@link PodSpecPatch} to a sandbox pod object IN PLACE — purely ADDITIVE. Volumes + volumeMounts are
|
|
27
|
-
* APPENDED (never replaced); securityContext objects are shallow-MERGED onto any existing; labels are merged. It
|
|
28
|
-
* deliberately touches ONLY spec.volumes / spec.securityContext / containers[0].{volumeMounts,securityContext} /
|
|
29
|
-
* metadata.labels — so the hard isolation invariants (runtimeClassName, automountServiceAccountToken,
|
|
30
|
-
* restartPolicy, activeDeadlineSeconds, image, command) are preserved by construction. Pure + exported for testing.
|
|
31
|
-
*/
|
|
32
|
-
export declare function applyPodSpecPatch(pod: {
|
|
67
|
+
type SandboxPodShape = {
|
|
33
68
|
metadata: {
|
|
34
69
|
labels?: Record<string, string>;
|
|
35
70
|
};
|
|
36
71
|
spec: Record<string, unknown>;
|
|
37
|
-
}
|
|
72
|
+
};
|
|
73
|
+
/** Which pod a patch lands on: one that carries the broker container or not. */
|
|
74
|
+
export interface PodPatchPresence {
|
|
75
|
+
readonly broker: boolean;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Apply a {@link PodSpecPatch} to a sandbox pod object IN PLACE — purely ADDITIVE, through the one field table
|
|
79
|
+
* ({@link POD_SPEC_PATCH_FIELDS}): a key outside the table is refused; EVERY present field is judged before ANY lands (a refusal leaves the pod
|
|
80
|
+
* byte-identical). Volumes + volumeMounts are APPENDED (never replaced); securityContext objects are shallow-MERGED onto any existing; labels are
|
|
81
|
+
* merged under the identity keys. It touches ONLY spec.volumes / spec.securityContext / containers[0].{volumeMounts,securityContext} /
|
|
82
|
+
* metadata.labels — so the hard isolation invariants (runtimeClassName, automountServiceAccountToken, restartPolicy, activeDeadlineSeconds, image,
|
|
83
|
+
* command) are preserved by construction. Throws (the caller surfaces it as a provisioning failure). Pure + exported for testing.
|
|
84
|
+
* `presence.broker` ⇒ the table's broker column judges first (see {@link POD_SPEC_PATCH_FIELDS}).
|
|
85
|
+
*/
|
|
86
|
+
export declare function applyPodSpecPatch(pod: SandboxPodShape, patch: PodSpecPatch, presence?: PodPatchPresence): void;
|
|
87
|
+
/**
|
|
88
|
+
* The patch judge alone — the same one {@link applyPodSpecPatch} runs — for a pod that carries the broker: the refusal sentence, or `undefined`
|
|
89
|
+
* when the patch may land. The boot reads it on the deployment's static patch (a refusal turns the member-credential plane off,
|
|
90
|
+
* `pod_patch_reaches_broker`); the adapter runs it again on every pod it builds with the broker.
|
|
91
|
+
*/
|
|
92
|
+
export declare function podSpecPatchBrokerRefusal(patch: PodSpecPatch | undefined): string | undefined;
|
|
38
93
|
/**
|
|
39
94
|
* TLS options for the exec WebSocket, shaped for BOTH runtimes (2026-06-11 in-cluster drill finding):
|
|
40
95
|
* Bun's `ws` shim IGNORES node-ws's top-level `ca`/`rejectUnauthorized` (handshake fails against a
|
|
@@ -50,4 +105,5 @@ export declare function exitCodeFromStatus(statusJson: string): {
|
|
|
50
105
|
} | {
|
|
51
106
|
error: string;
|
|
52
107
|
};
|
|
108
|
+
export {};
|
|
53
109
|
//# sourceMappingURL=k8s-exec-protocol.d.ts.map
|