@sema-agent/server 7.126.0 → 7.127.1
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 +38 -0
- package/USAGE.md +86 -8
- package/deploy/sema-up/chart/templates/networkpolicy-sandbox.yaml +15 -5
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +28 -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 +16 -6
- package/deploy/sema-up/kube-up.sh +10 -4
- package/deploy/sema-up/lib/node-addrs.sh +42 -0
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +20 -1
- package/dist/audit.js +1 -0
- package/dist/boot/execution-env.js +17 -4
- package/dist/boot/leader.d.ts +2 -1
- package/dist/boot/memory-consolidation.d.ts +37 -3
- package/dist/boot/memory-consolidation.js +18 -2
- package/dist/boot/runner-deps.d.ts +2 -1
- package/dist/boot/session-faces.d.ts +2 -1
- package/dist/boot/stage-06-runners.js +5 -2
- package/dist/config-catalog.js +3 -1
- package/dist/config-types.d.ts +5 -1
- package/dist/config.js +2 -0
- package/dist/execution-lane-caps.d.ts +12 -0
- package/dist/execution-lane-caps.js +16 -2
- package/dist/fleet/subagent-tail-bus.js +15 -5
- package/dist/http/admission.js +5 -1
- package/dist/http/durable-run-leg.js +1 -3
- package/dist/http/resume-legs.js +15 -6
- package/dist/http/route-ctx.d.ts +7 -1
- package/dist/http/routes/runs.js +23 -1
- package/dist/http/routes/tasks.js +17 -8
- package/dist/http/routes/workflows.js +8 -0
- package/dist/http/server.js +1 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lsp/manager.js +1 -1
- package/dist/observability/fail-open.d.ts +16 -0
- package/dist/observability/fail-open.js +16 -0
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal.js +3 -0
- package/dist/plugins/caching-session-store.d.ts +39 -22
- package/dist/plugins/caching-session-store.js +16 -9
- package/dist/plugins/host-platform.d.ts +18 -3
- package/dist/plugins/host-platform.js +23 -10
- package/dist/plugins/k8s-exec-protocol.js +3 -0
- package/dist/plugins/one-time-migrations.d.ts +28 -4
- package/dist/plugins/one-time-migrations.js +22 -4
- package/dist/plugins/remote-env-host.d.ts +23 -6
- package/dist/plugins/remote-env-host.js +21 -12
- package/dist/plugins/remote-env-k8s.d.ts +40 -5
- package/dist/plugins/remote-env-k8s.js +119 -25
- package/dist/plugins/session-store.d.ts +4 -3
- package/dist/plugins/session-store.js +1 -1
- package/dist/plugins/store-backend.d.ts +12 -5
- package/dist/plugins/store-backend.js +12 -3
- package/dist/plugins/store-contracts.d.ts +3 -0
- package/dist/plugins/store-open-wait.js +3 -0
- package/dist/request-key-closure.d.ts +20 -7
- package/dist/request-key-closure.js +22 -6
- package/dist/run-acceptance.d.ts +51 -0
- package/dist/run-acceptance.js +27 -0
- package/dist/runs.d.ts +7 -11
- package/dist/runs.js +5 -16
- package/dist/sandbox-egress-policy.d.ts +61 -0
- package/dist/sandbox-egress-policy.js +235 -0
- package/dist/session-error-wire.d.ts +2 -2
- package/dist/session-titler.d.ts +5 -3
- package/dist/trace/core-keyset-guard.d.ts +3 -2
- package/dist/trace/ledger-events.d.ts +9 -3
- package/dist/trace/ledger-sink.d.ts +7 -0
- package/dist/trace/ledger-sink.js +14 -4
- package/dist/trace/project.d.ts +49 -8
- package/dist/trace/project.js +39 -7
- package/dist/trace/self-minted-wire.d.ts +19 -29
- package/dist/trace/self-minted-wire.js +2 -9
- package/dist/trace/tool-output-media.d.ts +26 -0
- package/dist/trace/tool-output-media.js +57 -0
- package/dist/trace/wire-projection-faces.d.ts +7 -1
- package/dist/trace/wire-projection-faces.js +2 -1
- package/package.json +3 -3
package/MIGRATION.md
CHANGED
|
@@ -7,6 +7,44 @@
|
|
|
7
7
|
> ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
|
|
8
8
|
> 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
|
|
9
9
|
|
|
10
|
+
## 7.127.1 —— Windows 宿主车道:策略链读 `cdPath` 不再因 shell 未落定而拒每条 Bash(行为修复,升级零动作)
|
|
11
|
+
|
|
12
|
+
升级零动作:无 DDL、无配置变化、无 wire 变化。**谁受影响**:Windows 宿主部署里 server 进程环境找不到 Git Bash 的(PATH 无 Git、`ProgramFiles` 不对、经 ssh 起服白名单环境等)—— 7.127.0 及之前每条 Bash 都被策略链拒成「policy check threw / aborted」且真因被吞;本版起策略链读 `cdPath` 不再碰 shell,命令真要跑时才以真因拒(装 Git for Windows 或设 `HOST_SHELL_PATH`)。装了 Git Bash 的普通部署行为不变。回滚到 7.127.0 安全。
|
|
13
|
+
|
|
14
|
+
## 7.127.0 —— 引擎 7.50.2(精确钉)· 🔴 `tool_end.output` 带图:SSE 整块、账本只存描述符 · 新帧 `task_started`(SSE 与账本同在)· 🔴 e2b / k8s 车道不再挂 LSP · 记忆整理按人的路径规则判写(自动腿)· 附件请求键 400 · `~/…` 规则在无家目录车道上转问
|
|
15
|
+
|
|
16
|
+
1. **`tool_end.output` 两形(BREAKING,消费方)**:`output` 可以是串,也可以是块数组;块里可有图像 / 文档 `{ type, data, mimeType }`。
|
|
17
|
+
- **SSE**:媒体块整块逐字(`data` 不过脱敏),一帧最大约为部署媒体预算的 4/3(缺省约 40 MiB)—— **按块 `data` 长界帧**,不要再按 16 384 字符界帧。
|
|
18
|
+
读得慢的连接(本帧媒体 + 未送出字节 > 64 MiB)上,本帧媒体降成下面的描述符形、帧级 `truncated: true` + `totalChars`;要整图就及时读流。
|
|
19
|
+
- **账本面**(账本行与读这本账的回放面:`GET /v1/runs/:id/events` —— 对 `POST /v1/runs` 提交的异步 run 它就是活流,所以这类客户端本版拿不到整图;整图只在 `/v1/tasks/stream` 与子代 tail 流 ——
|
|
20
|
+
以及轨迹面 `GET /v1/tasks/:id/stream` 的 `tool-result` 帧与 `GET /v1/tasks/:id/turns` 的 tool-result 块,`output` 原样取自账本行):带 `data` 的媒体块换成 `{ type, mimeType?, bytes, truncated: true }`,帧级 `truncated: true` 与 `totalChars`(原值真实大小:文本块字符数 + 媒体块 `data` 串长;引擎已截断的帧保留引擎给的值)。
|
|
21
|
+
- **做法**:两形都处理;无 `data` 且 `truncated: true` 的块按 `mimeType` / `bytes` 渲染「未送达」;不要把 `truncated: true` 读成「`output` 是串」。
|
|
22
|
+
2. **新帧 `task_started`**:委派子代每个周期一帧,先于子代的首个 `task_progress`;SSE 上有,**账本里也有**(与 `task_progress` 同样落账 ⇒ `GET /v1/runs/:id/events` 上同样先于首个 `task_progress`)。
|
|
23
|
+
**做法**:按 `type` 容忍未知帧的消费方零动作;对帧类型穷举的消费方(同步流与账本 tail 两面)加一臂(或忽略)。
|
|
24
|
+
3. **e2b / k8s 车道不再挂 LSP(BREAKING,部署)**:引擎 7.50.0 起 LSP 只读执行环境绑定持有的对象,这两条车道的执行环境本版不实现绑定读。开着 `LSP_ENABLED=true` 的部署照常起服,
|
|
25
|
+
起服打一行 `lsp_not_mounted`;模型名册里没有 LSP 工具,`wiring_manifest` 上没有 `lsp` 段。**做法**:零动作(代码导航回到 grep / read);想去掉那行日志就把 `LSP_ENABLED` 撤掉。host 车道不变。
|
|
26
|
+
4. **记忆整理按人的路径规则判写**:自动整理(任务结束按建议起跑)遇到点名记忆库路径的 deny / ask(人的持久规则或部署的调用方策略)会被拒 —— 库目录被点名 ⇒ 运维行 `engine_notice` 一条 `memory.consolidation_refused`,
|
|
27
|
+
只点名产物文件 ⇒ `memory.consolidation_incomplete`(`stop: "refused"`);用户任务不受影响。手动阀门 `POST /v1/admin/memory/consolidation/run` 本版不判这些规则(与此前同),每次写记一次 fail-open 计数
|
|
28
|
+
`server.memory-consolidation.operator-valve-unjudged`。**做法**:要让整理写就删 / 收窄那条规则;按日志里的码告警。
|
|
29
|
+
5. **附件请求键 `userAttachments` ⇒ `400`(键落 `unsupportedKeys`)**:引擎 7.50.0 的附件座本服务本版不受理 —— 提交口、steer 口与 interrupt 带 text 形同拒(不静默丢)。**做法**:不带这个键;附件内容放进目标正文。
|
|
30
|
+
**steer 口与 interrupt 带 text 形同批收成键闭集**:run steer(`text` / `mode` / `priority`)、子代 steer / resume 与 workflow 子代 steer(`content`)、`POST /v1/runs/:id/interrupt` 带 text 形(`text`)上受理集外的任何顶层键 ⇒ `400 request.body_shape`(此前静默丢)。
|
|
31
|
+
**做法**:steer / interrupt 体只带受理集内的键(interrupt 的档位是动词本身,不带 `priority`)。
|
|
32
|
+
6. **`~/…` 形规则在没有家目录的车道上转问(随引擎收窄)**:k8s / local-docker / ssh / adb / device 车道不声明家目录(除非 `REMOTE_EXEC_HOME_DIR`)。这些车道上一条 `~/…` 的 deny / ask 行 ⇒ 文件工具的每个调用都成真人问(bypass 也问),
|
|
33
|
+
无审批面即拒。**做法**:声明 `REMOTE_EXEC_HOME_DIR=<执行环境里的绝对路径>`,或把规则改写成绝对路径。host / e2b / 未设 `REMOTE_EXEC` 不受影响。
|
|
34
|
+
7. **规则店读不出 = 问人(随引擎收窄)**:持久规则店某个分区读不出时,该分区的 deny / ask 行照旧生效、allow 行不在,射程内的调用真人问(bypass 也问);run 一条 `permission.rule_store_unavailable`。
|
|
35
|
+
无人值守的 bypass 部署在店读不出期间会停。**做法**:让规则店可读;按该通告告警。
|
|
36
|
+
8. **订正 7.126.0 节第 16 条(文字订正)**:「同步腿 **400**,零 token」在 7.126.x 上对自动标题器不成立 —— 那一版这类拒仍为标题器花一次廉价模型调用;
|
|
37
|
+
本版起成立:引擎在受理之前拒掉的提交,标题器不再起名(留下的会话与此前同,无标题、零消息)。`verify` / `cascade` 提交的自动标题在 run 结束时才落。
|
|
38
|
+
9. **订正 7.126.0 节第 30 条「拒启前只会建出本版新表」(行为修复:退役 schema 的拒启先于任何建表;持久数据面)**。跳版升级的旧库(退役表 `snapshot_manifest` / 退役列 `checkpoint.pending_steer` 还在)起服**仍然拒启** `config.schema_mismatch`,
|
|
39
|
+
拒句不变(点名在场的坐标,出路:先在 **7.125.x** 对这只库起服一次让迁移跑完,再升);变的是时序 —— 库一连上就先探这两件,**探到即拒,拒启之前不再建任何表、不加任何列**
|
|
40
|
+
(7.126.0 是先建本版的新表与新列、再拒,第 30 条那一句写的就是它;本条取代那一句)。拒启不等 `STORE_LOCK_WAIT_MS` 的窗,当场拒。
|
|
41
|
+
- **谁需要**:从 ≤ 7.73.x 直接跳到本线、库里还带着退役表或退役列的部署,以及按拒句指路回退到 7.125.x 的运维 —— 盘上与交出去时一模一样,7.125.x 一次迁完,不会再撞「库比它新」的第二道拒。
|
|
42
|
+
- **谁受伤**:无。已被 7.126.0 拒过一次的旧库,本版新表与新列那时已经建出来了,出路不变(先在 7.125.x 起服一次,再升)。**经包根的公开序列(`createStoreBackend()` → `ensureSchema()` → `runOneTimeMigrations()`)带起后端的嵌入方零动作,也不是破坏性变更**——
|
|
43
|
+
`ensureSchema()` 自身先探后建,与本服务自己的起服路径是同一条路;7.126.0 上同一序列遇退役 schema 是在 `runOneTimeMigrations()` 里拒,本版前移到 `ensureSchema()` 里拒(同一只 `RetiredSchemaPresentError`,码 `config.schema_mismatch`,拒之前零 DDL),
|
|
44
|
+
包根不新增也不需要任何别的建连动词;按 `code` / `instanceof RetiredSchemaPresentError` 分派的代码不用改。
|
|
45
|
+
- **补偿**:无需动作,也无旋钮。
|
|
46
|
+
10. **升级顺序的唯一出处(跨仓)**:控制台 / 编排器 / 引擎的升级顺序只在 sema-web-admin 的 `DEPLOYMENT.md` §1「升级顺序(唯一出处)」一张表上(按版本加例外行)。本文件自本版起**不再重述顺序句**;此前各节里的顺序句(7.124.0 第 1 条、7.126.0 节头)是当时的快照,与该表不一致时以该表为准。
|
|
47
|
+
|
|
10
48
|
## 7.126.0 —— 🔴 会话 id 只收 canonical uuid(提交口 / A2A / run-local;自选名改成自留映射)· 🔴 团队编排面退役(请求键 `council` `debate` / `discuss` 场景 / `councilRuns`,零别名)· 🔴 角色词六个 · 🔴 审批卡词 `contentKind` → `approvalCard` · 🔴 `allowTools` 未知名拒跑 · 存量 SQL 库:`task_run` 加一列(幂等体指纹)+ 新表 `workspace_space_commit`(建表腿自动;期限列随行)· 会话删除 / 留存 sweep 回收空间快照对象(桶生命周期规则只作兜底)· 一次性迁移 M1–M3 退役:从没在 7.125.x 起过服的旧库起服即拒 ⇒ 先过 7.125.x 一次 · `WORKSPACE_PERSIST` 缺省 `off` → `space` · 记忆店根搬家 ⇒ 拒启 · 远端车道相对 `cd` 后删除问人 · 升级序:web-admin 先(council 键退役)、再 server;核心 7.49.5 / settings-schema 12.2.1 两包精确钉
|
|
11
49
|
|
|
12
50
|
1. **订正 7.125.0 节第 4 条(文字订正,行为不变)**:`PATCH /v1/sessions/:id` 与 2c 导入的 `title` 除 1–120 字符与拒 C0 外,**首尾空白也拒**:400 `request.field_invalid`「title must not start or end with whitespace」(先于 C0 判;服务端不代为 trim)。客户端在送出前自行 trim;7.125.0 节第 4 条与随包 USAGE 漏写了这一句,契约 §20 一直写着。
|
package/USAGE.md
CHANGED
|
@@ -38,7 +38,7 @@ docker compose up --build
|
|
|
38
38
|
> 把 PG 钉成 RR 或让部署把默认配成 RR/SERIALIZABLE,都会让每条 `FOR UPDATE` 变成 `40001`)。本仓所有
|
|
39
39
|
> `SELECT … FOR UPDATE` 的单赢家判据都以此为前提。运维读面:
|
|
40
40
|
> `GET /v1/diagnostics/wiring` 的 `sqlEngine` 段与 `GET /v1/capabilities` 的 `store.sql` 位(7.106.0 起;此前顶层 `sql`)。判据与升级句见
|
|
41
|
-
> `docs/DEPLOY-PREREQS.md
|
|
41
|
+
> `docs/DEPLOY-PREREQS.md`(源码仓内文档,不随 npm 包;下文同名指路同)「事务读语义的结构保证」。
|
|
42
42
|
```bash
|
|
43
43
|
IMAGE_REGISTRY=<registry-namespace> \
|
|
44
44
|
DB_BACKEND=mysql MYSQL_HOST=… MYSQL_USER=… MYSQL_PASSWORD=… MYSQL_DATABASE=sema_server \
|
|
@@ -533,6 +533,14 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
|
|
|
533
533
|
没有可报的拒 ⇒ 键缺席。名字是秘密形 / 空串 / `__proto__` 的不可用条目不上这两处(出口过名筛;引擎内部照旧拒它)。消费方读 `version`
|
|
534
534
|
判「缺席 = 没有拒」还是「老引擎、不可知」;读值用 settings-schema `readModelRefusals`。契约段:`docs/ASSISTANT-WIRE-CONTRACT.md`「模型拒因」。
|
|
535
535
|
钉:`test/model-refusals-s768.test.ts`。
|
|
536
|
+
- **`SESSION_CACHE_TTL_SEC`(缺省 `300`,单位 s;`0` = 关)—— 会话暖缓存,多副本安全**:每只副本把用过的会话留在内存里,
|
|
537
|
+
下一轮不必从店重新唤醒整只会话。每次命中先读店里这只会话的当前叶(SQL 后端一条主键 SELECT):与暖份的叶相同 ⇒ 照用;
|
|
538
|
+
不同(这只会话在别的副本上写过)或会话已不在 ⇒ 丢掉暖份、从店重读再用。所以 chart 缺省 Service、轮询负载均衡这类**任意路由**
|
|
539
|
+
下,同一会话轮流落到不同副本不会撞 `conflict`;会话亲和只提高命中率(少重读),不是正确性前提。代价:每次命中多一次读头
|
|
540
|
+
—— SQL 后端一条主键 SELECT;文件后端(`DB_BACKEND=local`)是把这只会话的存档文件再读一遍(只读头,不铸 `session.records_skipped`
|
|
541
|
+
帧)。命中却已陈旧的次数计在 `session_cache_stale_hit_total`(高 = 同一会话在 TTL 内常换副本,加亲和可省掉这些重读)。
|
|
542
|
+
`0` = 每一轮都从店唤醒,不复用。⚠️ 核的是叶:另一只副本用会话同步**整段替换**(含 `overwrite-dst`)把已有条目的内容改了、
|
|
543
|
+
末叶却没变的那一形,别的副本上的暖份在 TTL 内仍是旧内容(与改前同;写不会因此失败)。依赖这一形即时可见的部署设 `0`。
|
|
536
544
|
- 数据驻留提示:`DB_BACKEND=local` 下显式 `SESSION_BACKEND=memory` 会被收编为 **durable(local)**
|
|
537
545
|
(1.292+ 裸 boot 默认 durable;/health 的 `sessionBackend` 报 `durable(local)`)——session 行落盘在
|
|
538
546
|
数据根下,清数据/隐私预期要按「sessions 在 engine-data 里」来做,不要按「只在内存」。
|
|
@@ -579,12 +587,16 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
|
|
|
579
587
|
同一只等待腿 —— **不为前台形留第二套语义**。前台形要「立刻告诉我谁占着」就设 `STORE_LOCK_WAIT_MS=0`,
|
|
580
588
|
那是文档化的通道,而不是两条路各有一套默认。
|
|
581
589
|
🔴 **同一只窗 7.119.0 起也管「已配置的 SQL 库此刻连不上」**:`DB_BACKEND=mysql|pg`(含 `SESSION_BACKEND=auto`)boot 期开不了店 ⇒ 窗内每 250ms 重试、到窗拒启(点名后端与 host:port;
|
|
582
|
-
永久错如凭据 / DDL 权限也拖到窗尾才拒,要当场拒设 `0
|
|
590
|
+
永久错如凭据 / DDL 权限也拖到窗尾才拒,要当场拒设 `0`;唯一例外是**确定的拒启表态**,见下一条「跳版升级的旧库」,它不等窗)。k8s 清单里库与 server 同时起,所以清单把窗给到 `300000`。
|
|
583
591
|
⚠️ **裸 boot(没有显式 `DB_BACKEND`)那一形的时序也跟着变**:它的锁冲突走的不是拒启而是既有的
|
|
584
592
|
「退回内存并 warn」降级臂(`store_db_unreachable_fallback_memory`),所以第二只实例此前**当场**降级、
|
|
585
593
|
现在要**等满窗**才降级 —— 方向仍是好的(窗内拿到就用上真数据根,而不是静默丢掉持久面),但它是一次
|
|
586
594
|
真实的启动时长变化。显式 `DB_BACKEND=local` 不走降级臂(拒启),`0` 关掉两形的等待。
|
|
587
595
|
三问成文见 `docs/DEPLOY-PREREQS.md`。
|
|
596
|
+
- **跳版升级的旧库(退役表 `snapshot_manifest` / 退役列 `checkpoint.pending_steer` 还在)起服即拒 `config.schema_mismatch`,而且拒启之前库里一张表都没建**:
|
|
597
|
+
连上库后**先探这两件、再建表** —— 探针先于第一条 `CREATE`(两个 SQL 方言同一只判据),所以按拒句指路去 7.125.x 起服一次时,库还是你交给本版之前的样子,
|
|
598
|
+
不会带着本版新建的表与新列去撞 7.125.x 的对账。这是确定的拒启表态:不等 `STORE_LOCK_WAIT_MS` 的窗、当场拒,拒句点名在场的坐标与出路(先在 7.125.x 对这只库起服一次让迁移跑完,再升)。
|
|
599
|
+
探针只读(两条目录查询),新库与已经过带迁移的版本的库零命中;`DB_BACKEND=local` 没有 SQL,不探。
|
|
588
600
|
- **`RUN_STORE_STRICT_HYDRATE=on|off`(缺省 `off`)—— file 后端 run 账本的 fail-closed 启动门**:
|
|
589
601
|
`DB_BACKEND=local` 的 run 账本在 boot 时把 `<数据根>/runs/` 列一遍重建内存索引。那次 `readdir` 失败
|
|
590
602
|
(权限、挂载丢失、路径被换走)时**缺省行为是照常起动**,只留一行 `file_run_store_hydrate_unreadable`
|
|
@@ -1381,9 +1393,13 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
|
|
|
1381
1393
|
`ai-agent-code-lsp` 模板;⚠️ 该模板**只烤 TS/JS**——Python/Go 烤在 `dev-base`,dart/kotlin/java/swift
|
|
1382
1394
|
烤在 `dev-mobile`,四模板物理独立,`E2B_TEMPLATE` 选错则未烤语言的 LSP **静默退化到 grep/read**;
|
|
1383
1395
|
对照表见 ARCHITECTURE §7,真源=`src/lsp/e2b-bridge.ts`),host 腿缺省**开**(只要 PATH 上有 language server,没有就优雅退回 grep/read)。
|
|
1384
|
-
|
|
1396
|
+
拆分后沙箱腿是 `LSP_ENABLED`(opt-in)—— **本版在 e2b / k8s 上不挂任何东西**(见下段「LSP 的车道射程」);host 腿归 `LSP_HOST_ENABLED`(opt-out)。
|
|
1385
1397
|
🪦 `LSP_ENABLED=false`(拆分前唯一的 host 腿逃生舱)自 3.0.0 起是墓碑:拒启并指路 `LSP_HOST_ENABLED`。
|
|
1386
|
-
`LSP_ENABLED=true`
|
|
1398
|
+
`LSP_ENABLED=true` 照常起服,但本版只换来一行 `lsp_not_mounted`(沙箱两车道不挂 LSP,见下段);绑定读在这两条车道实现之日它重新生效。
|
|
1399
|
+
|
|
1400
|
+
**LSP 的车道射程(7.127.0 起,引擎 7.50.0)**:LSP 会话只读执行环境「绑定持有」的那只文件(判过的对象就是读到的对象)。host 车道实现了绑定读,LSP 照常(`LSP_HOST_ENABLED`);
|
|
1401
|
+
沙箱两车道(e2b / k8s)的执行环境本版不实现绑定读 —— 挂上就是每个文件同步都被拒 ⇒ **这两条车道不挂 LSP**:开着 `LSP_ENABLED=true` 照常起服,起服打一行
|
|
1402
|
+
`lsp_not_mounted`(带原因),模型名册里没有 LSP 工具、`wiring_manifest` 上没有 `lsp` 段,代码导航回到 grep / read。ssh / adb / device / local-docker 本来就不挂。
|
|
1387
1403
|
|
|
1388
1404
|
**host 腿语言服务器拿到的环境 + 点名放行 `LSP_ENV_ALLOW`(7.101.0 起)**:host 腿的语言服务器是本机子进程,它拿到的环境
|
|
1389
1405
|
= 服务进程环境**剥掉凭据形的键**(与模型可驱动的 host shell 同一只剥除:配置目录 `secret` 型键按名剥 ∘ 引擎按键名形状剥
|
|
@@ -1618,6 +1634,15 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
1618
1634
|
# → SSE,每条 data: <原始 TaskEvent>,如 {"type":"text_delta","delta":"…"} … {"type":"done","result":{…}}
|
|
1619
1635
|
# 注意:这是「同实例逐 token」;跨 worker 断线重连用 §4 的 /v1/runs(turn 粒度)。
|
|
1620
1636
|
```
|
|
1637
|
+
> **7.127.0 起两处帧形(引擎 7.50.0)**:
|
|
1638
|
+
> - **`task_started`**:委派子代每个周期一帧(首派 / 复活;耐久续跑腿不发),先于子代的首个 `task_progress`,与它走同一扇门(主流、转发道、子代 tail 流)。
|
|
1639
|
+
> 键与 `task_progress` 同读法(`taskId` / `sessionId` / `model` / `startedAt` 恒在;`taskType` 缺席 = 前台子代;`seq` 缺席 = 无后台行)。**与 `task_progress` 同样落账**:
|
|
1640
|
+
> `GET /v1/runs/:id/events`(异步 run 的活流与重连面)上它同样先于子代的首个 `task_progress`。
|
|
1641
|
+
> - **`tool_end.output` 两形**:串,或块数组。块里可有图像 / 文档 `{ type, data, mimeType }` —— 本流上整块逐字(`data` 是字节编码,不过文本脱敏),
|
|
1642
|
+
> 一帧最大约为部署媒体预算的 4/3(缺省约 40 MiB),请按块 `data` 长界帧;读得慢的连接(本帧媒体 + 还没送出去的字节 > 64 MiB)上本帧媒体降成描述符。
|
|
1643
|
+
> 媒体块 `data` 的内容由引擎准入负责:引擎本版只对自己产出的块验字节(读图像 / PDF、提交的图像、用户附件),MCP 服务器与宿主自定义工具交回的块不验(引擎下一 minor 收);本服务不在 wire 上对 `data` 做任何改写。
|
|
1644
|
+
> 账本面只存描述符 —— 账本行与 `GET /v1/runs/:id/events`(断线重连面;对 `POST /v1/runs` 提交的异步 run 它**就是活流**,所以这类客户端本版拿不到整图,整图只在本流与子代 tail 流上)
|
|
1645
|
+
> `{ type, mimeType?, bytes, truncated: true }`(帧级 `truncated: true` 与 `totalChars` = 原值真实大小 —— 文本块字符数 + 媒体块 `data` 串长,引擎已截断的帧保留引擎给的值);无 `data` 且 `truncated: true` 的块渲染成「未送达」。
|
|
1621
1646
|
|
|
1622
1647
|
## 7. 其它接口(按需)
|
|
1623
1648
|
| 端点 | 用途 |
|
|
@@ -1752,6 +1777,14 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
1752
1777
|
> 手动阀门照常可用。
|
|
1753
1778
|
> 未武装的部署,`wiring_manifest` 与它的 `configFingerprint` 对 7.20.1 **逐字节相同**;武装的部署,新段
|
|
1754
1779
|
> `autoConsolidation: { onRecommendation: true }` **只出现在 operator 帧上**(契约附录 G.13)。
|
|
1780
|
+
> - **整理每次写库都问写判官(7.127.0 起,引擎 7.50.0)**:
|
|
1781
|
+
> - **自动整理**(任务结束按建议起跑)用这条腿自己的判官 —— 该 run 主体的人的持久路径规则 + 部署的调用方策略,与模型自己的 `Write` 同一套判:
|
|
1782
|
+
> 规则点名库目录 ⇒ 在任何占位与模型调用之前拒,运维行 `engine_notice` 一条 `memory.consolidation_refused`(`detail.refusalCode: "memory.consolidation_write_refused"`);
|
|
1783
|
+
> 只点名产物文件 ⇒ 应用时拒、什么都不写,一条 `memory.consolidation_incomplete`(`stop: "refused"`);用户任务不受影响。没有主体的 run 读不到任何人的持久规则(只剩部署策略那一层)。
|
|
1784
|
+
> - **手动阀门**(`POST /v1/admin/memory/consolidation/run`)没有腿,拿不到引擎的那只判官;本服务不另造一只 ⇒ 它的写判官答「无异议」(与 7.126.0 该口行为逐字同),
|
|
1785
|
+
> 每次被问记一次 fail-open 计数 `server.memory-consolidation.operator-valve-unjudged`(计数 + 探针行 + 一次性告警)。驱动的每一条通告 —— 非收敛停的 `memory.consolidation_incomplete`、
|
|
1786
|
+
> 问判官**之前**推不出库家落点时同码拒的 `memory.consolidation_refused`(生产上可达,与判官答什么无关)—— 都经同一只 `engine_notice` 转发器进运维行;早问拒仍按路由的码臂答 `409 memory.consolidation_refused`。
|
|
1787
|
+
> - **怎么让整理写**:删掉或收窄点名记忆库路径的规则;只想禁止自动整理就设 `MEMORY_AUTO_CONSOLIDATION=off`。
|
|
1755
1788
|
> - `MEMORY_CONSOLIDATION_SCOPES=user:local[,org:acme]`:阀门的 scope 表 = 这台 worker **声明**它会折叠哪些库。
|
|
1756
1789
|
> **恰好一条**时它是两个口省略 `scope` 的唯一缺省;零条或多条时省略 `scope` 一律 400(在两个库之间替人挑
|
|
1757
1790
|
> 一个去花全库模型钱是本设计要消灭的形)。**表非空时显式 scope 必须在表里**,否则 400 —— 一个陈旧或敲错
|
|
@@ -1839,6 +1872,12 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
1839
1872
|
> 与历史记录轨迹;真正持久化的规则本身在 `permission_rule` 表,不受影响、不需要重建。
|
|
1840
1873
|
> 探针只在规则车道真被装配时跑(`PERMISSION_RULES_ENABLED=false` 的部署一次都不碰这张表,不受影响)。
|
|
1841
1874
|
>
|
|
1875
|
+
> **人的持久规则的读法(7.127.0 起,引擎 7.50.0)**:每个调用读一次规则店;一条持久 deny 行在门上拒它所在那一面的**每个**工具(Read / Grep / Glob / RepoMap /
|
|
1876
|
+
> 写工具 / 声明路径目标的调用方工具),`tool_end.gate.disposition.deniedBy = "persisted_rule"`。店的某个分区读不出 ⇒ 该分区上次读到的 deny / ask 行照旧生效、allow 行不在,
|
|
1877
|
+
> 射程内的调用真人问(bypass 也问,无审批面即拒),run 一条 `permission.rule_store_unavailable`。
|
|
1878
|
+
> **`~/…` 形的行以执行环境的家目录为基**:host / 未设 `REMOTE_EXEC` = 本机本用户,e2b = `/home/user`,k8s / local-docker / ssh / adb / device 不猜 —— 除非 `REMOTE_EXEC_HOME_DIR`
|
|
1879
|
+
> 声明。没声明家目录的车道上,一条 `~/…` 的 deny / ask 行判不出点的是哪里 ⇒ 该面文件工具的每个调用都成真人问(bypass 也问);要么声明 `REMOTE_EXEC_HOME_DIR`,要么把规则写成绝对路径。
|
|
1880
|
+
>
|
|
1842
1881
|
> **同批顺带:场景旧名 `team` / 工具旧名 `run_team` 退役(BREAKING)。** 此前过渡窗的别名层
|
|
1843
1882
|
> (`SCENARIO_ALIASES`)整体撤销:请求体里显式传 `scenario:"team"` 从「静默归一到 `discuss`」变成
|
|
1844
1883
|
> **未知名响亮 400**(`scenario_unknown`);指派/部署配置里悬空写着的旧名走**现有**
|
|
@@ -1947,7 +1986,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
1947
1986
|
| HTTP `200` + `terminal.kind:"completed"` | 成功 | 取 `result` |
|
|
1948
1987
|
| `terminal.kind:"blocked"` | agent 主动报卡住 | 看 `terminal.reason`,补信息再发 |
|
|
1949
1988
|
| HTTP `200` + `terminal.kind:"paused"` + 顶层 `status:"suspended"\|"needs_review"`(同步 `POST /v1/tasks`) | run **停在门上**(工具审批 / 计划审阅 / 资源分片),没跑完。🔴 **7.104.0 起**这张体就是那份结果本身(`terminal.gate` 说停在哪扇门、`checkpointId` 是对账位、`result`/`stats` 是停下来那一刻的读数),另叠 `taskId`/`sessionId`/`status` 三键 poll 回执 —— 修前只有这三键。恢复凭据**从不**在体上 | 按 `taskId` 轮询 `GET /v1/runs/:taskId`(park 中那一行同样带这份 `result`);审批走 `/v1/approvals`。判读用 `terminal.kind`,旧的 `status` 读法照旧成立。契约 §5.0c |
|
|
1950
|
-
| `terminal:{kind:"failed", code:"conflict"}` |
|
|
1989
|
+
| `terminal:{kind:"failed", code:"conflict"}` | 同一会话的两条写同时落在同一处(真并发:两处同时写一只会话),按叶的条件写只成一条;输的那一条以本码结束,撞上之后的内容没写进会话。多副本按任意路由轮流服务同一会话**不会**产生它(会话暖缓存命中前先核店里的当前叶,见 `SESSION_CACHE_TTL_SEC`) | 直接重试(重发接在会话当前的叶之后) |
|
|
1951
1990
|
| `terminal:{kind:"failed", code:"limits.max_walltime_exceeded"}` | 墙钟到限(6.0.0 起 `status:"timeout"` 退役;7.64.0 起平面 `status` 键退役)。🔴 **7.71.0 / 引擎 7.13.0 收窄**:上限若在最后一轮 turn **已经给出干净答案之后**才响,run 记 `completed`、答案就是 `result`,**不铸本码**——那次停机的唯一痕迹是 `engine_notice` 的 `task.interrupt_unconsumed{origin:"walltime"}`(附录 D.3)。本码此后只在**工作被真切断**时出现。⚠️ 按码计数的运维面要把 `origin ∈ walltime\|turns` 的那一帧计入才守恒(server 侧对偶计量 `limit_ceiling_after_answer_total{origin}`,与 `budget_exceeded_total{code}` **相加**才是撞顶总量) | 拆小任务 / 提高 `limits.maxWalltimeMs`(毫秒) |
|
|
1952
1991
|
| `terminal:{kind:"failed", code:"limits.max_turns_exceeded"}` | 轮数到帽。🔴 **同上收窄**(引擎 7.13.0):帽子若落在**那一轮答案自己**身上 ⇒ `completed` + `task.interrupt_unconsumed{origin:"turns"}`,不铸本码 | 提高 `limits.maxTurns` / 拆小任务 |
|
|
1953
1992
|
| HTTP `401` | 缺 `Authorization` / 缺 `x-agent-principal`(要求时) | 补头 |
|
|
@@ -1958,7 +1997,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
1958
1997
|
| HTTP `429` + `errorCode:"limit.cost_quota_exceeded"` | per-principal 累计成本配额越顶(`MAX_PRINCIPAL_COST_USD`;**进场门**,不打断在跑的 run) | 看 `Retry-After` / 体 `retryAfterSec` 退避(体 `windowResetsAt` = 当代窗滚过的时刻);窗滚过或调高上限后放行 |
|
|
1959
1998
|
| HTTP `400` + `errorCode:"request.id_invalid"` | 调用方给的 id 形不对。**会话 id 的形门只有一只** = 任意版本 canonical uuid(壳预设的 v4 也过门):会话口 `:id`(fork / 抹除 / 会话策略 / 2c 同步)与提交口(`POST /v1/tasks` / `/v1/tasks/stream` / `/v1/runs` 体里的 `sessionId`,A2A 的 `contextId` 同)同判,提交口的句子在句首之后点名「改用 uuid、自己记映射」;`GET /v1/sendfile-links?before=` 游标与 `body.taskId` 幂等键要求 uuidv7(时间序 / 幂等键契约) | 按 `errorCode` 分支;句子各自点名要求的形(`must be a canonical uuid` / `must be a uuidv7 …`),只作展示、别锚文案 |
|
|
1960
1999
|
| HTTP `400` + `errorCode:"request.field_invalid"`(`PATCH /v1/sessions/:id` / 2c 导入的 `title`) | 标题不合形:1–120 个 Unicode 字符、**首尾不得有空白**(服务端不代为 trim,先于 C0 判)、不得含 C0 控制字符;`title: null` 清回自动标题;空串是 `request.body_shape` | 客户端送出前自行 trim;按 `errorCode` 分支,句子只作展示 |
|
|
1961
|
-
| HTTP `400` + `errorCode:"config.tool_face_control"`(7.126.0 起;流式 / 后台腿是 `failed` 终局带同码) | 请求的 `allowTools` / `excludeTools` 里有一个这台部署上**没有任何工具答应**的名字(或 `allowTools` 写了 `mcp__*`、`excludeTools` 写了 `*`)
|
|
2000
|
+
| HTTP `400` + `errorCode:"config.tool_face_control"`(7.126.0 起;流式 / 后台腿是 `failed` 终局带同码) | 请求的 `allowTools` / `excludeTools` 里有一个这台部署上**没有任何工具答应**的名字(或 `allowTools` 写了 `mcp__*`、`excludeTools` 写了 `*`);引擎开跑前拒:主模型零调用、标题器零调用(7.127.0 起;7.126.x 上标题器仍会为这次提交调一次模型)。这次提交留下的会话无标题、零消息 | 只列这台部署挂得出的工具名;句子点名那一条与它在哪张表里 |
|
|
1962
2001
|
| HTTP `429` + `errorCode:"usage.window_exhausted"` | 部署级治理窗耗尽(`USAGE_WINDOWS`,token 或 $ 天花板先满者) | 看响应体 `retryAfterSec` 退避;窗滑动/桶到期后放行 |
|
|
1963
2002
|
| `terminal:{kind:"failed", code:"usage.window_exhausted"}` | 已受理的 run 在 **turn 边界**撞上治理窗且无法 durable 挂起 | 同上退避后重投;配好 checkpoint 基建则改为 `suspended` 等窗自动续跑 |
|
|
1964
2003
|
| HTTP `501`(`/v1/runs`) | 内存模式不支持异步 | 配持久存储(`DB_BACKEND=mysql` / `pg` + 坐标;7.106.0 起给了坐标必须点名 `DB_BACKEND`) |
|
|
@@ -2088,7 +2127,7 @@ k8s 车道每条 run 一只新 pod(pod 寿命 = 泄漏护栏,不跨 run 复用)
|
|
|
2088
2127
|
- **只盖 `/workspace`**:进程、内存态、`/tmp` 不跨轮(与挂起 / 续跑同一口径)。大小上界 = 快照时限 `WORKSPACE_SNAPSHOT_TIMEOUT_MS`(缺省 300000 = 5 分钟);超时 = 快照失败,不截断。
|
|
2089
2128
|
- **排除表**(`WORKSPACE_SNAPSHOT_EXCLUDE`,7.126.0 起):逗号分隔的 tar 排除模式,相对工作区根(`build,.gradle,node_modules/.cache`),每项一个 `--exclude=./<项>`,落在**那一只**打包上传上 —— run 末快照与停驻挂起是同一张表(挂起后续跑也没有被排除的目录;构建产物、依赖缓存本就可再生)。沙箱镜像的 GNU tar 按工作区根锚定(`build` 只排除 `/workspace/build`,不排除 `/workspace/app/build`;要排子模块的就写 `app/build`,或写通配 `*/build`);镜像若换成 busybox / libarchive 的 tar,同一项会在任意深度生效。不读项目的 `.gitignore`(那是另一套文法、按项目变、与 tar 的匹配不等价);中心若要下发模式表,下发的就是这一个 env 的值。项形是闭的:相对路径段,不含 `..`、前导 `/`、空白、引号(坏项拒启,码 `config.workspace_snapshot_exclude_invalid`,点名第几项)。⚠️ 已知形:打包先落 pod 里的临时文件再上传(对象存储的预签 PUT 要 `Content-Length`,流式上传不行)⇒ 打包期间工作区占双倍盘;排除表把它缩小。
|
|
2090
2129
|
- **提交围栏(下一条 run 等上一条存完)**:「这个空间有提交在飞」是 run 店里的一行(表 `workspace_space_commit`;local 后端是数据根下 `space-commits/` 的一份记录),不在对象存储里。写点只有一个次序:run 的终态写入与释放会话占用**同一个事务**里落 `pending`(只在这条 run 碰过沙箱时)→ run 末保存开始时改成提交中 → 上传完、写 `latest` 之前再认领一次(改成发布中)→ 存完或失败都删行。**一行一个期限,写者写、所有人读**:每次写行都把行的期限定为「此刻 + 写它的那台副本的 `WORKSPACE_SNAPSHOT_TIMEOUT_MS`」;读的一方只看行里的期限,不拿自己副本的数判别人的提交 —— 所以滚动改这个数时,新旧副本互不误判。下一条 run 连沙箱前读这一行:有 ⇒ **等**,每 1 秒重读一次,直到行消失(再按新的 `latest` 恢复)或过了行里的期限。读到过了期限 = 上一条的 worker 死在终态与保存之间、死在上传里、或死在写 `latest` 时 —— 也可能写者正在这一刻续期,所以只读一眼不算数:下一条 run 在一个事务里锁住这一行、按锁下读到的库钟再判一次,仍是那条 run 的、仍过期限才删掉它(弃)。删成了 ⇒ 一行 `workspace_commit_abandoned`(`space` / `remainingMs`:≤ 0,过了期限多久 / `state`),照常恢复 `latest`(它仍指上一份**完整**快照);没删成(写者恰好续了期、行已换主或已删)⇒ 立刻重读、照常等。弃与续期在行锁上排队,被弃的写者再认领时找不到自己的行 ⇒ 不推进 `latest`。期限按存储自己的钟算(SQL 后端 = 数据库的钟),不拿两台副本的墙钟比。提交自己也守同一个期限:开始保存时它的行已过期限(下一条 run 可能已按「放弃」继续)⇒ 不上传、不推进 `latest`(`workspace_snapshot_failed`、`stage: fence`);上传完、写 `latest` 前那次认领发现行已过期限或已不是自己的 ⇒ 同样不推进(`stage: fence`)。所以引擎拆环境的 15 秒上界、存储慢都不会让下一条 run 恢复到旧的一份。终态帧上的 `workspaceSnapshot` 只承载本 run 在终态帧之前落定的结局:保存比终态帧晚落定时这条 run 的帧不带,那次结局只留运维行,也不会挂到同一会话下一条 run 的帧上。
|
|
2091
|
-
- **仍余的一条残窗(如实)**:`latest` 指针写是单对象预签 PUT
|
|
2130
|
+
- **仍余的一条残窗(如实)**:`latest` 指针写是单对象预签 PUT,这次 PUT 的时限 = 围栏剩余时长。PUT 自己到点没答、写者放弃它(一行 `workspace_snapshot_failed`,`stage: "pointer"`、`code: "connect_failed"`,`error` 点名指针写)之后,对象存储侧那次 PUT 仍可能迟到落地 ⇒ 一个已失围栏的写者的 `latest` 覆盖新写者刚发布的。围栏丢失是另一臂(`stage: "fence"`、`code: "timeout"`):那一臂不发 PUT,构造不出这扇窗。只在多副本、围栏到期、且存储侧延迟大于剩余时长时构造得出;本版不读 ETag、不做条件写,只在此披露。收法(下一版):存储支持 `If-Match` 时用 ETag 条件 PUT,不支持时 `latest` 改为带提交世代的指针对象、读者取有围栏记录的最高世代;两形由能力位如实披露。
|
|
2092
2131
|
- **指针 / 列举 / 删除由 server 直接请求 `MINIO_ENDPOINT`**(快照字节仍是 pod ↔ MinIO 直传)。server 进程必须够得着这个端点(会话快照 blob 卸载本来就从 server 进程请求同一个端点)。
|
|
2093
2132
|
|
|
2094
2133
|
| 旋钮 | 缺省 | 说明 |
|
|
@@ -2104,7 +2143,7 @@ k8s 车道每条 run 一只新 pod(pod 寿命 = 泄漏护栏,不跨 run 复用)
|
|
|
2104
2143
|
- **没有存储后端**(`DB_BACKEND=memory`;配了 SQL 的 `DB_BACKEND` 却显式 `SESSION_BACKEND=memory` 时库不开,同一形)× `space`(含缺省,且配了快照存储)⇒ **拒启**,拒句一行点名 `WORKSPACE_PERSIST=space`、缺的存储后端、会话店种类(与 `/health.sessionBackend` 同一读法)与可用的后端。出路:`DB_BACKEND=local`,或 SQL(`DB_BACKEND=mysql|pg`,`SESSION_BACKEND` 不设 `memory`),或设 `WORKSPACE_PERSIST=off`。`SESSION_BACKEND=memory` 配 `DB_BACKEND=local` 不在此列:被收编成 local 店,照起。拒启而不是降级:否则能力位报 `space`、每条 run 却一份都不存。
|
|
2105
2144
|
- **读 `latest` 失败**(非 404 的 HTTP 错、网络错)、**读或弃提交围栏那一行失败**或**读会话住客失败**(会话店报错,或答出的不是住客令牌)⇒ 这条 run 连沙箱失败(`connect_failed`,工具调用报沙箱不可用;停驻 run 续跑时的恢复同样失败),**不会**悄悄给一个空目录、也不猜「没有提交在飞」—— 「以为恢复了其实没有」比失败更坏。读住客失败时一只 pod 都不建,另有一行 `workspace_space_unreadable_occupant`(`sessionId` / `code`:会话店错误的码,不带正文),每条 run 至多一次。修好存储 / 数据库后重跑,或设 `WORKSPACE_PERSIST=off` 即恢复。
|
|
2106
2145
|
- **`latest` 指向的快照对象不在了**(被删 / 被只盖 `.tar.gz` 的生命周期规则清掉)⇒ 沙箱**照常起**:一行 `workspace_latest_dangling`(`space` / `key` / `deleted`),删掉悬空的 `latest`,这条 run 从空目录开始,它的 run 末快照成为新的 `latest`(数据已被运维规则清走:响亮、可继续)。指针删不掉 ⇒ 同一行 `deleted: false` + `error`,下一条 run 再说一次。
|
|
2107
|
-
- **`latest` 指向的快照在、却拿不回来**(
|
|
2146
|
+
- **`latest` 指向的快照在、却拿不回来**(桶答了却不是 200 / 解压失败 / 超过快照时限 / 传输中断)⇒ 这条 run **仍然连沙箱失败**(`connect_failed`)、**不**退到空目录 —— 用户的改动还在桶里,静默空目录 = 让人以为丢了。**一条 run 只恢复一次**:瞬时类(`timeout` 超过快照时限 / `transport` 传输中断:执行通道断了,或 pod 取包的 GET 没拿到完整应答 —— 连不上、被重置、传到一半被截)在恢复这一步里换一只新 pod 再试一次(约 1 秒后),再试成了 ⇒ 照常起、不出行;`store_busy`(桶答 `408` / `429` / `500` / `502` / `503` / `504` —— 「现在不行」,同一请求再发可能换答案)同属瞬时类;非瞬时类(`download` 桶答了、答的是其余非 200 —— 如 `401` / `403` / `501`;`untar` 包完整下来了却解不开)不重试。每次没成的尝试,新起的 pod 当场删掉(残缺目录既不给工具用,也不会在 run 末被快照回去覆盖 `latest`)。恢复终局是拒 ⇒ **恰一行** `workspace_restore_failed`(`space` / `snapshotId` / `class` = `store_busy` \| `download` \| `untar` \| `timeout` \| `transport` / `attempts` = 这次恢复的尝试次数(`1` 或 `2`)/ `boundMs` = 每次尝试的时限 / `error`,预签 URL 已脱敏),这条 run 之后的每次沙箱操作都答**同一句**拒(不再建 pod、不再读 `latest`、不再出行);建 pod 本身失败不是恢复的结局(不出这行,下一次操作重来)。拒句写明类别与尝试次数,点名两条出路:**调大 `WORKSPACE_SNAPSHOT_TIMEOUT_MS`**(大工作区撞上界),或 **`WORKSPACE_PERSIST=off` 跑一轮**(空目录起,不删任何快照;改回 `space` 后下一条 run 照常恢复那一份)。没有「删指针」的接口:丢弃这份快照 = 丢弃用户数据,要做就由运维在对象存储侧有意识地删 `<前缀>/<化身>/space/latest`(下一条 run 从空目录起)。与上一条「悬空」分开:那是对象**不在**(没东西可恢复 ⇒ 空目录 + 响亮),这是对象**在**而拿不回来(⇒ 拒)。
|
|
2108
2147
|
- **run 末提交围栏失守、上传失败或 `latest` 写失败** ⇒ 一行 `workspace_snapshot_failed`(`space` / `stage` = `fence` \| `upload` \| `pointer` / `code` / `error`,预签 URL 已脱敏),这条 run 在 `GET /v1/fleet/stream` 上的终态 task 帧带 `workspaceSnapshot: "failed"`;pod 照删;`latest` 不动 ⇒ 下一条 run 恢复的是**上一份**。
|
|
2109
2148
|
- **删最老的失败** ⇒ 一行 `workspace_snapshot_prune_failed`,不影响本轮(空间里只是多留几份)。
|
|
2110
2149
|
- **空间结束时回收快照对象失败**(列举失败、列举被截断〔分区里超过 1000 个对象〕、某个对象删失败)⇒ 会话照删,每个失败的分区 / 阶段一行 `workspace_space_reclaim_failed`(`space` 前缀 / `sessionId` / `lane` = `space` \| `park` / `stage` = `list` \| `delete` \| `budget` \| `seat_threw`(回收座违约抛了,响亮接住、不炸进程)/ `error`,预签 URL 不进行)+ 计数 `server.workspace-space.reclaim-failed`;
|
|
@@ -2169,6 +2208,45 @@ k8s 车道每条 run 一只新 pod(pod 寿命 = 泄漏护栏,不跨 run 复用)
|
|
|
2169
2208
|
**能力位**:`GET /v1/capabilities.workspaceSync.cache = { mount, size }`,present-iff k8s 车道 ∧ `WORKSPACE_CACHE=volume`(与快照存储无关:没配存储而开了卷的部署,
|
|
2170
2209
|
`workspaceSync` 只有 `cache` 一个成员 —— 读跨轮保留一律读 `persist === "space"`)。
|
|
2171
2210
|
|
|
2211
|
+
### 9.3e k8s 车道:沙箱出网策略的射程与 pod 的 IPv6(7.127.0 起)
|
|
2212
|
+
|
|
2213
|
+
`deploy/sema-up` chart 的 `networkpolicy-sandbox.yaml` 给沙箱 ns 一条 egress 白名单。**律**:集群内只放行点名的脸(kube-dns、chart 自带 MinIO、
|
|
2214
|
+
`sandbox.networkPolicy.objectStore` 点名的 pod);集群外只放行 **IPv4 公网面** —— 私网 / 共享 / 链路本地 / 环回六段(`10/8`、`172.16/12`、`192.168/16`、`100.64/10`、`169.254/16`、`127/8`)
|
|
2215
|
+
整段不放行,公网面上的集群地址按 `clusterCidrs`(pod / service 段)与 `nodeCidrs`(全部节点的全部 IPv4 Internal / External 地址,`kube-up.sh` 遍历节点自动填)再除外;**IPv6 不写放行规则**。
|
|
2216
|
+
|
|
2217
|
+
- **为什么是整段而不是一张地址表**:节点的地址不止 kube 报的那一只(容器网桥、第二块网卡、隧道口、其余节点),点名表漏一只就漏一面;六段整段拒之后,落在私网里的节点地址
|
|
2218
|
+
不依赖枚举完整。只有**公网**节点地址仍要点名。`kube-up.sh` 自动填 `nodeCidrs` 与起服自检(下面)读的是同一处:节点对象上报的地址(`status.addresses` 里的
|
|
2219
|
+
InternalIP / ExternalIP)—— 上报了而没点名的,自检逐个报出来;**没有上报进节点对象的公网地址**(1:1 NAT 的弹性 IP、没上报的第二块公网网卡、k3s 没带
|
|
2220
|
+
`--node-external-ip` 起的节点)既不会被自动填进 `nodeCidrs`,也不会被自检报出,要运维自己写进 `nodeCidrs`;起服之后才加入的节点,到下一次起服才被判。
|
|
2221
|
+
- **为什么不写 `::/0`**:NetworkPolicy 只有放行规则、按并集叠加,`::/0 except …` 从不拒任何东西,只会把执行 IPv6 的集群上本来只剩点名脸的 v6 出网整面放开(连带没点名的节点全局 v6 地址)。
|
|
2222
|
+
不写 ⇒ 执行 v6 的实现上 v6 出网只剩点名的集群内脸(kube-dns、对象存储);**只执行 IPv4 的实现(k3s 单栈缺省即是)根本不管 pod 的 IPv6**(link-local 照通)—— 这一面要在 pod 侧关,见 `K8S_SANDBOX_IPV6`。
|
|
2223
|
+
- **射程(这张策略是收窄,不是边界)**:① pod 所在节点的往来流量是否放行由 CNI 的实现决定(k8s 规范本身不保证拒);② 宿主上被 DNAT 的发布端口(`docker -p` 一类)
|
|
2224
|
+
在策略评估之前改写目的地址,经**任何**节点地址(含点名的那只)都可能到达 —— 策略层管不到,宿主上别发布不该被沙箱摸到的端口;③ CNI 不执行 NetworkPolicy 时整张不生效(对象在、不拒启)。
|
|
2225
|
+
- **私网里的包源 / 代码托管**:六段整段拒之后沙箱到不了它们。要放行,在沙箱 ns 另建一条 NetworkPolicy 放行那个地址(或那台主机的 /32)—— 策略按并集叠加,chart 升级不碰它;
|
|
2226
|
+
放得太宽(覆盖到节点对象上报的地址)起服自检会逐个报出。
|
|
2227
|
+
- **`objectStore` 的 namespace**:条目省 `namespace` ⇒ 跟本 release 的 ns(与 chart 自带 MinIO 同一处取值);在别的 ns 的脸照写 `namespace`。
|
|
2228
|
+
|
|
2229
|
+
**pod 的 IPv6(`K8S_SANDBOX_IPV6`,chart values `sandbox.ipv6`)**:`enabled`(缺省)= 不动 pod,pod spec 与本版之前逐字同形;`disabled` = pod 级 `securityContext.sysctls`
|
|
2230
|
+
带 `net.ipv6.conf.all.disable_ipv6=1` 与 `net.ipv6.conf.default.disable_ipv6=1`。词表外 / 空串拒启(不收 `on` / `off`:YAML 会把它们读成布尔)。
|
|
2231
|
+
|
|
2232
|
+
- **前提**:这两只 sysctl 不在 k8s 的 safe sysctl 表里 ⇒ **每个节点**的 kubelet 要 `--allowed-unsafe-sysctls` 点名它们(k3s:`--kubelet-arg=allowed-unsafe-sysctls=…`),
|
|
2233
|
+
没放行的节点上沙箱 pod 起不来(车道连沙箱失败,拒句带 pod 的失败原因);沙箱 ns 不能 enforce PodSecurity `baseline` / `restricted`(那两档只许 safe 表)。
|
|
2234
|
+
Kata 运行时把 namespaced sysctl 设在客体内核里,作用在客体的网卡上。
|
|
2235
|
+
- **pod 的 sysctl 只有这一个属主**:嵌入方的 pod-spec 补丁带 `podSecurityContext.sysctls` ⇒ 拒(补丁是浅合并,会把这里的关闭项整只换掉)。
|
|
2236
|
+
|
|
2237
|
+
**起服自检**(`REMOTE_EXEC=k8s`,起服一次、不挡起服):服务用自己的 k8s 账号读节点表(`nodes` get / list,集群级)与沙箱 ns 的 NetworkPolicy(`networkpolicies` get / list),
|
|
2238
|
+
按「选中沙箱 pod 的 Egress 策略的放行规则并集」判每个节点的每个 InternalIP / ExternalIP。读出的缺口每个一行 warn `sandbox_egress_policy_gap`(带 `effect` 后果句)并计数
|
|
2239
|
+
`server.sandbox-egress.policy-gap`(fail-open 登记表第 135 行,记债);零缺口一行 info `sandbox_egress_policy_checked`。`kind` 闭集:
|
|
2240
|
+
|
|
2241
|
+
| `kind` | 什么时候 | 去哪改 |
|
|
2242
|
+
|---|---|---|
|
|
2243
|
+
| `node_address_open` | 某节点的某个地址落在一条放行 ipBlock 内(行带 `node` / `addressType` / `address` / `policy` / `ports`) | 把地址加进 `nodeCidrs`(kube-up 重跑会重新遍历节点),或收窄那条放行 |
|
|
2244
|
+
| `policy_absent` | 没有一条 NetworkPolicy 以 Egress 选中沙箱 pod | 开 `sandbox.networkPolicy.enabled`,或核 CNI 自己的策略 |
|
|
2245
|
+
| `ipv6_enabled` | `K8S_SANDBOX_IPV6=enabled` | 见上一段;集群执行 v6 时这一行只是提醒 |
|
|
2246
|
+
| `unreadable` | 节点表或策略表读不到(`what` = `nodes` / `networkpolicies`;`detail` = HTTP 码或错误句) | 给服务账号补两只只读(chart 与 cluster 静态清单已带;手写 RBAC 的部署要补) |
|
|
2247
|
+
|
|
2248
|
+
自检是**读数,不是边界**:起服之后加入的节点在下一次起服读到;射程里的三面(所在节点、宿主 DNAT、CNI 不执行)读不出,不在闭集里。
|
|
2249
|
+
|
|
2172
2250
|
### 9.4 auto 模式(`permissionMode:"auto"`):武装三态、本地 org deny、自查(与 core 的意图武装式合修)
|
|
2173
2251
|
|
|
2174
2252
|
**背景**:此前引擎只在「组织授予」(web-admin 解出的 per-principal `runtimeCaps.autoMode === true`)时武装
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
{{- /*
|
|
2
2
|
沙箱出网白名单(values: sandbox.networkPolicy)。沙箱 pod 由 server 的 k8s 车道按需创建、无固定标签,
|
|
3
3
|
所以 podSelector 选整个 ns。只写 egress:沙箱不需要任何入站(server 经 k8s API exec 进去,不走网络)。
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
放行:kube-dns 53;对象存储(chart 自带 MinIO 9000 + objectStore 点名的脸);IPv4 公网面(包源 / 代码托管)——
|
|
5
|
+
私网 / 共享 / 链路本地 / 环回六段整段除外,公网面上的集群地址(clusterCidrs、nodeCidrs = 全部节点的 IPv4 地址)再除外。
|
|
6
|
+
IPv6 不写放行规则(NetworkPolicy 只有放行规则:写 ::/0 只会放宽,执行 v6 的集群上 v6 出网因此只剩上面点名的集群内脸;
|
|
7
|
+
只执行 IPv4 的集群上 pod 的 v6 不受策略管,要关在 pod 侧 —— sandbox.ipv6)。
|
|
8
|
+
这张策略是收窄,不是边界:pod 所在节点的往来流量是否放行由 CNI 的实现决定;宿主上 DNAT 的发布端口
|
|
9
|
+
(docker -p 一类)在策略评估之前改写目的地址,经任何节点地址都可能到达;CNI 不执行 NetworkPolicy 时整张不生效。
|
|
10
|
+
server 起服时读节点表与本策略,读出的缺口每个一行 sandbox_egress_policy_gap。
|
|
6
11
|
*/ -}}
|
|
7
12
|
{{- if .Values.sandbox.networkPolicy.enabled }}
|
|
8
13
|
{{- $np := .Values.sandbox.networkPolicy }}
|
|
9
|
-
{{- if not $np.nodeCidrs }}{{ fail "sandbox.networkPolicy.nodeCidrs 必填(
|
|
14
|
+
{{- if not $np.nodeCidrs }}{{ fail "sandbox.networkPolicy.nodeCidrs 必填(全部节点的 IPv4 地址,/32;kube-up 遍历节点自动填)" }}{{ end }}
|
|
15
|
+
{{- range $k, $list := dict "clusterCidrs" $np.clusterCidrs "nodeCidrs" $np.nodeCidrs }}
|
|
16
|
+
{{- range $list }}
|
|
17
|
+
{{- if or (not (contains "/" .)) (contains ":" .) }}{{ fail (printf "sandbox.networkPolicy.%s 的 %q 不是带前缀的 IPv4 CIDR(例 203.0.113.7/32;IPv6 没有放行规则,不用列)" $k .) }}{{ end }}
|
|
18
|
+
{{- end }}
|
|
19
|
+
{{- end }}
|
|
10
20
|
apiVersion: networking.k8s.io/v1
|
|
11
21
|
kind: NetworkPolicy
|
|
12
22
|
metadata:
|
|
@@ -34,7 +44,7 @@ spec:
|
|
|
34
44
|
{{- range $np.objectStore }}
|
|
35
45
|
- to:
|
|
36
46
|
- namespaceSelector:
|
|
37
|
-
matchLabels: { kubernetes.io/metadata.name: {{ .namespace }} }
|
|
47
|
+
matchLabels: { kubernetes.io/metadata.name: {{ .namespace | default $.Release.Namespace }} }
|
|
38
48
|
podSelector:
|
|
39
49
|
matchLabels: {{ toJson .podLabels }}
|
|
40
50
|
ports:
|
|
@@ -43,5 +53,5 @@ spec:
|
|
|
43
53
|
- to:
|
|
44
54
|
- ipBlock:
|
|
45
55
|
cidr: 0.0.0.0/0
|
|
46
|
-
except: {{ concat
|
|
56
|
+
except: {{ concat (list "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16" "100.64.0.0/10" "169.254.0.0/16" "127.0.0.0/8") $np.clusterCidrs $np.nodeCidrs | uniq | toJson }}
|
|
47
57
|
{{- end }}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ── 沙箱 lane RBAC(k8s+kata;pod 五动词 + 缓存卷四动词)────────────────────────────────
|
|
1
|
+
# ── 沙箱 lane RBAC(k8s+kata;pod 五动词 + 缓存卷四动词 + 出网自检的两只只读:本 ns 的 NetworkPolicy、集群的节点表)────────────────────────────────
|
|
2
2
|
apiVersion: v1
|
|
3
3
|
kind: ServiceAccount
|
|
4
4
|
metadata:
|
|
@@ -33,6 +33,10 @@ rules:
|
|
|
33
33
|
- apiGroups: [""]
|
|
34
34
|
resources: [persistentvolumeclaims]
|
|
35
35
|
verbs: [get, create, delete, deletecollection]
|
|
36
|
+
# 起服的出网自检读本 ns 的 NetworkPolicy(只读)
|
|
37
|
+
- apiGroups: [networking.k8s.io]
|
|
38
|
+
resources: [networkpolicies]
|
|
39
|
+
verbs: [get, list]
|
|
36
40
|
---
|
|
37
41
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
38
42
|
kind: RoleBinding
|
|
@@ -47,3 +51,26 @@ subjects:
|
|
|
47
51
|
- kind: ServiceAccount
|
|
48
52
|
name: sema-runner
|
|
49
53
|
namespace: {{ .Values.sandbox.namespace }}
|
|
54
|
+
---
|
|
55
|
+
# 出网自检读节点表(节点是集群级对象 ⇒ ClusterRole;只读两动词)。没给 ⇒ server 起服一行 sandbox_egress_policy_gap{kind: unreadable}。
|
|
56
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
57
|
+
kind: ClusterRole
|
|
58
|
+
metadata:
|
|
59
|
+
name: sema-runner-nodes-{{ .Values.sandbox.namespace }}
|
|
60
|
+
rules:
|
|
61
|
+
- apiGroups: [""]
|
|
62
|
+
resources: [nodes]
|
|
63
|
+
verbs: [get, list]
|
|
64
|
+
---
|
|
65
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
66
|
+
kind: ClusterRoleBinding
|
|
67
|
+
metadata:
|
|
68
|
+
name: sema-runner-nodes-{{ .Values.sandbox.namespace }}
|
|
69
|
+
roleRef:
|
|
70
|
+
apiGroup: rbac.authorization.k8s.io
|
|
71
|
+
kind: ClusterRole
|
|
72
|
+
name: sema-runner-nodes-{{ .Values.sandbox.namespace }}
|
|
73
|
+
subjects:
|
|
74
|
+
- kind: ServiceAccount
|
|
75
|
+
name: sema-runner
|
|
76
|
+
namespace: {{ .Values.sandbox.namespace }}
|
|
@@ -70,6 +70,7 @@ spec:
|
|
|
70
70
|
- { name: K8S_NAMESPACE, value: {{ .Values.sandbox.namespace }} }
|
|
71
71
|
- { name: K8S_RUNTIME_CLASS, value: {{ .Values.sandbox.runtimeClass }} }
|
|
72
72
|
- { name: K8S_SANDBOX_IMAGE, value: {{ .Values.sandbox.image | quote }} }
|
|
73
|
+
- { name: K8S_SANDBOX_IPV6, value: {{ .Values.sandbox.ipv6 | quote }} }
|
|
73
74
|
- { name: K8S_TOKEN, valueFrom: { secretKeyRef: { name: sema-runner-token-copy, key: token } } }
|
|
74
75
|
readinessProbe: # the only probe path is /readyz (/health is diagnostic, always 200)
|
|
75
76
|
httpGet: { path: /readyz, port: 8090 }
|
|
@@ -83,6 +83,7 @@ spec:
|
|
|
83
83
|
- { name: K8S_RUNTIME_CLASS, value: {{ .Values.sandbox.runtimeClass }} }
|
|
84
84
|
{{- end }}
|
|
85
85
|
- { name: K8S_SANDBOX_IMAGE, value: {{ .Values.sandbox.image | quote }} }
|
|
86
|
+
- { name: K8S_SANDBOX_IPV6, value: {{ .Values.sandbox.ipv6 | quote }} }
|
|
86
87
|
- { name: K8S_TOKEN, valueFrom: { secretKeyRef: { name: sema-runner-token-copy, key: token } } }
|
|
87
88
|
readinessProbe: # the only probe path is /readyz (/health is diagnostic, always 200)
|
|
88
89
|
httpGet: { path: /readyz, port: 8090 }
|
|
@@ -110,18 +110,28 @@ sandbox:
|
|
|
110
110
|
# 小巧默认,重任务用 sandboxImageProfile 按需选 code-python/code-full 等,Hub 池 public 匿名可拉)。
|
|
111
111
|
# 裸 OS 镜像(如 ubuntu)没有 pkg-source hook——sandboxPkgSource=global 时官方源不会材料化,别退回。
|
|
112
112
|
image: docker.io/claybobby/code-node:20260707-749ab84f5fdd-786f5ef10ffd
|
|
113
|
+
# 沙箱 pod 的 IPv6(→ server env K8S_SANDBOX_IPV6;词:enabled | disabled —— 不写 on/off,YAML 会读成布尔):
|
|
114
|
+
# disabled = pod 级 sysctl 关 IPv6(net.ipv6.conf.all / default.disable_ipv6=1)。两只都不在 k8s 的 safe sysctl 表里 ⇒
|
|
115
|
+
# 每个节点的 kubelet 要 --allowed-unsafe-sysctls 点名它们,否则沙箱 pod 起不来;沙箱 ns 不能 enforce PodSecurity baseline。
|
|
116
|
+
# enabled = 不动 pod(缺省):只执行 IPv4 的出网策略(k3s 单栈缺省即是)管不到 pod 的 IPv6,server 起服每次一行提醒。
|
|
117
|
+
ipv6: enabled
|
|
113
118
|
# 沙箱出网策略(NetworkPolicy,egress 白名单;要集群 CNI 带 NetworkPolicy 控制器 —— k3s 缺省自带):
|
|
114
|
-
# 放行 = DNS(kube-dns)+ 对象存储(chart 自带的 MinIO 9000,加 objectStore 点名的脸)+
|
|
115
|
-
# 拒 =
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
+
# 放行 = DNS(kube-dns)+ 对象存储(chart 自带的 MinIO 9000,加 objectStore 点名的脸)+ IPv4 公网面;
|
|
120
|
+
# 拒 = 私网 / 共享 / 链路本地 / 环回六段(10/8、172.16/12、192.168/16、100.64/10、169.254/16、127/8)整段,
|
|
121
|
+
# 加公网面上的集群地址:clusterCidrs(pod / service 段,k3s 缺省值;别的发行版按实填)与 nodeCidrs;IPv6 不写地址块放行(只剩上面点名的集群内脸)。
|
|
122
|
+
# nodeCidrs = 全部节点的全部 IPv4 Internal / External 地址(/32;kube-up 遍历节点自动填),enabled 时必填;两张表只收带前缀的
|
|
123
|
+
# IPv4 CIDR。手写 values 的部署按实列,新加节点要补(server 起服会把落在放行面里的节点地址逐个报 sandbox_egress_policy_gap)。
|
|
124
|
+
# 射程:这是收窄、不是边界 —— pod 所在节点的往来流量是否放行由 CNI 实现决定;宿主上 DNAT 的发布端口(docker -p 一类)在策略
|
|
125
|
+
# 评估之前改写目的地址,经任何节点地址都可能到达;要访问私网里的包源 / 代码托管,在沙箱 ns 另建一条 NetworkPolicy 放行那个
|
|
126
|
+
# 地址(策略按并集叠加)。
|
|
127
|
+
# objectStore = 集群内还要放行的对象存储脸(pod 标签 + 端口;namespace 省略 = 跟本 release 的 ns):s3PublicEndpoint 指向
|
|
128
|
+
# NodePort 时,沙箱打到节点 IP 的流量被 kube-proxy DNAT 到背后的反代 pod,策略看到的是那个 pod —— 所以要按 pod 点名。
|
|
119
129
|
networkPolicy:
|
|
120
130
|
enabled: true
|
|
121
131
|
clusterCidrs: ["10.42.0.0/16", "10.43.0.0/16"]
|
|
122
132
|
nodeCidrs: []
|
|
123
133
|
objectStore:
|
|
124
|
-
- {
|
|
134
|
+
- { podLabels: { app: sema-ip-proxy }, port: 8445 }
|
|
125
135
|
|
|
126
136
|
# 私有 registry 拉取(6b:secret 由 cluster-up 建 + patch default SA;这里的显式引用是纵深,
|
|
127
137
|
# 留空=只靠 SA 注入)
|
|
@@ -37,6 +37,8 @@ unset _self _hops _dir
|
|
|
37
37
|
. "$HERE/lib/health.sh"
|
|
38
38
|
# 对象存储桶的唯一表(sema-up.sh / cluster-up.sh / smoke.sh 同读这一张)。
|
|
39
39
|
. "$HERE/lib/buckets.sh"
|
|
40
|
+
# 集群节点地址的唯一读法(NodePort 入口 + 沙箱出网策略要拒的全部节点地址;同读一份节点表)。
|
|
41
|
+
. "$HERE/lib/node-addrs.sh"
|
|
40
42
|
log() { printf '[kube-up] %s\n' "$*" >&2; }
|
|
41
43
|
die() { log "⛔ $*"; exit 1; }
|
|
42
44
|
|
|
@@ -201,8 +203,12 @@ envf_load MINIO_PASSWORD SERVICE_AUTH_TOKEN REGISTRY_ADMIN_PASSWORD REGISTRY_SES
|
|
|
201
203
|
GIT_ADMIN_USER GIT_ADMIN_PASSWORD REGISTRY_GITEA_BASE_URL REGISTRY_GITEA_OAUTH_ID REGISTRY_GITEA_OAUTH_SECRET
|
|
202
204
|
[ -n "${MODEL_API_KEY:-}" ] || die "需要 MODEL_API_KEY(--model-key 或 -f 配置)"
|
|
203
205
|
|
|
204
|
-
#
|
|
205
|
-
NODE_IP
|
|
206
|
+
# 节点表读一次,两个读数(lib/node-addrs.sh):
|
|
207
|
+
# · NODE_IP = 第一只节点的第一只 InternalIP —— NodePort 入口(kind 场景宿主上不可达,summary 会给 port-forward 替代);
|
|
208
|
+
# · NODE_CIDRS = 全部节点的全部 IPv4 Internal / External 地址(/32)—— 沙箱出网策略的 nodeCidrs(不止 items[0]、不止第一只地址)。
|
|
209
|
+
NODES_JSON="$($KUBECTL get nodes -o json)" || die "读不到节点表(kubectl get nodes)。下一步:核 kubeconfig 与权限"
|
|
210
|
+
NODE_IP="$(node_primary_ip "$NODES_JSON")" || die "节点表里第一只节点没有 InternalIP —— NodePort 入口定不下来。下一步:kubectl get nodes -o wide"
|
|
211
|
+
NODE_CIDRS="$(node_cidrs_json "$NODES_JSON")" || die "节点表里读不出任何 IPv4 节点地址 —— 沙箱出网策略要拒的节点地址列不出来,不装一份漏了节点的策略。下一步:kubectl get nodes -o wide"
|
|
206
212
|
|
|
207
213
|
# ── 5. helm 装配(凭据经 stdin;两遍:git=provision 时先无 OAuth 起栈,引导完 Gitea
|
|
208
214
|
# 再带 OAuth 升级——registry 第一遍已可表单登录,不是半成品)────────────────────
|
|
@@ -217,7 +223,7 @@ helm_values() { # helm_values <giteaBaseUrl> <oauthId> <oauthSecret>
|
|
|
217
223
|
V_REG_ENABLED="$REGISTRY_ENABLED" V_REG_IMAGE="${SEMA_REGISTRY_IMAGE:-}" \
|
|
218
224
|
V_REG_PW="${REGISTRY_ADMIN_PASSWORD:-}" V_REG_SS="${REGISTRY_SESSION_SECRET:-}" V_REG_PT="${REGISTRY_PULL_TOKEN:-}" \
|
|
219
225
|
V_GITEA_ENABLED="$([ "$GIT_MODE" = provision ] && echo true || echo false)" \
|
|
220
|
-
V_GITEA_IMAGE="${GITEA_IMAGE:-gitea/gitea:1.24}" V_NODE_IP="$NODE_IP" \
|
|
226
|
+
V_GITEA_IMAGE="${GITEA_IMAGE:-gitea/gitea:1.24}" V_NODE_IP="$NODE_IP" V_NODE_CIDRS="$NODE_CIDRS" \
|
|
221
227
|
V_MINIO_IMAGE="${MINIO_IMAGE:-}" \
|
|
222
228
|
python3 - <<'PY'
|
|
223
229
|
import json, os
|
|
@@ -241,7 +247,7 @@ v = {
|
|
|
241
247
|
"sandboxPkgSource": e["V_PKG_SOURCE"], "requirePrincipal": e["V_REQ_PRINCIPAL"],
|
|
242
248
|
},
|
|
243
249
|
"sandbox": {"runtimeClass": e["V_RT_CLASS"],
|
|
244
|
-
"networkPolicy": {"nodeCidrs":
|
|
250
|
+
"networkPolicy": {"nodeCidrs": json.loads(e["V_NODE_CIDRS"])}},
|
|
245
251
|
"registry": {"enabled": e["V_REG_ENABLED"] == "true", "image": e["V_REG_IMAGE"],
|
|
246
252
|
"storageClass": e["V_SC"], "adminPassword": e["V_REG_PW"],
|
|
247
253
|
"sessionSecret": e["V_REG_SS"], "pullToken": e["V_REG_PT"],
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# lib/node-addrs.sh — 「集群节点有哪些地址」的唯一读法(kube-up 用;输入 = `kubectl get nodes -o json` 的体)。
|
|
2
|
+
#
|
|
3
|
+
# 两个读数,同读一份节点表(一次 kubectl):
|
|
4
|
+
# · node_primary_ip —— 第一只节点的第一只 InternalIP:NodePort 入口(Gitea 克隆地址、OAuth 回调、summary 里的访问地址);
|
|
5
|
+
# · node_cidrs_json —— 全部节点的全部 IPv4 InternalIP / ExternalIP,各成 /32,去重保序,打印成 JSON 数组:沙箱出网策略
|
|
6
|
+
# `sandbox.networkPolicy.nodeCidrs`。IPv6 地址不列 —— 策略不写 IPv6 放行规则,v6 地址无可再拒。
|
|
7
|
+
# 读不出 / 零地址 ⇒ 非 0 退出、stdout 空(调用方 die,不渲一份漏了节点的策略)。
|
|
8
|
+
_node_addrs_py() { # _node_addrs_py <primary|cidrs> (stdin = 节点表 JSON)
|
|
9
|
+
python3 -c 'import ipaddress, json, sys
|
|
10
|
+
mode = sys.argv[1]
|
|
11
|
+
try:
|
|
12
|
+
items = json.load(sys.stdin).get("items")
|
|
13
|
+
except Exception:
|
|
14
|
+
sys.exit(2)
|
|
15
|
+
if not isinstance(items, list):
|
|
16
|
+
sys.exit(2)
|
|
17
|
+
def addrs(node, kinds):
|
|
18
|
+
out = []
|
|
19
|
+
for a in ((node or {}).get("status") or {}).get("addresses") or []:
|
|
20
|
+
if isinstance(a, dict) and a.get("type") in kinds and isinstance(a.get("address"), str):
|
|
21
|
+
try:
|
|
22
|
+
out.append(ipaddress.ip_address(a["address"]))
|
|
23
|
+
except ValueError:
|
|
24
|
+
pass
|
|
25
|
+
return out
|
|
26
|
+
if mode == "primary":
|
|
27
|
+
first = addrs(items[0], ("InternalIP",)) if items else []
|
|
28
|
+
if not first:
|
|
29
|
+
sys.exit(3)
|
|
30
|
+
print(first[0])
|
|
31
|
+
else:
|
|
32
|
+
seen = []
|
|
33
|
+
for n in items:
|
|
34
|
+
for ip in addrs(n, ("InternalIP", "ExternalIP")):
|
|
35
|
+
if ip.version == 4 and f"{ip}/32" not in seen:
|
|
36
|
+
seen.append(f"{ip}/32")
|
|
37
|
+
if not seen:
|
|
38
|
+
sys.exit(3)
|
|
39
|
+
print(json.dumps(seen))' "$1"
|
|
40
|
+
}
|
|
41
|
+
node_primary_ip() { printf '%s' "$1" | _node_addrs_py primary; }
|
|
42
|
+
node_cidrs_json() { printf '%s' "$1" | _node_addrs_py cidrs; }
|
|
@@ -92,7 +92,7 @@ kind: Service
|
|
|
92
92
|
metadata: { name: sema-minio, namespace: sema }
|
|
93
93
|
spec: { selector: { app: sema-minio }, ports: [{ name: s3, port: 9000 }, { name: console, port: 9001 }] }
|
|
94
94
|
---
|
|
95
|
-
# ── 沙箱 lane RBAC(k8s+kata;测试机同款五动词)────────────────────────────────
|
|
95
|
+
# ── 沙箱 lane RBAC(k8s+kata;测试机同款五动词 + 缓存卷 + 出网自检两只只读)────────────────────────────────
|
|
96
96
|
apiVersion: v1
|
|
97
97
|
kind: ServiceAccount
|
|
98
98
|
metadata: { name: sema-runner, namespace: sema-sandbox }
|
|
@@ -122,6 +122,10 @@ rules:
|
|
|
122
122
|
- apiGroups: [""]
|
|
123
123
|
resources: [persistentvolumeclaims]
|
|
124
124
|
verbs: [get, create, delete, deletecollection]
|
|
125
|
+
# 起服的出网自检读本 ns 的 NetworkPolicy(只读)
|
|
126
|
+
- apiGroups: [networking.k8s.io]
|
|
127
|
+
resources: [networkpolicies]
|
|
128
|
+
verbs: [get, list]
|
|
125
129
|
---
|
|
126
130
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
127
131
|
kind: RoleBinding
|
|
@@ -129,6 +133,21 @@ metadata: { name: sema-runner, namespace: sema-sandbox }
|
|
|
129
133
|
roleRef: { apiGroup: rbac.authorization.k8s.io, kind: Role, name: sema-runner }
|
|
130
134
|
subjects: [{ kind: ServiceAccount, name: sema-runner, namespace: sema-sandbox }]
|
|
131
135
|
---
|
|
136
|
+
# 出网自检读节点表(集群级对象 ⇒ ClusterRole;只读两动词)。与 chart 的 sandbox-rbac.yaml 同形。
|
|
137
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
138
|
+
kind: ClusterRole
|
|
139
|
+
metadata: { name: sema-runner-nodes-sema-sandbox }
|
|
140
|
+
rules:
|
|
141
|
+
- apiGroups: [""]
|
|
142
|
+
resources: [nodes]
|
|
143
|
+
verbs: [get, list]
|
|
144
|
+
---
|
|
145
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
146
|
+
kind: ClusterRoleBinding
|
|
147
|
+
metadata: { name: sema-runner-nodes-sema-sandbox }
|
|
148
|
+
roleRef: { apiGroup: rbac.authorization.k8s.io, kind: ClusterRole, name: sema-runner-nodes-sema-sandbox }
|
|
149
|
+
subjects: [{ kind: ServiceAccount, name: sema-runner, namespace: sema-sandbox }]
|
|
150
|
+
---
|
|
132
151
|
# ── 引擎(3 副本跨机打散;镜像=节点预灌 sema-server:local,imagePullPolicy Never
|
|
133
152
|
# ⚠ k3s-uninstall.sh 会连 containerd 镜像一起清,重装过的节点必须补灌)──────────
|
|
134
153
|
apiVersion: v1
|
package/dist/audit.js
CHANGED
|
@@ -128,6 +128,7 @@ const MESSAGE_READ_TREATMENT = {
|
|
|
128
128
|
role: "verbatim", content: "verbatim", timestamp: "verbatim", enginePrefixChars: "verbatim", engineSegments: "verbatim",
|
|
129
129
|
engineMinted: "verbatim", engineNoteKey: "verbatim", provenance: "verbatim", actor: "verbatim",
|
|
130
130
|
taskId: "verbatim",
|
|
131
|
+
attachment: "verbatim",
|
|
131
132
|
},
|
|
132
133
|
custom: { role: "verbatim", customType: "verbatim", content: "verbatim", display: "verbatim", details: "verbatim", provenance: "verbatim", timestamp: "verbatim" },
|
|
133
134
|
assistant: {
|