@sema-agent/server 7.105.0 → 7.107.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.
Files changed (175) hide show
  1. package/MIGRATION.md +105 -0
  2. package/README.md +2 -2
  3. package/USAGE.md +135 -43
  4. package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -0
  5. package/deploy/sema-up/cluster-up.sh +15 -2
  6. package/deploy/sema-up/kube-up.sh +8 -3
  7. package/deploy/sema-up/lib/flagshape.sh +29 -0
  8. package/deploy/sema-up/lib/parse-machines.py +4 -1
  9. package/deploy/sema-up/sema-up.sh +11 -9
  10. package/deploy/sema-up/stack/sema-stack-cluster.yaml +3 -1
  11. package/dist/approval-card.d.ts +1 -1
  12. package/dist/approval.d.ts +6 -1
  13. package/dist/approval.js +4 -4
  14. package/dist/auth-bridge.d.ts +14 -0
  15. package/dist/auth-bridge.js +7 -0
  16. package/dist/auto-mode-face.d.ts +49 -40
  17. package/dist/auto-mode-face.js +6 -68
  18. package/dist/bench/s1/live-deps.d.ts +9 -2
  19. package/dist/bench/s1/live-deps.js +13 -8
  20. package/dist/boot/budget-tracing.js +3 -2
  21. package/dist/boot/config-center.d.ts +4 -0
  22. package/dist/boot/config-center.js +95 -37
  23. package/dist/boot/coordinators.d.ts +1 -0
  24. package/dist/boot/coordinators.js +3 -1
  25. package/dist/boot/device-lane.d.ts +2 -1
  26. package/dist/boot/execution-env.d.ts +11 -0
  27. package/dist/boot/execution-env.js +30 -6
  28. package/dist/boot/execution-lane.js +2 -3
  29. package/dist/boot/leader.d.ts +2 -1
  30. package/dist/boot/leader.js +2 -0
  31. package/dist/boot/parked-revive-gate.d.ts +10 -7
  32. package/dist/boot/parked-revive-gate.js +4 -11
  33. package/dist/boot/resolve-spec.d.ts +1 -4
  34. package/dist/boot/resolve-spec.js +44 -21
  35. package/dist/boot/runner-deps.d.ts +5 -8
  36. package/dist/boot/runner-deps.js +3 -9
  37. package/dist/boot/side-query-lane.d.ts +2 -2
  38. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  39. package/dist/boot/stage-03-store-backend.js +2 -2
  40. package/dist/boot/stage-04-stores.d.ts +1 -1
  41. package/dist/boot/stage-05-execution-env.d.ts +2 -1
  42. package/dist/boot/stage-05-execution-env.js +2 -2
  43. package/dist/boot/stage-06-runners.d.ts +3 -2
  44. package/dist/boot/stage-06-runners.js +2 -5
  45. package/dist/boot/stage-07-capability-layer.d.ts +2 -2
  46. package/dist/boot/stage-07-capability-layer.js +1 -2
  47. package/dist/boot/stage-08-reapers.d.ts +2 -2
  48. package/dist/boot/stage-09-leader.d.ts +2 -2
  49. package/dist/boot/stage-09-leader.js +2 -2
  50. package/dist/boot/stage-10-http-server.d.ts +2 -2
  51. package/dist/boot/stage-10-http-server.js +11 -7
  52. package/dist/boot/stores.d.ts +1 -1
  53. package/dist/boot/stores.js +6 -4
  54. package/dist/brain.d.ts +24 -2
  55. package/dist/brain.js +29 -10
  56. package/dist/budget.js +3 -0
  57. package/dist/capabilities/center-wiring.d.ts +64 -0
  58. package/dist/capabilities/center-wiring.js +24 -0
  59. package/dist/capabilities/composite-members.d.ts +89 -0
  60. package/dist/capabilities/composite-members.js +20 -0
  61. package/dist/capabilities/execution-lane.d.ts +3 -0
  62. package/dist/capabilities/execution-lane.js +5 -2
  63. package/dist/config-catalog.d.ts +35 -5
  64. package/dist/config-catalog.js +92 -26
  65. package/dist/config-center/apply-effective.d.ts +1 -1
  66. package/dist/config-center/apply-effective.js +7 -8
  67. package/dist/config-center/apply-ledger.d.ts +17 -0
  68. package/dist/config-center/apply-ledger.js +5 -0
  69. package/dist/config-center/center-request.d.ts +17 -0
  70. package/dist/config-center/center-request.js +4 -0
  71. package/dist/config-center/pull-state.d.ts +29 -0
  72. package/dist/config-center/pull-state.js +2 -0
  73. package/dist/config-types.d.ts +41 -14
  74. package/dist/config-types.js +1 -0
  75. package/dist/config.d.ts +35 -0
  76. package/dist/config.js +69 -16
  77. package/dist/cross-session-settings.d.ts +1 -1
  78. package/dist/deployment-governance.d.ts +12 -1
  79. package/dist/deployment-governance.js +2 -0
  80. package/dist/execution-lane-caps.d.ts +30 -2
  81. package/dist/execution-lane-caps.js +12 -0
  82. package/dist/fleet-client.d.ts +18 -13
  83. package/dist/fleet-client.js +32 -22
  84. package/dist/fleet-lease.d.ts +72 -47
  85. package/dist/fleet-lease.js +66 -14
  86. package/dist/governance-http-status.d.ts +0 -1
  87. package/dist/governance-http-status.js +0 -1
  88. package/dist/hosted-posture.d.ts +6 -17
  89. package/dist/hosted-posture.js +11 -6
  90. package/dist/http/admission.js +14 -9
  91. package/dist/http/approval-sweeps.js +13 -1
  92. package/dist/http/dispatch.js +1 -1
  93. package/dist/http/durable-run-leg.js +2 -0
  94. package/dist/http/request-guards.d.ts +25 -0
  95. package/dist/http/request-guards.js +25 -5
  96. package/dist/http/resume-legs.js +19 -11
  97. package/dist/http/route-ctx.d.ts +9 -0
  98. package/dist/http/routes/approvals-assistant.js +0 -1
  99. package/dist/http/routes/capabilities.js +11 -12
  100. package/dist/http/routes/config-catalog.js +2 -1
  101. package/dist/http/routes/diagnostics.js +2 -0
  102. package/dist/http/routes/tasks.js +4 -1
  103. package/dist/http/server.d.ts +20 -4
  104. package/dist/http/session-inherit-env.d.ts +37 -0
  105. package/dist/http/session-inherit-env.js +6 -0
  106. package/dist/http/wire-types.d.ts +19 -28
  107. package/dist/index.d.ts +2 -1
  108. package/dist/index.js +2 -1
  109. package/dist/key-resolver.d.ts +74 -19
  110. package/dist/key-resolver.js +65 -7
  111. package/dist/leader/leader.d.ts +6 -0
  112. package/dist/leader/leader.js +2 -1
  113. package/dist/leader/repair-wire.js +2 -1
  114. package/dist/leader/wire.d.ts +9 -1
  115. package/dist/leader/wire.js +10 -6
  116. package/dist/observability/fail-open.d.ts +5 -1
  117. package/dist/observability/fail-open.js +5 -1
  118. package/dist/observability/metrics.js +2 -0
  119. package/dist/observability/permission-rule-events.d.ts +1 -1
  120. package/dist/observability/permission-rule-events.js +1 -1
  121. package/dist/observability/run-terminal.d.ts +40 -1
  122. package/dist/observability/run-terminal.js +2 -5
  123. package/dist/observability/secret-env-scrub.js +1 -1
  124. package/dist/observability/tool-trace.d.ts +2 -0
  125. package/dist/operator-ask.d.ts +29 -1
  126. package/dist/operator-ask.js +6 -0
  127. package/dist/parked-decide.d.ts +14 -1
  128. package/dist/parked-decide.js +8 -1
  129. package/dist/plugins/session-policy-store-sql.d.ts +38 -7
  130. package/dist/plugins/session-policy-store-sql.js +64 -22
  131. package/dist/plugins/sql-driver.d.ts +1 -1
  132. package/dist/plugins/store-backend.d.ts +12 -3
  133. package/dist/plugins/store-backend.js +16 -5
  134. package/dist/plugins/tidb-pool.js +1 -1
  135. package/dist/read-face-posture.d.ts +4 -4
  136. package/dist/request-key-closure.js +2 -2
  137. package/dist/request-value-closure.d.ts +17 -5
  138. package/dist/request-value-closure.js +12 -0
  139. package/dist/router/route-orchestration.d.ts +11 -0
  140. package/dist/router/route-orchestration.js +7 -6
  141. package/dist/run-local.js +4 -2
  142. package/dist/runs.d.ts +2 -1
  143. package/dist/runs.js +4 -4
  144. package/dist/runtime-caps-resolver.d.ts +4 -10
  145. package/dist/runtime-governance.d.ts +8 -11
  146. package/dist/runtime-governance.js +10 -41
  147. package/dist/sealed-key.d.ts +0 -40
  148. package/dist/sealed-key.js +0 -20
  149. package/dist/server-secret-env.d.ts +11 -12
  150. package/dist/session-policy-wire.d.ts +5 -3
  151. package/dist/session-titler.d.ts +11 -0
  152. package/dist/session-titler.js +6 -2
  153. package/dist/sql-engine-posture.d.ts +2 -2
  154. package/dist/store-posture.d.ts +53 -0
  155. package/dist/store-posture.js +32 -0
  156. package/dist/task-settings.d.ts +64 -62
  157. package/dist/task-settings.js +57 -27
  158. package/dist/tool-approval.d.ts +51 -22
  159. package/dist/tool-approval.js +44 -8
  160. package/dist/tool-declaration-http-status.d.ts +12 -0
  161. package/dist/tool-declaration-http-status.js +12 -0
  162. package/dist/trace/core-keyset-guard.d.ts +7 -7
  163. package/dist/trace/engine-notice-wire.d.ts +1 -1
  164. package/dist/trace/engine-notice-wire.js +5 -0
  165. package/dist/trace/project.d.ts +7 -5
  166. package/dist/trace/project.js +51 -3
  167. package/dist/trace/wire-projection-faces.d.ts +1 -1
  168. package/dist/trace/wire-projection-faces.js +1 -1
  169. package/dist/unattended-permission-mode.d.ts +110 -0
  170. package/dist/unattended-permission-mode.js +65 -0
  171. package/package.json +3 -3
  172. package/dist/boot/session-shell-gate-registry.d.ts +0 -39
  173. package/dist/boot/session-shell-gate-registry.js +0 -21
  174. package/dist/roster-bound-http-status.d.ts +0 -6
  175. package/dist/roster-bound-http-status.js +0 -6
