@sema-agent/server 7.118.0 → 7.119.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 +85 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +24 -13
- package/deploy/sema-up/README.md +2 -0
- package/deploy/sema-up/chart/templates/minio.yaml +2 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
- package/deploy/sema-up/chart/templates/server.yaml +1 -0
- package/deploy/sema-up/chart/values.yaml +7 -1
- package/deploy/sema-up/cluster-up.sh +20 -8
- package/deploy/sema-up/kube-up.sh +29 -16
- package/deploy/sema-up/lib/buckets.sh +21 -0
- package/deploy/sema-up/lib/health.sh +33 -0
- package/deploy/sema-up/sema-up.sh +14 -9
- package/deploy/sema-up/smoke.sh +16 -6
- package/deploy/sema-up/stack/compose.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -1
- package/dist/approval-ask-audit-store.js +2 -7
- package/dist/approval-card.d.ts +5 -5
- package/dist/approval.d.ts +51 -5
- package/dist/approval.js +76 -13
- package/dist/audit.d.ts +12 -1
- package/dist/audit.js +62 -2
- package/dist/auto-mode-face.d.ts +2 -3
- package/dist/bake-runner/main.js +1 -6
- package/dist/bake-runner/protocol.d.ts +18 -0
- package/dist/bake-runner/protocol.js +22 -2
- package/dist/bake-runner/runner.d.ts +12 -8
- package/dist/bake-runner/runner.js +5 -9
- package/dist/boot/config-center.js +1 -1
- package/dist/boot/coordinators.js +4 -1
- package/dist/boot/device-lane.d.ts +3 -3
- package/dist/boot/device-lane.js +1 -2
- package/dist/boot/leader.d.ts +4 -4
- package/dist/boot/leader.js +1 -2
- package/dist/boot/parked-revive-gate.d.ts +5 -1
- package/dist/boot/parked-revive-gate.js +8 -2
- package/dist/boot/resolve-spec.js +7 -4
- package/dist/boot/stage-07-capability-layer.js +5 -1
- package/dist/boot/stage-10-http-server.js +3 -2
- package/dist/boot/stores.js +12 -7
- package/dist/capabilities/center-plugins.d.ts +2 -0
- package/dist/capabilities/center-plugins.js +24 -6
- package/dist/capabilities/code-review-council.js +8 -11
- package/dist/capabilities/scenarios.d.ts +34 -7
- package/dist/capabilities/scenarios.js +33 -6
- package/dist/config-catalog.js +3 -3
- package/dist/config.js +2 -2
- package/dist/deployment-governance.d.ts +5 -2
- package/dist/deployment-governance.js +4 -3
- package/dist/host-decision.d.ts +52 -27
- package/dist/host-decision.js +14 -7
- package/dist/http/admission.js +6 -6
- package/dist/http/resume-legs.js +2 -1
- package/dist/http/routes/approvals-assistant.d.ts +25 -3
- package/dist/http/routes/approvals-assistant.js +9 -5
- package/dist/http/routes/capabilities.js +4 -3
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/runs.js +11 -5
- package/dist/http/routes/sessions.js +20 -16
- package/dist/http/wire-types.d.ts +12 -13
- package/dist/images/manifest.d.ts +7 -1
- package/dist/images/manifest.js +18 -16
- package/dist/memory-store-refusal.d.ts +7 -12
- package/dist/memory-store-refusal.js +4 -5
- package/dist/namespaced-rule-name.d.ts +4 -3
- package/dist/observability/fail-open.d.ts +5 -1
- package/dist/observability/fail-open.js +5 -1
- package/dist/operator-ask.d.ts +6 -5
- package/dist/plugins/approval-exemption-store.d.ts +4 -0
- package/dist/plugins/approval-exemption-store.js +1 -0
- package/dist/plugins/blob-backend.d.ts +13 -0
- package/dist/plugins/blob-backend.js +26 -0
- package/dist/plugins/local-session-store.d.ts +78 -47
- package/dist/plugins/local-session-store.js +152 -122
- package/dist/plugins/one-time-migrations.d.ts +6 -0
- package/dist/plugins/one-time-migrations.js +19 -1
- package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
- package/dist/plugins/snapshot-bucket-presence.js +40 -0
- package/dist/plugins/store-backend.d.ts +17 -6
- package/dist/plugins/store-backend.js +46 -16
- package/dist/plugins/store-open-wait.d.ts +33 -0
- package/dist/plugins/store-open-wait.js +73 -0
- package/dist/rule-entry-list.js +1 -1
- package/dist/run-local.js +4 -3
- package/dist/security.d.ts +5 -3
- package/dist/security.js +4 -0
- package/dist/session-policy-wire.js +7 -12
- package/dist/spec-fields.d.ts +6 -2
- package/dist/store-posture.d.ts +14 -6
- package/dist/store-posture.js +7 -4
- package/dist/task-settings.d.ts +27 -55
- package/dist/task-settings.js +6 -18
- package/dist/tool-approval.d.ts +97 -43
- package/dist/tool-approval.js +120 -31
- package/dist/trace/redact.d.ts +6 -0
- package/dist/trace/redact.js +9 -9
- package/dist/trace/self-minted-wire.d.ts +1 -1
- package/dist/user-input-ledger.d.ts +19 -15
- package/dist/user-input-ledger.js +12 -20
- package/package.json +2 -2
package/MIGRATION.md
CHANGED
|
@@ -7,6 +7,91 @@
|
|
|
7
7
|
> ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
|
|
8
8
|
> 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
|
|
9
9
|
|
|
10
|
+
## 7.119.0 —— 场景指派悬空不再静默落 `default`:`DEFAULT_SCENARIO` 不在册 ⇒ 拒启 · 中心指派的场景名本部署没有 ⇒ 该主体的 run 503 · `APPROVAL_REQUIRE` 的「本会话放行」恢复生效
|
|
11
|
+
|
|
12
|
+
**升级前核查(一句话)**:把 `DEFAULT_SCENARIO`(缺省 `code`)与**中心里每个主体 / 组 / 全局的场景指派**逐名对本部署的在册场景
|
|
13
|
+
(内建 `default` / `code` / `code-review` / `scan` / `discuss` ∪ 配置中心声明的场景;`GET /v1/capabilities` 的 `scenarios` 就是这张表)。
|
|
14
|
+
对不上的名字,7.118.x 上是**静默跑在 `default`**(工具、提示词、skills、MCP 整包跟着换,零日志);本版起:
|
|
15
|
+
|
|
16
|
+
1. **`DEFAULT_SCENARIO` 不在册 ⇒ 起服拒**,拒句点名那个名字与在册词表。改 env(或把那个场景声明回配置中心)再起。
|
|
17
|
+
`DEFAULT_SCENARIO` 点名的是**配置中心声明的**场景时,起服要在启动窗内拿到那份声明(中心不可达 / 超窗、且无 last-known-good 缓存 ⇒ 声明缺席 ⇒ 同样拒启,中心恢复后再起)。
|
|
18
|
+
2. **中心指派的场景名不在册 ⇒ 该主体不带 `scenario` 的提交答 `503 state.scenario_assignment_unknown`**,体 `detail: { scenario, source: "ruling" }`,
|
|
19
|
+
句子点名指派名与在册词表;零 run、零计费、不带 `Retry-After`。出路:在中心把指派改到在册的名字(或把那个场景加回本部署),然后原样重试
|
|
20
|
+
(本副本读到新指派要等它自己的缓存窗,缺省一分钟)。续跑腿上同一条拒折成 `409 resume_blocked_by_policy`(`blockedBy: "state.scenario_assignment_unknown"`)。
|
|
21
|
+
**在中心新增场景 X、同一拍把主体指派到 X**:本部署的场景表重启才生效、指派是热读的 ⇒ worker 重启之前那个主体的每一次 run 都是这条 503 —— 先重启 worker 让 X 进表,再指派。
|
|
22
|
+
3. 不变:请求体显式给的未知名仍是 `400 scenario_unknown`;体里显式给在册名照常服务(不受悬空指派影响);在册的指派 / 缺省照常。
|
|
23
|
+
4. 嵌入方自己装配 `resolveSpec`:缺省场景悬空的装配在**提交时**拿到同一条 503(`detail.source: "deployment"`),起服那一拍没人替你拒。
|
|
24
|
+
5. 库面 `selectScenario(scenarios, name)`:`name` 不在册(含 `""`)现在**抛**,不再回落 `default`;不传名(`undefined`)仍取内建 `default`。
|
|
25
|
+
6. `run-local`(单次本地 CLI):`--scenario <不在册的名>` 与 `DEFAULT_SCENARIO` 悬空都退出码 2、点名在册词表(此前静默落 `default`)。
|
|
26
|
+
|
|
27
|
+
**`SESSION_BACKEND=auto` 收紧(行为破坏性)**:配了坐标(`MYSQL_HOST` / `PG_HOST`)而库起服时连不上,7.118.x 上静默落内存(一行 warn、`/health` 200);本版起在 `STORE_LOCK_WAIT_MS`(缺省 60s)窗内重试、到窗**拒启**,拒句点名后端与 host:port。
|
|
28
|
+
升级前核查:库晚于 server 就绪的编排(k8s 同时起)把窗调大(`STORE_LOCK_WAIT_MS=300000`);要内存就显式写 `DB_BACKEND=memory`。`/health` 的 `sessionBackend` 读到 `memory` ⇒ 这台没落库。能力位 `store.degradedReason` 的词 `db_unreachable` 随之退役(只剩 `local_unwritable`)。
|
|
29
|
+
|
|
30
|
+
**对象存储桶与 MinIO 镜像(sema-up 部署)**:引擎配了对象存储(`MINIO_*` 三键)而 `SESSION_SNAPSHOT_BUCKET`(缺省 `session-snapshots`)桶不存在,7.118.x 上起服绿、之后每分钟一条 404;本版起起服核一次、缺则在 `STORE_LOCK_WAIT_MS` 窗内重试后**拒启**。
|
|
31
|
+
升级前:自己建桶的部署确认这只桶在(`mc mb <别名>/session-snapshots`)。sema-up 只有**新装、或重跑本版的 kube-up / cluster-up / sema-up.sh** 才建它;存量 sema-up 部署只做 `helm upgrade`(或只换引擎镜像)的,升级前先重跑本版脚本,或手工 `mc mb <别名>/session-snapshots`。同版 sema-up 的 MinIO 镜像从 `:latest` 钉成 `minio/minio:RELEASE.2025-09-07T16-13-09Z`(`imagePullPolicy: IfNotPresent`):
|
|
32
|
+
存量部署升级前对一下正在跑的镜像(`kubectl -n sema get pod -l app=sema-minio -o jsonpath='{..imageID}'`),不同就 `--set minio.image=<正在跑的那一版>`。
|
|
33
|
+
|
|
34
|
+
### 审批名单(`APPROVAL_REQUIRE`)的「本会话放行」恢复生效(订正 7.118.0 节第 13 条)
|
|
35
|
+
|
|
36
|
+
1. **零操作**。7.118.0 节第 13 条说「升级前已落的『本会话放行』对 `APPROVAL_REQUIRE` 的卡失效」—— 本版起**恢复生效**:名单的问是本服务自己的问,
|
|
37
|
+
它的「本会话放行」记在会话豁免店里,名单门在出问之前读它;同一会话、同一工具命中 ⇒ 名单门不出卡。豁免店的形没有变:7.117.x 及更早落下、
|
|
38
|
+
7.118.x 期间留在店里的行原样生效,不迁移。不想让哪一行生效:`DELETE /v1/approvals/:sessionId/exemptions/<工具名>` 撤掉,或删会话。
|
|
39
|
+
2. 名单卡上点「本会话放行」(活卡的 `allow_session` / `/decide` 的 `remember: "session"`)⇒ 记下:回执 `rememberApplied: true`,卡面不再带 `rememberRefused`。
|
|
40
|
+
卡上同时还有别的问(强制、治理、读会话目录外 ……)时照旧只放行这一次。
|
|
41
|
+
3. **不变**:名单的问照样 `permissionMode: "bypassPermissions"` 放不掉、auto 模式的分类器答不了 —— 免问只来自这一会话里人说过的「本会话放行」;
|
|
42
|
+
`APPROVAL_NEVER_AUTO`、耐久预算耗尽、请求的 `settings.permissions.ask` / 存下的 ask 行铸的卡照旧记不住(`rememberApplied: false`)。
|
|
43
|
+
4. **已知边界**:「本会话放行」只在读得到同一只豁免店的实例上生效(SQL 后端跨副本共享;`DB_BACKEND=local` 只在本机数据根)。
|
|
44
|
+
续跑落到读不到它的实例上 ⇒ 名单的问再问一次。
|
|
45
|
+
5. **回滚到 7.118.x**:豁免行留在店里、在 7.118.x 上不生效(那一版的读法),零操作。
|
|
46
|
+
|
|
47
|
+
### local 后端(`DB_BACKEND=local`):会话属主写进会话边档,跨重启保留
|
|
48
|
+
|
|
49
|
+
1. **零操作**。`<数据根>/session-incarnations.json` 的每一格从「只有化身(一个串)」扩成「化身 + 属主」(`{ "incarnation": …, "owner": … | null }`);
|
|
50
|
+
本版只在新建 / 登记 / 认领 / 导入时写新形,7.118.x 留下的旧格原样可读(读作「属主没被记下」)。不要手改这只文件:它是严格读的,形不对整只拒。
|
|
51
|
+
2. **升级窗内的认领**:7.118.x 及更早建的会话没有记下属主,读作无主(它的会话规则读不了也改不了:`409 conflict.session_ownerless`)。
|
|
52
|
+
升级之后在窗内**第一次带 principal 提交任务**到这只会话(壳的 `--resume` 就是),它被认领给那个 principal(日志一行 info `session_owner_claimed`),
|
|
53
|
+
之后它的规则照常读写。窗的日期见 `plugins/one-time-migrations.ts` 登记表 M10(由本版的发布准备提交回填);窗关之后不再认领,没被认领的存量会话一直读作无主 ——
|
|
54
|
+
要清它的规则就删会话(`DELETE /v1/sessions/:id`,规则随之删)。
|
|
55
|
+
3. **回滚到 7.118.x**:旧版读不懂新形的格(严格读 ⇒ 会话操作整只拒,拒句点名这只文件)。回滚前先把每一格改回只有化身的串
|
|
56
|
+
(`{ "<id>": { "incarnation": "T", "owner": … } }` → `{ "<id>": "T" }`;属主随之丢失,即 7.118.x 本来的行为)。
|
|
57
|
+
|
|
58
|
+
### 引擎 7.45.0(随本版提货):审批与删除的五处行为变化
|
|
59
|
+
|
|
60
|
+
1. **删除受保护位置一律要人答**:`rm` / `rmdir` 的目标是文件系统根、顶层目录、家目录、任务根 / 工作目录 / 附加工作目录或它们的祖先(含 `rm -rf *` 在任务根、`rm -rf ~/*`),
|
|
61
|
+
或 shell 拿到的是运行时才拼出的脚本(`bash -c "$CMD"`、`sh -c "$(…)"`、`eval "$(…)"`)—— 任何模式(含 `bypassPermissions`)、任何 allow(请求 `settings.permissions.allow` 的
|
|
62
|
+
`Bash` / `Bash(rm:*)`、`APPROVAL_GRANT`、存下的 allow 行、技能清单、PreToolUse hook 的 allow)、`auto` 的分类器都清不掉,出一张要人的卡(`askClass: "dangerous_delete"`);
|
|
63
|
+
没人答就拒(无人值守腿按部署政策拒,`run-local` 无终端时只指终端)。**升级前核查**:靠 `rm -rf *` / `rm -rf <任务根>` 清场、或在 bypass 下跑运行时拼出的 `bash -c` 的脚本化部署,
|
|
64
|
+
改成按名删(`rm -rf ./build ./dist`)或安排人答卡。没有旋钮放行这一类。已知边界:喂给 shell 标准输入的脚本(`bash <<'EOF' … EOF`、`echo … | bash`)这一版仍按旧判据
|
|
65
|
+
(`default` 出卡、bypass 放行)。
|
|
66
|
+
2. **`roots` 读面 × `auto`:根外的 shell 读出卡,不再交分类器**。单用户 turnkey 部署缺省 `READ_FACE=open`,不受影响;多租户部署(设了 `REQUIRE_PRINCIPAL`、没设 `READ_FACE`
|
|
67
|
+
⇒ 引擎缺省 `roots`)与显式 `READ_FACE=roots` 的部署上,`auto` 档的 `cat <根外文件>`、放不进根的拼法(heredoc 提交说明、变量路径)从分类器自动放行变成一张卡(与 `default`
|
|
68
|
+
同一张;没人在就拒)。要分类器继续答 ⇒ 显式 `READ_FACE=open`。生效来源见 `GET /v1/diagnostics/wiring` 的 `readFace.source`。
|
|
69
|
+
3. **子代 `agents[].denyTools` 拼错的名字不再让 run 失败**:没有工具叫这个名字(`Bahs`)⇒ 照跑,运维日志一行 `runner_config_advisory`(计数 `runner_config_advisory_total`)
|
|
70
|
+
点名它 —— 那一条 deny 没生效。`Tool(content)` 形(`Edit(src/**)`)⇒ 整只工具对该子代藏起并同样告一行。仍开跑前拒(`config.agent.unknown_tool`)的:带空白 / 不可见字符的条目、
|
|
71
|
+
非字符串条目。**退役拼法(`bash`)与大小写孪生(`BASH`)在本服务的部署形上也只告不拒**(7.118.x 上开跑前拒):本服务的委派工具带逐次注入的 extraTools,引擎开跑前列不全子代工具池,
|
|
72
|
+
不把像 `Bash` 的名字判成拼错;引擎给出宿主声明名册的座后这两形回到开跑前拒。**升级前核查**:把「拼错就失败」当校验的调用方改看那一行告警;靠子代 `denyTools` 藏 shell / 写工具的请求改用会话策略
|
|
73
|
+
`toolDeny` 或部署 deny 规则 —— `denyTools` / `allowTools` 只约束委派池里的工具,带手的子代自带的 Read / Write / Edit / Bash 不受它约束(写 `"Bash"` 也藏不住;引擎后续版本一张表盖全)。
|
|
74
|
+
4. **沙箱 / 设备车道上的 `//` 绝对形路径规则对相对路径生效**:这些车道上请求规则编译时没有任务根,此前一条 `Read(//w/proj/secrets/**)` 的 deny / ask 对 `secrets/k`、
|
|
75
|
+
`./secrets/k` 这类相对拼法不生效;现在按引擎在每条调用上带的根 / 工作目录解析(拒就拒、问就问)。调用上也没有根(无文件手的腿)⇒ 一张 `rule_unreadable` 卡
|
|
76
|
+
(`bypassPermissions` 带理由放行)。相对形的规则模式(`Read(src/**)`)在这些车道上仍 422。
|
|
77
|
+
5. **工具名里带不可见字符(零宽、软连字符、双向控制符 …)的条目读成文法错**:`APPROVAL_GRANT` 里有这种条目 ⇒ 拒启;技能 `disallowedTools` 里有 ⇒ 提交 400、
|
|
78
|
+
存量停驻体续跑 409(`resume_blocked_by_policy`)—— 升级前照 7.117.0 节跑排空判据 `skill-deny-inert`(它同数这一形);请求 `settings.permissions.allow` 里有 ⇒ 开跑时一帧
|
|
79
|
+
`permission.allowed_tool_not_granted`(`reason: "grammar"`)。拒句里那个字符看不见,指引句点名「invisible or control character」。
|
|
80
|
+
|
|
81
|
+
### 接口面
|
|
82
|
+
|
|
83
|
+
- **`400 rules.legacy_tool_name` 的体顶层多一键 `guidance`**(`PUT /v1/sessions/:id/policy`、同步导入的 `policy` 面):引擎判官对这一条的改法指引原句(退役名 / 对端段铸不出的
|
|
84
|
+
协议名带;无协议前缀的 `__` 名不带)。句子字节不变。对拒体做精确键集比对的客户端放宽一键。
|
|
85
|
+
- **运维 warn `memory_store_form_refused` 的 `form`** 只取引擎闭集里的词(`symlink` / `file` / `other`);闭集外的值不再写进这一行。409 码与句子不变。
|
|
86
|
+
- **嵌入方直接调引擎的 `steerPriorityOf` / `injectionLaneOf`**:档位不是 `now` / `next` / `later`(`null`、别的词、数字)现在抛 `steering.invalid_content`(此前原样返回,`null` 读作 `next`);
|
|
87
|
+
只有缺席读作 `next`。本服务的受理口与停驻 steer 列读回都不会把档外值交给它。
|
|
88
|
+
- **嵌入方自装 `Runner` 只给静态 `executionEnv`(没有 `executionEnvFactory`)**:委派的 `isolation: "worktree"` 现在在建任何东西之前拒(`isolation.unavailable`),
|
|
89
|
+
不再在调用方自己的根里跑子代。本服务的主车道带工厂,不受影响。
|
|
90
|
+
- **`council_done` 事件与 code-review 工具结果 `details.council`**:`lenses` / `perLens` 只含成功的镜头,失败的镜头移到新键 `failedLenses: [{ lens, reason }]`(键恒在,无失败 ⇒ `[]`);
|
|
91
|
+
对 `perLens` 键集或 `lenses` 计数做精确比对的消费方更新(此前失败镜头在 `perLens` 里是一条 `tokens: 0` 的行)。
|
|
92
|
+
- **请求面 `permissionMode` 多一词 `dontAsk`**(闭集 = 引擎 `PERMISSION_MODES` 全集,受理后原样投引擎);能力位 `permissionModes` 给全集,壳按位发词;词表外拒句的成员表多 `dontAsk`。
|
|
93
|
+
- **退役键 `settledBy` 的 `400 request.body_shape`**(live `/respond`、耐久 ask 账、`/decide` 三条决定口)体顶层多一键 `field: "settledBy"`,三口同一句;壳按码 + `field` 判,不锚文案。
|
|
94
|
+
|
|
10
95
|
## 7.118.0 —— 审批决定体的出处位 `decidedBy`(取代 `settledBy`)· 能力位 `approvalDecidedBy` · 有审批名单却无检查点店的装配,任务提交一律 501
|
|
11
96
|
|
|
12
97
|
**审批决定体用一只键说「谁做的这个决定」**:`decidedBy?: "person" | "policy"`(缺席 = `"person"`)。三条工具审批决定口 ——
|
package/README.md
CHANGED
|
@@ -189,7 +189,7 @@ The server is configured entirely through environment variables. The most import
|
|
|
189
189
|
| `SSH_HOST_FINGERPRINT` / `SSH_KNOWN_HOSTS` | unset | **SSH lane host-key verification** (`REMOTE_EXEC=ssh` only). `SSH_HOST_FINGERPRINT` = the host key's SHA256 base64 fingerprint (`SHA256:` prefix and `=` padding both optional); `SSH_KNOWN_HOSTS` = a path to a `known_hosts` file, matched literally on `host` / `[host]:port`. **Either one present ⇒ strict verification, a mismatch REFUSES the connection** (the error carries both fingerprints plus an `ssh-keyscan` line); both set ⇒ both must pass. **Both unset ⇒ a loud per-connection warning (`ssh_host_key_unverified`, man-in-the-middle risk) and the connection proceeds** — the deliberate posture for the batch-deploy lane, registered as a P-DEBT fail-open (`docs/FAIL-OPEN-CENSUS.md`). A malformed fingerprint or an unreadable `known_hosts` path **refuses to start**. Not supported in `known_hosts`, on purpose: hashed (`\|1\|…`) lines, wildcard patterns, `@cert-authority` — all skipped, so a host whose only entries are those is REFUSED (`@revoked` is honored and wins over any plain line, whatever the order) |
|
|
190
190
|
| `HOOKS_TIMEOUT_MS` | unset (engine default 600000) | Per-invocation time bound for every hook seat (core `Hooks.timeoutMs`). Unset = the engine's own default (this server does not copy upstream defaults). `0` is honored as written (every seat expires immediately). A non-integer, negative, or above-`setTimeout`-ceiling (2147483647) value **refuses to start** |
|
|
191
191
|
| `CONFIG_PROVIDER` | unset | Config source: `local` (file-backed `config.d/`, single machine) / `remote` (registry control plane). Closed set, matched exactly (7.104.0+): any other value (wrong case, surrounding spaces) refuses to boot; empty = unset |
|
|
192
|
-
| `DEFAULT_SCENARIO` | `code` | Default scenario when the request body names none |
|
|
192
|
+
| `DEFAULT_SCENARIO` | `code` | Default scenario when the request body names none; must be a scenario this deployment has (boot refuses otherwise) |
|
|
193
193
|
| `SANDBOX_PKG_SOURCE` | `global` | Package sources inside sandboxes: `global` (official upstreams) / `cn` (China mirrors) / `custom` / `none` |
|
|
194
194
|
| `SENSITIVE_WRITE_PATTERNS` | core's recommended set | Sensitive-path write deny list; comma-separated value replaces the set, `off` **or an empty value** disables. Applied unconditionally at the governance layer (independent of client permission mode, lane or settings presence) — including on the `run-local` leg. A value that cannot compile into a guard set (e.g. `/`, a pattern with no path segment) refuses to start |
|
|
195
195
|
| `WRITE_PROTECTED_EXTRA` | unset (engine default table) | **Adds** rows to core's write-protection table (the literal-name table whose hit demotes a surviving `allow` to `ask` on Write/Edit/NotebookEdit). Comma-separated bare names (a bare name matches ANY path segment; a name containing `/` matches a consecutive segment run) or a JSON array of `"name"` strings / `{name, kind}` rows (`kind`: `basename` \| `segment` \| `segment-run`). The value is composed as `[...WRITE_PROTECTED_DEFAULT_TABLE, …]`, so **no default row can be lost**. Unset = no seat is wired = the engine's default table is in force (this server never copies that table). Form discrimination is by content, not by first character: a value containing any JSON structural character (`[ ] { } "`) is parsed as JSON and **must** be a top-level array (a missing pair of brackets refuses to start instead of being split into junk bare names). An empty value, a malformed entry (glob metacharacter, unknown kind, kind/name mismatch) or setting this together with `WRITE_PROTECTED_TABLE_REPLACE` **refuses to start**. Read faces: `GET /v1/capabilities` → `writeProtection.{armed,rows,replaced}`; `GET /v1/diagnostics/wiring` → `writeProtection.{rows,source,droppedDefaultRows}` (operator-only) |
|
package/README.zh-CN.md
CHANGED
|
@@ -162,7 +162,7 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
|
|
|
162
162
|
| `SSH_HOST_FINGERPRINT` / `SSH_KNOWN_HOSTS` | 未设 | **SSH 通道主机密钥校验**(仅 `REMOTE_EXEC=ssh`)。前者 = 主机公钥的 SHA256 base64 指纹(`SHA256:` 前缀与 `=` 填充都可选);后者 = `known_hosts` 文件路径,按 `host` / `[host]:port` **逐字**匹配行。**任一在场 ⇒ 严格校验,不符即拒连**(拒因带两半指纹 + `ssh-keyscan` 取指纹指路一行);两只同时在场 ⇒ 合取(都要过)。**两只全缺席 ⇒ 每连接一条响亮 warn(`ssh_host_key_unverified`,中间人风险)后照常连** —— 批量部署通道的既定姿态,已登记为 P-DEBT fail-open(`docs/FAIL-OPEN-CENSUS.md`)。指纹拼错 / `known_hosts` 路径读不到 ⇒ **启动即拒**。`known_hosts` 里**刻意不支持**:hashed(`\|1\|…`)行、通配符 pattern、`@cert-authority` —— 一律跳过,于是只有这些条目的主机会被**拒连**(`@revoked` 照常生效,且无论写在哪一行都优先于普通匹配行) |
|
|
163
163
|
| `HOOKS_TIMEOUT_MS` | 未设(引擎缺省 600000) | 每一只 hook 席位**单次调用**的时间上限(core `Hooks.timeoutMs`)。未设 = 用引擎自己的缺省(本服务不复制上游默认值)。`0` **按字面生效**(每个席位立即到期)。非整数 / 负数 / 超 `setTimeout` 上限(2147483647)⇒ **启动即拒** |
|
|
164
164
|
| `CONFIG_PROVIDER` | 未设 | 配置来源:`local`(单机文件 `config.d/`)/ `remote`(registry 控制面)。闭集、逐字比较(7.104.0 起):其余任何值(大小写不同、带首尾空白)拒启;空串 = 未设 |
|
|
165
|
-
| `DEFAULT_SCENARIO` | `code` |
|
|
165
|
+
| `DEFAULT_SCENARIO` | `code` | 请求体未指定场景时的缺省场景;必须是本部署在册的场景名,否则拒启 |
|
|
166
166
|
| `SANDBOX_PKG_SOURCE` | `global` | 沙箱内装包源:`global`(官方源)/ `cn`(国内镜像)/ `custom` / `none` |
|
|
167
167
|
| `SENSITIVE_WRITE_PATTERNS` | core 推荐集 | 敏感路径写拒集;逗号分隔值为整体替换,`off` **或留空**关闭。在治理层无条件施加(与客户端权限模式/lane/settings 在场性无关),`run-local` 腿同样生效;编译不出守卫集的值(如 `/`)启动即拒 |
|
|
168
168
|
| `WRITE_PROTECTED_EXTRA` | 未设(引擎缺省表) | 给 core 的**写保护名表**加行(字面名表:命中即把幸存的 `allow` 降级成 `ask`,作用于 Write/Edit/NotebookEdit)。逗号分隔裸名(裸名匹配**任意路径段**,含 `/` 的名匹配连续段)或 JSON 数组(`"name"` 串 / `{name, kind}` 行,`kind`:`basename` \| `segment` \| `segment-run`)。值按 `[...core 缺省表, …]` 组合,**丢不掉任何缺省行**。未设 = 不铸座 = 引擎缺省表在岗(本仓从不复制那张表)。两形按**内容**判而不是猜首字符:值里出现 JSON 结构字符(`[ ] { } "`)即按 JSON 解析,且顶层**必须是数组**(少写一对方括号 ⇒ 拒启,而不是被拆成垃圾裸名静默收下)。空值 / 坏值(通配符、未知 kind、kind 与名字段数矛盾)/ 与 `WRITE_PROTECTED_TABLE_REPLACE` 同时设置 ⇒ **启动即拒**。读面:`GET /v1/capabilities` 的 `writeProtection.{armed,rows,replaced}`;`GET /v1/diagnostics/wiring` 的 `writeProtection.{rows,source,droppedDefaultRows}`(operator-only) |
|
package/USAGE.md
CHANGED
|
@@ -51,9 +51,10 @@ docker compose up --build --scale service=4 # 4 个无状态 worker,nginx
|
|
|
51
51
|
> 而 `DB_BACKEND` 缺省 ⇒ 探的是 mysql)。修前这三形**静默跑内存**(什么都不设是 local 文件持久,加一族坐标反而落到内存)。坐标被用到的存量形
|
|
52
52
|
> (`SESSION_BACKEND=mysql|auto` + MYSQL 坐标、不写 `DB_BACKEND`)照旧起服;「不用坐标」是显式表态的(`DB_BACKEND=memory|local`、
|
|
53
53
|
> `SESSION_BACKEND=memory`)也不拒。拒句点名在场的坐标键、为什么没人用、三条出路:要持久 `DB_BACKEND=pg|mysql`(或 `SESSION_BACKEND=mysql`);
|
|
54
|
-
> 要内存 去掉坐标或 `DB_BACKEND=memory
|
|
55
|
-
> 显式带 `DB_BACKEND=mysql`。同版:`DB_BACKEND=mysql|pg`
|
|
56
|
-
> `SESSION_BACKEND=auto`
|
|
54
|
+
> 要内存 去掉坐标或 `DB_BACKEND=memory`。本仓两份 compose
|
|
55
|
+
> 显式带 `DB_BACKEND=mysql`。同版:`DB_BACKEND=mysql|pg` 显式而店打不开,拒句点名 `DB_BACKEND=<引擎>`(修前只有驱动的一句 `connect ECONNREFUSED …`)。
|
|
56
|
+
> 🔴 **7.119.0 起 `SESSION_BACKEND=auto` 配了坐标而库连不上同样拒启,不再有「auto 内存起服」这条出路**:`auto` = 「配了该引擎的坐标就是那只库(boot 期窗内重试、到窗拒启),没配就是内存」,
|
|
57
|
+
> 由配置决定、不由起库成败决定;内存部署写 `DB_BACKEND=memory` 或 `SESSION_BACKEND=memory`。`GET /health` 的 `sessionBackend` 如实说装好的后端(`durable(pg)` / `memory`)。持久化姿态读面:caps `store`(附录 B)。
|
|
57
58
|
|
|
58
59
|
> 🔴 7.96.0 起根 `docker-compose.yml` 的 `REMOTE_EXEC`(执行车道)是**必填**:不设 ⇒ `docker compose config` 当场失败(此前静默起成一个没有手的 worker)。单人自用 `REMOTE_EXEC=host`(手落在 worker 容器里);多人 / 生产选隔离车道 `e2b`(配 `E2B_API_KEY`)或 `k8s`(配 `K8S_SANDBOX_IMAGE`)。
|
|
59
60
|
|
|
@@ -537,6 +538,8 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
|
|
|
537
538
|
🔴 **两条取数据根的路是同一条腿、同一只旋钮**:HTTP 服务的 local 臂与 `run-local`(一次性 CLI)共用
|
|
538
539
|
同一只等待腿 —— **不为前台形留第二套语义**。前台形要「立刻告诉我谁占着」就设 `STORE_LOCK_WAIT_MS=0`,
|
|
539
540
|
那是文档化的通道,而不是两条路各有一套默认。
|
|
541
|
+
🔴 **同一只窗 7.119.0 起也管「已配置的 SQL 库此刻连不上」**:`DB_BACKEND=mysql|pg`(含 `SESSION_BACKEND=auto`)boot 期开不了店 ⇒ 窗内每 250ms 重试、到窗拒启(点名后端与 host:port;
|
|
542
|
+
永久错如凭据 / DDL 权限也拖到窗尾才拒,要当场拒设 `0`)。k8s 清单里库与 server 同时起,所以清单把窗给到 `300000`。
|
|
540
543
|
⚠️ **裸 boot(没有显式 `DB_BACKEND`)那一形的时序也跟着变**:它的锁冲突走的不是拒启而是既有的
|
|
541
544
|
「退回内存并 warn」降级臂(`store_db_unreachable_fallback_memory`),所以第二只实例此前**当场**降级、
|
|
542
545
|
现在要**等满窗**才降级 —— 方向仍是好的(窗内拿到就用上真数据根,而不是静默丢掉持久面),但它是一次
|
|
@@ -859,12 +862,18 @@ UNATTENDED_APPROVAL_POLICY=park
|
|
|
859
862
|
|
|
860
863
|
**「本会话不再询问」对引擎的问也生效(7.106.0 起)**
|
|
861
864
|
|
|
862
|
-
- 审批面 `POST /v1/approvals/:sessionId/decide {decision:"approve", remember:"session"}` 与活卡的 `allow_session`
|
|
863
|
-
|
|
864
|
-
|
|
865
|
+
- 审批面 `POST /v1/approvals/:sessionId/decide {decision:"approve", remember:"session"}` 与活卡的 `allow_session` 记下的放行,两处兑现 —— **谁问的,谁读它的答案**:
|
|
866
|
+
本服务自己的问在它们自己的层上、出问之前读,命中就不问:`APPROVAL_REQUIRE` 名单的问(7.119.0 起;卡面带「本会话放行」,回执 `rememberApplied:true`,
|
|
867
|
+
同一会话下一次调用名单门不出问,引擎看到的只是「这次调用没出问」)、`run_workflow` 问句、acceptEdits 的域外写;**引擎自己的问**(`default`
|
|
868
|
+
模式下的每一次写、证不了只读的 shell 命令、没人表态的服务器工具)由服务的**审批席**(工具审批协调器)在下一次到达时代答。后一半要审批席在场:
|
|
865
869
|
`TOOL_APPROVAL_ENABLED` 开(多租户缺省关);不想要活卡(纯 park 部署)再配 `STREAM_ASK_WINDOW_MS=0`。
|
|
866
|
-
|
|
870
|
+
名单的问在引擎眼里仍是 `ask_rule`:`permissionMode: "bypassPermissions"` 放不掉、auto 模式的分类器答不了 —— 免问只来自这一会话里人亲口说过的「本会话放行」。
|
|
871
|
+
- 代答不到的:`APPROVAL_DENY` / 敏感路径守卫(是拒,不是问);`APPROVAL_NEVER_AUTO` 与耐久预算耗尽的问(「恒问人」,回执 `rememberApplied:false`);
|
|
872
|
+
人写的 ask 规则(请求的 `settings.permissions.ask`、存下的 ask 行 —— 引擎的问,类表说站着的答案清不掉,回执 `rememberApplied:false`);
|
|
867
873
|
委派子代经父链继承到祖先席上的引擎问(引擎盖强制位,席依约不代答)。
|
|
874
|
+
- 已知边界:「本会话放行」记在本部署的会话豁免店(SQL 后端跨副本共享;`DB_BACKEND=local` 只在这台机器的数据根里)。续跑落到另一台**读不到这只店**的
|
|
875
|
+
实例上(local 车道换机 / 换数据根)⇒ 名单的问再问一次 —— 方向是多问一次,不是静默放行。名单靠别名命中的调用(嵌入方给工具挂了 `Bash` 别名)
|
|
876
|
+
按调用的 wire 名记、按 wire 名读;一张卡上同时有名单的问与别人的问(人写的 ask 规则、强制位、读会话目录外 ……)⇒ 那张卡的「本会话放行」不记。
|
|
868
877
|
|
|
869
878
|
**无人值守腿:`default` 模式 + 按类作答的审批席(7.109.0 起;S-934 / S-906)—— 没有旋钮**
|
|
870
879
|
|
|
@@ -1392,7 +1401,7 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
|
|
|
1392
1401
|
| `sessionId` | — | 续聊:带上次返回的 `sessionId`,服务端自动 wake 历史 |
|
|
1393
1402
|
| `images` | — | 图文输入 `[{data,mimeType}|{url}]`(模型需支持 vision) |
|
|
1394
1403
|
| `attachmentIds` | — | D-1 通用文件上传(1.289+):先 `POST /v1/attachments?name=…`(raw body,content-type=mime)拿句柄,提交时引用 ≤16 个;文件物化到执行环境工作目录 `attachments/` 下,objective 尾部自动追加文件清单(内容不进会话流)。单文件缺省 ≤32 MiB(`ATTACHMENT_MAX_BYTES`);可配 mime 白名单(`ATTACHMENT_MIME_ALLOWLIST` CSV,缺省不限);上传后未引用的按 `ATTACHMENT_UNBOUND_TTL_MS`(缺省 24h)回收。**云形态(tidb/pg)字节本体存对象存储——MinIO 必配**(`MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY`,与快照 lane 同一组变量),未配则附件面 501;local 形走本地文件店。 |
|
|
1395
|
-
| `scenario` | — | 省略时取本部署的 `DEFAULT_SCENARIO`(工厂缺省场景名 = **`code`**——CC 编码 persona 蒸馏版,default 全量工具面,终验旋钮独立;`default` 场景**已非出厂缺省**,要用它须显式传 `scenario:"default"`,或部署方显式设 `DEFAULT_SCENARIO=default`)。可选内建名:`default`(通用/助理场景,域中性 persona,CC agent loop 全量工具面)/ `code-review`(见 §5)/ `scan`(同 §5 的 repo 只读工具但**中性无框架提示词**——objective+中心下发 skill 全权主导输出,OA 扫描类用;与 `code-review`/`discuss` 同属 clone-free 无执行环境场景,见 §5 末)/ **配置控制面可声明任意新场景**(`{name, toolset: none\|repo-readonly, prompt?}`,组合即配置、能力写死在部署;restart-to-apply;center 可覆盖内建名,boot 日志 `config_center_scenarios.shadowsBuiltin` 可审计) |
|
|
1404
|
+
| `scenario` | — | 省略时取本部署的 `DEFAULT_SCENARIO`(工厂缺省场景名 = **`code`**——CC 编码 persona 蒸馏版,default 全量工具面,终验旋钮独立;`default` 场景**已非出厂缺省**,要用它须显式传 `scenario:"default"`,或部署方显式设 `DEFAULT_SCENARIO=default`)。可选内建名:`default`(通用/助理场景,域中性 persona,CC agent loop 全量工具面)/ `code-review`(见 §5)/ `scan`(同 §5 的 repo 只读工具但**中性无框架提示词**——objective+中心下发 skill 全权主导输出,OA 扫描类用;与 `code-review`/`discuss` 同属 clone-free 无执行环境场景,见 §5 末)/ **配置控制面可声明任意新场景**(`{name, toolset: none\|repo-readonly, prompt?}`,组合即配置、能力写死在部署;restart-to-apply;center 可覆盖内建名,boot 日志 `config_center_scenarios.shadowsBuiltin` 可审计)。**缺省与指派都必须是本部署在册的场景名,悬空不回落 `default`**:`DEFAULT_SCENARIO` 不在册 ⇒ **起服拒**(拒句点名那个名字与在册词表);中心按主体指派的场景名本部署没有 ⇒ 该主体的 run 答 **503 `state.scenario_assignment_unknown`**(体 `detail: { scenario, source: "ruling" }`,不带 `Retry-After`;修法 = 中心改指派,或把那个场景加回本部署,然后原样重试);请求体显式给的未知名仍是 400 `scenario_unknown` |
|
|
1396
1405
|
| `repo` / `council` / `debate` | — | `repo` 为 `code-review`/`scan` 必填;`council`/`debate` 仅 `code-review`,见 §5 |
|
|
1397
1406
|
| `systemPrompt` / `appendSystemPrompt` | — | 调用方自带的系统提示 / 追加段(各 ≤16384 字符,超限 400 `request.field_invalid`;`appendSystemPrompt` 必须非空串)—— 任务自带时优先于角色 / 全局缺省(见 §模型角色) |
|
|
1398
1407
|
| `model` | — | 本次用哪个模型:只能点服务端**已配置目录**里的名字 / 档位词 / 模型 id(目录见 `GET /v1/models`;请求体带不进新模型)。未知 ⇒ 400 `request.unknown_reference`(带 `available` 列表);目录里有、但本部署服务不了 ⇒ 400 `request.model_unavailable`;空串 / 非字符串 ⇒ 400 `request.field_invalid` |
|
|
@@ -1516,7 +1525,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
1516
1525
|
## 7. 其它接口(按需)
|
|
1517
1526
|
| 端点 | 用途 |
|
|
1518
1527
|
|---|---|
|
|
1519
|
-
| `GET /v1/sessions/<id>` | 审计回溯:当前上下文 + 摘要(owner 校验) |
|
|
1528
|
+
| `GET /v1/sessions/<id>` | 审计回溯:当前上下文 + 摘要(owner 校验);消息里模型 / 工具产出的文本与 live 帧、账本帧同一只脱敏(密钥形换占位,其余字节不变) |
|
|
1520
1529
|
| `GET /v1/approvals?owner=user:42` | 高危写审批(durable,需 `DURABLE_APPROVAL=true`):**operator** 看待办队列(可按 owner 过滤);非 operator 只看自己的 |
|
|
1521
1530
|
| `POST /v1/approvals/<sessionId>/decide` `{"decision":"approve"|"deny","reason":"…"}` | 批/否并恢复挂起任务(CAS,重复决议 409);**仅 operator**(非 operator → 403)。5.0.0 起旧轮询腿 `POST /v1/approvals/<id>` 已退役 |
|
|
1522
1531
|
| `GET /v1/agents/roster` | 后台 agent **名册**(7.29 起,需 durable background-agent store,否则 501 `capability.background_agent_store_required`):调用方自己 scope 的 `a…` 行,新→旧。行只带 `handle`/`name`/`agentType`/`status`/`spawnedAt`/`updatedAt`/`settledAt`(content-free;子代产物正文走 `GET /v1/runs/<id>/subagents/<handle>/output`)。查询:`?limit=`(1..100,默认 20)、`?before=`(上一页的 `nextBefore` 游标)、`?status=`(running\|parked\|completed\|failed\|killed)、`?session=`(按会话树窄化)、`?scope=`(**仅** service-token/单用户形;带 principal 的调用方锁定自己)。回体 `{agents,total,nextBefore?,truncated?}`;`truncated:true` = 租户行数超过名册窗(500),`total` 此时是窗内数。非属主的行**不出现也不计数**(无存在性 oracle) |
|
|
@@ -1866,7 +1875,9 @@ tag 词表与每条的最坏后果见 `docs/FAIL-OPEN-CENSUS.md`。引擎装配
|
|
|
1866
1875
|
(core 给宿主定级的判别词;词表外原样透传并标 `classificationKnown:false`),指标 `runner_config_advisory_total`
|
|
1867
1876
|
按 `code` / `classification` / `level` 打标;其中 `shell-gate-off` 在本 session 显式声明 `permissionMode:
|
|
1868
1877
|
"bypassPermissions"` 时是调用方要的姿态,降为 **info** 并标 `expected`——只有没表态落到引擎默认 off 的无壳直连
|
|
1869
|
-
形才 warn
|
|
1878
|
+
形才 warn。子代 `agents[].denyTools` / `allowTools` 的**名字**也归引擎开跑前一只判、同落这一行:无人挂的名只告不拒;**本服务部署形上退役拼法 / 大小写孪生
|
|
1879
|
+
(`bash` / `BASH`)也只告不拒**(委派工具带逐次注入的 extraTools,引擎列不全子代工具池),名字按工具名原拼写 `Bash`;且这两张表只约束**委派池**里的工具 ——
|
|
1880
|
+
内建 hands 带的工具(如 `Write`)不受 `denyTools` 约束(引擎后续版本一张表盖全),今天要对子代藏内建工具用会话策略 `toolDeny` / 部署 deny 规则。
|
|
1870
1881
|
|
|
1871
1882
|
🔴 **`shell-gate-off` 这条注的语义自 7.73.0(core 7.15.0)起收窄,文案由引擎改写**:`off` 档**不再**意味着
|
|
1872
1883
|
「这条会话的 shell 完全没有门」。**读边界**(内建读拒表 + 工作区容纳面)现在**每一档都判**,`shellGate` 只管
|
|
@@ -1986,7 +1997,7 @@ GET /v1/capabilities?permissionMode=default
|
|
|
1986
1997
|
```
|
|
1987
1998
|
|
|
1988
1999
|
- `armed`:按上式此刻会不会武装(`?permissionMode=` 折进本次意图;查询串缺席 = 按 auto 意图答「若请求会不会武装」;
|
|
1989
|
-
|
|
2000
|
+
core 闭集之外 400 `request.field_invalid`)。
|
|
1990
2001
|
- `reason`(未武装时,闭集**六词**,判序即武装式合取序):`mode_not_auto`(意图不是 auto)/ `deployment_incapable`
|
|
1991
2002
|
(装配面没挂分类器席位——生产装配恒挂,桩与一次性 CLI 不挂)/ `org_denied`(`caps.autoMode === false`:center 显式 deny、
|
|
1992
2003
|
center 硬失败 fail-closed 拒项)/ `local_denied`(本地旋钮 `PERMISSIONS_DISABLE_AUTO_MODE=true`,改本机 env)/ `settings_denied`
|
|
@@ -2258,10 +2269,10 @@ server 进程(HTTP 服务与 `run-local`)**所有走 undici 的出网跳**都认
|
|
|
2258
2269
|
`tag`(本地 tag 在、tag 树的版本对)→ `precheck`(registry 上本版在不在;读不出 ⇒ 红,不盲发)→ `token` → `clone`(干净 tag 树 =
|
|
2259
2270
|
`git clone --depth 1 --branch v<v> file://<仓>`)→ `npm-ci` → `prepublish`(`npm run prepublishOnly`)→ `pack` → `publish`(**在 tag 树目录里**
|
|
2260
2271
|
`npm publish --ignore-scripts`)→ `readback-wait`(轮询 registry 至多 20 min)→ `readback-shasum`(dist.shasum = 本地 tarball 的 sha1)→
|
|
2261
|
-
`readback-bytes`(curl 回读 tarball,与本地 `cmp`
|
|
2272
|
+
`readback-bytes`(curl 回读 tarball,与本地 `cmp` 逐字节同;**传输失败**在 `readback-wait` 用的同一只窗里重试、窗满才红,**字节差**不重试、立即红,两种红句分开说)→ `githead`(registry 的 gitHead = tag commit)。脚本钉 `umask 022`(文件权限进 tar 头,
|
|
2262
2273
|
换 umask 重跑会 pack 出另一份字节);每次 registry 请求有硬上限(60 s),回读等待另有 20 min 墙钟截止,registry 挂住不会挂死链。成功尾行
|
|
2263
2274
|
`PUBLISH v=<v> shasum=<sha1> gitHead=<sha> bytes=<n> readback=identical`(发车帖 / CHANGELOG 回填 shasum 与 gitHead 从这里取);任一步红 ⇒
|
|
2264
|
-
`PUBLISH-FAIL v=<v> step=<步骤>` + 非零退出 +
|
|
2275
|
+
`PUBLISH-FAIL v=<v> step=<步骤>` + 非零退出 + 一句点名步骤的红因(红因与步骤行同走 stdout,链日志里步骤行之后紧跟)。
|
|
2265
2276
|
- **幂等**:registry 上已有本版 ⇒ 不重发,只做回读比对 —— 与本地 pack 逐字节同且 gitHead 对 ⇒ 绿并说「已在」;不同 ⇒ 红(别人发的字节,版本不可覆盖)。
|
|
2266
2277
|
门 3.5 红了(tag 已推):修好后单独重跑 `bash scripts/publish-local.sh <v>`,再前台跑 `bash scripts/post-publish-gates.sh <v>`。
|
|
2267
2278
|
**手跑的前置**:两只脚本开头都封口,封口要 import `@sema-agent/core`(封口的判据 = core `scrubSecretEnv`)⇒ 在本树装着依赖的前提下跑
|
package/deploy/sema-up/README.md
CHANGED
|
@@ -133,6 +133,8 @@ data-center:2c/2G/20G)以下 BLOCK——确实无法稳定部署,提前警告不
|
|
|
133
133
|
- **registry**:`SEMA_REGISTRY_IMAGE`(GHCR/Hub 的 `sema-registry` 镜像,迁移中,公开仓匿名可拉;
|
|
134
134
|
SWR 已弃用;仅换 private 仓时才需 k8s 路径凭据=env `IMAGE_PULL_USER`/`IMAGE_PULL_TOKEN`)。
|
|
135
135
|
- 公共镜像(postgres/minio/gitea)直接拉;国内网络不通时配 dockerd `registry-mirrors`。
|
|
136
|
+
- **MinIO**:钉死的 tag(`minio/minio:RELEASE.…`,chart / cluster 静态清单 / compose 三份同值;k8s `imagePullPolicy: IfNotPresent`,不再 `:latest` + `Always`)。换源 = 换整条引用:
|
|
137
|
+
compose 与 kube-up 读环境变量 `MINIO_IMAGE`(kube-up 给了才递给 chart),chart 直接 `--set minio.image=<源>/minio/minio:<tag>`(预灌镜像的节点 `--set minio.pullPolicy=Never`)。
|
|
136
138
|
|
|
137
139
|
## 凭据纪律
|
|
138
140
|
|
|
@@ -37,7 +37,8 @@ spec:
|
|
|
37
37
|
spec:
|
|
38
38
|
containers:
|
|
39
39
|
- name: minio
|
|
40
|
-
image: {{ .Values.minio.image }}
|
|
40
|
+
image: {{ .Values.minio.image | quote }}
|
|
41
|
+
imagePullPolicy: {{ .Values.minio.pullPolicy }}
|
|
41
42
|
args: ["server", "/data", "--console-address", ":9001"]
|
|
42
43
|
env:
|
|
43
44
|
- name: MINIO_ROOT_USER
|
|
@@ -32,6 +32,7 @@ spec:
|
|
|
32
32
|
- { name: PORT, value: "8090" }
|
|
33
33
|
- { name: DB_BACKEND, value: pg }
|
|
34
34
|
- { name: SESSION_BACKEND, value: auto }
|
|
35
|
+
- { name: STORE_LOCK_WAIT_MS, value: {{ .Values.server.storeWaitMs | quote }} }
|
|
35
36
|
- { name: PG_HOST, value: sema-pg-rw.{{ .Release.Namespace }}.svc }
|
|
36
37
|
- { name: PG_PORT, value: "5432" }
|
|
37
38
|
- { name: PG_USER, value: sema }
|
|
@@ -43,6 +43,7 @@ spec:
|
|
|
43
43
|
- { name: PORT, value: "8090" }
|
|
44
44
|
- { name: DB_BACKEND, value: pg }
|
|
45
45
|
- { name: SESSION_BACKEND, value: auto }
|
|
46
|
+
- { name: STORE_LOCK_WAIT_MS, value: {{ .Values.server.storeWaitMs | quote }} }
|
|
46
47
|
- { name: PG_HOST, value: sema-pg-rw.{{ .Release.Namespace }}.svc }
|
|
47
48
|
- { name: PG_PORT, value: "5432" }
|
|
48
49
|
- { name: PG_USER, value: sema }
|
|
@@ -26,7 +26,10 @@ pg:
|
|
|
26
26
|
bucket: s3://sema-pg-backup/
|
|
27
27
|
|
|
28
28
|
minio:
|
|
29
|
-
|
|
29
|
+
# 钉死的 tag(不用 `:latest`:k8s 对它的缺省拉取策略是 Always,每次 Pod 重建都拉「今天恰好是什么」)。换国内源 = 换整条引用
|
|
30
|
+
# (`--set minio.image=<源>/minio/minio:<tag>`,同 server.image / gitea.image 的形);cluster 静态清单与 compose 默认写同一个 tag(测试咬合)。
|
|
31
|
+
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
|
|
32
|
+
pullPolicy: IfNotPresent # tar 预灌=Never(同 server.pullPolicy)
|
|
30
33
|
storageSize: 200Gi
|
|
31
34
|
storageClass: longhorn
|
|
32
35
|
rootUser: sema
|
|
@@ -37,6 +40,9 @@ server:
|
|
|
37
40
|
pullPolicy: IfNotPresent # tar 预灌=Never
|
|
38
41
|
replicas: 3
|
|
39
42
|
nodePort: 30890
|
|
43
|
+
# 引擎 boot 期等库的窗(ms,env STORE_LOCK_WAIT_MS):CNPG 与 server Pod 同时起、库初始化常慢于 server 就绪 ——
|
|
44
|
+
# 窗内重试、到窗仍连不上才拒启(拒启后 kubelet 重拉;引擎不会再退成内存起服)。0 = 第一次失败就拒。
|
|
45
|
+
storeWaitMs: 300000
|
|
40
46
|
serviceAuthToken: "" # REQUIRED
|
|
41
47
|
modelGatewayBaseurl: "" # REQUIRED
|
|
42
48
|
modelId: "" # REQUIRED
|
|
@@ -31,6 +31,10 @@ unset _self _hops _dir
|
|
|
31
31
|
. "$HERE/lib/shell-access.sh"
|
|
32
32
|
# 取值旗的形(S-621):判据与词表同 sema-up.sh 一处定义。
|
|
33
33
|
. "$HERE/lib/flagshape.sh"
|
|
34
|
+
# 引擎 /health 体里「落在哪个后端」的读法与期望值(smoke.sh / kube-up.sh 同一只)。
|
|
35
|
+
. "$HERE/lib/health.sh"
|
|
36
|
+
# 对象存储桶的唯一表(sema-up.sh / kube-up.sh / smoke.sh 同读这一张)。
|
|
37
|
+
. "$HERE/lib/buckets.sh"
|
|
34
38
|
log() { printf '[cluster-up] %s\n' "$*" >&2; }
|
|
35
39
|
die() { log "⛔ $*"; exit 1; }
|
|
36
40
|
|
|
@@ -419,8 +423,9 @@ PY
|
|
|
419
423
|
# 凭据清单经 stdin 直接 apply,不落远端盘(落 /tmp 失败时会残留)
|
|
420
424
|
rrun 0 k3s kubectl apply -f - < "$RENDERED" >/dev/null
|
|
421
425
|
fi
|
|
422
|
-
rsudo 0 <<'EOF'
|
|
426
|
+
rsudo 0 $(buckets_prefixed local/ $(buckets_k8s)) <<'EOF'
|
|
423
427
|
set -e
|
|
428
|
+
BUCKETS=("$@") # 要建的桶(local/<桶>):清单只在 lib/buckets.sh 一处,经位置参数递给远端
|
|
424
429
|
# runner token 跨 ns 复制要赶在 server 起 pod 前(晚了=CreateContainerConfigError 干等——真踩);
|
|
425
430
|
# SA token secret 由控制器异步填充,先等它有值
|
|
426
431
|
TOK=""
|
|
@@ -431,6 +436,12 @@ done
|
|
|
431
436
|
[ -n "$TOK" ] || { echo "sema-runner-token 未被控制器填充" >&2; exit 1; }
|
|
432
437
|
k3s kubectl create secret generic sema-runner-token-copy -n sema --from-literal=token="$(printf '%s' "$TOK" | base64 -d)" \
|
|
433
438
|
--dry-run=client -o yaml | k3s kubectl apply -f - >/dev/null
|
|
439
|
+
# MinIO 建桶(备份桶+引擎桶)—— 排在等 PG healthy 与等 server rollout **之前**:引擎起服会核快照桶在不在,桶缺时 server 不就绪、rollout 永远等不到,而建桶又排在它后面 ⇒ 互等;
|
|
440
|
+
# MinIO 不依赖 PG,建桶也就不必陪着等 PG(冷启拉镜像 + init 可要几分钟,会把引擎的等桶窗耗掉)
|
|
441
|
+
k3s kubectl rollout status deployment/sema-minio -n sema --timeout=300s >/dev/null
|
|
442
|
+
PW=$(k3s kubectl get secret sema-minio -n sema -o jsonpath='{.data.rootPassword}' | base64 -d)
|
|
443
|
+
k3s kubectl exec deploy/sema-minio -n sema -- sh -c \
|
|
444
|
+
"mc alias set local http://localhost:9000 sema '$PW' >/dev/null && mc mb --ignore-existing ${BUCKETS[*]}" >/dev/null
|
|
434
445
|
# PG healthy(冷启拉镜像+init 可要几分钟)
|
|
435
446
|
for t in $(seq 1 120); do
|
|
436
447
|
s=$(k3s kubectl get cluster sema-pg -n sema -o jsonpath='{.status.phase}' 2>/dev/null || true)
|
|
@@ -438,24 +449,25 @@ for t in $(seq 1 120); do
|
|
|
438
449
|
done
|
|
439
450
|
[ "$s" = "Cluster in healthy state" ] || { k3s kubectl get cluster -n sema; echo "PG 未达 healthy" >&2; exit 1; }
|
|
440
451
|
k3s kubectl rollout status deployment/sema-server -n sema --timeout=300s >/dev/null
|
|
441
|
-
# MinIO 建桶(备份桶+对象桶)
|
|
442
|
-
PW=$(k3s kubectl get secret sema-minio -n sema -o jsonpath='{.data.rootPassword}' | base64 -d)
|
|
443
|
-
k3s kubectl exec deploy/sema-minio -n sema -- sh -c \
|
|
444
|
-
"mc alias set local http://localhost:9000 sema '$PW' >/dev/null && mc mb --ignore-existing local/sema-pg-backup local/workspaces local/blobs" >/dev/null
|
|
445
452
|
echo STACK_OK
|
|
446
453
|
EOF
|
|
447
454
|
|
|
448
455
|
# ── 8. smoke(节点内经 NodePort;外网入口=LB 在云商侧配,打印指引)────────────────
|
|
449
456
|
log "8/8 smoke"
|
|
450
457
|
# NodePort 被 nodeport-addresses 钉在节点网段——loopback 不在其内,必须打 node-ip(真踩 curl exit 7)
|
|
451
|
-
|
|
452
|
-
|
|
458
|
+
# 第一行 = 两个 HTTP 码,其后 = /health 体(后端位在里面:200 + 401 测不出「服务在内存里跑」,验收必须读那一位)
|
|
459
|
+
SMOKE_OUT=$(rsudo 0 <<EOF
|
|
460
|
+
HB=\$(curl -m5 -s -w '\n%{http_code}' "http://$K3S0_IP:$NODEPORT/health" || printf '\n000')
|
|
461
|
+
H="\${HB##*\$'\n'}"
|
|
453
462
|
A=\$(curl -m5 -s -o /dev/null -w '%{http_code}' "http://$K3S0_IP:$NODEPORT/v1/runs" || echo 000)
|
|
454
463
|
echo "health=\$H auth=\$A"
|
|
464
|
+
printf '%s' "\${HB%\$'\n'*}"
|
|
455
465
|
EOF
|
|
456
466
|
)
|
|
467
|
+
SMOKE="${SMOKE_OUT%%$'\n'*}"
|
|
457
468
|
case "$SMOKE" in *"health=200 auth=401"*) : ;; *) die "smoke 失败:$SMOKE(期望 health=200 auth=401)" ;; esac
|
|
458
|
-
|
|
469
|
+
BK_WHY=$(health_backend_check "${SMOKE_OUT#*$'\n'}") || die "smoke 失败:$BK_WHY。下一步:kubectl -n sema logs deploy/sema-server(引擎在库没连上时不会再退内存起服 —— 它会拒启;读到 memory 多半是旧镜像)"
|
|
470
|
+
log "smoke ✓ $SMOKE backend=$SEMA_UP_EXPECT_SESSION_BACKEND"
|
|
459
471
|
cat >&2 <<EOF
|
|
460
472
|
|
|
461
473
|
══════════ cluster-up 完成($MACHINE_COUNT 节点)══════════
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
# 输入(sema-up.sh 注入;也可单独跑):
|
|
10
10
|
# WORK(状态目录)PROJECT REGION KUBE_MODE [KUBECONFIG_PATH] [DRY_RUN]
|
|
11
11
|
# USERS REGISTRY_ENABLED GIT_MODE [GIT_URL GIT_TOKEN]
|
|
12
|
-
# SEMA_SERVER_IMAGE(必需)[SEMA_REGISTRY_IMAGE][GITEA_IMAGE]
|
|
12
|
+
# SEMA_SERVER_IMAGE(必需)[SEMA_REGISTRY_IMAGE][GITEA_IMAGE][MINIO_IMAGE(整条引用,换国内源用;缺省 = chart 钉死的 tag)]
|
|
13
13
|
# MODEL_GATEWAY_BASEURL MODEL_ID MODEL_API_KEY
|
|
14
14
|
# [NAMESPACE=sema][STORAGE_CLASS=集群默认][CNPG_VERSION]
|
|
15
15
|
set -euo pipefail
|
|
@@ -33,6 +33,10 @@ unset _self _hops _dir
|
|
|
33
33
|
. "$HERE/lib/shell-access.sh"
|
|
34
34
|
# 取值旗的形(S-621):判据与词表同 sema-up.sh 一处定义。
|
|
35
35
|
. "$HERE/lib/flagshape.sh"
|
|
36
|
+
# 引擎 /health 体里「落在哪个后端」的读法与期望值(smoke.sh / cluster-up.sh 同一只)。
|
|
37
|
+
. "$HERE/lib/health.sh"
|
|
38
|
+
# 对象存储桶的唯一表(sema-up.sh / cluster-up.sh / smoke.sh 同读这一张)。
|
|
39
|
+
. "$HERE/lib/buckets.sh"
|
|
36
40
|
log() { printf '[kube-up] %s\n' "$*" >&2; }
|
|
37
41
|
die() { log "⛔ $*"; exit 1; }
|
|
38
42
|
|
|
@@ -214,6 +218,7 @@ helm_values() { # helm_values <giteaBaseUrl> <oauthId> <oauthSecret>
|
|
|
214
218
|
V_REG_PW="${REGISTRY_ADMIN_PASSWORD:-}" V_REG_SS="${REGISTRY_SESSION_SECRET:-}" V_REG_PT="${REGISTRY_PULL_TOKEN:-}" \
|
|
215
219
|
V_GITEA_ENABLED="$([ "$GIT_MODE" = provision ] && echo true || echo false)" \
|
|
216
220
|
V_GITEA_IMAGE="${GITEA_IMAGE:-gitea/gitea:1.24}" V_NODE_IP="$NODE_IP" \
|
|
221
|
+
V_MINIO_IMAGE="${MINIO_IMAGE:-}" \
|
|
217
222
|
python3 - <<'PY'
|
|
218
223
|
import json, os
|
|
219
224
|
e = os.environ
|
|
@@ -244,6 +249,8 @@ v = {
|
|
|
244
249
|
"gitea": {"enabled": e["V_GITEA_ENABLED"] == "true", "image": e["V_GITEA_IMAGE"],
|
|
245
250
|
"storageClass": e["V_SC"], "domain": e["V_NODE_IP"]},
|
|
246
251
|
}
|
|
252
|
+
# MinIO 镜像:给了才递(默认只在 chart values.yaml 写一处,不在脚本里再抄一份);国内源 = 整条引用
|
|
253
|
+
if e["V_MINIO_IMAGE"]: v["minio"]["image"] = e["V_MINIO_IMAGE"]
|
|
247
254
|
print(json.dumps(v))
|
|
248
255
|
PY
|
|
249
256
|
}
|
|
@@ -317,6 +324,12 @@ done
|
|
|
317
324
|
[ -n "$TOK" ] || die "sema-runner-token 未被控制器填充。下一步:kubectl -n sema-sandbox get secret,sa"
|
|
318
325
|
printf '%s' "$TOK" | base64 -d | $KUBECTL create secret generic sema-runner-token-copy -n "$NAMESPACE" \
|
|
319
326
|
--from-file=token=/dev/stdin --dry-run=client -o yaml | $KUBECTL apply -f - >/dev/null
|
|
327
|
+
$KUBECTL rollout status deployment/sema-minio -n "$NAMESPACE" --timeout=300s >/dev/null
|
|
328
|
+
# MinIO 建桶(幂等;备份桶+引擎桶,清单只在 lib/buckets.sh 一处)。引擎起服会核快照桶在不在,所以建桶排在等 PG healthy 与等 server rollout 之前
|
|
329
|
+
# (MinIO 不依赖 PG;PG 冷启可要几分钟,不该把引擎的等桶窗耗掉)
|
|
330
|
+
$KUBECTL exec deploy/sema-minio -n "$NAMESPACE" -- sh -c \
|
|
331
|
+
"mc alias set local http://localhost:9000 sema \"\$MINIO_ROOT_PASSWORD\" >/dev/null && mc mb --ignore-existing $(buckets_prefixed local/ $(buckets_k8s))" >/dev/null \
|
|
332
|
+
|| die "MinIO 建桶失败。下一步:kubectl -n $NAMESPACE logs deploy/sema-minio"
|
|
320
333
|
# PG healthy(冷启拉镜像+init 可要几分钟)
|
|
321
334
|
s=""
|
|
322
335
|
for t in $(seq 1 120); do
|
|
@@ -324,11 +337,6 @@ for t in $(seq 1 120); do
|
|
|
324
337
|
[ "$s" = "Cluster in healthy state" ] && break; sleep 5
|
|
325
338
|
done
|
|
326
339
|
[ "$s" = "Cluster in healthy state" ] || { $KUBECTL get cluster -n "$NAMESPACE" >&2 || true; die "PG 未达 healthy。下一步:kubectl -n $NAMESPACE describe cluster sema-pg;PVC Pending 时查 StorageClass"; }
|
|
327
|
-
$KUBECTL rollout status deployment/sema-minio -n "$NAMESPACE" --timeout=300s >/dev/null
|
|
328
|
-
# MinIO 建桶(幂等;备份桶+对象桶)
|
|
329
|
-
$KUBECTL exec deploy/sema-minio -n "$NAMESPACE" -- sh -c \
|
|
330
|
-
"mc alias set local http://localhost:9000 sema \"\$MINIO_ROOT_PASSWORD\" >/dev/null && mc mb --ignore-existing local/sema-pg-backup local/workspaces local/blobs local/backups" >/dev/null \
|
|
331
|
-
|| die "MinIO 建桶失败。下一步:kubectl -n $NAMESPACE logs deploy/sema-minio"
|
|
332
340
|
$KUBECTL rollout status deployment/sema-server -n "$NAMESPACE" --timeout=600s >/dev/null || die "sema-server 未就绪。下一步:kubectl -n $NAMESPACE logs deploy/sema-server(常见:镜像拉取失败→imagePullSecrets/私仓凭据)"
|
|
333
341
|
|
|
334
342
|
# ── Gitea 引导(provision:管理员/token/OAuth app;全部 in-pod 动作,不依赖 NodePort 可达)──
|
|
@@ -379,30 +387,35 @@ fi
|
|
|
379
387
|
|
|
380
388
|
# ── smoke(port-forward 直验——kind/远程 kubeconfig 下 NodePort 对本机不可达,别靠它)──
|
|
381
389
|
log "5/6 smoke"
|
|
382
|
-
smoke_pf() { # smoke_pf <svc> <port> <path> → http code
|
|
383
|
-
local pf_pid code
|
|
390
|
+
smoke_pf() { # smoke_pf <svc> <port> <path> → 第一行 http code,其后是响应体(调用方按需取)
|
|
391
|
+
local pf_pid out code body
|
|
384
392
|
$KUBECTL port-forward "svc/$1" -n "$NAMESPACE" "1$2:$2" >/dev/null 2>&1 &
|
|
385
393
|
pf_pid=$!
|
|
386
394
|
sleep 2
|
|
387
|
-
|
|
395
|
+
out=$(curl -m5 -s -w '\n%{http_code}' "http://127.0.0.1:1$2$3" || printf '\n000')
|
|
388
396
|
kill $pf_pid 2>/dev/null; wait $pf_pid 2>/dev/null || true
|
|
389
|
-
|
|
397
|
+
code="${out##*$'\n'}"; body="${out%$'\n'*}"
|
|
398
|
+
[ "$out" = "$code" ] && body="" # 没有换行 = 只有码(假 curl / 空体)
|
|
399
|
+
printf '%s\n%s' "$code" "$body"
|
|
390
400
|
}
|
|
391
|
-
|
|
401
|
+
HR=$(smoke_pf sema-server 8090 /health)
|
|
402
|
+
H="${HR%%$'\n'*}"
|
|
392
403
|
[ "$H" = "200" ] || die "smoke 失败:server /health=$H(期望 200)。下一步:kubectl -n $NAMESPACE logs deploy/sema-server"
|
|
393
|
-
|
|
404
|
+
# /health 200 ≠ 数据落了库:后端位必须是期望的持久后端(durable(pg));读到 memory = 服务在内存里跑、重启即丢,不许判绿
|
|
405
|
+
BK_WHY=$(health_backend_check "${HR#*$'\n'}") || die "smoke 失败:$BK_WHY。下一步:kubectl -n $NAMESPACE logs deploy/sema-server(引擎在库没连上时不会再退内存起服 —— 它会拒启;读到 memory 多半是旧镜像)"
|
|
406
|
+
A=$(smoke_pf sema-server 8090 /v1/runs); A="${A%%$'\n'*}"
|
|
394
407
|
[ "$A" = "401" ] || die "smoke 失败:无 token /v1/runs=$A(期望 401=auth 门在)。SERVICE_AUTH_TOKEN 未生效=裸奔,立刻查"
|
|
395
408
|
R="n/a"
|
|
396
409
|
if [ "$REGISTRY_ENABLED" = "true" ]; then
|
|
397
|
-
R=$(smoke_pf sema-registry 3000 /api/healthz)
|
|
410
|
+
R=$(smoke_pf sema-registry 3000 /api/healthz); R="${R%%$'\n'*}"
|
|
398
411
|
[ "$R" = "200" ] || die "smoke 失败:registry healthz=$R。下一步:kubectl -n $NAMESPACE logs deploy/sema-registry"
|
|
399
412
|
fi
|
|
400
413
|
G="n/a"
|
|
401
414
|
if [ "$GIT_MODE" = "provision" ]; then
|
|
402
|
-
G=$(smoke_pf sema-gitea 3000 /api/healthz)
|
|
415
|
+
G=$(smoke_pf sema-gitea 3000 /api/healthz); G="${G%%$'\n'*}"
|
|
403
416
|
[ "$G" = "200" ] || die "smoke 失败:gitea healthz=$G"
|
|
404
417
|
fi
|
|
405
|
-
log "smoke ✓ health=$H auth=$A registry=$R gitea=$G"
|
|
418
|
+
log "smoke ✓ health=$H backend=$(health_session_backend "${HR#*$'\n'}") auth=$A registry=$R gitea=$G"
|
|
406
419
|
|
|
407
420
|
# ── summary(一屏;凭据一次性显示)────────────────────────────────────────────────
|
|
408
421
|
log "6/6 完成"
|
|
@@ -414,7 +427,7 @@ log "6/6 完成"
|
|
|
414
427
|
echo " 本机不可达 NodePort 时(kind/远程):kubectl -n $NAMESPACE port-forward svc/sema-server 8090:8090"
|
|
415
428
|
# 壳对 NodePort 的明文 http 按设计拒连(非回环要 https)⇒ 经 port-forward 带到本机回环再 connect。
|
|
416
429
|
shell_access_text "$ENVF" "装了 kubectl、能连这个集群的机器上:" "kubectl -n $NAMESPACE port-forward svc/sema-server 8090:8090" "http://127.0.0.1:8090"
|
|
417
|
-
echo " 数据 CNPG sema-pg×$PG_INSTANCES($PG_AA)+ MinIO(
|
|
430
|
+
echo " 数据 CNPG sema-pg×$PG_INSTANCES($PG_AA)+ MinIO(桶:$(buckets_k8s | tr ' ' '/'))"
|
|
418
431
|
if [ "$REGISTRY_ENABLED" = "true" ]; then
|
|
419
432
|
echo " 配置中心 NodePort 30300 → http://$NODE_IP:30300"
|
|
420
433
|
if $NEW_REG_PW; then echo " 首登:${REGISTRY_ADMIN_USER:-zhaoheran} / ${REGISTRY_ADMIN_PASSWORD}(⚠ 只显示这一次,之后看 .env)"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# lib/buckets.sh — 部署侧「要预置哪些对象存储桶」的唯一表。sema-up.sh(compose 建桶 / 外接 S3 建桶 / 汇总)、kube-up.sh、cluster-up.sh(k8s 建桶)与
|
|
2
|
+
# smoke.sh(验桶)都读这一张,不各写一份串:各写一份的下场是清单漏一只桶,而漏桶的症状是起服后每一次对象写 / 每一拍 sweep 都 404。
|
|
3
|
+
#
|
|
4
|
+
# server **不建桶**(docs/DEPLOY-PREREQS.md「对象存储桶」),引擎起服只核一次快照桶在不在、不在就拒启 —— 所以这张表必须包含引擎要用的每一只:
|
|
5
|
+
# · `session-snapshots` = 引擎的 `SESSION_SNAPSHOT_BUCKET` 缺省名(snapshot-BLOB offload + SendUserFile 私有 presigned 轨 / 沙箱 staging);
|
|
6
|
+
# 由测试把这个名与引擎的缺省逐字咬合(引擎改缺省而这里没跟 ⇒ 红);
|
|
7
|
+
# · `sema-public`(SendUserFile 永久公链轨)**不在表里**:它要匿名 GetObject-only 策略,是运维另配的桶,脚本不替人开匿名读。
|
|
8
|
+
SEMA_UP_ENGINE_BUCKETS="workspaces blobs backups session-snapshots"
|
|
9
|
+
# CNPG 的 WAL 归档 / 每日基备桶(只有 k8s 两条路径有 PG 备份;名与 chart values 的 `pg.backup.bucket: s3://<名>/` 同名,由测试咬合)。
|
|
10
|
+
SEMA_UP_PG_BACKUP_BUCKET="sema-pg-backup"
|
|
11
|
+
|
|
12
|
+
# buckets_k8s → 打印 k8s 路径(kube-up / cluster-up)要建的全部桶,空格分隔,每只一次。
|
|
13
|
+
buckets_k8s() { printf '%s %s' "$SEMA_UP_PG_BACKUP_BUCKET" "$SEMA_UP_ENGINE_BUCKETS"; }
|
|
14
|
+
|
|
15
|
+
# buckets_prefixed <前缀> <桶…> → 每个桶名前加前缀(mc 的 `<别名>/<桶>` 写法),空格分隔。桶名是 S3 闭字符集,不含空白,按空白拆是安全的。
|
|
16
|
+
buckets_prefixed() {
|
|
17
|
+
local p="$1" b out=""
|
|
18
|
+
shift
|
|
19
|
+
for b in "$@"; do out="$out$p$b "; done
|
|
20
|
+
printf '%s' "${out% }"
|
|
21
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# lib/health.sh — 「装好的引擎 /health 体说它真落在哪个后端」的唯一读法 + sema-up 部署的期望值。
|
|
2
|
+
# smoke.sh / kube-up.sh / cluster-up.sh 三处验收都调它,不各写一份(各写一份的下场:有一处还只看 HTTP 200,把退到内存的服务判绿)。
|
|
3
|
+
#
|
|
4
|
+
# 事实(出处是引擎,不是推测):
|
|
5
|
+
# · `/health` 体里的 `sessionBackend` 是**装配后**的有效店:`durable(<kind>)`(kind = pg | mysql | local)或 `memory`
|
|
6
|
+
# (引擎侧只有一只读法:src/store-posture.ts 的 `sessionStoreLabelOf`);
|
|
7
|
+
# · sema-up 三条路径(compose / kube / 多机)起的引擎一律 `DB_BACKEND=pg`(stack/compose.yaml、chart/templates/server.yaml、
|
|
8
|
+
# stack/sema-stack-cluster.yaml 逐份写死),所以装好的引擎必须读到 `durable(pg)`。读到 `memory` = 会话 / 停驻 / run 账本在进程内存里、
|
|
9
|
+
# 重启即丢 —— /health 照样 200、401 门照样在,只有这一位说了实话,验收必须看它。
|
|
10
|
+
SEMA_UP_EXPECT_SESSION_BACKEND="durable(pg)"
|
|
11
|
+
|
|
12
|
+
# health_session_backend <health 体> → 打印 `sessionBackend` 的值;体读不成 JSON / 没有这一位 ⇒ 打印空串(调用方按「读不出」处置,不猜)。
|
|
13
|
+
health_session_backend() {
|
|
14
|
+
printf '%s' "$1" | python3 -c 'import json,sys
|
|
15
|
+
try:
|
|
16
|
+
v = json.load(sys.stdin).get("sessionBackend", "")
|
|
17
|
+
print(v if isinstance(v, str) else "")
|
|
18
|
+
except Exception:
|
|
19
|
+
print("")' 2>/dev/null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# health_backend_check <health 体> → 0 = 落在期望的后端;1 = 不是(stdout 打一句人话,供调用方拼进失败行)。
|
|
23
|
+
health_backend_check() {
|
|
24
|
+
local got
|
|
25
|
+
got="$(health_session_backend "$1")"
|
|
26
|
+
if [ "$got" = "$SEMA_UP_EXPECT_SESSION_BACKEND" ]; then return 0; fi
|
|
27
|
+
if [ -z "$got" ]; then
|
|
28
|
+
printf '/health 体里读不出 sessionBackend(期望 %s)' "$SEMA_UP_EXPECT_SESSION_BACKEND"
|
|
29
|
+
else
|
|
30
|
+
printf '/health.sessionBackend=%s,期望 %s' "$got" "$SEMA_UP_EXPECT_SESSION_BACKEND"
|
|
31
|
+
fi
|
|
32
|
+
return 1
|
|
33
|
+
}
|