@sema-agent/server 7.151.0 → 7.152.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 +266 -0
- package/README.md +15 -3
- package/README.zh-CN.md +10 -2
- package/USAGE.md +93 -64
- package/deploy/sema-up/README.md +5 -4
- package/deploy/sema-up/cluster-up.sh +6 -6
- package/deploy/sema-up/kube-up.sh +5 -5
- package/deploy/sema-up/lib/flagshape.sh +36 -8
- package/deploy/sema-up/machines.example.yaml +3 -1
- package/deploy/sema-up/sema-up.sh +22 -16
- package/dist/approval-ask-audit-store.d.ts +1 -1
- package/dist/approval-ask-machine.d.ts +4 -10
- package/dist/approval-ask-retention.d.ts +1 -1
- package/dist/approval-card.d.ts +10 -12
- package/dist/approval-gate-currency.d.ts +8 -8
- package/dist/approval-gate-currency.js +3 -4
- package/dist/approval-hmac.d.ts +17 -1
- package/dist/approval-hmac.js +7 -1
- package/dist/approval-queue-kind.d.ts +1 -1
- package/dist/approval.d.ts +2 -2
- package/dist/approval.js +2 -2
- package/dist/bind-host.d.ts +1 -3
- package/dist/bind-host.js +2 -3
- package/dist/boot/coordinators.d.ts +1 -1
- package/dist/boot/coordinators.js +9 -10
- package/dist/boot/device-lane.js +2 -0
- package/dist/boot/execution-env.d.ts +1 -0
- package/dist/boot/execution-env.js +31 -5
- package/dist/boot/leader.d.ts +31 -30
- package/dist/boot/leader.js +38 -28
- package/dist/boot/leg-path-adjudication.d.ts +40 -0
- package/dist/boot/leg-path-adjudication.js +18 -0
- package/dist/boot/parked-revive-gate.js +3 -15
- package/dist/boot/reapers.d.ts +1 -1
- package/dist/boot/reapers.js +1 -1
- package/dist/boot/resolve-spec.js +12 -26
- package/dist/boot/runner-deps.d.ts +39 -35
- package/dist/boot/runner-deps.js +92 -89
- package/dist/boot/shutdown.d.ts +6 -0
- package/dist/boot/shutdown.js +10 -4
- package/dist/boot/stage-02-web-admin-pull.js +4 -5
- package/dist/boot/stage-05-execution-env.js +2 -1
- package/dist/boot/stage-06-runners.d.ts +2 -0
- package/dist/boot/stage-06-runners.js +10 -4
- package/dist/boot/stage-07-capability-layer.d.ts +1 -0
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-08-reapers.d.ts +1 -0
- package/dist/boot/stage-09-leader.d.ts +1 -0
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +8 -0
- package/dist/boot/stage-10-http-server.js +11 -15
- package/dist/boot/stores.js +7 -4
- package/dist/boot/web-admin.js +2 -7
- package/dist/capabilities/approvals-push.d.ts +3 -3
- package/dist/capabilities/scenarios.d.ts +28 -13
- package/dist/capabilities/scenarios.js +2 -2
- package/dist/capability-facts.d.ts +22 -20
- package/dist/capability-facts.js +18 -34
- package/dist/card-rule-offers.d.ts +32 -0
- package/dist/card-rule-offers.js +130 -0
- package/dist/config-catalog.js +12 -11
- package/dist/config-dir-trust.js +4 -2
- package/dist/config-invariants.d.ts +18 -7
- package/dist/config-invariants.js +51 -8
- package/dist/config-lkg.d.ts +5 -19
- package/dist/config-lkg.js +8 -62
- package/dist/config-provider.d.ts +1 -1
- package/dist/config-root-migration.d.ts +2 -2
- package/dist/config-types.d.ts +28 -38
- package/dist/config.d.ts +7 -5
- package/dist/config.js +55 -16
- package/dist/control-socket.d.ts +51 -0
- package/dist/control-socket.js +115 -0
- package/dist/deployment-governance.d.ts +2 -1
- package/dist/device-ws-hub.d.ts +7 -0
- package/dist/device-ws-hub.js +7 -1
- package/dist/elicitation.js +2 -2
- package/dist/entry-problems.d.ts +40 -3
- package/dist/entry-problems.js +3 -1
- package/dist/execution-env-refusal.d.ts +8 -2
- package/dist/execution-env-refusal.js +3 -0
- package/dist/fleet/engine-signal.d.ts +12 -29
- package/dist/fleet/engine-signal.js +1 -7
- package/dist/fleet-client.js +2 -5
- package/dist/gate-settlement.d.ts +1 -8
- package/dist/gate-settlement.js +0 -4
- package/dist/history-anchor.js +1 -1
- package/dist/hitl-delivery.d.ts +2 -3
- package/dist/hitl-delivery.js +0 -2
- package/dist/hooks/hook-llm.js +4 -2
- package/dist/hooks/hook-runner.d.ts +3 -2
- package/dist/hooks/hook-runner.js +16 -1
- package/dist/host-decision.d.ts +11 -11
- package/dist/http/active-run-conflict.d.ts +35 -2
- package/dist/http/active-run-conflict.js +48 -19
- package/dist/http/approval-sweeps.d.ts +1 -1
- package/dist/http/approval-sweeps.js +5 -2
- package/dist/http/checkpoint-ctx.d.ts +2 -0
- package/dist/http/checkpoint-ctx.js +1 -0
- package/dist/http/dispatch.d.ts +6 -2
- package/dist/http/dispatch.js +25 -5
- package/dist/http/durable-run-leg.js +0 -1
- package/dist/http/health-reader.d.ts +16 -9
- package/dist/http/health-reader.js +2 -1
- package/dist/http/health-self-report.js +1 -1
- package/dist/http/owner-access.d.ts +2 -3
- package/dist/http/owner-access.js +3 -4
- package/dist/http/principal-gate.d.ts +49 -21
- package/dist/http/principal-gate.js +61 -11
- package/dist/http/request-channel.d.ts +23 -0
- package/dist/http/request-channel.js +9 -0
- package/dist/http/request-guards.js +17 -2
- package/dist/http/resume-ctx-read.js +20 -3
- package/dist/http/resume-legs.d.ts +2 -2
- package/dist/http/resume-legs.js +111 -41
- package/dist/http/route-ctx.d.ts +73 -7
- package/dist/http/route-ctx.js +17 -0
- package/dist/http/route-table.d.ts +4 -0
- package/dist/http/route-table.js +4 -0
- package/dist/http/routes/admin-config-refresh.js +3 -3
- package/dist/http/routes/admin-drain.d.ts +3 -0
- package/dist/http/routes/admin-drain.js +7 -7
- package/dist/http/routes/admin-memory-optout.js +3 -3
- package/dist/http/routes/adoption.js +3 -5
- package/dist/http/routes/approvals-assistant.d.ts +2 -1
- package/dist/http/routes/approvals-assistant.js +95 -48
- package/dist/http/routes/capabilities.d.ts +4 -0
- package/dist/http/routes/capabilities.js +30 -5
- package/dist/http/routes/config-catalog.js +2 -2
- package/dist/http/routes/devices.js +1 -1
- package/dist/http/routes/diagnostics.d.ts +3 -3
- package/dist/http/routes/diagnostics.js +19 -7
- package/dist/http/routes/images.js +10 -10
- package/dist/http/routes/memory-bundle.js +3 -3
- package/dist/http/routes/memory-compliance.js +3 -3
- package/dist/http/routes/memory-consolidation.js +2 -2
- package/dist/http/routes/memory-origin.js +3 -3
- package/dist/http/routes/memory-policy.js +2 -2
- package/dist/http/routes/observability.js +3 -3
- package/dist/http/routes/retention-ops.js +3 -3
- package/dist/http/routes/rules.js +5 -5
- package/dist/http/routes/run-permission-mode.d.ts +2 -1
- package/dist/http/routes/run-permission-mode.js +2 -2
- package/dist/http/routes/runs.d.ts +4 -9
- package/dist/http/routes/runs.js +73 -33
- package/dist/http/routes/session-sync.d.ts +18 -6
- package/dist/http/routes/session-sync.js +38 -36
- package/dist/http/routes/sessions.js +20 -9
- package/dist/http/routes/tasks.js +28 -37
- package/dist/http/routes/workflows.js +1 -1
- package/dist/http/server.d.ts +9 -3
- package/dist/http/server.js +15 -3
- package/dist/http/submit-ctx.d.ts +1 -1
- package/dist/http/submit-ctx.js +2 -0
- package/dist/human-facing-args.d.ts +33 -0
- package/dist/human-facing-args.js +33 -0
- package/dist/index.d.ts +1 -1
- package/dist/k8s-api-credentials.d.ts +29 -0
- package/dist/k8s-api-credentials.js +25 -0
- package/dist/lane-home-probe.d.ts +1 -1
- package/dist/lane-home-probe.js +2 -1
- package/dist/leader/diffup.d.ts +19 -0
- package/dist/leader/diffup.js +7 -0
- package/dist/leader/leader.d.ts +114 -10
- package/dist/leader/leader.js +69 -5
- package/dist/leader/repair-wire.js +2 -2
- package/dist/leader/run-entry.d.ts +2 -1
- package/dist/leader/run-entry.js +1 -1
- package/dist/leader/wire.d.ts +42 -132
- package/dist/leader/wire.js +81 -67
- package/dist/memory-scope.d.ts +20 -9
- package/dist/memory-scope.js +15 -3
- package/dist/model-entry-headers.d.ts +9 -10
- package/dist/model-entry-headers.js +0 -4
- package/dist/model-entry-refusal.d.ts +7 -4
- package/dist/model-entry-refusal.js +11 -5
- package/dist/model-plane-face.d.ts +1 -1
- package/dist/model-plane-face.js +3 -1
- package/dist/model-route-endpoint.d.ts +13 -7
- package/dist/model-route-endpoint.js +14 -11
- package/dist/model-select.d.ts +22 -0
- package/dist/model-select.js +9 -0
- package/dist/model-unreachable-notice.d.ts +11 -0
- package/dist/model-unreachable-notice.js +4 -4
- package/dist/object-store-buckets.d.ts +640 -6
- package/dist/object-store-buckets.js +166 -0
- package/dist/observability/fail-open.d.ts +20 -16
- package/dist/observability/fail-open.js +20 -16
- package/dist/observability/logger.js +3 -1
- package/dist/operator-roster.d.ts +68 -0
- package/dist/operator-roster.js +32 -0
- package/dist/orchestration/workflow-park-decision.d.ts +1 -1
- package/dist/outbound-dispatcher.d.ts +71 -8
- package/dist/outbound-dispatcher.js +48 -7
- package/dist/owner-faces.d.ts +1 -1
- package/dist/parked-decide.d.ts +2 -2
- package/dist/permission-mode-admission.d.ts +59 -26
- package/dist/permission-mode-admission.js +37 -10
- package/dist/plugins/approval-ask-store-file.d.ts +4 -0
- package/dist/plugins/approval-ask-store-file.js +3 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +4 -0
- package/dist/plugins/approval-ask-store-memory.js +7 -1
- package/dist/plugins/approval-ask-store-sql.d.ts +26 -2
- package/dist/plugins/approval-ask-store-sql.js +12 -1
- package/dist/plugins/blob-backend.d.ts +83 -7
- package/dist/plugins/blob-backend.js +57 -6
- package/dist/plugins/checkpoint-index-columns.d.ts +20 -16
- package/dist/plugins/checkpoint-index-columns.js +3 -6
- package/dist/plugins/checkpoint-store-face.d.ts +119 -61
- package/dist/plugins/checkpoint-store-face.js +134 -55
- package/dist/plugins/landed-attendants.d.ts +2 -2
- package/dist/plugins/local-checkpoint-store.d.ts +13 -3
- package/dist/plugins/local-checkpoint-store.js +38 -49
- package/dist/plugins/one-time-migrations.d.ts +51 -21
- package/dist/plugins/one-time-migrations.js +67 -45
- package/dist/plugins/pg-pool.js +11 -9
- package/dist/plugins/remote-env-host.d.ts +29 -10
- package/dist/plugins/remote-env-host.js +99 -19
- package/dist/plugins/remote-env-k8s.d.ts +68 -2
- package/dist/plugins/remote-env-k8s.js +107 -24
- package/dist/plugins/replica-registration.d.ts +1 -1
- package/dist/plugins/replica-registration.js +1 -1
- package/dist/plugins/s3-presign.d.ts +23 -0
- package/dist/plugins/s3-presign.js +37 -3
- package/dist/plugins/scheduler-support.d.ts +2 -2
- package/dist/plugins/scheduler-support.js +0 -3
- package/dist/plugins/schema-indexes.js +2 -0
- package/dist/plugins/send-file-ledger.d.ts +2 -2
- package/dist/plugins/send-file-ledger.js +2 -2
- package/dist/plugins/send-user-file.d.ts +3 -4
- package/dist/plugins/send-user-file.js +56 -52
- package/dist/plugins/session-blob-claim-token.d.ts +15 -0
- package/dist/plugins/session-blob-claim-token.js +20 -0
- package/dist/plugins/session-blob-namespace.d.ts +46 -0
- package/dist/plugins/session-blob-namespace.js +116 -0
- package/dist/plugins/snapshot-bucket-presence.d.ts +25 -2
- package/dist/plugins/snapshot-bucket-presence.js +8 -2
- package/dist/plugins/sql-json-column.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +44 -2
- package/dist/plugins/store-backend.js +75 -53
- package/dist/plugins/task-attachment-store.d.ts +2 -1
- package/dist/plugins/task-attachment-store.js +5 -1
- package/dist/plugins/tidb-pool.js +19 -24
- package/dist/plugins/workspace-custody.d.ts +4 -3
- package/dist/question.d.ts +2 -1
- package/dist/question.js +5 -13
- package/dist/request-form.d.ts +6 -1
- package/dist/request-form.js +4 -1
- package/dist/rule-issue-reading.d.ts +1 -1
- package/dist/rules-consent.d.ts +1 -1
- package/dist/run-local.js +8 -9
- package/dist/runs.d.ts +17 -8
- package/dist/runs.js +15 -2
- package/dist/runtime-governance.d.ts +2 -2
- package/dist/sema-config-dir.d.ts +2 -1
- package/dist/session-sync.d.ts +27 -11
- package/dist/session-sync.js +15 -7
- package/dist/stores-sql/blob-index-sql.d.ts +30 -9
- package/dist/stores-sql/blob-index-sql.js +40 -21
- package/dist/stores-sql/checkpoint-store-sql.d.ts +54 -27
- package/dist/stores-sql/checkpoint-store-sql.js +70 -146
- package/dist/stores-sql/file-history-store-sql.d.ts +64 -16
- package/dist/stores-sql/file-history-store-sql.js +100 -28
- package/dist/stores-sql/retention-store-sql.js +3 -9
- package/dist/task-settings.d.ts +6 -5
- package/dist/task-settings.js +3 -3
- package/dist/tool-approval.d.ts +342 -518
- package/dist/tool-approval.js +498 -1207
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/ledger-events.d.ts +3 -3
- package/dist/trace/project.d.ts +2 -3
- package/dist/trace/redact.js +3 -2
- package/dist/trace/self-minted-wire.d.ts +287 -157
- package/dist/trace/self-minted-wire.js +93 -48
- package/dist/web-admin-client/apply-effective.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +2 -2
- package/dist/web-admin-client/read-warnings.js +1 -1
- package/dist/web-admin-client/types.d.ts +1 -1
- package/dist/workspace-space.d.ts +10 -4
- package/dist/workspace-space.js +4 -1
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +1 -1
- package/package.json +6 -2
- package/scripts/embed-minimal.mjs +3 -3
- package/wire-changes/server@7.152.0.json +900 -0
- package/dist/config-d-migration.d.ts +0 -37
- package/dist/config-d-migration.js +0 -60
package/dist/config-types.d.ts
CHANGED
|
@@ -398,20 +398,16 @@ export interface A2aServeConfig {
|
|
|
398
398
|
* off (the control plane never mounts). The runner-facing facts (the runner principal, the build-host CACHE_BASE,
|
|
399
399
|
* the default base ref) live here so image-api injects only VETTED, server-fixed values into the build.sh argv —
|
|
400
400
|
* operator free-text never reaches the runner. */
|
|
401
|
-
/** [ref] 车3([ref] 流内审批协议)
|
|
402
|
-
*
|
|
403
|
-
* ([ref]:`STREAM_ASK_WINDOW_MS`
|
|
401
|
+
/** [ref] 车3([ref] 流内审批协议)的配置面。协议上不上场只由装配在场性定(审批席 ∧ store backend ∧ park 设施,
|
|
402
|
+
* `resolveStreamApprovalGate`)—— 没有总开关:审批只有呈卡族一个帧族,开关的「关」(回退发旧族活卡)随旧族退役。
|
|
403
|
+
* 从属旋钮**除 `windowMs` 外**只在协议上场时生效([ref]:`STREAM_ASK_WINDOW_MS` 与协议上不上场解耦 —— 逐条辖域见各字段域注)
|
|
404
404
|
* (语义与「谁需要 / 谁被伤 / 什么补偿」三问见 `ServiceConfigFlat.streamApproval` 的注)。 */
|
|
405
405
|
export interface StreamApprovalConfig {
|
|
406
|
-
/** 协议总开关。`STREAM_APPROVAL_ENABLED`,默认 **true**(clay 裁 2026-08-08,[ref] 翻真验证后;7.5.0 起)。显式 false ⇒ 全链逐字 7.3.0 前行为(唯一干净还原键)。
|
|
407
|
-
* 翻真要等回决端点(车4)与对账收敛器(车5)到位——在此之前是个不完整协议(有卡无正规回决口、
|
|
408
|
-
* 有 PARKING 无收敛器、跨副本回决无唤醒路径)。 */
|
|
409
|
-
enabled: boolean;
|
|
410
406
|
/** `ToolApprovalCoordinator` 的 `ttlMs` —— **一只活卡等人多久**([ref] §3.3 的「配置窗默认」)。
|
|
411
407
|
* `STREAM_ASK_WINDOW_MS`,默认 **300000**(同裁与 sync 腿既有 5min 活卡窗对齐)。
|
|
412
408
|
*
|
|
413
409
|
* 🔴 **辖域([ref] 起):无条件** —— 本值在**每一条**呈活卡的腿上生效,与流内协议上不上场无关
|
|
414
|
-
* (
|
|
410
|
+
* (无 park 设施的部署上它管的是「没有卡、每只问当场交政策口」之前的那一格 —— 那里没有窗可计)。
|
|
415
411
|
* 旧文「开关为假 ⇒ 本值不参与,窗保持既有 `DEFAULT_APPROVAL_TTL_MS`(5min)」是**已修的病**,不是
|
|
416
412
|
* 设计:那让一根部署级旋钮的生效与否挂在装配在场性上,运维配了它、没有任何一行说它没生效
|
|
417
413
|
* (clay 令:部署级旋钮禁挂在场性派生腿)。缺省值与那个常量同为 300000 ⇒ **没配过这根 env 的部署
|
|
@@ -1238,14 +1234,9 @@ export interface ServiceConfigFlat {
|
|
|
1238
1234
|
/** plugins 域 clone host 白名单(`PLUGINS_ALLOW_HOSTS`,缺省 github.com;契约 [ref]① 双保险面,
|
|
1239
1235
|
* 消费在 capabilities/center-plugins.ts)。 */
|
|
1240
1236
|
pluginsAllowHosts: string[];
|
|
1241
|
-
/**
|
|
1242
|
-
* `BIND_HOST`(兼容 `HOST`)。显式值恒生效;缺省见 {@link resolveBindHost}——**无鉴权写面
|
|
1237
|
+
/** 监听绑址:唯一来源 = `BIND_HOST`(`HOST` 不读 —— zsh 恒设 HOST=机器名,读它等于被 shell 环境暗配 bind 面)。显式值恒生效;缺省见 {@link resolveBindHost}——**无鉴权写面
|
|
1243
1238
|
* (allowUnauthedWrites 且零凭证)自动收窄到 127.0.0.1**,其余维持全接口(既有部署零影响)。 */
|
|
1244
1239
|
bindHost?: string;
|
|
1245
|
-
/** [ref]③ `bindHost` 的取值来源(BIND_HOST 显式 / HOST 兼容名 / 未设)。`"HOST"` 时 main.ts 打一行
|
|
1246
|
-
* 启动告警:zsh 常把 HOST 设成机器名,`{...process.env}` 起底的壳会静默继承给引擎——bind 面被 shell
|
|
1247
|
-
* 环境暗配而部署者不知情(HOST=机器名解析到 LAN 口时=静默扩暴露面)。 */
|
|
1248
|
-
bindHostSource?: "BIND_HOST" | "HOST";
|
|
1249
1240
|
/** D-1 附件孤儿对象 GC 的 grace(毫秒;`ATTACHMENT_ORPHAN_GRACE_MS`,默认 1h,**0=关掉本腿**)。
|
|
1250
1241
|
* 复审 F10:同 tick 邻居(workflowOrphanGraceMs/attachmentUnboundTtlMs/scratchpadSweepTtlMs)都有
|
|
1251
1242
|
* 旋钮——存量对象巨大的部署要调慢/停掉,不该被迫取消 MinIO 配置(那会连带关掉整个附件面)。 */
|
|
@@ -1266,7 +1257,8 @@ export interface ServiceConfigFlat {
|
|
|
1266
1257
|
* the unguessable-key capability URLs be enumerated); the private presigned track shares
|
|
1267
1258
|
* SESSION_SNAPSHOT_BUCKET (default "session-snapshots") under a "sendfile/" prefix.
|
|
1268
1259
|
* SEND_USER_FILE_URL_TTL = default ttl seconds when the tool call omits one; 0 = permanent (the settled
|
|
1269
|
-
* default). Unset/
|
|
1260
|
+
* default). Unset / empty ⇒ 0; anything but canonical decimal digits does not load (read as 0 it would turn a link meant to expire into a
|
|
1261
|
+
* permanent public one), nor does a value over the SigV4 ceiling (`config.ts` `sendUserFileUrlTtlKnob`). */
|
|
1270
1262
|
sendUserFile?: {
|
|
1271
1263
|
endpoint: string;
|
|
1272
1264
|
publicEndpoint?: string;
|
|
@@ -1387,6 +1379,11 @@ export interface ServiceConfigFlat {
|
|
|
1387
1379
|
* "empty = everyone on the approval queue" reading is gone). A single-user deployment must answer "who operates it" — a named list here or {@link operatorSingleUser}; neither ⇒ refusal
|
|
1388
1380
|
* (`operator-declaration-required`: the server process refuses at boot stage 1, and `createHttpServer` refuses for every assembler, embedders included). */
|
|
1389
1381
|
operatorPrincipals: string[];
|
|
1382
|
+
/** `OPERATOR_DRAIN_PRINCIPALS` —— 名册的**排空档**(服务主体名单;档表 `src/operator-roster.ts` 的 `drain` 行):名单内的验过主体只持 `drain` 一只运维权力、不是人 ——
|
|
1383
|
+
* 它的身份只在声明了 `drain` 权力的路由(`POST /v1/admin/drain`)上算数,其余每条路由整次请求 403 `auth.principal_confined`,`GET /health` 按没出示凭据分档。
|
|
1384
|
+
* 缺席 ≡ 空 = 这一档没人(少给权力,fail-closed;`loadConfig` 恒写)。与 `OPERATOR_PRINCIPALS` 同一个名字 / 不是主体名的条目 / 不是名字列表 ⇒ 拒启(`operator-roster-well-formed`);与 `OPERATOR_SINGLE_USER=true` 同在 ⇒ 拒启
|
|
1385
|
+
* (`operator-single-user`)。env 独有(没有中心写位),起服冻结。不算运维声明:单用户形只写它 ⇒ 照旧 `operator-declaration-required` 拒。 */
|
|
1386
|
+
operatorDrainPrincipals?: readonly string[];
|
|
1390
1387
|
/** `OPERATOR_SINGLE_USER=true` —— 单用户形显式声明「这台部署的每个调用方都是运维」([ref],[ref] ① 裁 A)。缺席 = 不声明(没人是,除非在名单里)。
|
|
1391
1388
|
* 只在单用户形(`REQUIRE_PRINCIPAL` 未开)且名单为空时成立 —— 否则拒启(`config-invariants.ts` 的 `operator-single-user`:一个事实一个来源)。
|
|
1392
1389
|
* 单用户形上它与具名名单二者必有其一,都没有 ⇒ 拒启(服务进程起服第 1 段;嵌入方在 `createHttpServer`;`operator-declaration-required`,[ref];`false` 与缺席同判)。
|
|
@@ -2027,10 +2024,12 @@ export interface ServiceConfigFlat {
|
|
|
2027
2024
|
* {@link import("./question.js").DEFAULT_QUESTION_THROTTLE}(与本旋钮族默认逐字同值)。 */
|
|
2028
2025
|
questionThrottle?: QuestionThrottle;
|
|
2029
2026
|
/** [ref]/[ref]② live tool-approval HITL — mount the ToolApprovalCoordinator on `RunnerDeps.onAsk` + the
|
|
2030
|
-
* `POST /v1/
|
|
2027
|
+
* ask decision door (`POST /v1/tasks/:taskId/asks/:askId/decision`) so a policy `ask` (the fs-write gate, ask-lists, non-durable safety
|
|
2031
2028
|
* asks) routes to the live human as the CC three-choice card on the run's stream (core 1.290 sync-ask leg). A CC
|
|
2032
2029
|
* cap → posture-gated like askQuestion (single-user turnkey → ON; multi-tenant opt-in);
|
|
2033
|
-
* `TOOL_APPROVAL_ENABLED=true/false` overrides. Absent ⇒ core's headless auto-deny stands ([ref]⑤ fail-closed).
|
|
2030
|
+
* `TOOL_APPROVAL_ENABLED=true/false` overrides. Absent ⇒ core's headless auto-deny stands ([ref]⑤ fail-closed).
|
|
2031
|
+
* Necessary, not sufficient: the coordinator mounts only when this worker presents approval cards (this knob ∧ a store backend ∧ a park
|
|
2032
|
+
* facility — `resolveStreamApprovalGate`); on a worker without them the knob has nothing to mount and the headless auto-deny stands too. */
|
|
2034
2033
|
toolApprovalEnabled: boolean;
|
|
2035
2034
|
/**
|
|
2036
2035
|
* [ref] 车二(core [ref] 持久化权限规则「不再询问」车道)**总开关**。`PERMISSION_RULES_ENABLED`,
|
|
@@ -2061,17 +2060,11 @@ export interface ServiceConfigFlat {
|
|
|
2061
2060
|
* 旋钮本身缺省对现行为零影响(D1)。`STREAM_ASK_WINDOW_MARGIN_MS`,默认 10000(10s)。ttl 概念沿用既有
|
|
2062
2061
|
* `approvalTimeoutSec` 族,本旋钮不新增第二个 TTL 概念。 */
|
|
2063
2062
|
streamAskWindowMarginMs: number;
|
|
2064
|
-
/** [ref] 车3([ref] 流内审批协议)
|
|
2063
|
+
/** [ref] 车3([ref] 流内审批协议)的从属旋钮(没有总开关:协议上不上场只由装配在场性定,见 {@link StreamApprovalConfig} 顶注)。
|
|
2065
2064
|
*
|
|
2066
|
-
*
|
|
2067
|
-
*
|
|
2068
|
-
* `DEFAULT_APPROVAL_TTL_MS`
|
|
2069
|
-
* 发布线上落在 7.4.0 之后的下一个发布,所以一台自报 7.4.0 的**已发布** worker 仍是 OFF,别只按
|
|
2070
|
-
* `/health` 的版本号推默认。显式 `STREAM_APPROVAL_ENABLED=false` 是**唯一干净还原键** ⇒ 全链逐字回到
|
|
2071
|
-
* 翻转前行为:不发 `approval_request` 帧、不落 `approval_ask` 行、不起收敛器腿、`askStore` 不注入
|
|
2072
|
-
* 协调器。⚠️ **窗不在这份还原清单里了([ref])**:`STREAM_ASK_WINDOW_MS` 自此在关协议的部署上照样
|
|
2073
|
-
* 生效(缺省 300000 == `DEFAULT_APPROVAL_TTL_MS` ⇒ 没配过它的部署仍然字节不变),理由见
|
|
2074
|
-
* `StreamApprovalConfig.windowMs` 的辖域段。
|
|
2065
|
+
* **总开关 `STREAM_APPROVAL_ENABLED` 已退役**:它的「关」= 回退发旧族活卡(`tool_approval`),旧族退役后没有可关的东西;
|
|
2066
|
+
* 部署 env 里还留着这一键 ⇒ 起服拒(退役墓碑,不论值)。`STREAM_ASK_WINDOW_MS` 默认 **300000**(5min,与
|
|
2067
|
+
* `DEFAULT_APPROVAL_TTL_MS` 对齐),理由见 `StreamApprovalConfig.windowMs` 的辖域段。
|
|
2075
2068
|
*
|
|
2076
2069
|
* **旋钮为什么存在(2026-08-08 翻转前的成文前提,已被上面的裁定取代,留作沿革)**:那一版默认 false +
|
|
2077
2070
|
* 60s 窗,三问记的是那个世界——
|
|
@@ -2084,7 +2077,7 @@ export interface ServiceConfigFlat {
|
|
|
2084
2077
|
* ⇒ 翻真时「窗变短」这一项被从翻转里摘出(窗默认抬回 300000),被伤面①②因此消失;60s 只在运维显式设
|
|
2085
2078
|
* `STREAM_ASK_WINDOW_MS=60000` 时才回来。
|
|
2086
2079
|
*
|
|
2087
|
-
*
|
|
2080
|
+
* 键:`STREAM_ASK_WINDOW_MS` / `STREAM_APPROVAL_REPLAY_MAX` /
|
|
2088
2081
|
* `STREAM_APPROVAL_ADMIT_MAX_PER_TASK` / `STREAM_APPROVAL_ADMIT_MAX_PER_OWNER` /
|
|
2089
2082
|
* `STREAM_APPROVAL_RECONCILE_BATCH` / `STREAM_APPROVAL_PENDING_GRACE_MS` /
|
|
2090
2083
|
* `STREAM_APPROVAL_ADHOC_GRACE_MS` / `STREAM_APPROVAL_ORPHAN_TTL_MS`(后四键 = 车5 收敛器)。 */
|
|
@@ -2103,7 +2096,7 @@ export interface ServiceConfigFlat {
|
|
|
2103
2096
|
*
|
|
2104
2097
|
* 施加点 = `ToolApprovalCoordinator` 的构造参 `unattendedPolicy`,五条「无人可答」的臂读同一
|
|
2105
2098
|
* 个值;对账收敛器的同名构造参([ref],`boot/reapers.ts` 恒传同一个部署值)只供它的孤儿段打同一只终态 claim ——
|
|
2106
|
-
* 两处读同一个值,于是崩溃恢复与 live 腿同答(设计稿 §1 的表:(a) 有店窗到期赢 CAS /(b) 无店窗到期 /(c) 店报错 catch /(d) windowZero 与
|
|
2099
|
+
* 两处读同一个值,于是崩溃恢复与 live 腿同答(设计稿 §1 的表:(a) 有店窗到期赢 CAS /(b) 无店窗到期(7.152.0 起随无账协调器退役)/(c) 店报错 catch /(d) windowZero 与
|
|
2107
2100
|
* headless 腿 /(e) 断连强转 + emit 全灭 + 写侧准入超限 + **投递集合入口活性过滤清空**([ref] 归
|
|
2108
2101
|
* 位:与「查无活流」同一件事实的两个时刻,详见 `askBroadcast` 的该臂注))。`"timeout"` 宿主自报只留在 (b)(c) ——
|
|
2109
2102
|
* 结束等待的确实是本仓的窗;其余三臂结束等待的是容量/连接/装配,自报恒缺席。
|
|
@@ -2115,14 +2108,11 @@ export interface ServiceConfigFlat {
|
|
|
2115
2108
|
* `"timeout"` 自报(`approval_window_expired`)。
|
|
2116
2109
|
*
|
|
2117
2110
|
* 🔴 **射程边界(成文,不是遗漏)**:
|
|
2118
|
-
* ·
|
|
2119
|
-
*
|
|
2120
|
-
*
|
|
2121
|
-
*
|
|
2122
|
-
*
|
|
2123
|
-
* 而不是 server 的 ask 行,「无 askStore 但有 durable 设施」正是 R-13 点名的**主受益部署**。
|
|
2124
|
-
* 要回滚 R-13 的行为面,键是 `UNATTENDED_APPROVAL_POLICY=deny`,**不是**协议开关。
|
|
2125
|
-
* 射程钉:`test/unattended-approval-policy.test.ts` 的 codex R2-F2 两格。
|
|
2111
|
+
* · 本旋钮只在审批协调器在场时有处施加;协调器只在呈卡协议上场时构造(`TOOL_APPROVAL_ENABLED` ∧ store backend ∧ park 设施,
|
|
2112
|
+
* `boot/coordinators.ts`)。不上场的三形各自的终局与本旋钮无关:开关关 ⇒ core 自己的姿态(有 durable 设施时照常 park);
|
|
2113
|
+
* 开关开而无 store backend / 无 park 设施 ⇒ 没有审批席也没有停驻处,core 的无审批席臂当场拒。**不造推断腿**:这一句是成文而不是代码分支。
|
|
2114
|
+
* · 总开关 `STREAM_APPROVAL_ENABLED` 7.152.0 起退役;要回滚 R-13 的行为面,键是 `UNATTENDED_APPROVAL_POLICY=deny`。
|
|
2115
|
+
* 射程钉:`test/unattended-approval-policy.test.ts` 的「呈卡协议不上场与本旋钮的关系」一组。
|
|
2126
2116
|
* · APPROVAL_REQUIRE 名单工具:**要不要 ask** 由 `createApprovalBaselinePolicy` 的 durable gate 类裁决,那一层
|
|
2127
2117
|
* 结构上读不到本旋钮;但那只 ask 的**答法**在协调器在场时与治理 ask 同走流内臂、同政策(引擎先问活席
|
|
2128
2118
|
* `onAsk`,答 `"unavailable"` 才 park)⇒ `deny` 部署对名单工具同样当场拒。旧注「不经流内臂、不受本旋钮
|
|
@@ -2415,7 +2405,7 @@ export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled"
|
|
|
2415
2405
|
* 三者都是 operator 在配置面写下的**治理声明**(与 autonomy/commandPolicy 同族),不是存储旋钮
|
|
2416
2406
|
* ——`retentionPolicy` 尤其容易被误归 store 组:它的消费点是 core 的启动期能力校验与将来的调度面,
|
|
2417
2407
|
* config 这一层它只是一句声明。 */
|
|
2418
|
-
export type ServiceAuthConfig = Pick<ServiceConfigFlat, "authToken" | "authTokens" | "allowUnauthedWrites" | "corsOrigins" | "principalHeader" | "requirePrincipal" | "autonomy" | "commandPolicy" | "compliancePosture" | "lockedConfigKeys" | "configCatalogEnabled" | "retentionPolicy" | "retentionSweep" | "operatorPrincipals" | "operatorSingleUser" | "serviceTokenActsForDeployment" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "authBridgeIssuer" | "principalJwtMaxTtlSec" | "bindHost"
|
|
2408
|
+
export type ServiceAuthConfig = Pick<ServiceConfigFlat, "authToken" | "authTokens" | "allowUnauthedWrites" | "corsOrigins" | "principalHeader" | "requirePrincipal" | "autonomy" | "commandPolicy" | "compliancePosture" | "lockedConfigKeys" | "configCatalogEnabled" | "retentionPolicy" | "retentionSweep" | "operatorPrincipals" | "operatorDrainPrincipals" | "operatorSingleUser" | "serviceTokenActsForDeployment" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "authBridgeIssuer" | "principalJwtMaxTtlSec" | "bindHost">;
|
|
2419
2409
|
/** 组:orchestration(编排 + 执行车道 + 沙箱面 + workflow/后台 agent 存留)。 */
|
|
2420
2410
|
export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "sandboxGit" | "remoteExec" | "remoteExecHomeDir" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentStallMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sandboxPkgSourceCustom" | "sessionAutoTitle" | "sessionAutoTitleSource" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspHostEnabled" | "lspEnvAllow" | "hookEnvAllow" | "autoBackgroundOnTimeout" | "imageBakes" | "readFace" | "readFaceSource" | "taskWriteFaceEscalation" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "readDenyFloorRequired" | "sandboxReadFloor" | "agentConfigDir" | "configLkgPath" | "configLkgEnabled">;
|
|
2421
2411
|
/** 组:limitsHttp(HTTP 面 + 各类上限/配额/回收窗)。 */
|
package/dist/config.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type ConfigProviderWord } from "./config-types.js";
|
|
|
3
3
|
import { type CenterEndpoint } from "./web-admin-client/center-request.js";
|
|
4
4
|
import { type ExecutionLane } from "./execution-lane-caps.js";
|
|
5
5
|
import { type Autonomy } from "./runtime-governance.js";
|
|
6
|
+
import { type EntryProblemReason } from "./entry-problems.js";
|
|
6
7
|
import { type RunnerEnvNameAllowlistKnob, type ServerEnvNameAllowlistKnob } from "./env-name-allowlist-knobs.js";
|
|
7
8
|
import type { ServiceConfig, LoadedConfig, RemoteExecConfig, ServiceConfigFlat, ServiceConfigGroups } from "./config-types.js";
|
|
8
9
|
/** [ref] A1: the config TYPE face lives in the leaf module config-types.ts; re-exported here so
|
|
@@ -61,9 +62,9 @@ export declare function parseNumOrFail(name: string, raw: string | undefined): n
|
|
|
61
62
|
*/
|
|
62
63
|
export declare function parseNumOrFailNonNegative(name: string, raw: string | undefined): number;
|
|
63
64
|
/** Optional FAIL-SAFE numeric env for an OPTIONAL feature knob: a non-numeric / non-positive value yields `undefined`
|
|
64
|
-
* (the caller falls back to its own default) rather than NaN OR a hard startup throw.
|
|
65
|
-
*
|
|
66
|
-
*
|
|
65
|
+
* (the caller falls back to its own default) rather than NaN OR a hard startup throw. A presigned-URL lifetime is not such a
|
|
66
|
+
* knob: its spelling is judged by `presignLifetimeSpelled` and a non-decimal one does not load (a typo there must neither sign
|
|
67
|
+
* `X-Amz-Expires=NaN` nor a `Number()` reading of what the operator wrote). */
|
|
67
68
|
/** S20 (SILENT-FALLBACK P1): soft-knob parse rejects, collected here because config parsing runs BEFORE the
|
|
68
69
|
* logger exists. main.ts drains this once after createLogger and warns per entry ("configured X=raw is
|
|
69
70
|
* invalid, using the default"). Module-level on purpose; drain clears it (repeat loadConfig in tests is fine). */
|
|
@@ -387,9 +388,10 @@ export interface UnmatchableToolName {
|
|
|
387
388
|
guidance: string;
|
|
388
389
|
/**
|
|
389
390
|
* 这一条为什么永远不生效 —— 机读位的 `reason`(`entry-problems.ts`):引擎名字判官的判词 `retired` / `unmintable_segment` / `unprefixed_namespace`、引擎规则文法的码原样(`invalid.*` /
|
|
390
|
-
* `unsupported.*`)、近似名 `near_miss`、名表消费者的 CC 规则形 `cc_rule_form
|
|
391
|
+
* `unsupported.*`)、近似名 `near_miss`、名表消费者的 CC 规则形 `cc_rule_form`(本仓自铸,取闭集 `CC_RULE_FORM`)。句子里的 `guidance` 给人读,本字给机器读;两者在同一处同时铸。
|
|
392
|
+
* 型 = 条目问题 `reason` 的全部词表之并({@link EntryProblemReason}):这里写一个哪张表都没有的词 = tsc 红。
|
|
391
393
|
*/
|
|
392
|
-
reason:
|
|
394
|
+
reason: EntryProblemReason;
|
|
393
395
|
}
|
|
394
396
|
/**
|
|
395
397
|
* [ref](core 7.9.0 [ref])—— **这条名单条目喂给谁**,决定「永不匹配」这句话怎么判。三个消费者形态:
|
package/dist/config.js
CHANGED
|
@@ -32,11 +32,11 @@ import { MODEL_THINKING_LEVELS, THINKING_LEVELS, THINKING_FORMATS, buildModelCom
|
|
|
32
32
|
import {} from "./posture-source.js";
|
|
33
33
|
import { COST_QUOTA_WINDOW_SEC_RANGE, costQuotaWindowSecInRange } from "./observability/cost-quota.js";
|
|
34
34
|
import { NAME_TABLE_BEHAVIOR, TOOL_NAME_ROSTER, describeUnmatchableRuleName, isRetiredLeftOut, retiredLeftOutEntries, ruleEntryToolName, unmatchableRuleNameOf } from "./namespaced-rule-name.js";
|
|
35
|
-
import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, centerIdentityRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal, operatorSingleUserRefusal, remoteConfigProviderUrlRefusal, retentionLaneRefusal, workflowParkDecisionDomainRefusal } from "./config-invariants.js";
|
|
35
|
+
import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, centerIdentityRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal, operatorSingleUserRefusal, operatorRosterWellFormedRefusal, remoteConfigProviderUrlRefusal, retentionLaneRefusal, workflowParkDecisionDomainRefusal } from "./config-invariants.js";
|
|
36
36
|
import { declaredSemaConfigDir } from "./sema-config-dir.js";
|
|
37
37
|
import { lkgPathOverrideOf } from "./config-lkg.js";
|
|
38
38
|
import { K8S_WORKSPACE_MOUNT_DEFAULT, WORKSPACE_PERSIST_DEFAULT, WORKSPACE_PERSIST_WORDS, WORKSPACE_SNAPSHOT_KEEP_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_RANGE, WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE, parseWorkspaceSnapshotExclude, parseWorkspaceSnapshotPrefix } from "./workspace-space.js";
|
|
39
|
-
import { SESSION_SNAPSHOT_BUCKET_DEFAULT, WORKSPACE_SNAPSHOT_BUCKET_DEFAULT } from "./object-store-buckets.js";
|
|
39
|
+
import { S3_PUBLIC_BUCKET_DEFAULT, SENDFILE_PRIVATE_PREFIX, SESSION_SNAPSHOT_BUCKET_DEFAULT, SIGV4_PRESIGN_MAX_SEC, WORKSPACE_SNAPSHOT_BUCKET_DEFAULT, sessionSnapshotPrefixProblem } from "./object-store-buckets.js";
|
|
40
40
|
import { laneLivenessOf } from "./exec-bounds.js";
|
|
41
41
|
import { SANDBOX_IPV6_DEFAULT, SANDBOX_IPV6_WORDS } from "./sandbox-egress-policy.js";
|
|
42
42
|
import { WORKSPACE_CACHE_DEFAULT, WORKSPACE_CACHE_MOUNT_DEFAULT, WORKSPACE_CACHE_MOUNT_RESERVED, WORKSPACE_CACHE_SIZE_DEFAULT, WORKSPACE_CACHE_WORDS, isK8sQuantity, mountsOverlap, reservedMountCollision } from "./workspace-cache.js";
|
|
@@ -46,6 +46,7 @@ import { sandboxForwardedEnvOf } from "./execution-config-root.js";
|
|
|
46
46
|
import { SANDBOX_READ_FLOOR_DEFAULT, SANDBOX_READ_FLOOR_WORDS, laneMissingEnv, readDeclaredLaneTable } from "./execution-lane-caps.js";
|
|
47
47
|
import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
|
|
48
48
|
import { AUTONOMY_MODES } from "./runtime-governance.js";
|
|
49
|
+
import { CC_RULE_FORM } from "./entry-problems.js";
|
|
49
50
|
import { deploymentRuleRefusal } from "./rule-issue-reading.js";
|
|
50
51
|
import { MAX_NAMED_INERT_ENTRIES, allowLayerEntryCountIssue, describeInertRuleEntries, inertRuleEntriesOf, inertRuleEntryGuidance, ruleEntryListShapeOk } from "./rule-entry-list.js";
|
|
51
52
|
import { readReadDenyEntry } from "./read-deny-entry.js";
|
|
@@ -494,6 +495,41 @@ function parseDeviceTrustedProxyCidrsEnv(raw) {
|
|
|
494
495
|
`(e.g. 10.0.0.0/8,fd00::/8); unset it to read no proxy header at all (the cap is then keyed on the socket peer).`);
|
|
495
496
|
});
|
|
496
497
|
}
|
|
498
|
+
function sessionSnapshotPrefixKnob(prefix) {
|
|
499
|
+
const problem = sessionSnapshotPrefixProblem(prefix);
|
|
500
|
+
if (problem !== undefined)
|
|
501
|
+
throw new Error(`SESSION_SNAPSHOT_PREFIX=${JSON.stringify(prefix)} is refused: ${problem} — give each engine on the bucket a prefix that does not end in a domain segment (e.g. "engine-a/")`);
|
|
502
|
+
return prefix;
|
|
503
|
+
}
|
|
504
|
+
function presignLifetimeKnob(knob, sec, alternative) {
|
|
505
|
+
if (sec > SIGV4_PRESIGN_MAX_SEC) {
|
|
506
|
+
throw new Error(`${knob}=${sec} is longer than a presigned URL can live (at most ${SIGV4_PRESIGN_MAX_SEC} s (7 days)) — set ${knob} to at most ${SIGV4_PRESIGN_MAX_SEC}${alternative ? `, ${alternative}` : ""}`);
|
|
507
|
+
}
|
|
508
|
+
return sec;
|
|
509
|
+
}
|
|
510
|
+
function presignLifetimeSpelled(knob, raw, legal, alternative) {
|
|
511
|
+
const spelled = raw?.trim() ?? "";
|
|
512
|
+
if (spelled === "")
|
|
513
|
+
return undefined;
|
|
514
|
+
if (!/^(0|[1-9]\d*)$/.test(spelled)) {
|
|
515
|
+
const shown = raw.length > 40 ? `${raw.slice(0, 40)}…` : raw;
|
|
516
|
+
throw new Error(`${knob}=${JSON.stringify(shown)} is not ${legal}`);
|
|
517
|
+
}
|
|
518
|
+
return presignLifetimeKnob(knob, Number(spelled), alternative);
|
|
519
|
+
}
|
|
520
|
+
function sendUserFileUrlTtlKnob(raw) {
|
|
521
|
+
return (presignLifetimeSpelled("SEND_USER_FILE_URL_TTL", raw, `a link lifetime — set 0 (or leave it unset) for a permanent public link, or a whole number of seconds from 1 to ${SIGV4_PRESIGN_MAX_SEC} (7 days) for a private presigned link that expires`, "or 0 for a permanent link") ?? 0);
|
|
522
|
+
}
|
|
523
|
+
function sessionSnapshotTtlKnob(raw) {
|
|
524
|
+
const sec = presignLifetimeSpelled("SESSION_SNAPSHOT_TTL_SEC", raw, `a presigned-URL lifetime — leave it unset for the default, or set a whole number of seconds from 1 to ${SIGV4_PRESIGN_MAX_SEC} (7 days) in decimal digits`);
|
|
525
|
+
if (sec !== 0)
|
|
526
|
+
return sec;
|
|
527
|
+
CONFIG_WARNINGS.push({ env: "SESSION_SNAPSHOT_TTL_SEC", raw: raw });
|
|
528
|
+
return undefined;
|
|
529
|
+
}
|
|
530
|
+
function workspaceSnapshotTtlKnob(raw) {
|
|
531
|
+
return presignLifetimeSpelled("WORKSPACE_SNAPSHOT_TTL_SEC", raw, `a presigned-URL lifetime — leave it unset (or 0) for the default, or set a whole number of seconds from 1 to ${SIGV4_PRESIGN_MAX_SEC} (7 days) in decimal digits`);
|
|
532
|
+
}
|
|
497
533
|
function parseFileHistoryRetention() {
|
|
498
534
|
const raw = process.env.FILE_HISTORY_RETENTION_KEEP;
|
|
499
535
|
if (raw === undefined || raw === "")
|
|
@@ -1036,8 +1072,8 @@ function parseStoreDomain(ctx) {
|
|
|
1036
1072
|
accessKey: process.env.MINIO_ACCESS_KEY,
|
|
1037
1073
|
secretKey: process.env.MINIO_SECRET_KEY,
|
|
1038
1074
|
...(process.env.MINIO_REGION ? { region: process.env.MINIO_REGION } : {}),
|
|
1039
|
-
...(process.env.SESSION_SNAPSHOT_PREFIX ? { keyPrefix: process.env.SESSION_SNAPSHOT_PREFIX } : {}),
|
|
1040
|
-
...((ttl) => (ttl !== undefined ? { presignTtlSec: ttl } : {}))(
|
|
1075
|
+
...(process.env.SESSION_SNAPSHOT_PREFIX ? { keyPrefix: sessionSnapshotPrefixKnob(process.env.SESSION_SNAPSHOT_PREFIX) } : {}),
|
|
1076
|
+
...((ttl) => (ttl !== undefined ? { presignTtlSec: ttl } : {}))(sessionSnapshotTtlKnob(process.env.SESSION_SNAPSHOT_TTL_SEC)),
|
|
1041
1077
|
}
|
|
1042
1078
|
: undefined,
|
|
1043
1079
|
snapshotBlobSqlMaxBytes: optFinitePositiveEnv("SNAPSHOT_BLOB_SQL_MAX_BYTES"),
|
|
@@ -1050,14 +1086,14 @@ function parseStoreDomain(ctx) {
|
|
|
1050
1086
|
...(process.env.S3_PUBLIC_ENDPOINT || process.env.S3_ENDPOINT
|
|
1051
1087
|
? { publicEndpoint: (process.env.S3_PUBLIC_ENDPOINT || process.env.S3_ENDPOINT) }
|
|
1052
1088
|
: {}),
|
|
1053
|
-
publicBucket: process.env.S3_PUBLIC_BUCKET ??
|
|
1089
|
+
publicBucket: process.env.S3_PUBLIC_BUCKET ?? S3_PUBLIC_BUCKET_DEFAULT,
|
|
1054
1090
|
privateBucket: process.env.SESSION_SNAPSHOT_BUCKET ?? SESSION_SNAPSHOT_BUCKET_DEFAULT,
|
|
1055
|
-
privateKeyPrefix:
|
|
1091
|
+
privateKeyPrefix: SENDFILE_PRIVATE_PREFIX,
|
|
1056
1092
|
...(process.env.SEND_USER_FILE_SANDBOX_PUT_ENDPOINT ? { sandboxPutEndpoint: process.env.SEND_USER_FILE_SANDBOX_PUT_ENDPOINT } : {}),
|
|
1057
1093
|
accessKey: process.env.MINIO_ACCESS_KEY,
|
|
1058
1094
|
secretKey: process.env.MINIO_SECRET_KEY,
|
|
1059
1095
|
...(process.env.MINIO_REGION ? { region: process.env.MINIO_REGION } : {}),
|
|
1060
|
-
defaultTtlSec: (
|
|
1096
|
+
defaultTtlSec: sendUserFileUrlTtlKnob(process.env.SEND_USER_FILE_URL_TTL),
|
|
1061
1097
|
}
|
|
1062
1098
|
: undefined,
|
|
1063
1099
|
};
|
|
@@ -1363,7 +1399,7 @@ function findUnmatchableToolNames(names, opts) {
|
|
|
1363
1399
|
if (opts.consumer === "tool-name-table") {
|
|
1364
1400
|
const ccGuidance = ccRuleFormGuidance(name);
|
|
1365
1401
|
if (ccGuidance !== undefined) {
|
|
1366
|
-
out.push({ name, guidance: ccGuidance, reason:
|
|
1402
|
+
out.push({ name, guidance: ccGuidance, reason: CC_RULE_FORM });
|
|
1367
1403
|
continue;
|
|
1368
1404
|
}
|
|
1369
1405
|
}
|
|
@@ -1419,7 +1455,6 @@ export function approvalGrantRefusal(names) {
|
|
|
1419
1455
|
}
|
|
1420
1456
|
function streamApprovalConfig(gates) {
|
|
1421
1457
|
const cfg = {
|
|
1422
|
-
enabled: boolEnv("STREAM_APPROVAL_ENABLED", true),
|
|
1423
1458
|
windowMs: numEnv("STREAM_ASK_WINDOW_MS", gates.windowMsSource === "posture" ? "86400000" : "300000"),
|
|
1424
1459
|
replayMax: numEnv("STREAM_APPROVAL_REPLAY_MAX", "50"),
|
|
1425
1460
|
admitMaxPerTask: numEnv("STREAM_APPROVAL_ADMIT_MAX_PER_TASK", "32"),
|
|
@@ -1443,7 +1478,7 @@ function streamApprovalConfig(gates) {
|
|
|
1443
1478
|
if (cfg.windowMs + cfg.adhocGraceMs >= cfg.orphanTtlMs) {
|
|
1444
1479
|
throw new Error(`env STREAM_ASK_WINDOW_MS=${cfg.windowMs} + STREAM_APPROVAL_ADHOC_GRACE_MS=${cfg.adhocGraceMs} must be STRICTLY LESS than STREAM_APPROVAL_ORPHAN_TTL_MS=${cfg.orphanTtlMs} — the adhoc arm fires at (createdAt + window + grace) while the orphan backstop fires at (createdAt + ttl); if the backstop wins, every adhoc-leg row is attributed "orphan_ttl_exceeded" instead of "adhoc_leg_no_durable_domain" (attribution honesty, design/172 §6.1)`);
|
|
1445
1480
|
}
|
|
1446
|
-
const suspendedRowsPossible =
|
|
1481
|
+
const suspendedRowsPossible = gates.toolApprovalEnabled && gates.unattendedPolicy !== "deny";
|
|
1447
1482
|
if (suspendedRowsPossible && cfg.unreachedTtlMs + cfg.adhocGraceMs >= cfg.orphanTtlMs) {
|
|
1448
1483
|
throw new Error(`env UNREACHED_ASK_TTL_MS=${cfg.unreachedTtlMs} + STREAM_APPROVAL_ADHOC_GRACE_MS=${cfg.adhocGraceMs} must be STRICTLY LESS than STREAM_APPROVAL_ORPHAN_TTL_MS=${cfg.orphanTtlMs} — an ask minted with NO live stream (case A: a workflow child ask) waits UNREACHED_ASK_TTL_MS, so its row expires at (createdAt + that window); the orphan backstop fires at (createdAt + ttl). If the backstop wins, every suspended row is attributed "orphan_ttl_exceeded" instead of its real cause (attribution honesty, same rule as the STREAM_ASK_WINDOW_MS line above)`);
|
|
1449
1484
|
}
|
|
@@ -2021,7 +2056,6 @@ function parseAuthDomain(ctx) {
|
|
|
2021
2056
|
const authTokens = parseServiceAuthTokens(process.env.SERVICE_AUTH_TOKENS);
|
|
2022
2057
|
return {
|
|
2023
2058
|
bindHost: process.env.BIND_HOST || undefined,
|
|
2024
|
-
bindHostSource: process.env.BIND_HOST ? "BIND_HOST" : undefined,
|
|
2025
2059
|
authToken: process.env.SERVICE_AUTH_TOKEN,
|
|
2026
2060
|
authTokens,
|
|
2027
2061
|
allowUnauthedWrites: boolEnv("ALLOW_UNAUTHED_WRITES", false),
|
|
@@ -2031,6 +2065,7 @@ function parseAuthDomain(ctx) {
|
|
|
2031
2065
|
autonomy: parseAutonomy(process.env.AUTONOMY),
|
|
2032
2066
|
...parseGovernanceDeclarations(),
|
|
2033
2067
|
operatorPrincipals: csv("OPERATOR_PRINCIPALS"),
|
|
2068
|
+
operatorDrainPrincipals: csv("OPERATOR_DRAIN_PRINCIPALS"),
|
|
2034
2069
|
operatorSingleUser: boolEnv("OPERATOR_SINGLE_USER", false),
|
|
2035
2070
|
serviceTokenActsForDeployment: boolEnv("SERVICE_TOKEN_ACTS_FOR_DEPLOYMENT", true),
|
|
2036
2071
|
configCatalogEnabled: boolEnv("CONFIG_CATALOG_ENABLED", true),
|
|
@@ -2662,8 +2697,8 @@ const MEMORY_GROUP_KEYS = [
|
|
|
2662
2697
|
const AUTH_GROUP_KEYS = [
|
|
2663
2698
|
"authToken", "authTokens", "allowUnauthedWrites", "corsOrigins", "principalHeader", "requirePrincipal", "autonomy",
|
|
2664
2699
|
"commandPolicy", "compliancePosture", "lockedConfigKeys", "retentionPolicy", "retentionSweep",
|
|
2665
|
-
"operatorPrincipals", "operatorSingleUser", "serviceTokenActsForDeployment", "configCatalogEnabled", "principalJwtPubkeys", "principalJwtIss", "principalJwtAud",
|
|
2666
|
-
"principalJwtMaxTtlSec", "authBridgeIssuer", "bindHost",
|
|
2700
|
+
"operatorPrincipals", "operatorDrainPrincipals", "operatorSingleUser", "serviceTokenActsForDeployment", "configCatalogEnabled", "principalJwtPubkeys", "principalJwtIss", "principalJwtAud",
|
|
2701
|
+
"principalJwtMaxTtlSec", "authBridgeIssuer", "bindHost",
|
|
2667
2702
|
];
|
|
2668
2703
|
const ORCHESTRATION_GROUP_KEYS = [
|
|
2669
2704
|
"sandboxGit", "remoteExec", "remoteExecHomeDir", "worktreeIsolation", "leaderEnabled", "leaderFanoutEnabled", "routerEnabled", "selfOrchestrationEnabled",
|
|
@@ -2761,7 +2796,7 @@ function k8sWorkspaceStore() {
|
|
|
2761
2796
|
secretKey,
|
|
2762
2797
|
...(process.env.MINIO_REGION ? { region: process.env.MINIO_REGION } : {}),
|
|
2763
2798
|
...(keyPrefix !== undefined ? { keyPrefix } : {}),
|
|
2764
|
-
...((n) => (n !== undefined ? { presignTtlSec: n } : {}))(
|
|
2799
|
+
...((n) => (n !== undefined ? { presignTtlSec: n } : {}))(workspaceSnapshotTtlKnob(process.env.WORKSPACE_SNAPSHOT_TTL_SEC)),
|
|
2765
2800
|
},
|
|
2766
2801
|
};
|
|
2767
2802
|
}
|
|
@@ -2836,7 +2871,7 @@ export function loadConfig() {
|
|
|
2836
2871
|
throw new Error(retentionRefusal);
|
|
2837
2872
|
}
|
|
2838
2873
|
const { directApprovalDoor, durableApproval: dgDurable, approvalHmacKeys: dgHmacKeys } = approval;
|
|
2839
|
-
const { principalJwtPubkeys, principalJwtIss, principalJwtAud, operatorPrincipals: dgOperators, operatorSingleUser, authTokens } = auth;
|
|
2874
|
+
const { principalJwtPubkeys, principalJwtIss, principalJwtAud, operatorPrincipals: dgOperators, operatorDrainPrincipals, operatorSingleUser, authTokens } = auth;
|
|
2840
2875
|
const directDoorActive = directApprovalDoor && dgDurable && dgHmacKeys.length > 0 && principalJwtPubkeys.length > 0 && !!principalJwtIss && !!principalJwtAud;
|
|
2841
2876
|
const doorRefusal = halfOpenDirectDoorRefusal({
|
|
2842
2877
|
directApprovalDoor,
|
|
@@ -2855,10 +2890,14 @@ export function loadConfig() {
|
|
|
2855
2890
|
if (operatorsRefusal !== undefined) {
|
|
2856
2891
|
throw new Error(operatorsRefusal);
|
|
2857
2892
|
}
|
|
2858
|
-
const singleUserOperatorRefusal = operatorSingleUserRefusal({ operatorSingleUser, requirePrincipal, operatorPrincipals: dgOperators });
|
|
2893
|
+
const singleUserOperatorRefusal = operatorSingleUserRefusal({ operatorSingleUser, requirePrincipal, operatorPrincipals: dgOperators, operatorDrainPrincipals });
|
|
2859
2894
|
if (singleUserOperatorRefusal !== undefined) {
|
|
2860
2895
|
throw new Error(singleUserOperatorRefusal);
|
|
2861
2896
|
}
|
|
2897
|
+
const rosterRefusal = operatorRosterWellFormedRefusal({ operatorPrincipals: dgOperators, operatorDrainPrincipals });
|
|
2898
|
+
if (rosterRefusal !== undefined) {
|
|
2899
|
+
throw new Error(rosterRefusal);
|
|
2900
|
+
}
|
|
2862
2901
|
const bakeOperatorsRefusal = bakeOperatorPrincipalsRefusal({ imageBakes: orchestration.imageBakes, operatorPrincipals: dgOperators });
|
|
2863
2902
|
if (bakeOperatorsRefusal !== undefined) {
|
|
2864
2903
|
throw new Error(bakeOperatorsRefusal);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Server } from "node:http";
|
|
2
|
+
/** 开不了口的原因词(闭集;`listening` 行的 `controlSocketUnavailable` 取值;顺序 = 开口序)。 */
|
|
3
|
+
export declare const CONTROL_SOCKET_UNAVAILABLE_REASONS: readonly ["unsupported_platform", "data_root_not_exclusive", "path_too_long", "dir_not_private", "path_occupied", "bind_failed"];
|
|
4
|
+
export type ControlSocketUnavailableReason = (typeof CONTROL_SOCKET_UNAVAILABLE_REASONS)[number];
|
|
5
|
+
/** 开口的结局:在听(绝对路径)或缺席(原因词)—— 恰一形。 */
|
|
6
|
+
export type ControlSocketOpening = {
|
|
7
|
+
readonly listening: true;
|
|
8
|
+
readonly path: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly listening: false;
|
|
11
|
+
readonly reason: ControlSocketUnavailableReason;
|
|
12
|
+
};
|
|
13
|
+
/** 数据根(锁所守的物理路径)→ 控制 socket 的绝对路径。唯一推导:`listening` 行报的、开口判的、附着方连的是同一只。 */
|
|
14
|
+
export declare function controlSocketPathOf(physicalDataRoot: string): string;
|
|
15
|
+
/** 私有判的读数(`lstat`,不跟符号链接)。`absent` = 不在;`unreadable` = 读不出(按不私有处置)。 */
|
|
16
|
+
export type ControlPathReading = {
|
|
17
|
+
readonly kind: "absent";
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: "unreadable";
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: "directory" | "socket" | "symlink" | "other";
|
|
22
|
+
readonly uid: number;
|
|
23
|
+
readonly mode: number;
|
|
24
|
+
};
|
|
25
|
+
/** 读一只路径(`lstat`)。不在 ⇒ `absent`;别的读错 ⇒ `unreadable`。 */
|
|
26
|
+
export declare function readControlPath(path: string): ControlPathReading;
|
|
27
|
+
/** `control/` 私有吗:是目录(不是符号链接)∧ 属主 = `euid` ∧ 无组 / 他人位。任何别的读数 ⇒ 否。 */
|
|
28
|
+
export declare function controlDirIsPrivate(reading: ControlPathReading, euid: number): boolean;
|
|
29
|
+
/** `engine.sock` 是不是本用户的 socket:是 socket(不是符号链接)∧ 属主 = `euid`。任何别的读数 ⇒ 否。 */
|
|
30
|
+
export declare function controlSocketIsOwned(reading: ControlPathReading, euid: number): boolean;
|
|
31
|
+
export interface ControlSocketOpenOptions {
|
|
32
|
+
/** 本进程持锁独占的物理数据根(`StoreBackend.exclusiveDataRoot()`);缺席 ⇒ 不开口。 */
|
|
33
|
+
readonly exclusiveDataRoot: string | undefined;
|
|
34
|
+
/** 平台读数(缺省 = 全仓唯一平台读点 `HOST_PLATFORM`)。 */
|
|
35
|
+
readonly platform?: string;
|
|
36
|
+
/** 属主判对照的 euid(缺省 `process.geteuid()`)。 */
|
|
37
|
+
readonly euid?: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 在 `server`(控制口那只 `http.Server`,`createHttpServer` 交出的 `controlSocketServer`)上开控制 socket —— 判序见头注。
|
|
41
|
+
* 结局恰一形;`listening: false` 时 `server` 不在听(绑上又判不过的那一形已先关)。调用点:TCP listen 成功之后、`listening` 行之前(起服第 10 段)。
|
|
42
|
+
*/
|
|
43
|
+
export declare function openControlSocket(server: Server, opts: ControlSocketOpenOptions): Promise<ControlSocketOpening>;
|
|
44
|
+
/** `listening` 行的两键 —— 同一次开口结局的投影,恰一键在场(`controlSocket` ⟺ 在听;`controlSocketUnavailable` ⟺ 缺席)。 */
|
|
45
|
+
export interface ControlSocketLineKeys {
|
|
46
|
+
readonly controlSocket?: string;
|
|
47
|
+
readonly controlSocketUnavailable?: ControlSocketUnavailableReason;
|
|
48
|
+
}
|
|
49
|
+
/** 开口结局 → `listening` 行两键(唯一投影)。 */
|
|
50
|
+
export declare function controlSocketLineKeysOf(opening: ControlSocketOpening): ControlSocketLineKeys;
|
|
51
|
+
//# sourceMappingURL=control-socket.d.ts.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { lstatSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { ensureDir } from "@sema-agent/core";
|
|
4
|
+
import { HOST_PLATFORM } from "./plugins/host-platform.js";
|
|
5
|
+
export const CONTROL_SOCKET_UNAVAILABLE_REASONS = ["unsupported_platform", "data_root_not_exclusive", "path_too_long", "dir_not_private", "path_occupied", "bind_failed"];
|
|
6
|
+
const SUN_PATH_MAX_BYTES = {
|
|
7
|
+
darwin: 104,
|
|
8
|
+
linux: 107,
|
|
9
|
+
aix: "unsupported",
|
|
10
|
+
android: "unsupported",
|
|
11
|
+
freebsd: "unsupported",
|
|
12
|
+
haiku: "unsupported",
|
|
13
|
+
openbsd: "unsupported",
|
|
14
|
+
sunos: "unsupported",
|
|
15
|
+
win32: "unsupported",
|
|
16
|
+
cygwin: "unsupported",
|
|
17
|
+
netbsd: "unsupported",
|
|
18
|
+
};
|
|
19
|
+
export function controlSocketPathOf(physicalDataRoot) {
|
|
20
|
+
return join(physicalDataRoot, "control", "engine.sock");
|
|
21
|
+
}
|
|
22
|
+
function readingOf(stats) {
|
|
23
|
+
const kind = stats.isSymbolicLink() ? "symlink" : stats.isDirectory() ? "directory" : stats.isSocket() ? "socket" : "other";
|
|
24
|
+
return { kind, uid: stats.uid, mode: stats.mode };
|
|
25
|
+
}
|
|
26
|
+
export function readControlPath(path) {
|
|
27
|
+
try {
|
|
28
|
+
return readingOf(lstatSync(path));
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
return err.code === "ENOENT" ? { kind: "absent" } : { kind: "unreadable" };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function controlDirIsPrivate(reading, euid) {
|
|
35
|
+
return reading.kind === "directory" && reading.uid === euid && (reading.mode & 0o077) === 0;
|
|
36
|
+
}
|
|
37
|
+
export function controlSocketIsOwned(reading, euid) {
|
|
38
|
+
return reading.kind === "socket" && reading.uid === euid;
|
|
39
|
+
}
|
|
40
|
+
function sunPathMaxBytesOf(platform) {
|
|
41
|
+
if (!Object.hasOwn(SUN_PATH_MAX_BYTES, platform))
|
|
42
|
+
return undefined;
|
|
43
|
+
const limit = SUN_PATH_MAX_BYTES[platform];
|
|
44
|
+
return typeof limit === "number" ? limit : undefined;
|
|
45
|
+
}
|
|
46
|
+
const unavailable = (reason) => ({ listening: false, reason });
|
|
47
|
+
async function ioStep(act) {
|
|
48
|
+
try {
|
|
49
|
+
await act();
|
|
50
|
+
return { ok: true };
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return { ok: false, error };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function listenOn(server, path) {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const onError = (err) => reject(err);
|
|
59
|
+
server.once("error", onError);
|
|
60
|
+
server.listen(path, () => {
|
|
61
|
+
server.removeListener("error", onError);
|
|
62
|
+
resolve();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export async function openControlSocket(server, opts) {
|
|
67
|
+
const maxBytes = sunPathMaxBytesOf(opts.platform ?? HOST_PLATFORM);
|
|
68
|
+
if (maxBytes === undefined)
|
|
69
|
+
return unavailable("unsupported_platform");
|
|
70
|
+
const euid = opts.euid ?? process.geteuid?.();
|
|
71
|
+
if (euid === undefined)
|
|
72
|
+
return unavailable("unsupported_platform");
|
|
73
|
+
const root = opts.exclusiveDataRoot;
|
|
74
|
+
if (root === undefined)
|
|
75
|
+
return unavailable("data_root_not_exclusive");
|
|
76
|
+
const path = controlSocketPathOf(root);
|
|
77
|
+
if (Buffer.byteLength(path, "utf8") > maxBytes)
|
|
78
|
+
return unavailable("path_too_long");
|
|
79
|
+
const dir = dirname(path);
|
|
80
|
+
await ioStep(() => ensureDir(dir));
|
|
81
|
+
if (!controlDirIsPrivate(readControlPath(dir), euid))
|
|
82
|
+
return unavailable("dir_not_private");
|
|
83
|
+
const existing = readControlPath(path);
|
|
84
|
+
switch (existing.kind) {
|
|
85
|
+
case "absent":
|
|
86
|
+
break;
|
|
87
|
+
case "socket":
|
|
88
|
+
if (!controlSocketIsOwned(existing, euid))
|
|
89
|
+
return unavailable("path_occupied");
|
|
90
|
+
if (!(await ioStep(() => unlinkSync(path))).ok)
|
|
91
|
+
return unavailable("bind_failed");
|
|
92
|
+
break;
|
|
93
|
+
case "directory":
|
|
94
|
+
case "symlink":
|
|
95
|
+
case "other":
|
|
96
|
+
return unavailable("path_occupied");
|
|
97
|
+
case "unreadable":
|
|
98
|
+
return unavailable("bind_failed");
|
|
99
|
+
default: {
|
|
100
|
+
const unknownReading = existing;
|
|
101
|
+
throw new Error(`control socket: unknown path reading ${JSON.stringify(unknownReading)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!(await ioStep(() => listenOn(server, path))).ok)
|
|
105
|
+
return unavailable("bind_failed");
|
|
106
|
+
if (!controlSocketIsOwned(readControlPath(path), euid) || !controlDirIsPrivate(readControlPath(dir), euid)) {
|
|
107
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
108
|
+
return unavailable("bind_failed");
|
|
109
|
+
}
|
|
110
|
+
return { listening: true, path };
|
|
111
|
+
}
|
|
112
|
+
export function controlSocketLineKeysOf(opening) {
|
|
113
|
+
return opening.listening ? { controlSocket: opening.path } : { controlSocketUnavailable: opening.reason };
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=control-socket.js.map
|
|
@@ -64,7 +64,8 @@ export interface PathAdjudication {
|
|
|
64
64
|
* · `durable` —— checkpoint suspend/resume 服 ask(HTTP 腿 durable 开、park 赎回腿)。core 的问答策略
|
|
65
65
|
* (`createDurableQuestionPolicy()`,原形逐字)**只**叠在这一形上:它对引擎的问题工具答 `ask`,落点归 core 的同步优先
|
|
66
66
|
* (有问题席 ⇒ 先投卡、席在窗内拿到答即交答;没拿到 ⇒ 停驻 —— 卡由活面呈出而窗走完的那一问除外:席抛,引擎的投递失败臂不停驻)。
|
|
67
|
-
* · `live` —— Runner 的活 onAsk 席当场服 ask(run-local 的 `createLocalApprover`:TTY 问 y/N / 无 TTY 当场拒;
|
|
67
|
+
* · `live` —— Runner 的活 onAsk 席当场服 ask(run-local 的 `createLocalApprover`:TTY 问 y/N / 无 TTY 当场拒;leader 车道不耐久的腿
|
|
68
|
+
* (`leader/leader.ts` `leaderLegDurability` 判为不耐久:各修复腿、没递停驻设施或续跑上下文没落的 worker)的无人值守层:答集内的类按策略答,其余交回 core 的缺席路 —— 这条腿没有停驻设施 ⇒ 拒;
|
|
68
69
|
* core `isLiveApproverSeat(onAsk)` 为真时 `suspendAsk` 早退,ask 落回调)。三张名单与 durable 席**同一只**策略。
|
|
69
70
|
* · `absent` —— 没有谁服 ask ⇒ adjudicated allow-all;**只**服务零名单(HTTP 腿「durable 关 ∧ 单用户零意图」)。
|
|
70
71
|
* 带名单进来 = 响亮拒:名单被一条没有席的腿静默吞掉,正是 [ref] 的病形。
|
package/dist/device-ws-hub.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ import { type DeviceOwner, type DeviceStore } from "./device-store.js";
|
|
|
40
40
|
import type { DeviceEnrollment, DeviceRejection } from "./device-enrollment.js";
|
|
41
41
|
import { type CidrSet } from "./net-address.js";
|
|
42
42
|
import { type DeviceChunkKind, type DeviceInstructionArgsByKind, type DeviceInstructionKind, type DeviceInstructionOutcome } from "./device-ws-protocol.js";
|
|
43
|
+
import type { NonPersonTier } from "./operator-roster.js";
|
|
43
44
|
/** 交给调用方的一片上行流(`data` 已从 base64 解回字节)。 */
|
|
44
45
|
export interface DeviceChunkDelivery {
|
|
45
46
|
streamSeq: number;
|
|
@@ -129,6 +130,12 @@ export interface DeviceWsHubOptions {
|
|
|
129
130
|
enrollment: DeviceEnrollment;
|
|
130
131
|
/** 本副本 id(写进连接租约行,跨副本排障用)。 */
|
|
131
132
|
replicaId: string;
|
|
133
|
+
/**
|
|
134
|
+
* hello 准入的属主圈禁读者:这一台设备行的属主此刻落在名册的哪一档**非人档**(圈禁,`undefined` = 不圈禁)。答「是」⇒ `helloReject: auth_failed`,不夺租约、不顶替旧连接。
|
|
135
|
+
* 汇聚端自己不 import 名册、不判档位:判官只有一只(`http/principal-gate.ts` 的 `confinedTierOf`,设备读点 `deviceOwnerConfinedTier`),装配方(`boot/device-lane.ts`)把它连进来。
|
|
136
|
+
* **必填**:可选会让漏接静默(没接 = 不圈禁,安全轴上的静默放宽)—— 漏接编译红。每次 hello 现读(名册是现态,不拍快照);读者抛 ⇒ 握手失败、连接关(`device_hello_failed`),不放行。
|
|
137
|
+
*/
|
|
138
|
+
ownerConfinedTier: (owner: DeviceOwner) => NonPersonTier | undefined;
|
|
132
139
|
logger?: DeviceWsHubLogger;
|
|
133
140
|
metrics?: {
|
|
134
141
|
inc(name: string, labels?: Record<string, string>): void;
|
package/dist/device-ws-hub.js
CHANGED
|
@@ -121,7 +121,7 @@ function forwardedHopAddress(hop) {
|
|
|
121
121
|
return canonicalAddress(t);
|
|
122
122
|
}
|
|
123
123
|
export function createDeviceWsHub(options) {
|
|
124
|
-
const { store, enrollment, replicaId, logger, metrics } = options;
|
|
124
|
+
const { store, enrollment, replicaId, ownerConfinedTier, logger, metrics } = options;
|
|
125
125
|
const heartbeatIntervalMs = options.heartbeatIntervalMs ?? 15_000;
|
|
126
126
|
const heartbeatMisses = options.heartbeatMisses ?? 3;
|
|
127
127
|
const leaseSeconds = options.leaseSeconds ?? 45;
|
|
@@ -438,6 +438,12 @@ export function createDeviceWsHub(options) {
|
|
|
438
438
|
}
|
|
439
439
|
if (row.status === "revoked")
|
|
440
440
|
return rejectHello(conn, "revoked");
|
|
441
|
+
const confined = ownerConfinedTier(row.owner);
|
|
442
|
+
if (confined !== undefined) {
|
|
443
|
+
logger?.warn("device_hello_owner_confined", { deviceId: row.deviceId, tier: confined });
|
|
444
|
+
metrics?.inc("device_hello_rejected_total", { code: "auth_failed" });
|
|
445
|
+
return rejectHello(conn, "auth_failed");
|
|
446
|
+
}
|
|
441
447
|
const existing = conns.get(row.deviceId);
|
|
442
448
|
if (existing && existing !== conn) {
|
|
443
449
|
const fresh = Date.now() - existing.lastFrameAt < heartbeatIntervalMs * heartbeatMisses;
|
package/dist/elicitation.js
CHANGED
|
@@ -6,7 +6,7 @@ import { refusalBody } from "./http/refusal-body.js";
|
|
|
6
6
|
import { keyAdmits, ownerCellOf, recoveredSubject } from "./owner-faces.js";
|
|
7
7
|
import { createHitlOpenRace } from "./hitl-delivery.js";
|
|
8
8
|
const MAX_ELICIT_MESSAGE_CHARS = 4096;
|
|
9
|
-
const
|
|
9
|
+
const MAX_ELICIT_SCHEMA_CHARS = 8192;
|
|
10
10
|
const RELEASE_ACTION = { refused: "decline", unanswered: "cancel" };
|
|
11
11
|
export const DEFAULT_ELICITATION_THROTTLE = {
|
|
12
12
|
maxConcurrentPerRun: 2,
|
|
@@ -42,7 +42,7 @@ function boundSchema(schema) {
|
|
|
42
42
|
return undefined;
|
|
43
43
|
const redacted = redactDeep(schema);
|
|
44
44
|
try {
|
|
45
|
-
if (JSON.stringify(redacted).length >
|
|
45
|
+
if (JSON.stringify(redacted).length > MAX_ELICIT_SCHEMA_CHARS)
|
|
46
46
|
return undefined;
|
|
47
47
|
}
|
|
48
48
|
catch {
|