@@ -29,6 +29,7 @@ PROJECT="${PROJECT:-sema}"
29
29
  REGION="${REGION:-global}"
30
30
  KUBE_MODE="${KUBE_MODE:?KUBE_MODE 未设置(existing|k3s)}"
31
31
  DRY_RUN="${DRY_RUN:-false}"
32
+ USERS_GIVEN="${USERS:+1}" # S-600:显式给没给(缺省 single 之前记下;决策见下方 users_decide)
32
33
  USERS="${USERS:-single}"
33
34
  REGISTRY_ENABLED="${REGISTRY_ENABLED:-true}"
34
35
  GIT_MODE="${GIT_MODE:-none}"
@@ -45,6 +46,10 @@ flag_enum "--users(USERS)" "$USERS" $FLAG_WORDS_USERS
45
46
  flag_bool "--registry(REGISTRY_ENABLED)" "$REGISTRY_ENABLED"
46
47
  flag_enum "--git(GIT_MODE)" "$GIT_MODE" $FLAG_WORDS_GIT
47
48
  flag_enum "--region(REGION)" "$REGION" $FLAG_WORDS_REGION
49
+ # S-600:租户形 = 显式 USERS ∨ 上一次落下的部署事实 ∨ single(规则与拒句在 lib/flagshape.sh 的 users_decide)——
50
+ # 在动集群之前判,不带旗的重跑不会把多租部署降成单用户。
51
+ ENVF="$WORK/.env"
52
+ users_decide "$USERS_GIVEN"
48
53
 
49
54
  # ── 0. kubeconfig / 工具解析 ─────────────────────────────────────────────────────
50
55
  if [ "$KUBE_MODE" = "k3s" ]; then
@@ -124,13 +129,12 @@ if [ "$REGISTRY_ENABLED" = "true" ] && [ -z "${SEMA_REGISTRY_IMAGE:-}" ]; then
124
129
  die "registry=true(默认)需要 SEMA_REGISTRY_IMAGE。下一步:--registry-image=<仓>/sema-registry:<tag>,或 --registry=false"
125
130
  fi
126
131
 
127
- ENVF="$WORK/.env"
128
132
  if [ "$DRY_RUN" = "true" ]; then
