@sema-agent/server 7.96.0 → 7.98.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 +5 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +10 -1
- package/deploy/sema-up/cluster-up.sh +8 -4
- package/deploy/sema-up/kube-up.sh +40 -15
- package/deploy/sema-up/lib/envf.sh +173 -0
- package/deploy/sema-up/sema-up.sh +113 -94
- package/deploy/sema-up/smoke.sh +8 -1
- package/dist/approval.d.ts +28 -0
- package/dist/approval.js +24 -0
- package/dist/boot/budget-tracing.d.ts +1 -0
- package/dist/boot/budget-tracing.js +3 -2
- package/dist/boot/config-center.js +4 -7
- package/dist/boot/coordinators.js +2 -2
- package/dist/boot/deferred-sandbox-path-env.d.ts +5 -2
- package/dist/boot/deferred-sandbox-path-env.js +3 -1
- package/dist/boot/memory-boundary.d.ts +17 -2
- package/dist/boot/memory-boundary.js +11 -0
- package/dist/boot/reapers.d.ts +3 -1
- package/dist/boot/reapers.js +2 -2
- package/dist/boot/resolve-spec.js +15 -10
- package/dist/boot/runner-deps.d.ts +2 -1
- package/dist/boot/session-faces.d.ts +4 -1
- package/dist/boot/session-faces.js +5 -5
- package/dist/boot/stage-04-stores.d.ts +1 -0
- package/dist/boot/stage-04-stores.js +2 -2
- package/dist/boot/stage-05-execution-env.d.ts +1 -0
- package/dist/boot/stage-06-runners.d.ts +1 -0
- package/dist/boot/stage-07-capability-layer.d.ts +1 -0
- package/dist/boot/stage-07-capability-layer.js +10 -14
- package/dist/boot/stage-08-reapers.d.ts +2 -3
- package/dist/boot/stage-08-reapers.js +2 -2
- 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 +1 -0
- package/dist/boot/stores.js +5 -11
- package/dist/brain.js +28 -18
- package/dist/capabilities/execution-lane.d.ts +5 -7
- package/dist/capabilities/execution-lane.js +2 -1
- package/dist/capabilities/memory-notice.d.ts +30 -12
- package/dist/capabilities/memory-notice.js +8 -1
- package/dist/capabilities/skills.d.ts +14 -0
- package/dist/capabilities/skills.js +4 -0
- package/dist/config-catalog.js +5 -4
- package/dist/config-center/apply-effective.js +7 -1
- package/dist/config-center/facade.d.ts +2 -1
- package/dist/config-invariants.d.ts +2 -2
- package/dist/config-invariants.js +9 -1
- package/dist/config-types.d.ts +20 -3
- package/dist/config.d.ts +9 -0
- package/dist/config.js +46 -8
- package/dist/env-facts.js +2 -4
- package/dist/execution-lane-caps.d.ts +36 -6
- package/dist/execution-lane-caps.js +4 -0
- package/dist/file-history-capture.d.ts +65 -0
- package/dist/file-history-capture.js +21 -0
- package/dist/hooks/hook-llm.d.ts +1 -1
- package/dist/hooks/hook-llm.js +3 -1
- package/dist/hosted-posture.d.ts +1 -1
- package/dist/http/resume-legs.d.ts +14 -4
- package/dist/http/resume-legs.js +26 -17
- package/dist/http/route-ctx.d.ts +35 -9
- package/dist/http/routes/approvals-assistant.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +16 -6
- package/dist/http/routes/capabilities.js +2 -0
- package/dist/http/routes/runs.js +2 -2
- package/dist/http/routes/session-sync.js +6 -1
- package/dist/http/server.d.ts +4 -1
- package/dist/http/server.js +1 -1
- package/dist/http/verify-rounds.d.ts +1 -1
- package/dist/memory-scope.d.ts +68 -13
- package/dist/memory-scope.js +35 -5
- package/dist/model-provider.d.ts +13 -0
- package/dist/model-provider.js +3 -0
- package/dist/model-route-endpoint.d.ts +82 -0
- package/dist/model-route-endpoint.js +55 -0
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/project-identity.d.ts +29 -0
- package/dist/project-identity.js +120 -0
- package/dist/run-local.d.ts +14 -4
- package/dist/run-local.js +12 -10
- package/dist/session-sync.d.ts +4 -1
- package/dist/session-sync.js +2 -2
- package/dist/task-cwd.d.ts +9 -3
- package/dist/task-cwd.js +3 -1
- package/dist/trace/redact.d.ts +1 -1
- package/package.json +1 -1
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
set -euo pipefail
|
|
16
16
|
|
|
17
17
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
18
|
+
# `.env` 文法(写口 encode / 读口 ENVF_WORD 与规范行判据)的唯一定义 —— 写读两半从这一处取判据。
|
|
19
|
+
. "$HERE/lib/envf.sh"
|
|
18
20
|
# 执行车道表的唯一读者(S-599)。定义在**最前面**:`--help` 的词表也从表渲(手抄的词表会在加一条
|
|
19
21
|
# 车道时静默分叉),而 `-h` 在参数循环里就被处理,那时后面的函数还没定义。
|
|
20
22
|
lanes() { python3 "$HERE/lib/lanes.py" "$@"; }
|
|
@@ -120,60 +122,27 @@ if [ -n "$CONF" ]; then
|
|
|
120
122
|
fi
|
|
121
123
|
|
|
122
124
|
WORK="$HOME/.sema-up/$PROJECT"; mkdir -p "$WORK"; chmod 700 "$HOME/.sema-up" "$WORK"
|
|
123
|
-
# 凭据/配置文件的坐标
|
|
124
|
-
# (
|
|
125
|
+
# 凭据/配置文件的坐标(S-599 提到这里:车道决策要在模型面之前读既有 .env)。**读写原语全在 lib/envf.sh**
|
|
126
|
+
# (envf_get / envf_has / envf_load / envf_put / setk / setk_new / resetk,S-618):决策键严格读口、写口序列化、
|
|
127
|
+
# 数据版回读都是那一处的同一套文法;本脚本只调用,不再自带任何一份。物理行假设由 up 之前的
|
|
128
|
+
# `reconcile_render`(`docker compose config` 渲染对账,compose 唯一属主)兜底。
|
|
125
129
|
ENVF="$WORK/.env"
|
|
126
|
-
has_env() { [ -f "$ENVF" ] && grep -q "^$1=..*" "$ENVF"; }
|
|
127
|
-
# ── 决策键的 `.env` 严格读口(7.96.0 合并复审)────────────────────────────────────────────────────
|
|
128
|
-
# 决策(租户形 / 车道)读到的每个键 —— `REQUIRE_PRINCIPAL`、`REMOTE_EXEC`、所选车道的必需键 —— 只经这一只读口。
|
|
129
|
-
# 它**不去猜** compose 会怎么读 `.env`:compose 的 dotenv 方言认 `export ` 前缀、键两侧空格、`K: v`、行内注释、
|
|
130
|
-
# 重复键末次生效、`$VAR` 展开……在 bash 里复刻其中任何一部分都是一份手抄方言,漏一处就是「读口看到
|
|
131
|
-
# false、容器拿到 true」(合并复审实证:`=false` 之后再来一行 `export …=true` ⇒ 本脚本落 host,容器拿到 true)。
|
|
132
|
-
# 所以本读口是一条**闭文法**,fail-closed:
|
|
133
|
-
# ① 凡是在任一方言下**可能**给 <键> 赋值的行(行首空白?+ `export `? + <键> + 空白? + `=` 或 `:`)至多一行;
|
|
134
|
-
# ② 那一行必须是规范形 `<键>=<值>`:值 = 空 | 一个不含空白 / 引号 / `#` / `$` / 反斜杠 / 反引号的词 |
|
|
135
|
-
# 一对同型引号包住的这样一个词(sema-up 自己写出的形全在这里面)。
|
|
136
|
-
# 违反任一条 ⇒ 响亮拒并点名行号(**不回显行内容**:必需键里有凭据)。通过 ⇒ 打印值(剥掉那对引号;空 = 未设)。
|
|
137
|
-
# 调用形恒为 `v="$(envf_get K)" || exit 1`(拒因走 stderr,退出码带回调用方)。
|
|
138
|
-
# ⚠️ **已知未闭环(如实披露,不是本读口的承诺范围)**:本读口按**物理行**判,不认引号跨行 —— 另一个键的跨行
|
|
139
|
-
# 单/双引号值里若有一行恰好长成 `K=<值>`,本读口会把它当成 K 的那一行。生成段的 setk / has_env / resetk
|
|
140
|
-
# 同样按物理行判(早于本读口就是这样),两者对这种输入的误读方向一致:容器拿到的是 compose 的缺省值。
|
|
141
|
-
# 统一形(另立一件):生成 .env 之后用 `docker compose config` 渲染一次,拿容器真正拿到的值与决策对账 ——
|
|
142
|
-
# 那一步以 compose 自己为唯一属主,本读口与 setk 的物理行假设可以一起退役。
|
|
143
|
-
ENVF_WORD='[^[:space:]"'"'"'#$`\\]'
|
|
144
|
-
envf_get() {
|
|
145
|
-
[ -f "$ENVF" ] || return 0
|
|
146
|
-
local k="$1" hits n nums line v
|
|
147
|
-
hits="$(grep -nE "^[[:space:]]*(export[[:space:]]+)?${k}[[:space:]]*[=:]" "$ENVF" || true)"
|
|
148
|
-
[ -n "$hits" ] || return 0
|
|
149
|
-
n="$(printf '%s\n' "$hits" | wc -l | tr -d '[:space:]')"
|
|
150
|
-
nums="$(printf '%s\n' "$hits" | cut -d: -f1 | paste -sd, -)"
|
|
151
|
-
if [ "$n" -gt 1 ]; then
|
|
152
|
-
log "⛔ $ENVF 里可能给 $k 赋值的行有 $n 行(第 $nums 行)—— 决策键只许一行"
|
|
153
|
-
log " ↳ 下一步:只留一行规范形 $k=<值>,其余删掉"
|
|
154
|
-
log " ↳ 为什么不挑一行用:compose 读 .env 的规则(export 前缀、键旁空格、重复键末次生效)与本脚本不同,挑哪一行都可能不是容器真正拿到的那一行"
|
|
155
|
-
return 1
|
|
156
|
-
fi
|
|
157
|
-
line="${hits#*:}"
|
|
158
|
-
if ! printf '%s\n' "$line" | grep -qE "^${k}=(${ENVF_WORD}*|\"${ENVF_WORD}*\"|'${ENVF_WORD}*')\$"; then
|
|
159
|
-
log "⛔ $ENVF 第 $nums 行给 $k 赋值,但不是本脚本认得的规范形"
|
|
160
|
-
log " ↳ 下一步:改成 $k=<值>(行首顶格、不带 export、键旁无空格、值是一个词或一对同型引号包住的一个词、行尾无注释),或删掉这一行"
|
|
161
|
-
log " ↳ 为什么不猜:这一行 compose 怎么读(注释、空白、展开)与本脚本怎么读一旦不一致,容器拿到的就不是决策读到的值"
|
|
162
|
-
return 1
|
|
163
|
-
fi
|
|
164
|
-
v="${line#*=}"
|
|
165
|
-
case "$v" in \"*\") v="${v#\"}"; v="${v%\"}" ;; \'*\') v="${v#\'}"; v="${v%\'}" ;; esac
|
|
166
|
-
printf '%s' "$v"
|
|
167
|
-
}
|
|
168
130
|
# ── 部署事实(租户形与车道两个决策共用)────────────────────────────────────────────────
|
|
169
131
|
# 事实 = `.env`(严格读口)∪ shell / `-f` 环境。compose 插值里 shell **优先于** --env-file(本脚本 teardown 段同一
|
|
170
132
|
# 事实),所以两处都非空而不同 ⇒ 拒并点名两处 —— 不「取第一个非空」:放行的唯一形是两处至多一个值,无论谁赢,
|
|
171
133
|
# 赢的都是它。设 FACT_VAL(可空 = 没有事实)与 FACT_SRC(来源描述)。直接调用,不进命令替换(拒要退整个脚本)。
|
|
172
134
|
deploy_fact() {
|
|
173
|
-
local k="$1" fenv fsh shsrc
|
|
135
|
+
local k="$1" fenv fsh shsrc shset
|
|
174
136
|
fenv="$(envf_get "$k")" || exit 1
|
|
175
|
-
|
|
137
|
+
# C(S-618):shell **导出为空串** ≠ shell 未设。compose 插值里 shell(即便是空串)优先于 --env-file,
|
|
138
|
+
# 所以导出空会**盖掉** .env 的值 —— 必须按「有值 = 空」参与冲突判定,不许当缺席。`${k+x}` 区分两者。
|
|
139
|
+
if eval "[ \"\${$k+x}\" = x ]"; then shset=1; eval "fsh=\"\${$k}\""; else shset=0; fsh=""; fi
|
|
176
140
|
shsrc="${CONF:+-f $CONF / }shell 环境"
|
|
141
|
+
if [ "$shset" = 1 ] && [ -z "$fsh" ] && [ -n "$fenv" ]; then
|
|
142
|
+
log "⛔ 部署事实冲突:$ENVF 里 $k=$fenv,但 $shsrc 里把 $k **导出成了空串** —— compose 插值里 shell 优先于 --env-file,空串会盖掉 .env 的值(容器拿到空 = 走 compose 缺省,不是 $fenv)"
|
|
143
|
+
log " ↳ 下一步:unset $k(用 .env 里的 $fenv),或把 $k 显式设成你要的值"
|
|
144
|
+
exit 1
|
|
145
|
+
fi
|
|
177
146
|
if [ -n "$fenv" ] && [ -n "$fsh" ] && [ "$fenv" != "$fsh" ]; then
|
|
178
147
|
log "⛔ 部署事实冲突:$ENVF 里 $k=$fenv,$shsrc 里 $k=$fsh —— 两处不一致"
|
|
179
148
|
log " ↳ 下一步:两处只留一处(或改成同一个值)"
|
|
@@ -266,13 +235,14 @@ emit() { [ "$MODE_INTERACTIVE" = "json" ] && printf '%s\n' "$1" >&3 || log "$2";
|
|
|
266
235
|
# ── teardown ─────────────────────────────────────────────────────────────────────────────────
|
|
267
236
|
if $TEARDOWN; then
|
|
268
237
|
cd "$HERE/stack"
|
|
269
|
-
if [ -f "$
|
|
270
|
-
|
|
238
|
+
if [ -f "$ENVF" ]; then
|
|
239
|
+
# 读 .env 一律经 lib 的严格读口(S-618 续):裸 grep|cut 会连编码形的单引号一起读出来。
|
|
240
|
+
OV=""; envf_has DATA_ROOT_DIR && OV="-f compose.dataroot.yaml"
|
|
271
241
|
# 全 profile down 会对未启用服务也做 config 校验——.env 缺 SEMA_*_IMAGE(该面没开过)时
|
|
272
242
|
# 整个 down 失败(真踩:假「已卸载」)。补占位符只为过校验;shell env 优先级高于 --env-file
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if ! dc --project-name "$PROJECT" --env-file "$
|
|
243
|
+
envf_has SEMA_SERVER_IMAGE || export SEMA_SERVER_IMAGE="teardown-placeholder"
|
|
244
|
+
envf_has SEMA_REGISTRY_IMAGE || export SEMA_REGISTRY_IMAGE="teardown-placeholder"
|
|
245
|
+
if ! dc --project-name "$PROJECT" --env-file "$ENVF" -f compose.yaml $OV \
|
|
276
246
|
--profile data --profile pg --profile s3 --profile agent --profile registry --profile git \
|
|
277
247
|
down $($TEARDOWN_VOLUMES && echo -v); then
|
|
278
248
|
log "⛔ 卸载未完全成功。下一步:docker ps -a --filter label=com.docker.compose.project=$PROJECT 手查残留"
|
|
@@ -280,11 +250,11 @@ if $TEARDOWN; then
|
|
|
280
250
|
fi
|
|
281
251
|
log "已卸载 project=$PROJECT $($TEARDOWN_VOLUMES && echo '(含数据卷)' || echo '(数据卷保留:pgdata/miniodata/registrydata/gitdata)')"
|
|
282
252
|
if $TEARDOWN_VOLUMES && [ -n "$OV" ]; then
|
|
283
|
-
DR="$(
|
|
253
|
+
DR="$(envf_get DATA_ROOT_DIR)" || exit 1
|
|
284
254
|
# ${DR} 必须带花括号:后面紧跟中文破折号,bash 3.2(macOS 系统壳)会把多字节并进变量名(真踩)
|
|
285
255
|
log "⚠ 数据根在大盘 bind 目录 ${DR}——卷对象已删,盘上文件还在;确认不要了再手动 rm -rf"
|
|
286
256
|
fi
|
|
287
|
-
else log "没有找到 $
|
|
257
|
+
else log "没有找到 $ENVF,无可卸载"; fi
|
|
288
258
|
exit 0
|
|
289
259
|
fi
|
|
290
260
|
|
|
@@ -507,7 +477,10 @@ if [ "$REGION" = "auto" ]; then
|
|
|
507
477
|
REGION=$(awk -v a="$t_cn" -v b="$t_gl" 'BEGIN{print (a<b)?"cn":"global"}')
|
|
508
478
|
fi
|
|
509
479
|
HOSTIP=$(host_ip)
|
|
510
|
-
|
|
480
|
+
# E(S-618):进度日志只打**已校验的决策词**,不回显未过校验的原始输入。REGISTRY_ENABLED 到这里还是运维
|
|
481
|
+
# argv 的原样值(真正的校验在 resetk 写口),曾把带标记的 --registry 值抢先泄进 stderr(test srv796 实测 NEW-5)——
|
|
482
|
+
# 收成 on/off 决策词。region/profile/users/lane/git 到这里都已定死(车道块 / git 词校验都在上面)。
|
|
483
|
+
log "region=$REGION profile=$PROFILE users=$USERS sandbox=${SANDBOX_LANE:-n/a} lane=${LANE_REMOTE_EXEC:-n/a} registry=$([ "$REGISTRY_ENABLED" = "true" ] && echo on || echo off) git=$GIT_MODE"
|
|
511
484
|
|
|
512
485
|
# ── 数据盘选择(多盘机自动选大容量盘;--data-root 显式优先;macOS 不自动——bind 要经 VM 挂载,
|
|
513
486
|
# 显式给了才用)。首装落 .env 后不漂移:换盘=先 --teardown-volumes 迁数据再重装。──────────────
|
|
@@ -539,12 +512,12 @@ case "$PROFILE" in full-single|local-plus) COMPOSE_PROFILES="$COMPOSE_PROFILES,a
|
|
|
539
512
|
[ "$GIT_MODE" = "provision" ] && COMPOSE_PROFILES="$COMPOSE_PROFILES,git"
|
|
540
513
|
COMPOSE_PROFILES="${COMPOSE_PROFILES#,}"; COMPOSE_PROFILES="${COMPOSE_PROFILES%,}"
|
|
541
514
|
case ",$COMPOSE_PROFILES," in *,agent,*)
|
|
542
|
-
if [ -z "${SEMA_SERVER_IMAGE:-}" ] && !
|
|
515
|
+
if [ -z "${SEMA_SERVER_IMAGE:-}" ] && ! envf_has SEMA_SERVER_IMAGE; then
|
|
543
516
|
log "⛔ $PROFILE 档需要 sema-server 镜像。下一步:-f 配置里加 SEMA_SERVER_IMAGE=<registry>/sema-server:<tag>(默认=ghcr.io/sema-agent/sema-server:<tag> 公开仓匿名可拉;CN 兜底=内网 Gitea 仓;SWR 已弃用);镜像分发=D4"
|
|
544
517
|
exit 1
|
|
545
518
|
fi ;;
|
|
546
519
|
esac
|
|
547
|
-
if [ "$REGISTRY_ENABLED" = "true" ] && [ -z "${SEMA_REGISTRY_IMAGE:-}" ] && !
|
|
520
|
+
if [ "$REGISTRY_ENABLED" = "true" ] && [ -z "${SEMA_REGISTRY_IMAGE:-}" ] && ! envf_has SEMA_REGISTRY_IMAGE; then
|
|
548
521
|
log "⛔ registry=true(现在是各档默认)需要 sema-registry 镜像。下一步(三选一):"
|
|
549
522
|
log " ① --registry-image=ghcr.io/clayboby/sema-registry:<tag>(GHCR/Hub 公开仓,匿名可拉;SWR 已弃用)"
|
|
550
523
|
log " ② -f 配置里加 SEMA_REGISTRY_IMAGE=<你的仓>/sema-registry:<tag>"
|
|
@@ -592,25 +565,10 @@ PG_TUNE_FLAGS="-c max_connections=100 -c shared_buffers=${SB}MB -c effective_cac
|
|
|
592
565
|
# ── .env 生成(幂等:已存在则只补缺,凭据绝不轮换;0600) ───────────────────────────────────
|
|
593
566
|
genpw() { openssl rand -hex 24; }
|
|
594
567
|
touch "$ENVF"; chmod 600 "$ENVF"
|
|
595
|
-
#
|
|
596
|
-
#
|
|
597
|
-
#
|
|
598
|
-
#
|
|
599
|
-
# ⇒ **写之前**拒(先判后动:resetk 的删旧行也排在判定之后),拒因不回显值(可能是凭据)。
|
|
600
|
-
envf_check() {
|
|
601
|
-
case "$2" in
|
|
602
|
-
*$'\n'*|*$'\r'*)
|
|
603
|
-
log "⛔ 要写进 $ENVF 的 $1 的值里有换行 / 回车 —— .env 一行一条赋值,换行后的内容会被 compose 当成另一条赋值"
|
|
604
|
-
log " ↳ 下一步:去掉值里的换行(查 $1 对应的命令行参数或 -f 配置项)后重跑"
|
|
605
|
-
exit 1 ;;
|
|
606
|
-
esac
|
|
607
|
-
}
|
|
608
|
-
envf_put() { envf_check "$1" "$2"; printf '%s=%s\n' "$1" "$2" >> "$ENVF"; }
|
|
609
|
-
setk() { grep -q "^$1=" "$ENVF" || envf_put "$1" "$2"; }
|
|
610
|
-
# setk_new:同 setk,但新写入时返回 0(summary 用来判断「密码只显示一次」)
|
|
611
|
-
setk_new() { if grep -q "^$1=" "$ENVF"; then return 1; else envf_put "$1" "$2"; return 0; fi; }
|
|
612
|
-
# resetk:非凭据键每次重写(翻转要落 .env,不然 teardown/汇总口径漂)。先判后删:拒了就一行都不动。
|
|
613
|
-
resetk() { envf_check "$1" "$2"; grep -v "^$1=" "$ENVF" > "$ENVF.tmp" || true; mv "$ENVF.tmp" "$ENVF"; chmod 600 "$ENVF"; envf_put "$1" "$2"; }
|
|
568
|
+
# 写口 = lib/envf.sh 的 envf_put(setk / setk_new / resetk 都落到它):值按同一文法序列化 —— 安全裸词原样,
|
|
569
|
+
# 含空白 / 特殊字符的走整值单引号编码形(compose 读成字面量、闭引号后不续解析;被 source 也不执行);换行 /
|
|
570
|
+
# 回车 / 单引号 ⇒ 写之前响亮拒,点名键、不回显值。setk 判「键在不在」也走严格读口:旧文件里该键那一行不是
|
|
571
|
+
# 规范形(比如同物理行闭引号带出的二次赋值)⇒ 响亮拒,不再「见行在就跳过」把毒行原样留着(test srv796 RT1)。
|
|
614
572
|
setk SEMA_PROJECT "$PROJECT"
|
|
615
573
|
setk PG_PASSWORD "$(genpw)"
|
|
616
574
|
setk MINIO_PASSWORD "$(genpw)"
|
|
@@ -621,8 +579,9 @@ setk MINIO_CONSOLE_PORT "$MINIO_CONSOLE_PORT"
|
|
|
621
579
|
setk SERVER_PORT "$SERVER_PORT"
|
|
622
580
|
# data-center 数据面对外服务(agent 在别处连);full-single 数据面仅回环、只有 server 对外
|
|
623
581
|
if [ "$PROFILE" = "data-center" ]; then setk PG_BIND "0.0.0.0"; setk MINIO_BIND "0.0.0.0"; else setk PG_BIND "127.0.0.1"; setk MINIO_BIND "127.0.0.1"; fi
|
|
624
|
-
# PG_TUNE_FLAGS 每次重算(规格可能变;非凭据,轮换无害)
|
|
625
|
-
|
|
582
|
+
# PG_TUNE_FLAGS 每次重算(规格可能变;非凭据,轮换无害)。值带空白 ⇒ 写口按文法自动整值单引号
|
|
583
|
+
# (原来手工加双引号=在写口之外自己拼一套引用,正是本批要消灭的第二套文法);compose 读成同一个字面量。
|
|
584
|
+
resetk PG_TUNE_FLAGS "$PG_TUNE_FLAGS"
|
|
626
585
|
if [ "$PROFILE" = "full-single" ] || [ "$PROFILE" = "local-plus" ]; then
|
|
627
586
|
setk MODEL_GATEWAY_BASEURL "$GATEWAY"
|
|
628
587
|
setk MODEL_ID "$MODEL"
|
|
@@ -703,9 +662,10 @@ elif [ "$GIT_MODE" = "connect" ]; then
|
|
|
703
662
|
resetk GIT_TOKEN "$GIT_TOKEN"
|
|
704
663
|
fi
|
|
705
664
|
|
|
706
|
-
# DATA_ROOT 落定(首装写 .env 后为准——重跑换值会造出一套空卷,数据看似丢失)
|
|
707
|
-
|
|
708
|
-
|
|
665
|
+
# DATA_ROOT 落定(首装写 .env 后为准——重跑换值会造出一套空卷,数据看似丢失)。首装值经严格读口解码回读
|
|
666
|
+
# (S-618 续:路径含空格时写口落整值单引号形,旧的裸 grep|cut 会把引号当成路径的一部分)。
|
|
667
|
+
_stored="$(envf_get DATA_ROOT_DIR)" || exit 1
|
|
668
|
+
if [ -n "$_stored" ]; then
|
|
709
669
|
if [ -n "$DATA_ROOT" ] && [ "$DATA_ROOT" != "$_stored" ]; then
|
|
710
670
|
log "⚠ --data-root=$DATA_ROOT 与首装选定的 $_stored 不同——沿用首装值(换盘流程:--teardown-volumes 后迁数据重装)"
|
|
711
671
|
fi
|
|
@@ -717,9 +677,54 @@ if [ -n "$DATA_ROOT" ]; then
|
|
|
717
677
|
for d in pgdata miniodata registrydata gitdata; do mkdir -p "$DATA_ROOT/$d"; done
|
|
718
678
|
fi
|
|
719
679
|
|
|
680
|
+
# A(S-618):`.env` 生成完毕 —— 把 setk 持久化的**生效端口**回读进 shell 变量(替代旧 source 的这半)。
|
|
681
|
+
# 重跑省略端口旗时,容器绑定的是首装端口(.env),本脚本后续的 API 调用 / 健康检查 / 汇总必须用同一份;
|
|
682
|
+
# 首装(或 .env 无该键)时回读跳过、保留 shell 缺省。凭据在各自用点单独回读(见下 envf_load)。
|
|
683
|
+
envf_load SERVER_PORT PG_PORT MINIO_PORT MINIO_CONSOLE_PORT REGISTRY_PORT GIT_HTTP_PORT GIT_SSH_PORT
|
|
684
|
+
|
|
720
685
|
command -v docker >/dev/null || { log "⛔ 未装 docker。下一步:curl -fsSL https://get.docker.com | sh(cn region 用镜像站,如 https://mirrors.aliyun.com/docker-ce)"; exit 1; }
|
|
721
686
|
compose() { ( cd "$HERE/stack" && dc --project-name "$PROJECT" --env-file "$ENVF" -f compose.yaml ${DATA_ROOT:+-f compose.dataroot.yaml} "$@" ); }
|
|
722
687
|
|
|
688
|
+
# ── D:渲染对账(S-618 的类闭环;生成 .env 之后、compose up 之前)──────────────────────────────────
|
|
689
|
+
# .env 的写口 / 读口都按物理行判,认不出 compose dotenv 的跨行引号值等边角;唯一能确知「容器真正拿到
|
|
690
|
+
# 什么」的是 compose 自己。所以在 up 之前跑一次 `compose config`——与 up **同一组 -f / profile / 同一 shell
|
|
691
|
+
# 环境**(于是运维导出的空串等也算进来)——从渲染结果取 agent 服务的 REQUIRE_PRINCIPAL / REMOTE_EXEC /
|
|
692
|
+
# REQUIRE_EXECUTION_LANE,与本次决策逐值对比,不一致 ⇒ 拒并点名渲染值与决策值。**以 compose 为唯一属主**。
|
|
693
|
+
# docker 不可达在上面 `command -v docker` 那道已 fail-loud 拦下;config 本身失败(YAML/插值坏)也拒,
|
|
694
|
+
# 不新铸降级臂:未经对账就不 up。只有 agent 面有这三个决策键,别的档跳过。
|
|
695
|
+
reconcile_render() {
|
|
696
|
+
case ",$COMPOSE_PROFILES," in *,agent,*) : ;; *) return 0 ;; esac
|
|
697
|
+
local exp_rp="$REQUIRE_PRINCIPAL_WANT" exp_re="$LANE_REMOTE_EXEC" exp_rl rendered
|
|
698
|
+
exp_rl="$(printf '%s\n' "$LANE_ENV" | sed -n 's/^REQUIRE_EXECUTION_LANE=//p')"
|
|
699
|
+
rendered="$(COMPOSE_PROFILES="$COMPOSE_PROFILES" compose config --format json 2>&1)" || {
|
|
700
|
+
local rc=$?
|
|
701
|
+
# 7.98.0 合并复审 F3:compose 的报文**不回显** —— 它解析 .env 失败时会原样引用那一行(凭据就在行里)。只报退出码 + 出路。
|
|
702
|
+
log "⛔ 渲染对账:docker compose config 失败(退出码 $rc),无法确认容器会拿到什么 —— 拒绝在未对账的情况下 up"
|
|
703
|
+
log " ↳ 下一步:在本机亲跑 docker compose --project-name $PROJECT --env-file $ENVF -f $HERE/stack/compose.yaml config 看报文(本脚本不转印它:.env 解析失败的报文会原样引用那一行,可能带凭据);常见成因是手改过的 .env 行"
|
|
704
|
+
exit 1
|
|
705
|
+
}
|
|
706
|
+
RECON_JSON="$rendered" python3 - "$exp_rp" "$exp_re" "$exp_rl" <<'PY' || {
|
|
707
|
+
import json, os, sys
|
|
708
|
+
want = {"REQUIRE_PRINCIPAL": sys.argv[1], "REMOTE_EXEC": sys.argv[2], "REQUIRE_EXECUTION_LANE": sys.argv[3]}
|
|
709
|
+
d = json.loads(os.environ["RECON_JSON"])
|
|
710
|
+
env = ((d.get("services") or {}).get("server") or {}).get("environment") or {}
|
|
711
|
+
bad = []
|
|
712
|
+
for k, w in want.items():
|
|
713
|
+
got = env.get(k)
|
|
714
|
+
got = "" if got is None else str(got)
|
|
715
|
+
if got != w:
|
|
716
|
+
bad.append("%s:渲染=%r 决策=%r" % (k, got, w))
|
|
717
|
+
if bad:
|
|
718
|
+
sys.stderr.write("[sema-up] ↳ 明细:" + " | ".join(bad) + "\n")
|
|
719
|
+
raise SystemExit(3)
|
|
720
|
+
PY
|
|
721
|
+
log "⛔ 渲染对账失败:compose 渲染出的容器决策值与本次决策不一致 —— 以 compose 为唯一属主,拒绝 up"
|
|
722
|
+
log " ↳ 下一步:检查 .env 里 REQUIRE_PRINCIPAL / REMOTE_EXEC / REQUIRE_EXECUTION_LANE,以及 shell / -f 里是否**导出**了同名变量(compose 插值 shell 优先于 --env-file,导出为空会盖掉 .env)"
|
|
723
|
+
exit 1
|
|
724
|
+
}
|
|
725
|
+
log "渲染对账通过:REQUIRE_PRINCIPAL=$exp_rp REMOTE_EXEC=$exp_re REQUIRE_EXECUTION_LANE=$exp_rl(compose 渲染逐值一致)"
|
|
726
|
+
}
|
|
727
|
+
|
|
723
728
|
# ── 外接端点连接检测(装配前 fail-loud;只验兼容可用,不校验证书——内网自签是常态)────────────
|
|
724
729
|
if [ -n "$PG_URL" ]; then
|
|
725
730
|
log "外接 PG 连接检测(host=$EXT_PG_HOST:$EXT_PG_PORT db=$EXT_PG_DB)"
|
|
@@ -740,7 +745,8 @@ GIT_MANUAL_NOTES=()
|
|
|
740
745
|
if [ "$GIT_MODE" = "provision" ]; then
|
|
741
746
|
log "git 面:先起 Gitea(引导管理员/token/OAuth 要在 registry 之前)"
|
|
742
747
|
COMPOSE_PROFILES=git compose up -d --wait git || { log "⛔ Gitea 起不来。下一步:docker compose -p $PROJECT logs git;镜像拉不动时 --gitea-image=<可达镜像仓>/gitea:1.24"; exit 1; }
|
|
743
|
-
.
|
|
748
|
+
# A(S-618):不 source .env —— 回读 sema-up 自己生成、没进 shell 变量的凭据(管理员账号/口令)。
|
|
749
|
+
envf_load GIT_ADMIN_USER GIT_ADMIN_PASSWORD
|
|
744
750
|
GIT_CTR="${PROJECT}-git-1"
|
|
745
751
|
GIT_API="http://127.0.0.1:${GIT_HTTP_PORT}/api/v1"
|
|
746
752
|
# 引导管理员(幂等:已存在即跳过;user 1000=镜像内 git 用户)
|
|
@@ -752,7 +758,7 @@ if [ "$GIT_MODE" = "provision" ]; then
|
|
|
752
758
|
log "Gitea 管理员 $GIT_ADMIN_USER 已建"
|
|
753
759
|
fi
|
|
754
760
|
# 管理员 access token(只生成一次;丢了 .env 就换个名再生成,旧 token 在 Gitea 里可手动清)
|
|
755
|
-
if !
|
|
761
|
+
if ! envf_has GIT_ADMIN_TOKEN; then
|
|
756
762
|
TOK_NAME="sema-up-$(openssl rand -hex 3)"
|
|
757
763
|
TOK=$(curl -m10 -s -u "$GIT_ADMIN_USER:$GIT_ADMIN_PASSWORD" -X POST "$GIT_API/users/$GIT_ADMIN_USER/tokens" \
|
|
758
764
|
-H 'Content-Type: application/json' -d "{\"name\":\"$TOK_NAME\",\"scopes\":[\"all\"]}" \
|
|
@@ -762,9 +768,13 @@ except Exception: print("")')
|
|
|
762
768
|
[ -n "$TOK" ] || { log "⛔ Gitea access token 生成失败。下一步:curl -u <admin> $GIT_API/users/$GIT_ADMIN_USER/tokens 手查;或看 docker compose -p $PROJECT logs git"; exit 1; }
|
|
763
769
|
setk GIT_ADMIN_TOKEN "$TOK"
|
|
764
770
|
fi
|
|
765
|
-
.
|
|
771
|
+
envf_load GIT_ADMIN_TOKEN # A:回读刚生成/已存的 token,不 source .env
|
|
772
|
+
# OAuth 接线只对它被创建时的 git 目标有效(锚 = REGISTRY_GITEA_BASE_URL):目标变了 ⇒ 上一目标的接线作废、
|
|
773
|
+
# 本次重建(建不成 = registry 走表单登录),不把旧身份源静默带进这一次(车MH codex r2 [high],两条路径同一条规则)。
|
|
774
|
+
envf_invalidate_unless REGISTRY_GITEA_BASE_URL "http://$HOSTIP:$GIT_HTTP_PORT" REGISTRY_GITEA_OAUTH_ID REGISTRY_GITEA_OAUTH_SECRET
|
|
775
|
+
if [ "$ENVF_INVALIDATED" = 1 ]; then log "git 面:上一次登记的 OAuth 接线属于另一个 git 目标 —— 已作废,本次重建"; fi
|
|
766
776
|
# registry 登录集成:自动建 OAuth app(真自动化,不是纸面;client secret 只在创建响应里给一次)
|
|
767
|
-
if [ "$REGISTRY_ENABLED" = "true" ] && !
|
|
777
|
+
if [ "$REGISTRY_ENABLED" = "true" ] && ! envf_has REGISTRY_GITEA_OAUTH_ID; then
|
|
768
778
|
REDIRECT="http://$HOSTIP:$REGISTRY_PORT/api/auth/oauth/callback"
|
|
769
779
|
OAUTH=$(curl -m10 -s -H "Authorization: token $GIT_ADMIN_TOKEN" -X POST "$GIT_API/user/applications/oauth2" \
|
|
770
780
|
-H 'Content-Type: application/json' \
|
|
@@ -776,27 +786,33 @@ except Exception: print("")')
|
|
|
776
786
|
try: print(json.load(sys.stdin).get("client_secret",""))
|
|
777
787
|
except Exception: print("")')
|
|
778
788
|
if [ -n "$OA_ID" ] && [ -n "$OA_SECRET" ]; then
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
789
|
+
resetk REGISTRY_GITEA_OAUTH_ID "$OA_ID"
|
|
790
|
+
resetk REGISTRY_GITEA_OAUTH_SECRET "$OA_SECRET"
|
|
791
|
+
resetk REGISTRY_GITEA_BASE_URL "http://$HOSTIP:$GIT_HTTP_PORT"
|
|
782
792
|
log "Gitea→registry OAuth 登录已接(app=sema-registry)"
|
|
783
793
|
else
|
|
784
794
|
GIT_MANUAL_NOTES+=("Gitea OAuth app 自动创建失败——registry 仍可表单登录;手动接法:Gitea 设置→应用→新建 OAuth2(回调 $REDIRECT),把 client id/secret 写进 $ENVF 的 REGISTRY_GITEA_OAUTH_ID/SECRET+REGISTRY_GITEA_BASE_URL 后重跑本命令")
|
|
785
795
|
fi
|
|
786
796
|
fi
|
|
787
797
|
elif [ "$GIT_MODE" = "connect" ]; then
|
|
788
|
-
.
|
|
798
|
+
# A(S-618):不 source .env —— GIT_URL / GIT_TOKEN 就是本次命令行/‑f 传进来的 shell 变量,直接用。
|
|
799
|
+
# (旧代码 source 是历史遗留:那时 source 会拿 .env 旧值盖掉命令行新值,故先 resetk 再 source 找回;
|
|
800
|
+
# 不 source 之后新值本就在 shell 里,连这道找回都不需要了。)
|
|
789
801
|
# 连通验证(fail-loud:接不上就别继续,别部署完才发现 git 面是死的)。
|
|
790
802
|
# 任意三方 git 都支持:探测链 Gitea API → GitHub API → 通用 git(ls-remote/HTTP 可达);
|
|
791
803
|
# 证书一律不校验(-k / http.sslVerify=false)——内网自签是常态,验的是兼容可用性。
|
|
792
804
|
GIT_KIND=""
|
|
805
|
+
# OAuth 接线只对它被创建时的 git 目标有效(锚 = REGISTRY_GITEA_BASE_URL):目标变了 ⇒ 上一目标的接线作废、
|
|
806
|
+
# 本次重建(建不成 = registry 走表单登录),不把旧身份源静默带进这一次(车MH codex r2 [high],两条路径同一条规则)。
|
|
807
|
+
envf_invalidate_unless REGISTRY_GITEA_BASE_URL "$GIT_URL" REGISTRY_GITEA_OAUTH_ID REGISTRY_GITEA_OAUTH_SECRET
|
|
808
|
+
if [ "$ENVF_INVALIDATED" = 1 ]; then log "git=connect:上一次登记的 OAuth 接线属于另一个 git 目标 —— 已作废,本次按新目标重建"; fi
|
|
793
809
|
GT_VER=$(curl -k -m10 -s -H "Authorization: token $GIT_TOKEN" "$GIT_URL/api/v1/version" | python3 -c 'import json,sys
|
|
794
810
|
try: print(json.load(sys.stdin).get("version",""))
|
|
795
811
|
except Exception: print("")')
|
|
796
812
|
if [ -n "$GT_VER" ]; then
|
|
797
813
|
GIT_KIND="gitea"
|
|
798
814
|
log "git=connect:Gitea $GT_VER 验证通过"
|
|
799
|
-
if [ "$REGISTRY_ENABLED" = "true" ] && !
|
|
815
|
+
if [ "$REGISTRY_ENABLED" = "true" ] && ! envf_has REGISTRY_GITEA_OAUTH_ID; then
|
|
800
816
|
REDIRECT="http://$HOSTIP:$REGISTRY_PORT/api/auth/oauth/callback"
|
|
801
817
|
OAUTH=$(curl -k -m10 -s -H "Authorization: token $GIT_TOKEN" -X POST "$GIT_URL/api/v1/user/applications/oauth2" \
|
|
802
818
|
-H 'Content-Type: application/json' \
|
|
@@ -808,9 +824,9 @@ except Exception: print("")')
|
|
|
808
824
|
try: print(json.load(sys.stdin).get("client_secret",""))
|
|
809
825
|
except Exception: print("")')
|
|
810
826
|
if [ -n "$OA_ID" ] && [ -n "$OA_SECRET" ]; then
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
827
|
+
resetk REGISTRY_GITEA_OAUTH_ID "$OA_ID"
|
|
828
|
+
resetk REGISTRY_GITEA_OAUTH_SECRET "$OA_SECRET"
|
|
829
|
+
resetk REGISTRY_GITEA_BASE_URL "$GIT_URL"
|
|
814
830
|
log "已有 Gitea→registry OAuth 登录已接(app=sema-registry)"
|
|
815
831
|
else
|
|
816
832
|
GIT_MANUAL_NOTES+=("OAuth app 自动创建失败(token 可能缺 write:user scope)——手动接法:该 Gitea 设置→应用→新建 OAuth2(回调 $REDIRECT),把 client id/secret 写进 $ENVF 的 REGISTRY_GITEA_OAUTH_ID/SECRET+REGISTRY_GITEA_BASE_URL 后重跑本命令")
|
|
@@ -846,17 +862,20 @@ except Exception: print("")')
|
|
|
846
862
|
fi
|
|
847
863
|
|
|
848
864
|
# ── 装配(Layer 1 compose;profiles 映射形态) ────────────────────────────────────────────────
|
|
865
|
+
# D(S-618):up 之前先 `compose config` 渲染对账 —— 容器真正拿到的决策键与本次决策逐值对齐才 up。
|
|
866
|
+
reconcile_render
|
|
849
867
|
log "装配:docker compose profiles=$COMPOSE_PROFILES project=$PROJECT"
|
|
850
868
|
COMPOSE_PROFILES="$COMPOSE_PROFILES" compose up -d --wait --remove-orphans \
|
|
851
869
|
|| { log "⛔ compose up 失败。下一步:docker compose -p $PROJECT logs(常见:镜像拉取失败→检查镜像引用/docker login;端口占用→--port 换口)"; exit 1; }
|
|
852
870
|
# 建桶(幂等,一次性动作归 Layer 0;只对本地 MinIO——外接 S3 已在连接检测时确保)
|
|
853
|
-
.
|
|
871
|
+
envf_load MINIO_PASSWORD # A:回读生成的 MinIO 口令(建桶用),不 source .env
|
|
854
872
|
case ",$COMPOSE_PROFILES," in *,s3,*)
|
|
855
873
|
docker run --rm --network "${PROJECT}_default" -e MC_HOST_x="http://sema:${MINIO_PASSWORD}@minio:9000" "${MC_IMAGE:-minio/mc:latest}" mb --ignore-existing x/workspaces x/blobs x/backups >/dev/null || { log "⛔ MinIO 建桶失败。下一步:docker compose -p $PROJECT logs minio"; exit 1; } ;;
|
|
856
874
|
esac
|
|
857
875
|
|
|
858
876
|
# ── 验证(装后即验,§3d smoke 的子集;全量随时可跑 smoke.sh --project=$PROJECT)──────────────
|
|
859
|
-
.
|
|
877
|
+
# A:回读验证 / 汇总要用的凭据与登记值(不 source .env);缺的键回读成空,由下游 :- 缺省处置。
|
|
878
|
+
envf_load MINIO_PASSWORD REGISTRY_ADMIN_USER REGISTRY_ADMIN_PASSWORD REGISTRY_GITEA_BASE_URL GIT_ADMIN_USER GIT_ADMIN_PASSWORD
|
|
860
879
|
V_PG="n/a" V_BK="n/a"
|
|
861
880
|
case ",$COMPOSE_PROFILES," in *,pg,*)
|
|
862
881
|
V_PG=$(docker exec "${PROJECT}-pg-1" psql -U sema -d sema -tAc "SHOW shared_buffers" 2>/dev/null || echo FAIL) ;;
|
package/deploy/sema-up/smoke.sh
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
# 远程 URL 模式: smoke.sh --url=http://host:8090 --token=T (引擎面:health/auth/真任务)
|
|
6
6
|
# 每项 OK/FAIL 一行;FAIL 带原因+建议(§3d-2 报错规范);exit 0=全绿。
|
|
7
7
|
set -u
|
|
8
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
# `.env` 的文法与读原语只在 lib/envf.sh 定义(S-618 续):本脚本读 `.env` 只经 envf_load,不 source。
|
|
10
|
+
. "$HERE/lib/envf.sh"
|
|
8
11
|
|
|
9
12
|
PROJECT="" URL="" TOKEN=""
|
|
10
13
|
for a in "$@"; do case "$a" in
|
|
@@ -20,7 +23,11 @@ fail() { printf '[FAIL] %s — %s\n ↳ 建议:%s\n' "$1" "$2" "$3"; FAILS=
|
|
|
20
23
|
if [ -n "$PROJECT" ]; then
|
|
21
24
|
ENVF="$HOME/.sema-up/$PROJECT/.env"
|
|
22
25
|
[ -r "$ENVF" ] || { echo "找不到 $ENVF(先跑 sema-up.sh)"; exit 1; }
|
|
23
|
-
.
|
|
26
|
+
# A(S-618 续):不 source .env(值里的 $() / 反引号 / ; 是数据,不是代码)—— 只经严格读口取本脚本用到的键;
|
|
27
|
+
# 读到非规范行(比如旧写口留下的裸 `$(…)`)⇒ 响亮拒并点名行号,不猜、不执行。
|
|
28
|
+
envf_load SERVICE_AUTH_TOKEN DEPLOY_PROFILE SERVER_PORT MINIO_PASSWORD \
|
|
29
|
+
AGENT_PG_HOST AGENT_PG_USER PG_PASSWORD AGENT_PG_PORT AGENT_PG_DATABASE AGENT_MINIO_ENDPOINT AGENT_MINIO_USER \
|
|
30
|
+
REGISTRY_ENABLED REGISTRY_PORT GIT_MODE GIT_HTTP_PORT GIT_ADMIN_TOKEN GIT_ADMIN_USER
|
|
24
31
|
TOKEN="${TOKEN:-${SERVICE_AUTH_TOKEN:-}}"
|
|
25
32
|
# 引擎面只在带 agent 的档默认探(data-center 没有引擎,探 8090 必假红);--url 显式给了照跑
|
|
26
33
|
case "${DEPLOY_PROFILE:-full-single}" in
|
package/dist/approval.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { type ToolPolicy } from "@sema-agent/core";
|
|
7
7
|
import { type PostureSource } from "./posture-source.js";
|
|
8
|
+
import { type ExecutionLane } from "./execution-lane-caps.js";
|
|
8
9
|
/**
|
|
9
10
|
* 运维**是否表达了门意图** —— boot 的单用户 allow-all 基线只在"零门意图"时才允许铺开,
|
|
10
11
|
* 所以这个谓词漏一格 = 那一格的意图被 allow-all 静默吞掉。
|
|
@@ -39,6 +40,33 @@ export declare function assertGateIntentServiceable(config: {
|
|
|
39
40
|
durableApproval: boolean;
|
|
40
41
|
durableApprovalSource: PostureSource;
|
|
41
42
|
}, checkpointStorePresent: boolean): void;
|
|
43
|
+
/**
|
|
44
|
+
* S-602 C3 —— 「**无应答面**」两条 boot warn 的判据与文案(纯函数;stage-07 只负责打日志)。
|
|
45
|
+
*
|
|
46
|
+
* 两条 warn 警的是同一类失败:一个 `ask` 发生了,而这台部署既没有活体协调器(`TOOL_APPROVAL_ENABLED`)也没有
|
|
47
|
+
* durable 停车(`DURABLE_APPROVAL`)⇒ core `resolveAsk` 无 onAsk ⇒ headless 自动拒(fail-closed,写进转录)。
|
|
48
|
+
* 运维要在 **boot** 就知道,否则会把随之而来的拒绝读成故障。两条 warn 按「ask 从哪来」**划分车道**,互补不重叠:
|
|
49
|
+
* · `fs_write_gate_no_responder` —— 写门的 Write/Edit ask。它只在**手写本机盘**时才会发生(写门挂在 host
|
|
50
|
+
* 语义上,而 ask 要有手去触发)⇒ 判据 = {@link handsOnWorkerFilePlane}(今天 = `host`)。改前写的是
|
|
51
|
+
* `remoteExec === undefined || provider === "host"`(门 D R3 同义折):把没有手的 in-process 也算进来,
|
|
52
|
+
* 那一形上一次 Write ask 都不会发生,warn 是假话;
|
|
53
|
+
* · `workflow_gate_no_responder` —— `run_workflow` 的 ask,**每条车道都挂**(不以挂手为前提,见
|
|
54
|
+
* `createWorkflowAskPolicy`)⇒ 判据 = 自编排开着 ∧ **不在上一条的射程里**(上一条的文案已覆盖本机手的车道)。
|
|
55
|
+
* 改前写的是 `remoteExec !== undefined && provider !== "host"` —— 依赖上一条对 in-process 的兜底;
|
|
56
|
+
* 上一条收窄之后,in-process ∧ 自编排 ∧ 无应答面必须由这一条接住(否则整形零告警)。
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildNoResponderBootWarns(input: {
|
|
59
|
+
/** 本部署的执行车道(归一读口 `executionLaneOf` 的答案)。 */
|
|
60
|
+
lane: ExecutionLane;
|
|
61
|
+
selfOrchestrationEnabled: boolean;
|
|
62
|
+
/** 活体协调器 ∨ durable 停车 —— 任何一面在场,ask 都有人答。 */
|
|
63
|
+
approvalFaceWired: boolean;
|
|
64
|
+
}): Array<{
|
|
65
|
+
event: "fs_write_gate_no_responder" | "workflow_gate_no_responder";
|
|
66
|
+
fields: {
|
|
67
|
+
hint: string;
|
|
68
|
+
};
|
|
69
|
+
}>;
|
|
42
70
|
/**
|
|
43
71
|
* [ref] 件⑩ —— **center 的 `forceDurableGate` 在无 checkpoint 店的部署上会静默 inert**,补一条 boot 披露。
|
|
44
72
|
*
|
package/dist/approval.js
CHANGED
|
@@ -2,6 +2,7 @@ import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "@sema-ag
|
|
|
2
2
|
import { 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
|
+
import { handsOnWorkerFilePlane } from "./execution-lane-caps.js";
|
|
5
6
|
export function hasOperatorGateIntent(config) {
|
|
6
7
|
return (config.approvalRequire.length > 0 ||
|
|
7
8
|
config.approvalDeny.length > 0 ||
|
|
@@ -16,6 +17,29 @@ export function assertGateIntentServiceable(config, checkpointStorePresent) {
|
|
|
16
17
|
}
|
|
17
18
|
throw new Error("DURABLE_APPROVAL=true but no checkpoint store is available — the configured DB backend has no checkpoint face (or no backend is configured). Wire a MySQL-protocol/PostgreSQL/file backend, or unset DURABLE_APPROVAL.");
|
|
18
19
|
}
|
|
20
|
+
export function buildNoResponderBootWarns(input) {
|
|
21
|
+
if (input.approvalFaceWired)
|
|
22
|
+
return [];
|
|
23
|
+
const handsOnThisDisk = handsOnWorkerFilePlane(input.lane);
|
|
24
|
+
const out = [];
|
|
25
|
+
if (handsOnThisDisk) {
|
|
26
|
+
out.push({
|
|
27
|
+
event: "fs_write_gate_no_responder",
|
|
28
|
+
fields: {
|
|
29
|
+
hint: "host-semantics lane with no approval face: permissionMode default/auto Write/Edit asks will auto-deny (fail-closed); wire TOOL_APPROVAL_ENABLED=true or DURABLE_APPROVAL=true to answer them",
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (input.selfOrchestrationEnabled && !handsOnThisDisk) {
|
|
34
|
+
out.push({
|
|
35
|
+
event: "workflow_gate_no_responder",
|
|
36
|
+
fields: {
|
|
37
|
+
hint: "self-orchestration with no approval face: permissionMode default/auto/acceptEdits run_workflow asks will auto-deny (fail-closed); wire TOOL_APPROVAL_ENABLED=true or DURABLE_APPROVAL=true to answer them",
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
19
43
|
export function buildForceDurableGateInertNotice(input) {
|
|
20
44
|
if (!input.entitlementSourceWired || input.checkpointStorePresent)
|
|
21
45
|
return undefined;
|
|
@@ -43,5 +43,6 @@ export declare function createBudgetAndTracing(ctx: BudgetTracingCtx): {
|
|
|
43
43
|
toolResultStore: import("../plugins/store-backend.js").ToolResultStoreFull | undefined;
|
|
44
44
|
sessionPolicyStore: import("@sema-agent/core").SessionPolicyStore | undefined;
|
|
45
45
|
fileHistoryStore: import("../plugins/store-backend.js").ServiceFileHistoryStore | undefined;
|
|
46
|
+
fileHistoryErasure: import("../file-history-capture.js").FileHistoryErasureFace | undefined;
|
|
46
47
|
};
|
|
47
48
|
//# sourceMappingURL=budget-tracing.d.ts.map
|
|
@@ -4,6 +4,7 @@ import { FleetUsageAccumulator } from "../fleet-client.js";
|
|
|
4
4
|
import { createFleetLeaseFromEnv } from "../fleet-lease.js";
|
|
5
5
|
import { CostQuota } from "../observability/cost-quota.js";
|
|
6
6
|
import { buildQuotaBounds } from "./limit-sync.js";
|
|
7
|
+
import { assembleFileHistory } from "../file-history-capture.js";
|
|
7
8
|
export function createBudgetAndTracing(ctx) {
|
|
8
9
|
const { config, logger, metrics, backend, breakerState } = ctx;
|
|
9
10
|
const brain = createBrain(config, { breakerState });
|
|
@@ -29,10 +30,10 @@ export function createBudgetAndTracing(ctx) {
|
|
|
29
30
|
const sideQueryAccounting = createSideQueryAccountant(metrics, costQuota, fleetUsage, fleetLease, (m) => config.modelQuotaWeights[m] ?? 1);
|
|
30
31
|
const toolResultStore = backend?.toolResult ? backend.toolResult() : undefined;
|
|
31
32
|
const sessionPolicyStore = backend ? backend.sessionPolicy() : undefined;
|
|
32
|
-
const fileHistoryStore = backend
|
|
33
|
+
const { store: fileHistoryStore, erasure: fileHistoryErasure } = assembleFileHistory(backend, config.fileHistoryCapture);
|
|
33
34
|
return {
|
|
34
35
|
brain, pricing, counterDegradeHook, costQuota, modelUsageTracker, promptManifestTracker,
|
|
35
|
-
fleetUsage, fleetLease, tracer, sideQueryAccounting, toolResultStore, sessionPolicyStore, fileHistoryStore,
|
|
36
|
+
fleetUsage, fleetLease, tracer, sideQueryAccounting, toolResultStore, sessionPolicyStore, fileHistoryStore, fileHistoryErasure,
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
//# sourceMappingURL=budget-tracing.js.map
|
|
@@ -10,7 +10,7 @@ import { applyCenterPlugins } from "../capabilities/center-plugins.js";
|
|
|
10
10
|
import { applyCatalogToSource, withPromptArtifactBackfill } from "../capabilities/center-prompts.js";
|
|
11
11
|
import { validatePromptsDomain, CORE_ENGINE_VERSION } from "../prompts-domain-validate.js";
|
|
12
12
|
import { centerScenarios, centerScenarioDetails } from "../capabilities/scenarios.js";
|
|
13
|
-
import { configLkgEnabled } from "../config.js";
|
|
13
|
+
import { configLkgEnabled, isRosterGateArmed } from "../config.js";
|
|
14
14
|
import { defaultLkgPath, defaultSkillCacheDir, saveLkg, loadLkg } from "../config-lkg.js";
|
|
15
15
|
import { centerManagedConfigKeys } from "../config-center/apply-effective.js";
|
|
16
16
|
import { createConfigProvider, raceBootFetch, BOOT_FETCH_DEFERRED } from "../config-provider.js";
|
|
@@ -74,7 +74,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
74
74
|
...(config.configProvider === "local" ? { localDir: localRoot } : {}),
|
|
75
75
|
})
|
|
76
76
|
: undefined;
|
|
77
|
-
const rosterGateArmed = cc
|
|
77
|
+
const rosterGateArmed = isRosterGateArmed(cc);
|
|
78
78
|
const modelReadyState = { ready: !rosterGateArmed };
|
|
79
79
|
const markRosterLanded = (eff) => {
|
|
80
80
|
if (!modelReadyState.ready && (eff.models?.models ?? []).some((m) => m.enabled !== false)) {
|
|
@@ -388,11 +388,8 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
388
388
|
try {
|
|
389
389
|
const fileRx = await loadRemoteExec(localRoot);
|
|
390
390
|
if (fileRx) {
|
|
391
|
-
const preemptedLane = config.remoteExec?.provider
|
|
392
|
-
const isolatedLane = (lane) =>
|
|
393
|
-
const word = executionLaneOf(lane === "in-process" ? undefined : lane);
|
|
394
|
-
return word !== undefined && executionLaneCaps(word).isolation === "supported";
|
|
395
|
-
};
|
|
391
|
+
const preemptedLane = executionLaneOf(config.remoteExec?.provider);
|
|
392
|
+
const isolatedLane = (lane) => executionLaneCaps(lane).isolation === "supported";
|
|
396
393
|
const disclosePreemption = (winner) => {
|
|
397
394
|
if (winner === preemptedLane)
|
|
398
395
|
return;
|
|
@@ -7,6 +7,7 @@ import { createSandboxFileSend, TaskEnvRegistry } from "../capabilities/sandbox-
|
|
|
7
7
|
import { createSendUserFileIssuer } from "../plugins/send-user-file.js";
|
|
8
8
|
import { withLedgerRecording } from "../plugins/send-file-ledger.js";
|
|
9
9
|
import { basename, resolve } from "node:path";
|
|
10
|
+
import { toolsRunOnThisHost } from "../task-cwd.js";
|
|
10
11
|
import { stat as fsStat, readFile as fsReadFile } from "node:fs/promises";
|
|
11
12
|
export function createLiveCoordinators(ctx) {
|
|
12
13
|
const { config, logger, backend, sendUserFileTaskEnvs, ruleConsent, ruleScopeRootFor, getParkedRedeem } = ctx;
|
|
@@ -75,8 +76,7 @@ export function createLiveCoordinators(ctx) {
|
|
|
75
76
|
return undefined;
|
|
76
77
|
if (sendUserFileTaskEnvs)
|
|
77
78
|
return new SendUserFileEmitter();
|
|
78
|
-
|
|
79
|
-
if (provider === "host" && config.requirePrincipal !== true)
|
|
79
|
+
if (toolsRunOnThisHost(config) && config.requirePrincipal !== true)
|
|
80
80
|
return new SendUserFileEmitter();
|
|
81
81
|
return undefined;
|
|
82
82
|
})();
|
|
@@ -46,8 +46,11 @@
|
|
|
46
46
|
* 该面由 test/task-settings.test.ts 的「真身胜过名字」特征化钉机器可见。
|
|
47
47
|
*/
|
|
48
48
|
import { FileError, StubExecutionEnv, type ExecutionEnv, type ExecutionEnvFactory, type FileInfo, type Result } from "@sema-agent/core";
|
|
49
|
-
/** 哪些 lane 用本模块的代理裁决写门 ——
|
|
50
|
-
* 与 resolve-spec 的 `hostSemanticsLane` 是同一判别式的两面,取值处**只此一个**(两处各写一份正是漂移的成因)。
|
|
49
|
+
/** 哪些 lane 用本模块的代理裁决写门 —— 文件平面**不在** worker 本机的全部(= 能力表 `hostFilePlane` 答否)。
|
|
50
|
+
* 与 resolve-spec 的 `hostSemanticsLane` 是同一判别式的两面,取值处**只此一个**(两处各写一份正是漂移的成因)。
|
|
51
|
+
* 🔴 S-602:改前写的是 `provider !== undefined && provider !== "host"` —— 把缺席并进 host 的同义折(门 D R3),
|
|
52
|
+
* 今天改读表那一位(`in-process` / `host` 答 YES,其余答 NO;读数逐格不变)。认不出的词 ⇒ 走代理裁决:
|
|
53
|
+
* 代理 slot 空 ⇒ 写门 fail-closed(本模块头注裁定 2),与改前同向。 */
|
|
51
54
|
export declare function isSandboxPathAdjudicationLane(provider: string | undefined): boolean;
|
|
52
55
|
/** slot 取值的三态。`ok:false` 的两支都是 fail-closed,分开只为让留痕说得出**哪一种**缺席。 */
|
|
53
56
|
export type SandboxPathEnvLookup = {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { FileError, StubExecutionEnv, err, ok } from "@sema-agent/core";
|
|
2
2
|
import { normalizeAbsolutePathLexically } from "../task-cwd.js";
|
|
3
|
+
import { executionLaneCaps, executionLaneOf } from "../execution-lane-caps.js";
|
|
3
4
|
export function isSandboxPathAdjudicationLane(provider) {
|
|
4
|
-
|
|
5
|
+
const lane = executionLaneOf(provider);
|
|
6
|
+
return lane === undefined || executionLaneCaps(lane).hostFilePlane !== "supported";
|
|
5
7
|
}
|
|
6
8
|
const MAX_BOUND_SESSIONS = 4096;
|
|
7
9
|
export class SandboxPathEnvSlots {
|