129
133
  {
130
134
  echo "══════ kube-up dry-run(不写文件、不动集群)══════"
131
135
  echo " 集群: $NODES 节点(mode=$KUBE_MODE,kubeconfig=$KUBECONFIG)"
132
136
  echo " SC: ${STORAGE_CLASS:-默认($DEFAULT_SC)} kata: $KATA_OK(否=沙箱 runc 降级)"
133
- echo " 形态: pg×$PG_INSTANCES($PG_AA) server×$SERVER_REPLICAS registry=$REGISTRY_ENABLED git=$GIT_MODE"
137
+ echo " 形态: pg×$PG_INSTANCES($PG_AA) server×$SERVER_REPLICAS registry=$REGISTRY_ENABLED git=$GIT_MODE users=$USERS"
134
138
  echo " 将执行: CNPG operator $CNPG_VERSION → helm upgrade --install sema-stack -n $NAMESPACE"
135
139
  echo " → runner-token 复制 → MinIO 建桶 → $([ "$GIT_MODE" = provision ] && printf 'Gitea 引导(管理员/token/OAuth)→ ')smoke → summary"
136
140
  echo " 凭据: $ENVF $([ -f "$ENVF" ] && echo '(已存在:只补缺不轮换)' || echo '(将新建)')"
@@ -158,6 +162,7 @@ mkdir -p "$WORK"; chmod 700 "$WORK" 2>/dev/null || true
158
162
  touch "$ENVF"; chmod 600 "$ENVF"
159
163
  setk MINIO_PASSWORD "$(openssl rand -hex 24)"
160
164
  setk SERVICE_AUTH_TOKEN "$(openssl rand -hex 24)"
165
+ resetk REQUIRE_PRINCIPAL "$(principal_of_users "$USERS")" # S-600:部署事实(下次不带旗重跑据此沿用租户形)
161
166
  NEW_REG_PW=false NEW_GIT_PW=false
162
167
  if [ "$REGISTRY_ENABLED" = "true" ]; then
163
168
  setk_new REGISTRY_ADMIN_PASSWORD "$(openssl rand -hex 24)" && NEW_REG_PW=true
@@ -191,7 +196,7 @@ helm_values() { # helm_values <giteaBaseUrl> <oauthId> <oauthSecret>
191
196
  V_MODEL_GATEWAY="$MODEL_GATEWAY_BASEURL" V_MODEL_ID="$MODEL_ID" \
192
197
  V_SERVER_IMAGE="$SEMA_SERVER_IMAGE" V_REPLICAS="$SERVER_REPLICAS" \
193
198
  V_PG_INSTANCES="$PG_INSTANCES" V_PG_AA="$PG_AA" V_SC="$STORAGE_CLASS" \
194
- V_RT_CLASS="$RUNTIME_CLASS" V_REQ_PRINCIPAL="$([ "$USERS" = multi ] && echo true || echo false)" \
199
+ V_RT_CLASS="$RUNTIME_CLASS" V_REQ_PRINCIPAL="$(principal_of_users "$USERS")" \
195
200
  V_PKG_SOURCE="$([ "$REGION" = "global" ] && echo global || echo '')" \
196
201
  V_REG_ENABLED="$REGISTRY_ENABLED" V_REG_IMAGE="${SEMA_REGISTRY_IMAGE:-}" \
197
202
  V_REG_PW="${REGISTRY_ADMIN_PASSWORD:-}" V_REG_SS="${REGISTRY_SESSION_SECRET:-}" V_REG_PT="${REGISTRY_PULL_TOKEN:-}" \
@@ -18,6 +18,35 @@
18
18
  # ── 词表(单一属主)──────────────────────────────────────────────────────────────────────────────
19
19
  FLAG_WORDS_PROFILE="full-single data-center local-plus full-cluster"
20
20
  FLAG_WORDS_USERS="single multi"
21
+ # 租户形 ⇒ 容器上 REQUIRE_PRINCIPAL 的值 —— 这条翻译的**唯一**定义(S-600,7.105.0 从 sema-up.sh 搬来):sema-up 的 compose 生成段 /
22
+ # 事实核对,kube-up 的 helm values,cluster-up 的渲染清单与 helm values 同调这一只(修前 kube-up 另写一份同义翻译,S-619 D5;
23
+ # cluster-up 一份都没有 ⇒ --users=multi 在 --kube=machines 路径上被静默丢成单用户)。只认两词,别的词先过 flag_enum。
24
+ principal_of_users() { [ "$1" = "multi" ] && echo true || echo false; }
25
+
26
+ # users_decide <显式给了 USERS 吗(1 / 空)> —— kube 两条装配路径(kube-up / cluster-up)的租户形决策,与 sema-up 的
27
+ # compose 档同一条规则:**决策 = 显式 USERS ∨ 上一次落在 $ENVF 的部署事实 REQUIRE_PRINCIPAL ∨ single**;显式值与事实
28
+ # 不一致 ⇒ 拒并点名两处(不静默把一台多租部署降成单用户,也不静默把单用户升成多租)。事实 → 租户词只经
29
+ # principal_of_users 反查(两词逐个过正向比,不另写反表);认不出的事实值 ⇒ 拒。设 USERS。
30
+ # S-600 codex 一轮 [high]:machines 路径开始认 --users 之后,一次不带旗的重跑会拿缺省 single 把多租部署的
31
+ # REQUIRE_PRINCIPAL 翻回 false(kube-up 路径此前就同形)—— 事实由两只脚本各自在写凭据的同一处 resetk 落盘。
32
+ # 调用方先 source lib/envf.sh 并定好 ENVF(文件可以还不存在 = 首装,无事实)。
33
+ users_decide() {
34
+ local given="$1" fact fact_users="" u
35
+ fact="$(envf_get REQUIRE_PRINCIPAL)" || exit 1
36
+ [ -n "$fact" ] || return 0
37
+ for u in $FLAG_WORDS_USERS; do [ "$(principal_of_users "$u")" = "$fact" ] && fact_users="$u"; done
38
+ if [ -z "$fact_users" ]; then
39
+ log "⛔ 部署事实 $ENVF 里 REQUIRE_PRINCIPAL=$(flag_show "$fact"):本工具只读自己写出的两形 $(principal_of_users multi) / $(principal_of_users single)"
40
+ log " ↳ 下一步:改成 REQUIRE_PRINCIPAL=$(principal_of_users multi)(多人)或 REQUIRE_PRINCIPAL=$(principal_of_users single)(单人),再重跑"
41
+ exit 1
42
+ fi
43
+ if [ -z "$given" ]; then USERS="$fact_users"; return 0; fi
44
+ if [ "$USERS" != "$fact_users" ]; then
45
+ log "⛔ 部署事实冲突:$ENVF 里 REQUIRE_PRINCIPAL=$fact(= --users=$fact_users),本次 --users=$USERS 要写 REQUIRE_PRINCIPAL=$(principal_of_users "$USERS")"
46
+ log " ↳ 下一步:不带 --users 重跑即沿用这台部署的租户形;真要改租户形,先确认身份隔离的影响,再把 $ENVF 里那一行改掉后重跑"
47
+ exit 1
48
+ fi
49
+ }
21
50
  FLAG_WORDS_REGION="auto cn global"
22
51
  FLAG_WORDS_GIT="none provision connect"
23
52
  # kube 路径:单机两态走 kube-up.sh,machines(多机 ssh 装配)走 cluster-up.sh。
@@ -80,7 +80,10 @@ def main(path):
80
80
 
81
81
  emit("PROFILE", top.get("profile"))
82
82
  emit("REGION", top.get("region", "auto"))
83
- emit("USERS", top.get("users", "single"))
83
+ # S-600(codex 一轮 [high]):不写 users 就**不发射** —— 修前缺省发射 single,等于替运维表了态,sema-up 于是把它当显式旗,
84
+ # 盖掉 `.env` 里上一次落下的部署事实(多租部署不带旗重跑 ⇒ 被降成单用户,或撞事实冲突拒)。缺省归 sema-up 的决策
85
+ # (显式旗 ∨ 部署事实 ∨ 表缺省 single),不在解析器里另立一份。
86
+ emit("USERS", top.get("users"))
84
87
  # server 镜像(registry 引用;海外=docker.io/<ns>/sema-server:<tag>)
85
88
  emit("SEMA_SERVER_IMAGE", top.get("server_image"))
86
89
  emit("MACHINE_COUNT", str(len(machines)))
@@ -339,20 +339,21 @@ fi
339
339
  "full-cluster:k8s 准生产——3 机 ssh 装配 / 已有集群 kubeconfig / 本机装 k3s" \
340
340
  "local-plus:本机只跑 agent,连一个已经部署好的 data-center 数据面")
341
341
  # ── 两个决策(租户形 / 车道)同一条规则:决策 = 显式旗 ∨ 部署事实 ∨ 表缺省 ─────────────────────
342
- # 「部署事实」只对 compose 的 agent 档成立(`.env` 就是它们的部署状态;kube 路径的状态在 helm values 里,
343
- # data-center 不起 agent)。事实 → 决策词只经属主翻译,本脚本不抄第二份:
342
+ # 「部署事实」= 同一只 `$WORK/.env`:compose 的 agent 档由本脚本写,full-cluster 由 kube-up / cluster-up 在写凭据的同一处
343
+ # 写(S-600;修前 kube 两条路径不落这条事实 ⇒ 不带旗重跑拿缺省 single 把多租部署降回单用户);data-center 不起 agent。事实 → 决策词只经属主翻译,本脚本不抄第二份:
344
344
  # · REQUIRE_PRINCIPAL → --users:只认 sema-up 自己写出的两形 true / false(别的写法 ⇒ 拒;不复刻 server 的布尔词表);
345
345
  # · REMOTE_EXEC → --sandbox:`lanes choice-of`(车道表是唯一属主;表里没有的落盘值 ⇒ 拒并列在役词),见车道块。
346
346
  # 不带旗重跑 ⇒ 沿用事实(多租部署重跑不必再带 --users=multi);显式旗与事实不一致 ⇒ fact_conflict 拒并点名两处。
347
347
  # 🔴 为什么缺省不能先于事实(合并复审两条真案):把表缺省当成意图去和事实比,合法的无旗重跑就被拒;
348
348
  # 反过来若只比不拒,保留的多租 `.env` 在缺省 single 下会被车道表判成 host —— 容器拿到 REQUIRE_PRINCIPAL=true ∧ host。
349
- # 租户形 ⇒ 容器上 REQUIRE_PRINCIPAL 的值 —— 本脚本内**唯一**一处这条翻译。正向(生成段 setk、事实核对)与反向
350
- # (事实 → --users 缺省)都经它:反向是拿两个租户词逐个过这只函数去比,不另写一张反表(与车道的 `lanes choice-of`
351
- # 同一姿势)。⚠️ kube 路径另有一处同义翻译(`kube-up.sh` 的 V_REQ_PRINCIPAL,v7.95.1 既有,登记 DEBTS)。
352
- principal_of_users() { [ "$1" = "multi" ] && echo true || echo false; }
349
+ # 租户形 ⇒ 容器上 REQUIRE_PRINCIPAL 的值:`principal_of_users`(lib/flagshape.sh,三只脚本共用的唯一定义;S-600 起 kube-up /
350
+ # cluster-up 也调它)。正向(生成段 setk、事实核对)与反向(事实 → --users 缺省)都经它:反向是拿两个租户词逐个过这只函数去比,
351
+ # 不另写一张反表(与车道的 `lanes choice-of` 同一姿势)。
353
352
  USERS_DEF="single" RP_FACT="" RP_FACT_SRC=""
354
353
  case "$PROFILE" in
355
- full-single|local-plus)
354
+ # S-600:full-cluster 也读 —— kube-up / cluster-up 在写凭据的同一处把 REQUIRE_PRINCIPAL 落进同一只 .env(它们自己
355
+ # 单独跑时也按同一条规则判,见 lib/flagshape.sh 的 users_decide)。
356
+ full-single|local-plus|full-cluster)
356
357
  deploy_fact REQUIRE_PRINCIPAL; RP_FACT="$FACT_VAL" RP_FACT_SRC="$FACT_SRC"
357
358
  if [ -n "$RP_FACT" ]; then
358
359
  USERS_DEF=""
@@ -470,8 +471,9 @@ if [ "$PROFILE" = "full-cluster" ]; then
470
471
  case "$KUBE_MODE" in
471
472
  machines)
472
473
  [ "${MACHINE_COUNT:-0}" -ge 1 ] || { log "⛔ --kube=machines 需要 -f machines.yaml(机器清单);模板见 deploy/sema-up/machines.example.yaml"; exit 1; }
473
- $DRY_RUN && { log "dry-run:将经 cluster-up.sh 对 ${MACHINE_COUNT} 台机器做 k3s HA 装配(ssh)+ helm/render 装栈;不执行"; exit 0; }
474
- WORK="$WORK" REGION="$REGION" PROJECT="$PROJECT" \
474
+ $DRY_RUN && { log "dry-run:将经 cluster-up.sh 对 ${MACHINE_COUNT} 台机器做 k3s HA 装配(ssh)+ helm/render 装栈(users=$USERS);不执行"; exit 0; }
475
+ # S-600:租户形交给 cluster-up(修前不传 ⇒ 这条路径上的主 worker 恒单用户姿态,--users=multi 被静默丢)。
476
+ WORK="$WORK" REGION="$REGION" PROJECT="$PROJECT" USERS="$USERS" \
475
477
  MODEL_GATEWAY_BASEURL="$GATEWAY" MODEL_ID="$MODEL" MODEL_API_KEY="${MODEL_KEY:-${MODEL_API_KEY:-}}" \
476
478
  exec "$HERE/cluster-up.sh"
477
479
  ;;
@@ -1,5 +1,5 @@
1
1
  # sema-stack(full-cluster 形态,Phase 1b as-built;cluster-up.sh 渲染 __占位符__ 后 apply)
2
- # 占位符:__MINIO_PW__ __SVC_TOKEN__ __MODEL_KEY__ __MODEL_GATEWAY__ __MODEL_ID__
2
+ # 占位符:__MINIO_PW__ __SVC_TOKEN__ __MODEL_KEY__ __MODEL_GATEWAY__ __MODEL_ID__ __REQUIRE_PRINCIPAL__
3
3
  # __SERVER_IMAGE__ __SERVER_PULL_POLICY__(tar 预灌=Never / registry 拉取=IfNotPresent)
4
4
  # 前置(cluster-up.sh 负责):Longhorn 默认 SC、kata-deploy(runtimeclass kata-qemu)、CNPG operator 1.30、
5
5
  # runner token 跨 ns 复制成 sema/sema-runner-token-copy、MinIO 建桶 sema-pg-backup
@@ -169,6 +169,8 @@ spec:
169
169
  - { name: MODEL_ID, value: "__MODEL_ID__" }
170
170
  - { name: MODEL_API_KEY, valueFrom: { secretKeyRef: { name: sema-server-env, key: MODEL_API_KEY } } }
171
171
  - { name: MODEL_VISION, value: "false" }
172
+ # S-600:租户姿态与 chart 主 worker 同值(cluster-up 由 --users 渲:multi ⇒ true,否则 false);修前整块缺席 ⇒ 恒单用户
173
+ - { name: REQUIRE_PRINCIPAL, value: "__REQUIRE_PRINCIPAL__" }
172
174
  - { name: SANDBOX_PKG_SOURCE, value: "__PKG_SOURCE__" }
173
175
  - { name: REMOTE_EXEC, value: k8s }
174
176
  # S-599:显式表态「这台有手」(车道被改没了 ⇒ 拒启,不是安静的无手 worker)
@@ -415,7 +415,7 @@ type ToolApprovalPark = Extract<CorePendingAction, {
415
415
  *
416
416
  * 什么时候经本函数 —— 一句话:**筛点在 core;server 只在「读的是持久层原字节、中间没有 core 投影」的地方亲手调 core 的那一只筛**。
417
417
  * inbox 行读 core `CheckpointSummary.{mandate,origin}`(core `summarizeCheckpoint` 已用同一只 `isPersistedRuleMandate` / `isAskOrigin`
418
- * 筛过)⇒ 那一面直投、不经本函数(与 `ruleStoreUnreadable` 同一条法理);运维队列行读的是 park 行 `pendingAction` 子树的原字节
418
+ * 筛过)⇒ 那一面直投、不经本函数;运维队列行读的是 park 行 `pendingAction` 子树的原字节
419
419
  * (两店 `listPending`,中间没有 core 投影)⇒ 经本函数 —— 于是 inbox 行与队列行对同一张 park 行(含手改行 / 世代差的集外词)逐字同答。
420
420
  *
421
421
  * **不抄词表**([ref]):闭集判定 = core 的成员筛({@link CLOSED_WORD_SEAT_GUARDS});值型 = core 寄存行上那一座的型。本仓对这些词
@@ -136,7 +136,12 @@ export interface DurableAskOptions {
136
136
  neverAuto?: string[];
137
137
  /** Per-session approval-exemption probe ("本会话不再询问"): consulted ONLY for require-listed
138
138
  * tools, AFTER deny/neverAuto (both always win) and BEFORE the budget path. Canonical-space toolName.
139
- * A rejected promise is treated as NOT exempt (fail-closed → ask) at the call site below. */
139
+ * A rejected promise is treated as NOT exempt (fail-closed → ask) at the call site below.
140
+ * 🔴 S-877 候裁 16:会话授权**两个读位、一只店、按问句的作者分**(收车档 ⑩ 16):本探针兑现**本仓自己的问**(名单门)
141
+ * —— 在它自己的层上「不问」,于是委派子代经父约束链继承到的同一问也不问;引擎自装的问(模式写门 / 形状层)由审批席
142
+ * (`ToolApprovalCoordinator.standingGrantSettles`)兑现。本探针**不能**退役:子代的继承问到达祖先审批席时 core 盖
143
+ * `mandated`(`ruleClearanceOf` 的 inheritedUnresolved / ancestorResolved 门),席的判据依约不退役它 —— 只有本层的
144
+ * 「不问」够得着继承链(钉 test/parked-decide-leg.test.ts「[候裁② 修]remember 全链行为」)。 */
140
145
  exempt?: (canonicalToolName: string) => Promise<boolean>;
141
146
  /** Audit hook — fired when an exemption short-circuits the ask (the `approval_exempted` log line). */
142
147
  onExempted?: (canonicalToolName: string, rawToolName: string) => void;
package/dist/approval.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "@sema-agent/core";
2
- import { operatorAsk } from "./operator-ask.js";
2
+ import { operatorAlwaysHumanAsk, operatorAsk } from "./operator-ask.js";
3
3
  import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
4
4
  import { isExplicitPosture } from "./posture-source.js";
5
5
  import { handsOnWorkerFilePlane } from "./execution-lane-caps.js";
@@ -85,11 +85,11 @@ export function createDurableAskPolicy(opts) {
85
85
  if (deny(toolName))
86
86
  return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
87
87
  if (neverAuto(toolName))
88
- return operatorAsk(`APPROVAL_NEVER_AUTO(${toolName})`, `human approval required to run "${req.toolName}"`);
88
+ return operatorAlwaysHumanAsk(`APPROVAL_NEVER_AUTO(${toolName})`, `human approval required to run "${req.toolName}"`);
89
89
  if (require(toolName)) {
90
90
  const remEarly = req.budget?.resourceRemainingMicroUsd;
91
91
  if (remEarly !== undefined && remEarly <= 0) {
92
- return operatorAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
92
+ return operatorAlwaysHumanAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
93
93
  }
94
94
  if (opts.exempt && toolName !== ASK_USER_QUESTION_TOOL_NAME) {
95
95
  return (async () => {
@@ -115,7 +115,7 @@ export function createDurableAskPolicy(opts) {
115
115
  function decideRequired(req, toolName) {
116
116
  const rem = req.budget?.resourceRemainingMicroUsd;
117
117
  if (rem !== undefined && rem <= 0) {
118
- return operatorAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
118
+ return operatorAlwaysHumanAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
119
119
  }
120
120
  if (toolName !== ASK_USER_QUESTION_TOOL_NAME && budget > 0 && autoApproved < budget) {
121
121
  autoApproved++;
@@ -2,6 +2,20 @@ import { type AuthBridgeErrorCode, type VerifiedIdentity } from "@sema-agent/set
2
2
  import type { Logger } from "./observability/logger.js";
3
3
  import type { Metrics } from "./observability/metrics.js";
4
4
  export type { VerifiedIdentity } from "@sema-agent/settings-schema/api/auth-bridge";
5
+ /**
6
+ * S-866 ② —— 鉴权桥 issuer 的**唯一**取值(装配 `boot/stage-09-leader.ts` 与 caps `centerWiring.issuer` 同读,不各写一份回退):
7
+ * 显式 `AUTH_BRIDGE_ISSUER`(`config.authBridgeIssuer`)恒赢 ⇒ `source:"explicit"`;缺席则回退中心基址 `configCenter.baseUrl`
8
+ * (配了中心即默认开桥)⇒ `source:"fallback"`;两者都没有 ⇒ `undefined`(桥不装)。
9
+ */
10
+ export declare function authBridgeIssuerOf(config: {
11
+ readonly authBridgeIssuer?: string | undefined;
12
+ readonly configCenter?: {
13
+ readonly baseUrl: string;
14
+ } | undefined;
15
+ }): {
16
+ readonly issuer: string;
17
+ readonly source: "explicit" | "fallback";
18
+ } | undefined;
5
19
  export interface RegistryJwtVerifierOptions {
6
20
  /** R4 — 期望 iss(registry 的 SSO_ISSUER;默认=SEMA_REGISTRY_URL,双侧去尾斜杠比较)。 */
7
21
  issuer: string;
@@ -1,6 +1,13 @@
1
1
  import { createPublicKey, verify as cryptoVerify } from "node:crypto";
2
2
  import { AUTH_BRIDGE_ALGS, DEFAULT_CLOCK_SKEW_SEC, DEFAULT_JWKS_REFRESH_MIN_INTERVAL_SEC, DEFAULT_OFFLINE_GRACE_SEC, REGISTRY_JWKS_PATH, RegistryJwks, classifyTimeClaims, jwksWithinOfflineGrace, shouldForceJwksRefresh, toVerifiedIdentity, } from "@sema-agent/settings-schema/api/auth-bridge";
3
3
  import { buildDisplaySafeUrl } from "./trace/redact.js";
4
+ export function authBridgeIssuerOf(config) {
5
+ if (config.authBridgeIssuer !== undefined)
6
+ return { issuer: config.authBridgeIssuer, source: "explicit" };
7
+ if (config.configCenter !== undefined)
8
+ return { issuer: config.configCenter.baseUrl, source: "fallback" };
9
+ return undefined;
10
+ }
4
11
  const DEFAULT_JWKS_TTL_SEC = 15 * 60;
5
12
  const MAX_JWKS_BYTES = 256 * 1024;
6
13
  const MAX_TOKEN_CHARS = 16 * 1024;
@@ -1,3 +1,47 @@
1
+ /**
2
+ * [ref] ④(黑板 [ref]/[ref]/[ref],core [ref] 合修)—— auto 模式的**可自查读面**:`/v1/capabilities`
3
+ * 的 `permissionModeAuto.{armed, reason, model}` 三键怎么算。
4
+ *
5
+ * ## 病根一句话
6
+ * 壳 07-21 起默认点亮 `permissionMode:"auto"`,而引擎半场从未接通:core 旧武装式是「org 授予」极性
7
+ * (`runtimeCaps.autoMode === true ∧ deps.autoMode`),server 的授予路径只有 center ⇒ 无 center 的本地部署
8
+ * auto **永不武装** = default + 静态白名单(真实用户诊断,六断言全成立)。core [ref] 把谓词换成 CC 250 的
9
+ * 「用户开 + org 拒」极性;core 7.34.0([ref])再把意图收成**一个键** —— 模式词本身:
10
+ *
11
+ * armed ⟺ TaskSpec.permissionMode === "auto" ∧ RunnerDeps.autoMode !== undefined ∧ RuntimeCaps.autoMode !== false
12
+ *
13
+ * 任一合取项落空 ⇒ 引擎把这条腿折成 `default` 并发 `config.permission_mode_unavailable`
14
+ * (`by: "face_absent" | "operator_deny"`;码名按 core `ENGINE_NOTICE_CODES` 亲读)。
15
+ *
16
+ * 本模块按**server 侧可知的同三项**算一份「若此刻提交会不会武装」的裁决,供壳(cli `sema doctor permissions`)
17
+ * 与人自查。三项分别对应三个未武装原因(判序 = 谓词的合取序,先判到的先报):
18
+ * · `mode_not_auto` —— 本次意图不是 auto(`?permissionMode=` 查询串给的;缺席 = 按 auto 意图答);
19
+ * · `deployment_incapable` —— 装配面没挂 `RunnerDeps.autoMode` 分类器席位(生产装配恒挂;桩/一次性 CLI 不挂);
20
+ * · `org_denied` —— per-principal caps 的 `autoMode === false` 且本地 deny 未设(center 显式 deny、center
21
+ * 硬失败的 fail-closed 拒项);
22
+ * · `local_denied` —— 本地 `PERMISSIONS_DISABLE_AUTO_MODE=true`(catalog/settings 键 `permissions.disableAutoMode`);
23
+ * · `settings_denied` —— settings 层 `permissions.disableAutoMode` 取 `"disable"` 或 `true`([ref]:两套已发布拼写同义)
24
+ * 折掉本 run 的 auto([ref] 第六员;run 级面产,
25
+ * capabilities 查询串不带 settings ⇒ 本面不产);
26
+ * · `resolver_fault` —— resolver 抛错(core 同形全拒:autoMode:false + onError phase config)。
27
+ *
28
+ * 🔴 S-877(core 7.34.0 提货):「这台机器上装的 core 用哪套武装极性」的**安装包探针**(按文本扫
29
+ * `dist/core/runner/prepare-task.js` 找 `autoModeRequested`)整只退役 —— 7.34.0 起那个键被引擎按名拒,探针的主判
30
+ * 恒假、佐证恒真 ⇒ 矛盾臂 fatal,整台 server 起服即拒(S-878 探针 7.34.0 读数的 13 只红文件同一根因)。
31
+ * 极性不再是运行期要猜的事实:本仓把 core **精确钉版**(`package.json`,`test/core-dependency-floor.test.ts` 承重),
32
+ * 钉住的那一版的武装谓词就是上面那一行,由类型面(`TaskSpec.permissionMode` / `RunnerDeps.autoMode` /
33
+ * `RuntimeCaps.autoMode`)与 core CHANGELOG 同时陈述。⇒ 旧式臂(「org 授予」极性)与 `engineArmsOnIntent` 座一并删,
34
+ * 规则集少一整支;**不再读任何 dist 文本**。
35
+ *
36
+ * ## 闭集纪律([ref])
37
+ * 六词是 core [ref] seam ⑤ + [ref] PM 裁定的闭集;`AUTO_MODE_UNARMED_REASON_SEMANTICS` 是 `Record<Reason, string>` 锁步表——
38
+ * 词表加员而语义表不加、或反之,都是编译红。本面**不**给壳发 prose(机器码上 wire,人话归壳),语义表只
39
+ * 服务于文档/测试对拍。
40
+ *
41
+ * ## 判据不是「授权了」
42
+ * `entitlementSource`(既有位)说的是「center 源在不在」;`armed` 说的是「按三态谓词此刻会不会武装」。
43
+ * 两者刻意并存:前者告诉壳有没有组织面,后者告诉壳这台机器上 auto 到底是不是 default 的别名。
44
+ */
1
45
  import { type RunnerDeps, type RuntimeCaps } from "@sema-agent/core";
2
46
  import type { SettingsPermissionMode } from "./task-settings.js";
3
47
  /**
@@ -37,53 +81,19 @@ export declare const AUTO_MODE_UNARMED_REASONS: readonly ["mode_not_auto", "depl
37
81
  export type AutoModeUnarmedReason = (typeof AUTO_MODE_UNARMED_REASONS)[number];
38
82
  /** 锁步表:词表与语义一一对应(加员/删员任一侧不同步 = tsc 红)。语义只供文档/测试,不上 wire。 */
39
83
  export declare const AUTO_MODE_UNARMED_REASON_SEMANTICS: {
40
- readonly mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\";座缺席)——只属意图武装式,旧式 core 不读模式";
41
- readonly deployment_incapable: "装配面没挂 RunnerDeps.autoMode 分类器席位(本部署/本进程形不具备分类器);旧式 core 下另含「无 center 源 = 零授予路径」";
42
- readonly org_denied: "per-principal caps.autoMode === false 且本地 deny 未设:center 显式 deny / center 硬失败 fail-closed 拒项;旧式 core 下另含「center 在场但未授予」";
84
+ readonly mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\")";
85
+ readonly deployment_incapable: "装配面没挂 RunnerDeps.autoMode 分类器席位(本部署/本进程形不具备分类器)";
86
+ readonly org_denied: "per-principal caps.autoMode === false 且本地 deny 未设:center 显式 deny / center 硬失败 fail-closed 拒项";
43
87
  readonly local_denied: "本地 catalog 键 PERMISSIONS_DISABLE_AUTO_MODE=true(center/settings 键名 permissions.disableAutoMode)把 caps.autoMode 折成 false";
44
88
  readonly settings_denied: "settings 层 permissions.disableAutoMode 取 disable 或 true(两套已发布拼写同义;用户/managed 层 kill-switch)把本 run 折成非 auto —— capabilities 查询串不带 settings,本词由 run 级面产([6128] PM 裁定第六员)";
45
89
  readonly resolver_fault: "per-principal caps resolver 抛错/答非记录(core 同形全拒:autoMode:false + onError phase config)";
46
90
  };
47
- /**
48
- * 安装包探针:**这台机器上装的 core 用哪套武装极性**。
49
- *
50
- * 判据([ref] 改,原先用「854b202b 同批的运行时导出 `mailboxCrossProcessMountVerdict`」作代理 —— 那是 385 片2a 的
51
- * mailbox 面,与 auto 武装**语义无关**,只是恰好同一个 dist;core 若把 [ref] 单独发一个补丁版,代理就答错):
52
- *
53
- * · **主判(语义精确)= [ref] 自己的座**:武装点 `dist/core/runner/prepare-task.js` 里是否出现 `autoModeRequested`。
54
- * 亲验(2026-09-03,`git grep autoModeRequested` 两 ref 对拍):v7.2.0 全树**零**处提及,854b202b 起 prepare-task
55
- * 有 8 处 —— 单调、且探的就是「武装点读不读意图座」这件事本身。dist 未 mangle(属性名从不被 tsc/esbuild 改名),
56
- * d.ts 不参与判断(类型面运行期探不到,读 js 才是「引擎真这么跑」)。
57
- * · **佐证(只在主判为 false 时读)= 同批到货的运行时导出** `mailboxCrossProcessMountVerdict`(包内随发的
58
- * `node_modules/@sema-agent/core/test/export-surface.snapshot.json`(core 包内随发),core `files` 字段含它 —— 消费账本门 A 的同一真源)。
59
- * - 座缺席 ∧ 标记缺席 ⇒ 就是 npm 7.2.0 那一档 ⇒ `false`(旧式臂,兼容形);
60
- * - 座缺席 ∧ 标记**在场** ⇒ **矛盾**:dist 已是 854b202b 之后,而武装点不再提这个座名(core 重构/换文件)
61
- * ⇒ 判据失效,**抛**(不猜。静默回落旧式臂正是「854 之后的部署悄悄退回 org 授予极性」那一形)。
62
- * - 座在场 ⇒ `true`,不看佐证([ref] 单发的补丁版合法)。
63
- *
64
- * 🔴 不用 `import * as core`(消费账本门禁命名空间导入:每个消费名必须可按名对账);两个文件都经主入口 resolve
65
- * 定位(package `exports` 映射不暴露这些子路径)。**任何读不到 = 抛**,由 boot 装配点承接成拒启(见
66
- * {@link coreArmsAutoOnIntent})。
67
- *
68
- * @param distDir 测试注入的假 `dist/` 根;生产不传(从安装的 core 主入口解)。
69
- */
70
- export declare function coreArmsAutoOnIntentProbe(distDir?: string): boolean;
71
- /**
72
- * {@link coreArmsAutoOnIntentProbe} 的**惰性记忆化**访问器 —— 生产装配点(`main.ts` 的 `autoModeFace` 组装,
73
- * 早于 `server.listen`)调一次,探针抛就是**boot 期响亮拒启**并带指路。
74
- *
75
- * 🔴 为什么必须惰性([ref]):此前是模块顶层 `const … = coreArmsAutoOnIntentProbe()`,而 `main.ts` 顶部 import 本模块
76
- * ⇒ 探针在 **import 期**求值,读不到快照就在任何日志器/诊断存在之前把整台 server 抛死(一个 npm 安装瑕疵 =
77
- * 一台起不来且没有遗言的机器)。惰性之后,同一条失败落在 boot 装配点,遗言完整、退出码照旧。
78
- */
79
- export declare function coreArmsAutoOnIntent(): boolean;
80
- /** 装配面交给读面的四件事实(`main.ts` 从 runnerDeps / 合成后的 caps resolver / 到货位取,读面不自算)。 */
91
+ /** 装配面交给读面的三件事实(`main.ts` 从 runnerDeps / 合成后的 caps resolver 取,读面不自算)。
92
+ * S-877:第四件「引擎半场到货位」(`engineArmsOnIntent`,安装包探针)随极性探针一起退役。 */
81
93
  export interface AutoModeFaceDeps {
82
94
  /** `RunnerDeps.autoMode !== undefined`——**读**装配点那一位,不写字面 true(与 parked-revive 的
83
95
  * `autoModeSeatMounted` 同一纪律:席位哪天改成有条件挂载,这里不能静默错供)。 */
84
96
  readonly seatMounted: boolean;
85
- /** {@link coreArmsAutoOnIntent}(生产装配递探针访问器;测试可递 true 走正控)。false ⇒ `deployment_incapable`。 */
86
- readonly engineArmsOnIntent: boolean;
87
97
  /** 合成后的 per-principal caps resolver(= 递给 `RunnerDeps.runtimeCapsResolver` 的同一只:center 客户端
88
98
  * → observer env 基线 → memory grant → 本地 auto deny,单一属主 boot/runtime-caps.ts)。缺席 = 无任何
89
99
  * per-principal 源(无 center、无本地 deny)⇒ caps undefined ⇒ 三态之「缺席=不阻」。 */
@@ -115,7 +125,6 @@ export type AutoModeVerdict = {
115
125
  export declare function judgeAutoModeArming(face: AutoModeFaceDeps | undefined, input: {
116
126
  readonly requestedMode: SettingsPermissionMode | undefined;
117
127
  readonly principal: string | undefined;
118
- readonly entitlementSource: boolean;
119
128
  /** 本地 `PERMISSIONS_DISABLE_AUTO_MODE`(= catalog/settings 键 `permissions.disableAutoMode`)是否置 true —— caps false 时分词
120
129
  * `local_denied` / `org_denied`(seam ⑤;两者对壳的指路不同:改本机 env vs 找组织)。 */
121
130
  readonly localDeny: boolean;
@@ -1,7 +1,3 @@
1
- import { createRequire } from "node:module";
2
- import { readFileSync } from "node:fs";
3
- import { dirname, join } from "node:path";
4
- import { z } from "zod";
5
1
  import { classifierSeatModel } from "@sema-agent/core";
6
2
  import { resolveMainModel } from "./model-select.js";
7
3
  export function classifierRouteFor(deps) {
@@ -15,65 +11,17 @@ export function classifierRouteFor(deps) {
15
11
  }
16
12
  export const AUTO_MODE_UNARMED_REASONS = ["mode_not_auto", "deployment_incapable", "org_denied", "local_denied", "settings_denied", "resolver_fault"];
17
13
  export const AUTO_MODE_UNARMED_REASON_SEMANTICS = {
18
- mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\";座缺席)——只属意图武装式,旧式 core 不读模式",
19
- deployment_incapable: "装配面没挂 RunnerDeps.autoMode 分类器席位(本部署/本进程形不具备分类器);旧式 core 下另含「无 center 源 = 零授予路径」",
20
- org_denied: "per-principal caps.autoMode === false 且本地 deny 未设:center 显式 deny / center 硬失败 fail-closed 拒项;旧式 core 下另含「center 在场但未授予」",
14
+ mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\")",
15
+ deployment_incapable: "装配面没挂 RunnerDeps.autoMode 分类器席位(本部署/本进程形不具备分类器)",
16
+ org_denied: "per-principal caps.autoMode === false 且本地 deny 未设:center 显式 deny / center 硬失败 fail-closed 拒项",
21
17
  local_denied: "本地 catalog 键 PERMISSIONS_DISABLE_AUTO_MODE=true(center/settings 键名 permissions.disableAutoMode)把 caps.autoMode 折成 false",
22
18
  settings_denied: "settings 层 permissions.disableAutoMode 取 disable 或 true(两套已发布拼写同义;用户/managed 层 kill-switch)把本 run 折成非 auto —— capabilities 查询串不带 settings,本词由 run 级面产([6128] PM 裁定第六员)",
23
19
  resolver_fault: "per-principal caps resolver 抛错/答非记录(core 同形全拒:autoMode:false + onError phase config)",
24
20
  };
25
- function armingProbeFailure(what) {
26
- return new Error(`auto-mode arming probe cannot decide (${what}). This probe answers ONE question — does the INSTALLED @sema-agent/core arm ` +
27
- `auto mode from the task's INTENT seat (core #521: spec.autoModeRequested ∧ seat ∧ caps !== false), or from the older ` +
28
- `"org grants it" polarity (runtimeCaps.autoMode === true ∧ seat)? The two polarities are OPPOSITE on the permission axis, ` +
29
- `so guessing would silently put this deployment on the wrong one — either arming runs the org never granted, or reporting ` +
30
- `"not armed" at /v1/capabilities while the engine really is arming (#157: no silent fail-open on the permission axis). ` +
31
- `Refusing to start instead. Fix: reinstall the engine (rm -rf node_modules && npm ci) so the package ships its full dist, ` +
32
- `or — if core moved/renamed the arming site — update the probe in src/auto-mode-face.ts to read the new one.`);
33
- }
34
- export function coreArmsAutoOnIntentProbe(distDir) {
35
- const dist = distDir ?? dirname(createRequire(import.meta.url).resolve("@sema-agent/core"));
36
- const armingSite = join(dist, "core", "runner", "prepare-task.js");
37
- let armingSrc;
38
- try {
39
- armingSrc = readFileSync(armingSite, "utf8");
40
- }
41
- catch (err) {
42
- throw armingProbeFailure(`the engine's arming site is unreadable at ${armingSite}: ${err instanceof Error ? err.message : String(err)}`);
43
- }
44
- if (armingSrc.includes(CORE_AUTO_MODE_INTENT_SEAT))
45
- return true;
46
- const snapPath = join(dist, "..", "test", "export-surface.snapshot.json");
47
- let snap;
48
- try {
49
- const parsed = ExportSurfaceSnapshotSchema.safeParse(JSON.parse(readFileSync(snapPath, "utf8")));
50
- if (!parsed.success)
51
- throw new Error(`export-surface snapshot shape mismatch: ${parsed.error.issues.map((i) => i.path.join(".") + ":" + i.message).join("; ")}`);
52
- snap = parsed.data;
53
- }
54
- catch (err) {
55
- throw armingProbeFailure(`the arming seat is absent from ${armingSite} and the corroborating export-surface snapshot is unreadable at ${snapPath}: ${err instanceof Error ? err.message : String(err)}`);
56
- }
57
- if (!snap.exports || typeof snap.exports !== "object")
58
- throw armingProbeFailure(`core export-surface snapshot malformed at ${snapPath}`);
59
- if (typeof snap.exports.mailboxCrossProcessMountVerdict === "string") {
60
- throw armingProbeFailure(`contradiction — the installed core's export surface is at/after the batch that shipped #521, but its arming site ` +
61
- `(${armingSite}) never names \`${CORE_AUTO_MODE_INTENT_SEAT}\`, so the engine either moved the arming site or renamed the seat`);
62
- }
63
- return false;
64
- }
65
- const CORE_AUTO_MODE_INTENT_SEAT = "autoModeRequested";
66
- let armsOnIntentMemo;
67
- const ExportSurfaceSnapshotSchema = z.object({ exports: z.record(z.string(), z.string()).optional() }).passthrough();
68
- export function coreArmsAutoOnIntent() {
69
- if (armsOnIntentMemo === undefined)
70
- armsOnIntentMemo = coreArmsAutoOnIntentProbe();
71
- return armsOnIntentMemo;
72
- }
73
21
  export async function judgeAutoModeArming(face, input) {
74
22
  const route = face?.seatMounted ? face.classifierModel?.() : undefined;
75
23
  const model = route !== undefined ? { model: route } : {};
76
- if (face?.engineArmsOnIntent && input.requestedMode !== undefined && input.requestedMode !== "auto")
24
+ if (input.requestedMode !== undefined && input.requestedMode !== "auto")
77
25
  return { armed: false, reason: "mode_not_auto", ...model };
78
26
  if (face === undefined || !face.seatMounted)
79
27
  return { armed: false, reason: "deployment_incapable" };
@@ -84,18 +32,8 @@ export async function judgeAutoModeArming(face, input) {
84
32
  catch (err) {
85
33
  return { armed: false, reason: "resolver_fault", ...model, fault: err instanceof Error ? err.message : String(err) };
86
34
  }
87
- const deniedWord = input.localDeny ? "local_denied" : "org_denied";
88
- if (face.engineArmsOnIntent) {
89
- if (caps?.autoMode === false)
90
- return { armed: false, reason: deniedWord, ...model };
91
- return { armed: true, ...model };
92
- }
93
- if (caps?.autoMode === true)
94
- return { armed: true, ...model };
95
35
  if (caps?.autoMode === false)
96
- return { armed: false, reason: deniedWord, ...model };
97
- if (!input.entitlementSource)
98
- return { armed: false, reason: "deployment_incapable", ...model };
99
- return { armed: false, reason: "org_denied", ...model };
36
+ return { armed: false, reason: input.localDeny ? "local_denied" : "org_denied", ...model };
37
+ return { armed: true, ...model };
100
38
  }
101
39
  //# sourceMappingURL=auto-mode-face.js.map
@@ -35,6 +35,7 @@
35
35
  * `infraFailed` (EXCLUDED) — NEVER a fabricated uniform delivered=false loss for the whole TEAM column.
36
36
  */
37
37
  import { type TaskSpec } from "@sema-agent/core";
38
+ import { type UnattendedLeg } from "../../unattended-permission-mode.js";
38
39
  import type { ProfileDeps, SoloImplSpec, SupImplSpec } from "./arms.js";
39
40
  import type { TrapSpec } from "./tasks.js";
40
41
  /** Live runtime config — the SAME base model + budget every arm runs on (the §3.2 fairness root). */
@@ -92,8 +93,14 @@ export declare function buildLiveDeps(rt: LiveRuntimeConfig, trap: TrapSpec, see
92
93
  /** The per-cell base TaskSpec every arm's leaf projects from. EXPORTED so the shape test measures the REAL spec
93
94
  * handed to the runner (the BUDGET-MATCH anchor is spec.limits, not the harness's own RowBudget echo). */
94
95
  export declare function benchBaseSpec(rt: LiveRuntimeConfig, cellId: string): (objective: string) => TaskSpec;
95
- /** Project a `SoloImplSpec` (budget already stamped by the harness, all keys in `limits`) onto the bench base TaskSpec. */
96
- export declare function toTaskSpec(base: (objective: string) => TaskSpec, impl: SoloImplSpec): TaskSpec;
96
+ /**
97
+ * Project a `SoloImplSpec` (budget already stamped by the harness, all keys in `limits`) onto the bench base TaskSpec.
98
+ * 🔴 7.106.0 合并复审 X3:SOLO 是**无人值守腿**(verify→fix 跑在一只不接 `onAsk` 的 Runner 上,没有审批席)⇒ 经 `withUnattendedLeg` 接
99
+ * 与 TEAM 的 leader worker 同一条腿(模式 = `UNATTENDED_PERMISSION_MODE`、写面 roots、写保护拒层)。修前裸 spec ⇒ core 7.34.0「缺席 =
100
+ * default」让写与非只读命令 headless 拒、终态照旧 completed —— SOLO 与 TEAM 的比较混进了权限差异。SUP 不走这里:它有耐久审批席
101
+ * (checkpoint suspend),它的问要人答,bypass 会把 SUP 的门放掉。
102
+ */
103
+ export declare function toTaskSpec(base: (objective: string) => TaskSpec, impl: SoloImplSpec, leg: UnattendedLeg): TaskSpec;
97
104
  /** Project a `SupImplSpec` onto the bench base TaskSpec PLUS the durable wiring (checkpointStore + ask policy). */
98
105
  export declare function toSupTaskSpec(base: (objective: string) => TaskSpec, impl: SupImplSpec, trap: TrapSpec): TaskSpec;
99
106
  //# sourceMappingURL=live-deps.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import { outwardPlane } from "../../terminal.js";
2
2
  import { Runner, runWithVerification, resumeWithVerification, runRepairLoop, combinePolicies, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, } from "@sema-agent/core";
3
3
  import { loadConfig } from "../../config.js";
4
+ import { createUnattendedLeg, unattendedPermissionMode, withUnattendedLeg } from "../../unattended-permission-mode.js";
4
5
  import { createBrain } from "../../brain.js";
5
6
  import { buildPricing } from "../../budget.js";
6
7
  import { RemoteContainerExecutionEnv } from "../../plugins/remote-env-e2b.js";
@@ -57,7 +58,7 @@ function buildBrainIngredients(rt) {
57
58
  if (!p || ((p.inputPer1M ?? 0) === 0 && (p.outputPer1M ?? 0) === 0)) {
58
59
  throw new Error(`s1 live: zero pricing for ${rt.modelId} — C1 cost axis would be a fabricated $0 (set MODEL_COST_INPUT/OUTPUT)`);
59
60
  }
60
- return { brain, models: config.models, roles: config.roles, pricing };
61
+ return { brain, models: config.models, roles: config.roles, pricing, unattendedPermissionMode: unattendedPermissionMode(config) };
61
62
  }
62
63
  const sh = (env) => async (cmd) => {
63
64
  const r = await env.exec(`bash -lc ${JSON.stringify(cmd)}`);
@@ -167,8 +168,9 @@ function graderTransport(grader) {
167
168
  };
168
169
  }
169
170
  export function buildLiveDeps(rt, trap, seed, cellId) {
170
- const { brain, models, roles, pricing } = buildBrainIngredients(rt);
171
+ const { brain, models, roles, pricing, unattendedPermissionMode: leaderMode } = buildBrainIngredients(rt);
171
172
  const baseSpec = benchBaseSpec(rt, cellId);
173
+ const soloLeg = createUnattendedLeg(leaderMode, {});
172
174
  const state = {};
173
175
  const ensureWorker = async () => {
174
176
  if (!state.worker) {
@@ -206,7 +208,7 @@ export function buildLiveDeps(rt, trap, seed, cellId) {
206
208
  const deps = {
207
209
  runWithVerification: async (implSpec) => {
208
210
  const { runner } = await ensureWorker();
209
- return runWithVerification(runner, toTaskSpec(baseSpec, implSpec), { maxRounds: rt.maxRounds ?? 2 });
211
+ return runWithVerification(runner, toTaskSpec(baseSpec, implSpec, soloLeg), { maxRounds: rt.maxRounds ?? 2 });
210
212
  },
211
213
  runWithVerificationSup: async (implSpec) => {
212
214
  const { runner } = await ensureWorker();
@@ -282,7 +284,7 @@ export function buildLiveDeps(rt, trap, seed, cellId) {
282
284
  rmSync(tmp, { recursive: true, force: true });
283
285
  }
284
286
  };
285
- return runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId, captureIntegratedTreeToBundle);
287
+ return runLeaderForTrap(rt, brain, models, roles, pricing, leaderMode, trap, cellId, captureIntegratedTreeToBundle);
286
288
  },
287
289
  runOracle: () => runOracle(false),
288
290
  };
@@ -298,21 +300,24 @@ export function benchBaseSpec(rt, cellId) {
298
300
  limits: { maxTokens: rt.maxTokens ?? 8000, maxCostUsd: rt.maxCostUsd ?? 1.0, maxTurns: rt.maxTurns ?? 12 },
299
301
  });
300
302
  }
301
- export function toTaskSpec(base, impl) {
303
+ function projectImplBudget(base, impl) {
302
304
  return { ...base(impl.objective), limits: { ...impl.limits } };
303
305
  }
306
+ export function toTaskSpec(base, impl, leg) {
307
+ return withUnattendedLeg(projectImplBudget(base, impl), leg);
308
+ }
304
309
  export function toSupTaskSpec(base, impl, trap) {
305
310
  const requireApproval = trap.gatedTools ?? ["Bash"];
306
311
  const neverAuto = trap.neverAuto ?? [];
307
312
  return {
308
- ...toTaskSpec(base, impl),
313
+ ...projectImplBudget(base, impl),
309
314
  checkpointStore: impl.checkpointStore,
310
315
  durableApproval: { scope: impl.durableApprovalScope },
311
316
  toolPolicy: combinePolicies(createDurableQuestionPolicy(), createDurableAskPolicy({ requireApproval, ...(neverAuto.length > 0 ? { neverAuto } : {}) })),
312
317
  onQuestion: QUESTION_AWAITS_RESUME,
313
318
  };
314
319
  }
315
- async function runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId, importIntegratedTreeToWorker) {
320
+ async function runLeaderForTrap(rt, brain, models, roles, pricing, leaderMode, trap, cellId, importIntegratedTreeToWorker) {
316
321
  const { mkdtempSync, rmSync, writeFileSync, mkdirSync } = await import("node:fs");
317
322
  const { execFileSync, spawn } = await import("node:child_process");
318
323
  const { tmpdir } = await import("node:os");
@@ -365,7 +370,7 @@ async function runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId,
365
370
  process.env.LEADER_ENABLED = "true";
366
371
  process.env.LEADER_WORKER_BUDGET_USD = String((rt.maxCostUsd ?? 1.0) / 2);
367
372
  const run = createLeaderRunner({
368
- brain, models, roles, pricing, e2bApiKey: rt.e2bApiKey, workspace: W,
373
+ brain, models, roles, pricing, unattendedPermissionMode: leaderMode, e2bApiKey: rt.e2bApiKey, workspace: W,
369
374
  ...(s1DebugGit() ? { logger: { warn: (m, x) => console.error(`[S1_DEBUG_LEADER warn] ${m}`, x ? JSON.stringify(x).slice(0, 300) : ""), info: (m, x) => console.error(`[S1_DEBUG_LEADER info] ${m}`, x ? JSON.stringify(x).slice(0, 200) : "") } } : {}),
370
375
  });
371
376
  const teamSeedWrite = (trap.seedFiles ?? [])
@@ -2,6 +2,7 @@ import { createBrain, createDegradeRouteWatch } from "../brain.js";
2
2
  import { ModelUsageTracker, PromptManifestTracker, buildPricing, createSideQueryAccountant, createTracer } from "../budget.js";
3
3
  import { FleetUsageAccumulator } from "../fleet-client.js";
4
4
  import { createFleetLeaseFromEnv } from "../fleet-lease.js";
5
+ import { centerLaneConfigured } from "../config-center/center-request.js";
5
6
  import { CostQuota } from "../observability/cost-quota.js";
6
7
  import { buildQuotaBounds } from "./limit-sync.js";
7
8
  import { assembleFileHistory } from "../file-history-capture.js";
@@ -25,12 +26,12 @@ export function createBudgetAndTracing(ctx) {
25
26
  : new CostQuota(bounds.limitMicroUsd, bounds.windowMs);
26
27
  const modelUsageTracker = new ModelUsageTracker();
27
28
  const promptManifestTracker = new PromptManifestTracker();
28
- const fleetUsage = config.configCenter && process.env.FLEET_ADVERTISE_ADDRESS ? new FleetUsageAccumulator() : undefined;
29
+ const fleetUsage = centerLaneConfigured(config) ? new FleetUsageAccumulator() : undefined;
29
30
  const fleetLease = createFleetLeaseFromEnv(config, { logger, metrics });
30
31
  const tracer = createTracer(metrics, costQuota, modelUsageTracker, fleetUsage, fleetLease, (m) => config.modelQuotaWeights[m] ?? 1, promptManifestTracker);
31
32
  const sideQueryAccounting = createSideQueryAccountant(metrics, costQuota, fleetUsage, fleetLease, (m) => config.modelQuotaWeights[m] ?? 1);
32
33
  const toolResultStore = backend?.toolResult ? backend.toolResult() : undefined;
33
- const sessionPolicyStore = backend ? backend.sessionPolicy() : undefined;
34
+ const sessionPolicyStore = backend ? backend.sessionPolicy(logger) : undefined;
34
35
  const { store: fileHistoryStore, erasure: fileHistoryErasure } = assembleFileHistory(backend);
35
36
  return {
36
37
  brain, degradeRouteWatch, pricing, counterDegradeHook, costQuota, modelUsageTracker, promptManifestTracker,