@sema-agent/server 7.115.0 → 7.116.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 +167 -4
- package/USAGE.md +6 -3
- package/dist/approval-ask-audit-store.d.ts +25 -6
- package/dist/approval-ask-audit-store.js +6 -1
- package/dist/approval-card.d.ts +8 -4
- package/dist/approval-card.js +2 -0
- package/dist/approval-crash-convergence.d.ts +60 -0
- package/dist/approval-crash-convergence.js +75 -0
- package/dist/approval-deny-reasons.d.ts +13 -0
- package/dist/approval-deny-reasons.js +2 -0
- package/dist/approval.js +2 -1
- package/dist/boot/coordinators.js +0 -12
- package/dist/boot/execution-env.js +2 -1
- package/dist/boot/resolve-spec.js +1 -0
- package/dist/boot/stage-07-capability-layer.js +21 -15
- package/dist/boot/stores.js +4 -4
- package/dist/capabilities/hands-lane.d.ts +0 -23
- package/dist/capabilities/hands-lane.js +3 -2
- package/dist/capabilities/send-user-file-tool.d.ts +10 -22
- package/dist/capabilities/send-user-file-tool.js +1 -8
- package/dist/capability-facts.d.ts +296 -0
- package/dist/capability-facts.js +354 -0
- package/dist/config-center/apply-effective.js +1 -1
- package/dist/config.d.ts +16 -13
- package/dist/config.js +12 -12
- package/dist/http/admission.d.ts +6 -0
- package/dist/http/admission.js +22 -5
- package/dist/http/principal-gate.d.ts +3 -0
- package/dist/http/principal-gate.js +3 -0
- package/dist/http/resume-legs.js +10 -1
- package/dist/http/retired-skill-manifest.d.ts +23 -0
- package/dist/http/retired-skill-manifest.js +22 -0
- package/dist/http/route-table.d.ts +3 -2
- package/dist/http/route-table.js +5 -5
- package/dist/http/routes/a2a-serve.js +14 -21
- package/dist/http/routes/admin-config-refresh.js +7 -2
- package/dist/http/routes/admin-drain.js +7 -2
- package/dist/http/routes/admin-memory-optout.js +20 -6
- package/dist/http/routes/adoption.js +19 -5
- package/dist/http/routes/agents-roster.js +4 -2
- package/dist/http/routes/approvals-assistant.js +41 -18
- package/dist/http/routes/background.d.ts +0 -2
- package/dist/http/routes/background.js +4 -5
- package/dist/http/routes/capabilities.d.ts +2 -1
- package/dist/http/routes/capabilities.js +62 -65
- package/dist/http/routes/config-catalog.d.ts +4 -5
- package/dist/http/routes/config-catalog.js +16 -2
- package/dist/http/routes/devices.js +17 -11
- package/dist/http/routes/diagnostics.js +6 -1
- package/dist/http/routes/fleet.js +4 -2
- package/dist/http/routes/images.js +30 -3
- package/dist/http/routes/leader.js +7 -1
- package/dist/http/routes/mcp-reconnect.js +17 -10
- package/dist/http/routes/memory-bundle.js +20 -6
- package/dist/http/routes/memory-compliance.js +19 -5
- package/dist/http/routes/memory-consolidation.d.ts +9 -12
- package/dist/http/routes/memory-consolidation.js +17 -3
- package/dist/http/routes/memory-origin.js +19 -5
- package/dist/http/routes/notify-wake.js +5 -3
- package/dist/http/routes/observability.js +19 -5
- package/dist/http/routes/retention-ops.js +6 -1
- package/dist/http/routes/rules.js +49 -21
- package/dist/http/routes/run-memory-capture.js +5 -2
- package/dist/http/routes/runs.js +90 -60
- package/dist/http/routes/session-sync.js +64 -53
- package/dist/http/routes/sessions-list.js +8 -5
- package/dist/http/routes/sessions.js +39 -24
- package/dist/http/routes/shared-memory.js +5 -3
- package/dist/http/routes/tasks.js +2 -1
- package/dist/http/routes/trace-usage.js +18 -11
- package/dist/http/routes/workflows.js +36 -13
- package/dist/http/server.js +4 -1
- package/dist/http/wire-types.d.ts +6 -0
- package/dist/namespaced-rule-name.d.ts +54 -0
- package/dist/namespaced-rule-name.js +33 -0
- package/dist/observability/corrupt-read-seat.d.ts +4 -3
- package/dist/observability/corrupt-read-seat.js +4 -3
- package/dist/observability/fail-open.d.ts +5 -1
- package/dist/observability/fail-open.js +5 -1
- package/dist/observability/permission-rule-events.d.ts +3 -0
- package/dist/observability/permission-rule-events.js +4 -0
- package/dist/plugins/file-run-store.d.ts +7 -1
- package/dist/plugins/file-run-store.js +20 -7
- package/dist/plugins/memory-run-store.js +2 -1
- package/dist/plugins/one-time-migrations.d.ts +4 -2
- package/dist/plugins/one-time-migrations.js +26 -3
- package/dist/plugins/pg-session-storage.js +1 -1
- package/dist/plugins/run-store-sql.js +4 -4
- package/dist/plugins/session-policy-store-sql.js +4 -2
- package/dist/plugins/store-backend.d.ts +6 -1
- package/dist/plugins/store-backend.js +2 -1
- package/dist/plugins/store-contracts.d.ts +27 -8
- package/dist/plugins/store-contracts.js +12 -3
- package/dist/plugins/tidb-session-store.js +1 -1
- package/dist/request-key-closure.js +1 -1
- package/dist/run-local.d.ts +37 -3
- package/dist/run-local.js +48 -5
- package/dist/runs.d.ts +0 -58
- package/dist/runs.js +15 -20
- package/dist/session-policy-wire.d.ts +40 -60
- package/dist/session-policy-wire.js +60 -32
- package/dist/spec-fields.d.ts +32 -0
- package/dist/spec-fields.js +31 -16
- package/dist/standing-answer-grant.d.ts +8 -0
- package/dist/standing-answer-grant.js +5 -0
- package/dist/task-settings.d.ts +13 -3
- package/dist/tool-approval.d.ts +16 -0
- package/dist/tool-approval.js +29 -7
- package/dist/trace/core-keyset-guard.d.ts +4 -4
- package/dist/trace/self-minted-wire.d.ts +42 -0
- package/dist/trace/self-minted-wire.js +13 -0
- package/dist/user-input-ledger.d.ts +8 -5
- package/package.json +4 -2
- package/scripts/drain-check-local.mjs +115 -25
- package/scripts/drain-check.mysql.sql +67 -0
- package/scripts/drain-check.pg.sql +71 -0
package/MIGRATION.md
CHANGED
|
@@ -7,6 +7,168 @@
|
|
|
7
7
|
> ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
|
|
8
8
|
> 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
|
|
9
9
|
|
|
10
|
+
## 7.116.0 —— `@sema-agent/core` 7.40.1 → 7.41.2:技能 `manifest` 退役(400)· 技能清单从围栏改成本次 run 的规则来源(也是一次放宽)· 引擎 `Skill` 卡上「本会话放行」只放行一次(新卡面位 `rememberRefused`)· 新请求键 `allowedTools` / `disallowedTools` / `userInvokedSkills` 与能力位 `userInvokedSkills` / `skillToolLists` · 停驻的 `Skill` 卡两种拒法 · 回滚到 7.115.x 前排空停驻行 · 排空配方进包 · 新询问类 `requires_user_interaction` · 停驻批准后的调用再过一次 PreToolUse · 文件店拒绝句 / 会话同步导出 / 「没人能答」拒句 / 旧工具名拒体的四处读面小改 · 服务器段铸不出的协议规则名拒 · local 重启后对上一世审批卡的作答 200 → 410 · run 死亡判决的机读码 `errorCode`(含启动期一次性迁移补码)· 两段覆盖形 `mcp__<server>` 在配置表受理 · steer 的 `priority` 交给引擎(能力位 `fleet.steerPriority` → `"engine"`)· 能力位与端点的不可用答法读同一判据(多族 403 / 404 → 501、`a2aServe` 在无后端部署上由真变假、新位 `sessionSyncFileHistory` / `workflowsJournal`)· 后台子代三张读面的文本变了 · run-local 无终端拒句按问的类说话
|
|
11
|
+
|
|
12
|
+
- **🔴 技能的 `manifest` 退役(硬破坏,零别名)**。`body.skills[].manifest` 任何值 ⇒ 400 `request.field_invalid`(句子 `skill.manifest was retired in core 7.41.0 — …`);
|
|
13
|
+
`agents[].skills[].manifest` ⇒ 那条技能按未知键整条拒(`agent.skills must be …`)。改写:`manifest.allowTools` → 技能的 `allowedTools`;`allowPaths`(写围栏)没有替身。
|
|
14
|
+
≤7.115.x 上**停驻中**、提交体里顶层技能带 `manifest` 的任务,7.116.0 续跑时引擎在认领前按名拒(`409 checkpoint.config_refused`,`field:"taskConfig"`,卡仍 pending)——
|
|
15
|
+
升级前在当前部署上答完或取消(`POST /v1/runs/:id/cancel`);子代技能(`agents[].skills[]`)带 `manifest` 的存量体续跑时不被拒,那只子代定义被整只丢掉并留一条 warn 日志 `task_agent_dropped`(任务照跑、少一只子代)。
|
|
16
|
+
- **🔴 技能清单的语义变了(行为面)**:`allowedTools` 是本次 run 的 **allow 规则来源**,不是围栏 —— 技能被激活后列着的工具本 run 内免问(清的是 `unstated` / `ordinary` 两类问,
|
|
17
|
+
与持久 allow 规则同一只谓词;deny / ask 规则、plan 模式、shell 地板、写保护、服务器标记、必须到人的问照出),**没列的工具照常判,不再因为没列被拒**;`disallowedTools` 本 run 内拒。
|
|
18
|
+
模型激活一只带清单的技能先出一张 `Skill` 卡(`askClass: "unstated"`,卡文列出批准后免问的条目);无审批席 ⇒ 那张卡被拒。
|
|
19
|
+
① *谁受影响*:靠技能清单圈工具 / 圈写路径的部署与调用方。② *不改会怎样*:没列的工具在询问类模式下被问、在 `bypassPermissions` 或部署 allow 下直接跑。
|
|
20
|
+
③ *怎么改*:圈工具 —— deny 规则(请求 `settings.permissions.deny` / 运维 `APPROVAL_DENY`)或子代定义的 `agents[].allowTools`;圈写路径 —— 会话规则 `allowDirs`
|
|
21
|
+
(`PUT /v1/sessions/:id/policy`);某只工具必须问 —— `APPROVAL_REQUIRE`(ask 规则;技能清单清不掉它,预算自动批与会话放行照旧退得掉);必须到人 —— `APPROVAL_NEVER_AUTO`;
|
|
22
|
+
shell 必须问 —— `MANUAL_MODE_SHELL_GATE=always`;没有审批席又要用技能 —— 提交体 `userInvokedSkills` 点名(不出卡)。
|
|
23
|
+
- **🔴 这也是一次放宽:请求自带技能的清单 + `userInvokedSkills` 比同版 `settings.permissions.allow` 宽**。`settings.permissions.allow` 是表态,内置工具上引擎自己的写门 / shell 普通问照问;
|
|
24
|
+
技能清单(被 `userInvokedSkills` 点名、或模型激活且卡被批准之后)把这些问也放掉 —— 写进清单的 `Write` / `Edit` / 整只 `Bash`(非 `auto` 模式)本 run 内不再问人。
|
|
25
|
+
① *谁受影响*:依赖「`default` 模式下每个 `Bash` / 写调用都会到人」的部署,尤其不认 `bypassPermissions` 的托管 / 多租户部署 —— 调用方此前拿不到这一格,现在在自己的请求里就能拿到。
|
|
26
|
+
② *技能清单清不掉的*:deny 规则(请求 / `APPROVAL_DENY` / 会话 `toolDeny`)、ask 规则(请求 `settings.permissions.ask` / `APPROVAL_REQUIRE`)、`APPROVAL_NEVER_AUTO` 与治理层的问、
|
|
27
|
+
shell 地板(`MANUAL_MODE_SHELL_GATE=always`)、写保护与读边界、plan 模式的问、服务器声明要人答的工具、删除守卫、hook 的 ask。
|
|
28
|
+
③ *怎么改*:某只工具必须问 = `APPROVAL_REQUIRE`;必须到人 = `APPROVAL_NEVER_AUTO`;shell 每次都问 = `MANUAL_MODE_SHELL_GATE=always` —— 三只都在同一份清单 + 点名下照问。
|
|
29
|
+
- **引擎的 `Skill` 卡上「本会话放行」只放行这一次**:批准一张 `Skill` 卡 = 激活技能 + 本 run 内它清单上的工具免问,卡上读不出这份授予,所以活的 `allow_session` 与耐久
|
|
30
|
+
`/decide` 的 `remember:"session"` 在这张卡上都**不记**(决定照收、回执 `rememberApplied:false`),之前记下的会话放行也不代答它 —— 同会话后面每一张技能卡都给人看。
|
|
31
|
+
之前落下的 `(会话, "Skill")` 豁免行不再生效,`GET /v1/approvals/:sessionId/exemptions` 也不再列它(按名 `DELETE …/exemptions/Skill` 仍可清掉)。
|
|
32
|
+
新的卡面位 `rememberRefused: true`(活卡帧 / `approval_request` 卡 / `livePending` 行 / 运维队列行):在场 = 这张卡上的「本会话放行」不会被记住(四个理由同一位:
|
|
33
|
+
运维治理层的门 / 必须真人逐次批 / 强制 / 应答还授予别的东西);壳读它决定渲不渲「本会话放行」,不再从 `requiresRealApproval` / `mandated` / `governanceForced` 自己推断。
|
|
34
|
+
- **新请求键**:`skills[].allowedTools` / `skills[].disallowedTools`(字符串数组,两道技能门同判;条目文法 = 持久规则文法,`*` / 写路径 / 域名什么都不授予;服务器段铸不出 / 缺段的协议名、服务器段带通配的 `mcp__*` —— 400,见下面「协议规则名」一条;条目按引擎读法取工具名:`mcp__<server>(*)` / `mcp__<server>()` 读作整台服务器、照收)·
|
|
35
|
+
顶层 `userInvokedSkills`(非空串数组 —— 人用名字点过的技能,开跑即生效、不出卡;点了这一 run 没装的技能 ⇒ 同步腿 400 `config.user_invoked_skill_unknown`,
|
|
36
|
+
异步 / 活流腿终态 run 行同码)。≤7.115.x 的 worker 对 `userInvokedSkills` 答 400 `request.body_shape`。
|
|
37
|
+
**能力位(发之前探)**:`GET /v1/capabilities` 的 `userInvokedSkills`(恒真:server 认这个顶层键)与 `skillToolLists`(恒真:技能两只清单**生效** —— ≤7.115.x 上顶层技能的两只清单照收但引擎不读、静默不生效,没有 400 可探;子代技能上是未知键整条拒)。位缺席 ⇒ 当老 server。
|
|
38
|
+
- **停驻的 `Skill` 卡(两种拒法,出路相同:让模型自己重新激活)**:
|
|
39
|
+
① *批准时被拒*:`409 resume_outcome_invalid` + `field:"boundGrantDigest"`(卡仍 pending)—— 决定里把调用换成了另一只技能(`/decide` 体的 `updatedInput`),或卡上那只技能的清单在停驻与批准之间变了,
|
|
40
|
+
或那一行是 7.115.x 上经规则 / hook 的问停驻的 `Skill` 调用(行上没有清单摘要:批准被拒、拒绝照决)。清单变了这一形在本服务上**构造不出**(没有常规路径):带清单的技能只来自请求自带的 `skills[]`,清单随提交体冻结、续跑逐字重放;部署技能加载器与配置中心下发的技能都不带清单。
|
|
41
|
+
出路是把这行决成 deny、让模型重新激活(新卡列出当前清单);原样再批同一行过不了。
|
|
42
|
+
② *批准之后被拒*:行已被消费,所以**不是 409** —— 续跑腿在执行前再过一次 PreToolUse 钩子(请求 `settings.hooks` / 部署钩子),钩子若把已批准的 `Skill` 调用改写成另一只技能(或同一只技能但清单对不上卡上的摘要),
|
|
43
|
+
那次调用**不执行**:`tool_end` 的结果文本是拒因(`isError:true`,点名两只技能、说让模型重新激活它),`tool_end.gate.disposition` 为 `{ kind: "denied", deniedBy: "policy" }`(与人那一句 yes 并存),不写第二只技能的授予行。
|
|
44
|
+
改写成同一只技能、只改 `args` 照常执行。出路:让模型自己激活那只技能(出它自己的卡)。
|
|
45
|
+
- **🔴 回滚到 7.115.x 之前排空停驻行**:7.116.0 停驻的行可能带 7.115.x(引擎 7.40)不读的技能授予节 / 卡绑定,或续跑上下文里某只技能声明的非空 `disallowedTools`(那只技能停驻时还没被激活、没有授予行可冻 —— 回滚后模型再激活它,上一版什么也不拒)—— 在 7.115.x 上续跑这种行,这一 run 的技能禁止行会丢。
|
|
46
|
+
回滚前在 7.116.0 上跑排空配方(随包只读 SQL:`node_modules/@sema-agent/server/scripts/drain-check.pg.sql`(`psql "<连接串>" -f …`)/ `drain-check.mysql.sql`(`mysql … < …`),`skill-grants-rollback` 那一行 `hit_rows` 为 0 才回滚;
|
|
47
|
+
`DB_BACKEND=local` 用随包 `node scripts/drain-check-local.mjs [<数据根>]`,首行 `SKILL-GRANTS rows=0 …` 才回滚)。本脚本的末行从两行变四行(含下面的升级前判据):`SKILL-GRANTS …` 排第一,倒数第二行仍是 `PERMISSIONS-ALLOW-LIST …`、最后一行仍是 `RETIRED-MODE-SEED …`。
|
|
48
|
+
- **🔴 排空配方现在在包里(7.116.0 起)**:此前排空 SQL 只写在不随 npm 包出的 `docs/DEPLOY-PREREQS.md` 里,只装 npm 包的部署读不到。现在两只可直接执行的 SQL 文件随包出(`scripts/drain-check.mysql.sql` / `scripts/drain-check.pg.sql`,与本地脚本同一目录、
|
|
49
|
+
每条判据一段、整只文件一次跑完出一行读数 `criterion | hit_rows | sessions`),文档里不再持有 SQL。**还没升到 7.115.0 的部署**(要在升级前数):不必先升级服务 ——
|
|
50
|
+
`d=$(mktemp -d) && (cd "$d" && npm pack @sema-agent/server@7.116.0 && tar xzf sema-agent-server-7.116.0.tgz)`,拿解开的 `$d/package/scripts/drain-check.<mysql|pg>.sql`(或本地后端的 `drain-check-local.mjs`)去跑;
|
|
51
|
+
四条判据各自该在哪个版本边界跑看该条所在的那一节(`permissions-allow-list` 见 7.115.0 节、`retired-mode-seed` 见 7.113.0 节、`skill-grants-rollback` 与 `retired-skill-manifest` 见本节)。判据与读数不变,只是搬了家并多一条。
|
|
52
|
+
- **升级到 7.116.0 之前排空「停驻、体里技能带 `manifest`」的任务(新判据 `retired-skill-manifest`)**:上面第一条说的存量停驻行 —— 顶层 `skills[]` 带 `manifest` 的续跑会被引擎按名拒(行仍 pending,只能取消重提),
|
|
53
|
+
子代 `agents[].skills[]` 带 `manifest` 的**不被拒**,而是续跑重放时那只子代定义被整只丢掉并留一条 warn 日志 `task_agent_dropped`(任务照跑、少一只可委派的子代)—— 两层的命运不同,升级前没有别的办法数出各有多少。在仍跑 ≤7.115.x 的部署上(取 7.116.0 的包里的文件,见上一条)跑 `retired-skill-manifest` 一段 / 本地脚本的
|
|
54
|
+
`RETIRED-SKILL-MANIFEST rows=…` 那一行,期望 0;非 0 ⇒ 答完或取消(`POST /v1/runs/:id/cancel`)再升级(命中行点名是哪一层:`member=skills` / `member=agents.skills`)。
|
|
55
|
+
- **新询问类 `requires_user_interaction`**:MCP 服务器在工具申报里声明 `_meta["anthropic/requiresUserInteraction"] === true` ⇒ 这只工具每次调用在**每种模式含
|
|
56
|
+
`bypassPermissions`** 都问人,只有人的答清得掉(无审批席 ⇒ 拒;无人值守腿不答);按 `askClass` 穷举的客户端加一个词。
|
|
57
|
+
- **🔴 hook 作者:停驻批准后的调用走同一只 hook 站** —— `/decide` 批准一张停驻的工具审批后,续跑腿执行那次调用之前**再跑一次 PreToolUse**(deny 生效、hook 的 allow
|
|
58
|
+
不越过人的答、人的 `updatedInput` 优先),执行后 PostToolUse / PostToolUseFailure **必跑**(修前:零命中)。⇒ 有副作用或计数的 PreToolUse 钩子对**同一次批准见两次**
|
|
59
|
+
(停驻腿一次 + 续跑腿一次)。本服务的 command 钩子(请求 `settings.hooks` / 部署钩子)今天收不到「这是续跑腿」的标记(引擎上下文里有 `legKind`,
|
|
60
|
+
本服务的钩子载荷没有转出它)—— 计数类钩子按「一次批准两次 PreToolUse」计。续跑腿上 PreToolUse 拒 / 崩 ⇒ 那次调用不执行(批准已消费)。
|
|
61
|
+
- **文件店的拒绝句不带盘上位置**:local 车道上读不出的 usage-window 账本 / 规则桶 / 会话规则高水位标记等,拒绝句(会进 run 的失败文本、HTTP 拒体)只说对象、毛病与修法,
|
|
62
|
+
文件位置只进运维日志 `file_store_corrupt_read`(本版起 usage-window 账本也进这一行)。匹配拒绝句里路径的探针改读码(码不变)与运维日志。
|
|
63
|
+
- **会话同步导出**:本会话的规则行坏了 ⇒ `GET /v1/sessions/:id/sync/manifest` 答 **500 `corrupt`**(修前:200 且少那位 principal 的规则 —— 目标端会按更松的规则跑);
|
|
64
|
+
修好或删掉那只文件再导出。别的会话的抹除不再连带删掉这条坏行。
|
|
65
|
+
- **「没人能答」的拒绝句以 ` — <出路句>` 结尾**:接了审批席、但它对这一问答 `unavailable` 的那一形(例如无人值守腿)现在也带出路句,审批席那一句是
|
|
66
|
+
`make a person reachable through the approver that is wired …`;`engine_notice` 的 `delegation.ask_unresolvable` 带 `detail.remedy`。匹配拒绝句**结尾**的探针改按开头匹配。
|
|
67
|
+
- **会话规则里的旧工具名**:`400 rules.legacy_tool_name` 拒体多两格 `verdict`(`retired` / `unprefixed_namespace`)与 `currentName`(有 1:1 继任时);库里已经存着的旧名 deny,
|
|
68
|
+
**会话属主自己删得掉**(修前只有运营方)。
|
|
69
|
+
- **🔴 协议规则名的服务器段必须是引擎铸出来的那一段(收紧:此前静默收下、永不命中的条目现在响亮拒)**。引擎给 MCP 服务器 / A2A 对端铸工具注册名时会归一服务器段
|
|
70
|
+
(字符表外的字符换成 `_`、超过长度预算截断并加摘要、连串下划线折成一个、去掉尾下划线),而规则对服务器段**逐字**比 —— 所以 `mcp__github.com`、`mcp__github.com__*`、
|
|
71
|
+
`mcp__github.com__get_file` 这类写法一只工具都匹配不到(写在 deny 表上 = 以为拒了其实没拒)。7.116.0 起下面点名的这些面都拒它(没点名的两面见本条末「已知边界」),拒句点名那一条、说明它为什么永远匹配不到,
|
|
72
|
+
并给出引擎为这个服务器名铸出来的前缀(例:`mcp__github.com__*` ⇒ 写 `mcp__github_com__*`):
|
|
73
|
+
`APPROVAL_REQUIRE` / `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO` / `APPROVAL_GRANT` ⇒ 拒启;配置中心 `governance.approvalRequire` ⇒ 整组拒、保留前值、warn `sema_registry_approval_require_invalid`;
|
|
74
|
+
请求 `settings.permissions.{allow,ask,deny}` ⇒ 422;会话规则 `PUT /v1/sessions/:id/policy` 与 `POST /v1/sessions/:id/sync/import` 的 `policy` ⇒ **400 `request.field_invalid`**(店里零字节;
|
|
75
|
+
同步在建 staging 之前拒);技能的两只清单 `skills[].allowedTools` / `disallowedTools`(顶层 `skills[]` 与子代 `agents[].skills[]` 两处受理口)⇒ **400 `request.field_invalid`**
|
|
76
|
+
(句子 `skill.<清单> names "<条目>", which can never match a tool (…)`,条目按引擎的读法取工具名 —— `mcp__<server>(*)` / 带尾空格读作光杆名;服务器段带通配的 `mcp__*` 也拒,句子说服务器名不支持通配、写具体的服务器名)。会话规则两条写口对**缺段名**(`mcp__` / `mcp__<server>__` / `mcp____x`)也同样改拒(此前收下;配置表一直拒,现在一个名字一个答案)。
|
|
77
|
+
① *谁受影响*:配置里写着带点号 / 超长 / 带字符表外字符的服务器名覆盖形或全名的部署(升级后起服拒并被告知正确写法),以及这样写会话规则 / 请求权限规则的客户端
|
|
78
|
+
(含把本地会话规则同步上云的壳:本地规则里有这种条目,同步被拒)。
|
|
79
|
+
② *不改会怎样*:起服失败 / 请求与同步被拒。这些条目此前本就不生效。
|
|
80
|
+
③ *怎么改*:按拒句给出的前缀改写。**库里已经存着的这种会话规则读得出**(`GET` 照常返回),但非运营方的写:留着它 ⇒ 400、删掉它(`toolDeny` 里的那一条)⇒ 403 `loosen_forbidden`
|
|
81
|
+
(删一条 deny 今天仍算放宽)—— 由运营方的 `PUT`(operator 写可以放宽)改写成正确写法。全名的**工具段**若含字符表外字符同样匹配不到,本版不判。
|
|
82
|
+
**已知边界(两个收规则名的面本版不过这只判官)**:`/v1/rules`(持久规则写口与 `cc-import` 导入口)、子代的工具名表 `agents[].allowTools` / `denyTools` ——
|
|
83
|
+
在这两处写带铸不出服务器段的名字(如 `mcp__github.com__*`),本版仍被收下(受理门只判形)、不生效(永不命中;写在 deny 一侧 = 以为拒了其实没拒);圈工具请用上面已判的那几张表(deny 规则)。
|
|
84
|
+
- **`GET /v1/sessions?owner=`(空值,fleet-wide 调用方)**:由「不过滤、跨属主全列」改成「只列属主为空串的会话」(与存储层同一条读法:空串是一个具体的属主)。要跨属主全列,不带 `owner` 参数。
|
|
85
|
+
- **🔴 local 车道:重启后对上一世那张审批卡作答,由 200 变 410(行为面)**。挂着流内审批卡时进程崩溃 / 被杀(或有序关停没能在宽限窗内结清那张卡),
|
|
86
|
+
同一数据根重启后:≤7.115.x 的 `POST /v1/tasks/:taskId/asks/:askId/decision` 对那一问照做、答 200 + `consumerState:"absent"`(而 `GET /v1/approvals`
|
|
87
|
+
的 `crashConverged` 已把它列成收敛 —— 两个读面两个答案,那条 run 早已被重启回收);7.116.0 起启动时把它收成 `VOID`,决断答
|
|
88
|
+
**`410 gone.ask`**,体 `{state:"VOID", deniedReason:"crashed_before_park" | "shutdown_before_park"}`(与 `crashConverged[].cause` 同一个词)。
|
|
89
|
+
① *谁受影响*:local 部署(`DB_BACKEND=local` 或缺省)上,重启后仍拿着上一世的卡去决断口作答的壳 / 脚本。已经批过的问(批了、工具执行中崩)不变:
|
|
90
|
+
照旧 409 首决回显。SQL 后端不变(窗 + 宽限之后由对账器收,其间照旧 200 + `consumerState:"absent"`)。
|
|
91
|
+
② *不改会怎样*:把 410 当未知错误;按 `errorCode` 前缀 `gone.` 兜底的客户端读作「这条决定通道已关」即对。
|
|
92
|
+
③ *怎么改*:410 `gone.ask` 且 `deniedReason` 是这两个词之一 ⇒ 告诉人「上一次运行在等你答的时候结束了」,重新提交任务(新 run 会重新出卡);
|
|
93
|
+
要不要自动重发看 `crashConverged` 同一行的 `resumeSafe`。`gone.ask` 的人话句换了(现在指向 `deniedReason`),按句子匹配的探针改读码。
|
|
94
|
+
启动时这一步写不进 ask 账(盘满 / 只读)⇒ **拒启**,修好数据根再起。
|
|
95
|
+
- **`GET /v1/runs/:id` 的 `errorCode` 多三种取值(additive 读面)**:run 店自己判的终局此前只在 `error` 句子里 —— 现在同时带机读码:失联收割
|
|
96
|
+
`run.stalled`(句子 `run stalled (instance lost?)`;读面在收割落盘前提前折叠的那一形也是这个码)、local 车道重启 / 会话占用接管回收的
|
|
97
|
+
`run.orphaned`(句子 `run orphaned by engine restart`)、审批过期收割 `approval.expired`(行上早有、读面此前不投)。句子照旧。
|
|
98
|
+
① *谁受影响*:按 `error` 句子判「持有者没了」的消费端 —— 改按 `errorCode` 判;把「`errorCode` 在场」读成「core 报了错」的消费端 —— 先读 `status`,
|
|
99
|
+
再按码分支(这三码都不是请求写错,重试同一请求无意义,重新提交才有新 run)。升级前写下的行怎么补码见下一条。
|
|
100
|
+
- **升级前写下的死亡判决行:启动期一次性迁移补码,运维零动作**(持久数据面)。7.115.x 及更早写下的失联收割 / 重启回收行只有 `error` 句子、码列为空。
|
|
101
|
+
本版每次起服都把「`failed` ∧ 码列为空 ∧ 句子是那两句(会话占用接管的括注形按前缀认)」的行写回对应的码(SQL 两后端各两条 `UPDATE`;local 后端改写 `run.json`),
|
|
102
|
+
启动日志 `one_time_migrations_applied` 的 `holderDeathCodeRowsBackfilled` 是这一次写了多少行;幂等(第二次起零命中),不发 `ALTER`,不停机。
|
|
103
|
+
滚动升级窗里还在跑的 7.115.x 副本仍会写只有句子的行 —— SQL 后端的新副本在**读**这种行时同样读成码(这一半只为升级窗存在)。
|
|
104
|
+
这条迁移有窗:引入它的版本发布日 + 30 天之后的第一个小版本把写回与读侧那一半**一起删**(日期见起动日志行的 `removableAfter.M6`)⇒ **跨版跳升要经过一只还带着
|
|
105
|
+
这条迁移的版本**,并且在那之前让所有副本都升上来。删掉之后仍只有句子的行不再被判成持有者死亡:那条 run 上崩溃丢掉的 steer 不再告知用户,`GET /v1/runs/:id`
|
|
106
|
+
上 `errorCode` 缺席、只剩句子。
|
|
107
|
+
- **拒放集合的变化(逐条)**:
|
|
108
|
+
· `body.skills[].manifest`:受理 → **拒**(400 `request.field_invalid`)。
|
|
109
|
+
· `agents[].skills[].manifest`:受理(合法形) → **拒**(未知键,整条技能)。
|
|
110
|
+
· `body.skills[].allowedTools` / `disallowedTools`:受理任何值(原样透传) → 字符串数组受理、其余 **拒**(400 `request.field_invalid`)。
|
|
111
|
+
· `agents[].skills[].allowedTools` / `disallowedTools`:拒(未知键) → **受理**(字符串数组)。
|
|
112
|
+
· 顶层 `userInvokedSkills`:拒(400 `request.body_shape`) → **受理**(非空串数组);坏形 400 `request.field_invalid`。
|
|
113
|
+
· 配置表里的两段覆盖形 `mcp__<server>` / `a2a__<peer>`(`APPROVAL_REQUIRE` / `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO` / `APPROVAL_GRANT` 起服、配置中心
|
|
114
|
+
`governance.approvalRequire`、请求 `settings.permissions.{allow,ask,deny}`):拒 → **受理**,读作整台服务器(与 `mcp__<server>__*` 同义)。想要一只工具就写全名。
|
|
115
|
+
`mcp__` / `mcp__<server>__` / 服务器段带通配的 `mcp__*` 照拒。服务器段须是引擎铸出来的那一段(见下一条)。
|
|
116
|
+
· 服务器段不是引擎铸出来的那一段的协议规则名(两段覆盖形 / 三段覆盖形 / 全名;运维四表、配置中心 `governance.approvalRequire`、请求三桶、会话规则两条写口):
|
|
117
|
+
受理 → **拒**(拒启 / 整组拒 / 422 / 400 `request.field_invalid`)。
|
|
118
|
+
· 会话规则两条写口上的缺段名(`mcp__` / `mcp__<server>__` / `mcp____x` / `a2a__<peer>__`):受理 → **拒**(400 `request.field_invalid`)。
|
|
119
|
+
· 会话规则:属主(非运营方)删掉一条已存的旧名 deny:拒(403 `loosen_forbidden`) → **受理**。
|
|
120
|
+
· `GET /v1/sessions/:id/sync/manifest`(本会话有坏规则行):200(少一位 principal) → **500 `corrupt`**。
|
|
121
|
+
· 委派子代的 `SendUserFile`(部署审批面点名它时):子代名册里没有 → **有**;`APPROVAL_DENY` 下调用被拒、`APPROVAL_REQUIRE` / `NEVER_AUTO` 下调用问顶层审批人。
|
|
122
|
+
要子代根本看不到它,子代定义的 `allowTools` 不写它。
|
|
123
|
+
· local 车道重启后对上一世未答的审批问 `POST /v1/tasks/:taskId/asks/:askId/decision`:受理(200)→ **拒**(410 `gone.ask`)。
|
|
124
|
+
|
|
125
|
+
- **🔴 steer 的 `priority` 交给引擎(行为面变化)**。`POST /v1/runs/:id/steer` 请求体的 `priority`(`now` / `next` / `later`)此前收下、校验、回执里回显,
|
|
126
|
+
但不交给引擎(一律按 `next` 投递);7.116.0 起每条投递腿都把它交给引擎,回执回显的就是交出去的那个档位:
|
|
127
|
+
`now` = 有在飞的 turn 时当场切断它(正在执行的工具收到中止、没启动的结算成 interrupted、本副本上挂着的审批卡被撤),run 继续、这句话排在最前;
|
|
128
|
+
run 事件流上一帧 `engine_notice` `task.turn_interrupted`(`detail.inputId` = 回执 `messageId`)是切断的证据;
|
|
129
|
+
`later` = 不再在下一个边界投,改在 run 本会停下的那一 turn 之后投(模型收尾后多走一 turn);`next` 与不带 `priority` 逐字节不变。
|
|
130
|
+
run 停在审批 / 评审门上或已结束(回执 `delivery:"queued"` / `"parked_for_wake"`)时,三档都在续跑(或 wake)的首轮投递,档位随停放条目存着。
|
|
131
|
+
① *谁受影响*:今天就在请求里带 `priority` 的调用方(SDK 的 `runs.steer` 收这个参数并原样转发)—— 带 `now` 的开始切 turn,带 `later` 的晚到。
|
|
132
|
+
② *不改会怎样*:带 `now` 的插话会中止正在执行的工具调用,并撤掉本副本上挂着的审批卡。
|
|
133
|
+
③ *怎么改*:要旧行为就不带 `priority`(或带 `"next"`);要「切断 + 插话」且 run 不在本副本跑时被拒而不是停放,用 `POST /v1/runs/:id/interrupt`。
|
|
134
|
+
- **同一个 `Idempotency-Key`、同正文、不同档位的 steer 重放**:受理(200 / 202 —— 档位没交给引擎,两次是同一条)→ **拒**(409 `steering.duplicate_input_id`,
|
|
135
|
+
引擎的幂等身份含档位)。换档请换 key。「不带 `priority`」与「带 `"next"`」是**同一档**(引擎缺省档就是 `next`),在三条投递腿上(live、`queued`、
|
|
136
|
+
`parked_for_wake`)同读:同 key 同正文互相重发是幂等重放(回执同一个 `messageId`、队列仍是原来那一条)—— 含升级前停放的 steer(上一版不交档位、条目上没有档位字段),
|
|
137
|
+
升级后用同一 key 重发同一份带 `"next"` 的请求。真换档(`next` → `later`、不带或 `next` → `now`)与同 key 换正文照旧拒。
|
|
138
|
+
- **能力位 `fleet.steerPriority`:`"advisory"` → `"engine"`(值变更,零别名)**。按 `=== "advisory"` 判「档位无效」的消费端改判 `=== "engine"`
|
|
139
|
+
(档位交给引擎,各档语义 = 引擎的定义);键缺席 = 老 server。
|
|
140
|
+
|
|
141
|
+
- **🔴 能力位与「这台部署上不可用」的端点门读同一个判据(硬破坏:一位在无后端部署上由真变假、两位值不变但广告的端点收窄、两位新增、一批端点的不可用答统一成 501)**。
|
|
142
|
+
`GET /v1/capabilities` 里凡是某个端点也在判的位,值与那个端点的不可用门出自同一只判据;此前是两边各写一份,三处在真实部署上说了假话。逐位:
|
|
143
|
+
· **`workflowsList` 值不变(= workflow 行店,与 7.115.x 同),广告的端点收窄成 list / detail / stream 三口;journal 口归新位 `workflowsJournal`**(= 行店 ∧ journal 店)。
|
|
144
|
+
在「没有存储后端 + `SELF_ORCHESTRATION_ENABLED=true`」的三种部署上(`DB_BACKEND=memory`、多租户裸起即 `REQUIRE_PRINCIPAL=true` 且未设 `DB_BACKEND`、`DB_BACKEND=mysql|pg` 而坐标没配齐)
|
|
145
|
+
行店在、journal 店缺席:`workflowsList` 为真(三口真能用),`workflowsJournal` 为假(`GET /v1/workflows/:id/journal` 每次 501 `capability.workflow_store_required`)。
|
|
146
|
+
消费方要做的:**journal 入口改读 `workflowsJournal`**(仍按 `workflowsList` 点亮 journal 的,在那三形上与 7.115.x 一样会点亮一个恒 501 的入口);新位缺席 = 老 worker,按 `workflowsList` 兜底即旧行为。
|
|
147
|
+
· **`a2aServe`:真 → 假**,在 `A2A_SERVE_ENABLED=true` 而没有 durable run store 的部署上(同上三形)。此前卡照发、`message/send` / `tasks/get` 每次答 JSON-RPC `-32004`;
|
|
148
|
+
现在这个 peer 对外不存在:`GET /.well-known/agent-card.json` 与 `POST /v1/a2a` 都 404,`-32004` 不再出现。对端 orchestrator 会看到拉卡 404(与旋钮关同一形)。
|
|
149
|
+
要它对外存在:给它一个 durable run store(`DB_BACKEND=mysql|pg|local`)。启动时有一行 warn 点名这一形。
|
|
150
|
+
· **`sessionSync`:值在三种生产后端上不变,但它广告的端点变少**;新位 **`sessionSyncFileHistory`**(SQL 两后端真,`DB_BACKEND=local` 与无后端假)接走文件历史 blob 两口
|
|
151
|
+
(`PUT /v1/sessions/:id/sync/blobs/:hash`、`GET /v1/sessions/:id/sync/history/blobs/:hash`)。local 上 `sessionSync` 为真而 blob 两口恒拒,修前同一位对它们说了「能」。
|
|
152
|
+
消费方会看到:local 引擎上新位为 `false`;同步只搬对话、不搬文件历史(任一端为 `false` 就别推 `fileHistory`、别 PUT blob)。无 blob 面时 history blob GET 由 404 `not_found.blob` 改 501。
|
|
153
|
+
`sessionSync` 的判据另加三项(会话属主面、条目流式导出面、policy 店能清),三种生产后端都具备,值不变;缺任一的装配整族 501(policy 店擦不动时此前只在 overwrite-dst 的 commit 前拒)。
|
|
154
|
+
· **自带会话策略店、且没实现清除动词(`deleteBySession`)的嵌入方:会话同步由「只有覆盖式导入被拒」变成整族 501、位为假**。≤7.115.x 上只有 consented `overwrite-dst` 的导入在 commit 前答 501 `capability.policy_purge_required`(句首 `overwrite-dst cannot proceed`),同一部署上其余同步端点(manifest / entries / plan / 普通导入 / identical 重推)照常;7.116.0 起「策略店擦得动」是 `sessionSync` 事实的一项:缺这只动词 ⇒ 整族第一口答 501 `capability.policy_purge_required`(句首 `session sync is not available on this deployment`)、能力位 `sessionSync` 为 `false`(`sessionSyncFileHistory` 随之为 `false`)。仓内自带的策略店(SQL / 引擎自带的内存与文件店)都有这只动词,**受影响的只有嵌入方自带策略店的部署**;没有挂策略店的装配不算缺(该位不判)。出路 = 给店实现 `deleteBySession`(按会话清掉全部 principal 的规则行、各键的 rev 高水位留着)。
|
|
155
|
+
· **operator 面在 `OPERATOR_PRINCIPALS` 为空时:403 → 501**。有能力位的八族(收编、记忆治理携出、出处 / 抹除、外源标记、记忆整理、opt-out 授权表、`/v1/outcomes`(仅多租户)、
|
|
156
|
+
配置目录)此时位本就为假,端点由 403 `auth.operator_only` 改答 501 `capability.operator_principals_required`,先于授权。名单非空而调用方不在名单上照旧 403。
|
|
157
|
+
按 403 判「我不是 operator」的消费方:501 这一码的出路是「这台部署没配 operator 名单」。**没有能力位的 operator 口同一条规则**(规则不需要位):`POST /v1/admin/drain` /
|
|
158
|
+
`POST /v1/admin/config/refresh` / `/v1/ops/retention/*` 三口 / `GET /v1/diagnostics/wiring`(多租户形;单用户名单空形另有本机自用律,不变)/ `POST /v1/images/register` /
|
|
159
|
+
`/v1/rules` 的跨 principal 三动词(读 / 写 / 撤销**别人的**规则;自己的规则不经运维门)—— 名单空同样由 403 改答 501,先于授权;名单非空而不在名单上照旧 403。
|
|
160
|
+
(镜像 bake 四口同一条规则;生产上那一形不可达:开 `IMAGE_BAKES_ENABLED` 而名单空在起服就被拒。)
|
|
161
|
+
· **这些面在位为假时由 404 改 501**:`/v1/approvals/*` 与 `/v1/assistant/*`(`capability.checkpoint_store_required`)、`/v1/leader*`(新码 `capability.leader_endpoint_required`)、
|
|
162
|
+
`/v1/shared-memory/*`(新码 `capability.shared_memory_required`)、`GET /v1/sessions/:id`(`capability.session_store_required`)、记忆整理两口(阀门关:新码 `feature.memory_consolidation_disabled`)、
|
|
163
|
+
`GET /v1/config/catalog`(旋钮关:新码 `feature.config_catalog_disabled`)。位值不变;按 404 判「这台没有这个面」的消费方改按 501 判(或直接读位)。错方法从 404 变成 405(路由在)。
|
|
164
|
+
· 零新旋钮、零 DDL。读法:位为真 ⇒ 它广告的每个端点都不会答「不可用」;位为假 ⇒ 至少有一个答 501(唯一例外:`a2aServe` 的两条路由答 404)。
|
|
165
|
+
|
|
166
|
+
- **后台子代的三张读面文本变了(随引擎 7.41.0 自动生效;本服务不自挂这三只工具,由引擎自挂)**:模型调 `AgentTranscript` 读后台子代时,回答**第一行**变成状态行 `State: <status> · last activity <age> ago[ · approval <卡 id> <state> (deadline in <age> | deadline passed <age> ago)]`(原来的 `Agent a… — last N tool step(s)` 退到第二行),每条步骤行多一个时间前缀 `[<age> ago] `(没结果的调用是 `[started <age> ago, no result yet] `);`TaskOutput`(后台子代)在 `status:` 之后多一行 `state: …`;`ListAgents` 的子代行多活动列(停驻的再多审批列);三张读面的 `details` 多 `agentState`(`details.steps[i]` 多 `at` / `inFlight`)。
|
|
167
|
+
① *谁受影响*:钉这三张读面首行 / 步骤行字节 / `TaskOutput` 第二行 / `ListAgents` 行列数的探针与渲染器,以及按 `details` 键在场判形的消费端。② *不改会怎样*:字节钉的探针读到新首行而红。
|
|
168
|
+
③ *怎么改*:步骤正文从 `] ` 之后读;状态读 `details.agentState`、别从文本首行抠。
|
|
169
|
+
**如实说**:`agentState` 经本服务事件流到客户端这一路,仓内没有 wire 级钉(`src` 与 `test` 里 `agentState` 零命中);文本半场随 `tool_end` 帧的 `output` 过境,`details` 是否经 `tool_end.structured` 到客户端取决于引擎对卡面 `type` 的白名单(本服务对 `structured` 只做脱敏),这三只工具的 `details` 卡在不在名单里本服务没有核过。
|
|
170
|
+
- **run-local 无终端的拒句按「问的类」说话(只换 stderr 的文字;退出码、门记录与给模型的拒因句不变)**:无 TTY 的 `run-local`(CI / cron / 管道)上被拒的审批问,stderr 那一句此前对任何问都指向同一串治理旋钮;现在按这一问的类说出路 —— 没人表态的服务器工具调用(类 `unstated`)指 `APPROVAL_GRANT`;写下的 ask 名单(类 `ask_rule`)指 `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO`(`APPROVAL_DENY` 是当场拒、不经这一席),`APPROVAL_AUTO_BUDGET` 那半句只在这一类问放得掉时出现;部署强制要人答的问(类 `operator_forced`)用「one of」列出能铸它的全部来源 —— `APPROVAL_NEVER_AUTO`、`commandPolicy` 的 ask 条目、或 allowlist 形里没列的命令(命令策略写在 `config.d/governance.json` 的顶层键 `commandPolicy`,文件就是治理域对象、没有外层包裹,元素 `{command, decision}`;stderr 句子里沿用旧拼法 `runtime.commandPolicy`);shell 地板(类 `shell_floor`)另一句只列 `AUTONOMY=ask` / `MANUAL_MODE_SHELL_GATE=always`;其余各类不点任何旋钮、只说去终端答;引擎没说明类时说实话、也只指向终端。① *谁受影响*:按旧 stderr 句子匹配的脚本。② *不改会怎样*:匹配落空;退出码照旧(拒 = 退 1)。③ *怎么改*:读退出码,别按 stderr 文字判。
|
|
171
|
+
|
|
10
172
|
## 7.115.0 —— `@sema-agent/core` 7.39.2 → 7.40.1:没人表态的服务器工具调用要问 · `settings.permissions.allow` 从名单翻成 allow 规则(表态,不是免问)· 升级时停驻、带 `allow` 的任务续跑被拒(升级前排空) · 运维的 allow 规则 `APPROVAL_GRANT`(新旋钮,四条腿同装) · 能力位 `settings.permissions.allowGrant` · hook 只给上下文不再替它放行 · 续跑入口取消一个码 · 随包排空脚本改名
|
|
11
173
|
|
|
12
174
|
- **🔴 询问类模式下,MCP 服务器工具 / A2A 技能的调用若没人表态 ⇒ 出卡(行为面硬破坏,引擎变更经本服务过境)**。① *谁受影响*:挂了 MCP / A2A 服务器(运维的 `config.d/mcp.json` / 配置中心,或请求体 `mcpServers` / `a2aPeers`),
|
|
@@ -18,7 +180,8 @@
|
|
|
18
180
|
两处写的都是**服务器工具 / A2A 技能**的规则(`mcp__<server>__*` / `a2a__<peer>__*` / 它们的精确名):allow 规则给出「有人表态」—— 它回答的是没人表态的服务器工具 / A2A 技能调用;内置工具上引擎自己的写门、shell 检查照问,deny / ask 照赢 —— 写 `Bash` / `Write` **不会**让它们不再被问。
|
|
19
181
|
另可:PreToolUse hook 显式回 `permissionDecision: "allow"` · 接审批面(流内审批 / `DURABLE_APPROVAL=true`)· `permissionMode: "bypassPermissions"`(只请求腿)。
|
|
20
182
|
③ *不改会怎样*:每次服务器工具调用一张卡;无审批面 ⇒ 每次被拒。拒绝句**只在**引擎判「这条腿根本没接审批人」那一形里带出路句;接了审批协调器的 HTTP 腿
|
|
21
|
-
(含同步腿审批窗为 0、当场答「没人在」的形)与无人值守腿(`run-local` / leader)
|
|
183
|
+
(含同步腿审批窗为 0、当场答「没人在」的形)与无人值守腿(`run-local` / leader)上被拒时,拒绝句在 7.115.x 上**不带**出路句 —— 出路就是上面 ② 的 allow 规则 / 审批面。
|
|
184
|
+
(7.116.0 起引擎 7.41.0 补齐:两种「没人能答」都以 ` — <出路句>` 结尾、通告带 `detail.remedy`,只有审批席那一句随席位变 —— 见 7.116.0 节。)
|
|
22
185
|
- **运维的 allow 规则 `APPROVAL_GRANT`(新 env 旋钮,缺省关;零迁移,在无人值守腿上用 MCP 服务器的部署升级前设它)**。值 = 逗号分隔的工具名规则:精确名、`mcp__<server>__*`(整台服务器)、`a2a__<peer>__*`(整个 peer);命中 = 运维替这些调用表了态(回答的是没人表态的服务器工具 / A2A 技能调用),
|
|
23
186
|
未命中 = 不表态。与 `settings.permissions.allow` 同一只编译口、同一只校验,HTTP 提交(`POST /v1/tasks` / `/v1/runs`)、durable 续跑(每一次从持久化的提交体重建,含 leader worker 停驻后经 `/decide` 续跑的那一段)、停驻后台 agent 的跨副本赎回、`run-local` 四条腿同装(leader worker 的首跑与修复腿到不了),**不越过**任何一层的 deny / ask(`APPROVAL_DENY` 拒、`APPROVAL_REQUIRE` 问、请求 settings 的 deny / ask、会话规则照旧压过它)。
|
|
24
187
|
**不是白名单**(不收窄任何东西;「只许这些」是会话规则的 `toolAllow`)。坏值(退役名、无前缀短名、`mcp__figma` 两段形、反斜杠、超长、`Bash(npm:*)` 这类内容形)⇒ 启动拒,错误句点名哪一条、怎么改。
|
|
@@ -48,7 +211,7 @@
|
|
|
48
211
|
且它提交时请求体带了非空 `settings.permissions.allow` 的部署 —— 那份 `allow` 是按名单写的,本版不按新读法(allow 规则)重放它(那会让名单外的工具在续跑时静默放开):
|
|
49
212
|
这些任务的每一个续跑入口(`/decide`、`/resume`、`/plan_review`、带 / 不带消息的 `/wake`)答 `409 checkpoint.unsupported_version`(`retriable:true` = 卡仍 pending,**不是**「重试会成功」;挂着 run 的停驻可以取消,唤醒停驻没有可取消的 run),
|
|
50
213
|
任务字节不动、仍停驻(工具审批 / 计划审阅 / 资源停车三类挂着 `suspended` 的 run 行、会话锁仍在;`task_done` 唤醒停驻的原任务早已终局,没有 run 行、不占会话锁,被拒后那张停驻留到过期);SLA 到期拒扫跳过它们(不逐拍重试),收割器在终局时刻收。7.115.0 起新停驻的任务不受影响,提交时没带 `allow`(或带空数组)的不受影响。
|
|
51
|
-
② *怎么改*:升级**前**在仍跑 7.114.x
|
|
214
|
+
② *怎么改*:升级**前**在仍跑 7.114.x 的部署上数这类任务(7.115.0 的包不带 SQL 文件 —— 数的办法:取 7.116.0 的包里 `scripts/drain-check.<mysql|pg>.sql` 的 `permissions-allow-list` 一段,跑法见上面 7.116.0 节;`DB_BACKEND=local` 跑**本版(7.115.0)包里**的随包脚本 `scripts/drain-check-local.mjs` ——
|
|
52
215
|
仍跑 7.114.x 时已装的包里只有旧名脚本、没有本条判据,读出的「干净」是假的;不必先升级服务:`d=$(mktemp -d) && (cd "$d" && npm pack @sema-agent/server@7.115.0 && tar xzf sema-agent-server-7.115.0.tgz) && node "$d/package/scripts/drain-check-local.mjs"`,
|
|
53
216
|
看 `PERMISSIONS-ALLOW-LIST rows=` 那一行),期望 0;非 0 ⇒ 让它们跑完,或 `POST /v1/runs/:id/cancel` 取消(唤醒停驻没有可取消的 run:唤醒它跑完,或升级后在那个会话上直接新提交)。已经升级才发现 ⇒ 有 run 行的三类取消后重提、唤醒停驻直接在该会话上新提交(要「只许这些工具」改用会话规则 `toolAllow` / 请求 `excludeTools`),或一次性回滚到 7.114.x 续跑完再升回来。
|
|
54
217
|
③ *不改会怎样*:这些任务续不了:有 run 行的三类只能取消后重提,取消之前会话锁一直被它们占着(同一会话上新提交 409 `conflict.session_active_run`);唤醒停驻续不了,但不挡同一会话上的新提交。这条拒是一次性迁移:等所有 7.114.x 停驻的任务结束后,后续版本把它删掉。
|
|
@@ -56,7 +219,7 @@
|
|
|
56
219
|
配方只数「本会话有待决行」的会话,宿主会话自己没有待决行,数不到它;撞得上的窗口 = 宿主会话闲置未超过 `REAP_RUN_STALE_SEC`(出厂 120 s,调大则更久)。
|
|
57
220
|
出路:升级前把待决的 workflow 子代审批先决掉;升级后撞上 ⇒ 在该宿主会话上重新提交任务。
|
|
58
221
|
- **🔴 随包排空脚本改名(硬破坏,零别名):`scripts/retired-mode-seed-drain-local.mjs` → `scripts/drain-check-local.mjs`**。7.115.0 起这只脚本一次跑两条判据
|
|
59
|
-
(`permissions-allow-list` 与 `retired-mode-seed`),旧名只说了一条;新名与
|
|
222
|
+
(`permissions-allow-list` 与 `retired-mode-seed`),旧名只说了一条;新名与 SQL 配方的标记 `-- drain-check:<判据>:<后端>` 同一个词(7.116.0 起 SQL 在随包的 `scripts/drain-check.*.sql` 里)。
|
|
60
223
|
① *谁受影响*:按旧文件名调这只脚本的运维脚本 / runbook(7.113.0 与 7.114.0 的包里它就叫旧名)。
|
|
61
224
|
② *怎么改*:换文件名 —— 同一只脚本,参数(可选的数据根)、退出码(0 / 1 / 2)、两条汇总行(`PERMISSIONS-ALLOW-LIST …` 与恒在最后的 `RETIRED-MODE-SEED …`)的名字与顺序都不变。
|
|
62
225
|
③ *不改会怎样*:7.115.0 的包里没有旧名的文件(也没有转发壳)⇒ `node` 报找不到文件。⚠️ 找不到文件时 `node` 的退出码是 **1**,而这只脚本自己的 rc 1 是「有命中、有停驻行」—— 只看退出码的下游会把「脚本没跑起来」读成「有停驻行」;
|
|
@@ -119,7 +282,7 @@
|
|
|
119
282
|
不再是「受理、引擎在 prepare 拒」。升级前就停放、存量体里带这个键的任务续跑时**丢键照跑**(它要的提醒今天由引擎在 plan 模式下自己铸,不丢意图)。
|
|
120
283
|
- **🔴 升级前排空 ≤ core 7.33.x 时期停放的 checkpoint 行**(core 7.39.0 删掉一次性迁移):`inheritedGate` 仍带退役模式种子(`autoModeRequested` / `approverPosture` /
|
|
121
284
|
非 `"always"` 的 `shellGate`)的 pending 行 —— 只可能是 server ≤7.105.0 时期停放、至今仍 pending 的行 —— 升级后续跑被拒 `409 checkpoint.unsupported_version`
|
|
122
|
-
(行字节不动、仍 pending)。**在还跑 ≤7.112.0
|
|
285
|
+
(行字节不动、仍 pending)。**在还跑 ≤7.112.0 的部署上**用随包 `scripts/drain-check.<mysql|pg>.sql` 的 `retired-mode-seed` 一段数行(7.116.0 起在包里,取法见上面 7.116.0 节;期望 0),非 0 就答完或取消那些 run 再换钉;
|
|
123
286
|
漏排的行升级后可 `POST /v1/runs/:id/cancel` 放弃,或一次性回滚到 7.112.x 续跑完再升回来。
|
|
124
287
|
- **`DB_BACKEND=local`(File 店)车道的排空**:没有 SQL 可跑 —— 用随包的只读脚本 `scripts/retired-mode-seed-drain-local.mjs`(7.113.0 起在 npm 包里)。
|
|
125
288
|
**升级前你装的还是 ≤7.112.0,脚本不在已装的包里,但不必先升级服务**:`d=$(mktemp -d) && (cd "$d" && npm pack @sema-agent/server@7.113.0 && tar xzf sema-agent-server-7.113.0.tgz) && node "$d/package/scripts/retired-mode-seed-drain-local.mjs"`
|
package/USAGE.md
CHANGED
|
@@ -692,7 +692,7 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
|
|
|
692
692
|
| 记忆整理阀门判决 + 驱动席(`MEMORY_CONSOLIDATION_DRIVER=on`,或未设而出厂缺省按目录派生 —— 7.104.0 起) | 重启生效 | 阀门开关与整理跑用的模型都在启动期定一次;中心改了 roles / 档位 / 目录条目后,**判决会翻**(派生开 ↔ 关,含只把主模型挪到整理座那一条上)或开着而座漂了 ⇒ `/health` 报 `restartRequired`,`restart.reasons` 含 `memory-consolidation`(7.100.0 新理由;7.104.0 起派生关的那台同样报) |
|
|
693
693
|
|
|
694
694
|
生效与否的观测口=`/health` 世代账键(`configTargetVersion`/`configAppliedVersion`+两 ordinal+`configApplyStaleMs`,见 API 表 `/health` 行)——target≠applied 持续=「改了没生效」的机读信号。
|
|
695
|
-
- **`run-local`(一次性本机 CLI)上三张审批名单同样执法**(7.101.0 起,TTY 问 / 无 TTY 拒):`APPROVAL_DENY` 当场拒;`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中 ⇒ stdin 是 TTY 就 `y/N` 问人,否则当场拒并在 stderr
|
|
695
|
+
- **`run-local`(一次性本机 CLI)上三张审批名单同样执法**(7.101.0 起,TTY 问 / 无 TTY 拒):`APPROVAL_DENY` 当场拒;`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中 ⇒ stdin 是 TTY 就 `y/N` 问人,否则当场拒并在 stderr 说出路(7.116.0 起按**这一问的类**说,不再对每一问打同一句全量旋钮清单:没人表态的服务器工具调用 ⇒ 指 `APPROVAL_GRANT`;名单产的问 ⇒ 点名三张名单键;部署治理盖了出身的问 ⇒ 点治理旋钮;引擎自己的问 ⇒ 不点旋钮、只说去终端答;引擎没有说明这一问的类 ⇒ 说明这一点、只给终端出路);`APPROVAL_AUTO_BUDGET` 照常生效(never-auto 不吃预算);TTY 问句带上这次调用的参数,人答 N 而停下的 run 退 1(停因 `haltedOnUserRejection`)。**无 TTY 的拒记在部署政策名下**(7.110.0 起,S-956;此前记成人拒):该次调用 `tool_end.gate.settlement` = `{kind:"policy_refused", who:{party:"policy"}}`(7.113.0 起;7.110.x–7.112.x 为 `party:"none"`),模型读到的是引擎的策略句(「部署政策拒了、此处没有交互审批,别重试,先做不需要审批的部分」)加一句拒因,而不是「用户不想继续,停下来等他」—— 不停批,模型接着做别的部分;那只要人批的动作没执行 ⇒ run 照样退 1,非 `--json` 形 stderr 停因行是 `task incomplete (approval unanswered): …`。参数展示不出而不问就拒的那一形(TTY 在场也一样)同此。这条腿没有 checkpoint 店也**不**拒启 —— 它有一只当场作答的席,名单的 ask 不需要 park(上表「无 checkpoint 店 ⇒ 整组拒」只说服务腿)。此前三张名单在这条腿上无声不生效。
|
|
696
696
|
- **仅 `/v1/tasks`(同步)+ `/v1/runs`(异步)**——`/v1/tasks/stream` 不支持(多次尝试非单流,400)。与 `verify` **互斥**(同时给 → 400)。
|
|
697
697
|
- 成本上界 = 任务的 `maxCostUsd`(防冷重跑税)。**三条 core 警示**:① 每档**冷重跑**重付输入成本(便宜档常过才划算);② 门收到**未脱敏**输出(自定义门转发外部 verifier 要脱敏);③ **写工具会跑 N 次**——**只用于只读/幂等任务**(每次升级整任务重跑)。开放式任务(找全 bug/文笔)没有可判定 oracle、级联会空转,那种用 `discuss`(广度交叉)而非级联(深度阶梯)。
|
|
698
698
|
|
|
@@ -893,9 +893,9 @@ APPROVAL_GRANT='mcp__figma__*,a2a__planner__*' # 逗号分隔的工具名规
|
|
|
893
893
|
本键在请求腿上**同样生效**(一条规则,HTTP 提交(`POST /v1/tasks` / `/v1/runs`)、durable 续跑(每一次从持久化的提交体重建,含 leader worker 停驻后经 `/decide` 续跑的那一段)、
|
|
894
894
|
停驻后台 agent 的跨副本赎回、`run-local` 四条腿同装)。**到不了**:leader worker 的首跑与修复腿(它们的工具策略在 leader 编排里自己折出来,不经部署治理的构造口);
|
|
895
895
|
这两条腿今天不挂 MCP / A2A,所以本键在那里没有对象。
|
|
896
|
-
- **值形**:精确名(`mcp__figma__get_file`)、`mcp__<server>__*`(
|
|
896
|
+
- **值形**:精确名(`mcp__figma__get_file`)、`mcp__<server>__*` 或两段形 `mcp__<server>`(整台服务器,含它将来新增的工具;两段形 7.116.0 起受理)、`a2a__<peer>__*` / `a2a__<peer>`(整个 peer);
|
|
897
897
|
与请求体的 allow 桶**同一只**判据。坏值 ⇒ **启动拒**(错误句点名 `APPROVAL_GRANT`、哪一条、怎么改):退役名(`Task` ⇒ `Agent`)、
|
|
898
|
-
无协议前缀的短名(`figma__x`)
|
|
898
|
+
无协议前缀的短名(`figma__x`)、缺段形(`mcp__` / `mcp__figma__`,够不着任何工具)、反斜杠、超 512 字符、内容形 / 参数级
|
|
899
899
|
(`Bash(npm:*)`、`Read(./x)` —— allow 规则只收工具名形,内容形的常设放行在持久规则店:`/v1/rules/cc-import` 导入,或在卡上答 yes 时留规则)。
|
|
900
900
|
- **不越过任何一层的 deny / ask**:`APPROVAL_DENY` 拒、`APPROVAL_REQUIRE` 问、请求 settings 的 deny / ask、会话规则(`toolDeny` /
|
|
901
901
|
白名单 `toolAllow`)照旧压过它;plan 模式下服务器工具是 plan 自己的写问,本键不放它。引擎自己的安全地板(不可逆探针、写保护表、
|
|
@@ -1939,6 +1939,9 @@ USAGE_WINDOWS='[{"windowMs":18000000,"maxTokens":5000000,"maxCostUsd":10,"anchor
|
|
|
1939
1939
|
⚠️ 一处**如实登记的差异**:`POST /v1/runs` 的幂等重放预读在窗门之前,leader 的幂等裁决在门之后 ⇒ 窗满期间
|
|
1940
1940
|
用**同一个** `Idempotency-Key` 重投拿到的是 429 而不是原来那张 202 收据。这不丢 run:429 **不消费键**,窗一
|
|
1941
1941
|
放行,同键重投照旧回原 `leaderRunId` 且不会起第二条后台腿(第二条 = 第二次真 `git push`)。
|
|
1942
|
+
- **账本读不出(`DB_BACKEND=local`)**:账本文件坏了 ⇒ 那把键的计费与回查**拒**(`usage_window.store_corrupt`,码不变,任务失败而不是把用量当零),
|
|
1943
|
+
拒绝句只说是哪把键、怎么修(修好或删掉那只文件 —— 删 = 那把键没有用量记录),**不带文件位置**;位置在运维日志 `file_store_corrupt_read` 的
|
|
1944
|
+
`path` 里(7.116.0 起 —— 此前本服务另建的那只账本店不接这个披露座,坏值只拒不说在哪)。
|
|
1942
1945
|
- **$ 窗(core 7.0.0 起)**:`maxCostUsd` 要求 run **可计价**——模型既无 pricing 条目也无
|
|
1943
1946
|
`Model.cost` 声明时,cost 不是 0 而是**不存在**,$ 窗对它不可求值 ⇒ 任务门口拒
|
|
1944
1947
|
(`config.usage_window_unpriced`),绝不记 0 让天花板静默失效。MIXED 声明(部署里同时有 $ 窗与纯
|
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
* 当时:local 车道 ask 账进程内易失(`InMemoryApprovalAskStore`),活卡窗内崩溃 = **零痕迹**:重启后
|
|
10
10
|
* `/v1/approvals` 两数组空,那次审批连一条审计行都没有。**S-384 起 local 的 ask 账已是持久的**
|
|
11
11
|
* (`FileApprovalAskStore`),所以「零痕迹」那句不再成立;本店今天还在的理由收窄成它**独有**的那一格:
|
|
12
|
-
* `crashConverged` 这个 local-only 的 additive 读面(孤儿行的 `orphanState` / `resumeSafe` 两位)
|
|
13
|
-
*
|
|
12
|
+
* `crashConverged` 这个 local-only 的 additive 读面(孤儿行的 `orphanState` / `resumeSafe` 两位)—— **腿证据只在这里**
|
|
13
|
+
* (`legTracked` / `leg_closed` / 关停记号),ask 账没有。🔴 **「这一问还能不能决」不归本店答**:答案源是 ask 账的行状态
|
|
14
|
+
* (决断口只读它)。启动收敛是**一只**函数(`approval-crash-convergence.ts`):先跑本店的收敛(下面「boot 收敛判据」),再把 ask 账里
|
|
15
|
+
* 前世留下的 `STREAM_PENDING` 行收成 `VOID`,成因词取本店对同一 `approvalId` 的 {@link FileApprovalAskAuditStore.causeFor} —— 成因
|
|
16
|
+
* 判一次、写两处(ask 行 `deniedReason` 与本店收敛行 `cause` 同词)。并账(把腿证据搬上 ask 行、本店退役)不是本形:本店在
|
|
17
|
+
* 协议不上场的 local 部署上也在,那里没有 ask 行可并。本店把 ask **铸造 / 决议 / 腿闭**三类事件
|
|
14
18
|
* append 进磁盘账本(挂线点 = `ToolApprovalCoordinator`,见 `askAudit` ctor opt),boot 时收敛器把
|
|
15
19
|
* 孤儿行标 `crashed_before_park` 终态成因注,`GET /v1/approvals` 的 additive 键 `crashConverged` 供
|
|
16
20
|
* 操作员追溯。
|
|
@@ -165,8 +169,11 @@
|
|
|
165
169
|
*/
|
|
166
170
|
import type { HostDecision } from "./host-decision.js";
|
|
167
171
|
import { AppendLog } from "@sema-agent/core";
|
|
172
|
+
import type { HolderGoneReason, VOID_REASONS } from "./approval-deny-reasons.js";
|
|
168
173
|
/** 数据子目录名(挂在 local 数据根下,与 `workflow-notify/` 同层同形)。 */
|
|
169
174
|
export declare const APPROVAL_ASK_AUDIT_DIR = "approval-ask-audit";
|
|
175
|
+
/** 账本文件名(目录内唯一一只;启动收敛据它判「盘上有没有前世审计证据」,见 `approval-crash-convergence.ts` 的 `openAuditEvidenceForConvergence`)。 */
|
|
176
|
+
export declare const APPROVAL_ASK_AUDIT_LEDGER = "ledger.jsonl";
|
|
170
177
|
/** 崩溃收敛行的读面上限(新前旧后截断;有界 wire —— 读面不许随月份线性长)。 */
|
|
171
178
|
export declare const CRASH_CONVERGED_LIST_MAX = 200;
|
|
172
179
|
/** 追加口的窄形(core `AppendLog` 的 `append` / `close`)—— 店的测试注入席({@link FileApprovalAskAuditStore} 构造器 `internals.appendLog`)按它造替身。 */
|
|
@@ -193,7 +200,7 @@ export interface AuditAskInput {
|
|
|
193
200
|
* **计坏行**(`corruptLines`,坏值响亮律 —— 不静默读成「没写成因」),但**不丢该 settle 行的结清**(F4:outcome / parkRouted 照认 ——
|
|
194
201
|
* 因为一个成因词把整条结清行丢掉,会把一张已结清的卡收成 pending 孤儿,方向比「少认一个成因」更坏)。
|
|
195
202
|
*/
|
|
196
|
-
export type AuditShutdownCause =
|
|
203
|
+
export type AuditShutdownCause = typeof VOID_REASONS.HOLDER_SHUTDOWN;
|
|
197
204
|
/**
|
|
198
205
|
* 成因词的读盘词表(F4:`satisfies Record<AuditShutdownCause, true>` ⇒ 闭集加一个词而本表没跟 = **编译红**;判定走 `Object.hasOwn`,
|
|
199
206
|
* 原型链上的名字不算词 —— CLAUDE.md 词表律)。唯一读者 = {@link readShutdownCause}。
|
|
@@ -202,9 +209,11 @@ export declare const AUDIT_SHUTDOWN_CAUSE_WORDS: {
|
|
|
202
209
|
readonly shutdown_before_park: true;
|
|
203
210
|
};
|
|
204
211
|
/** 收敛行 `cause` 的闭集(`GET /v1/approvals` `crashConverged[].cause`):崩溃孤儿 / 有序关停截断的腿(S-929 +1;与 `crashed_before_park` 同形)。
|
|
212
|
+
* 🔴 **词表属主 = `approval-deny-reasons.ts` 的 `VOID_REASONS`**(`HOLDER_CRASHED` / `HOLDER_SHUTDOWN`):启动收敛把同一问的问账行收成 VOID 时
|
|
213
|
+
* `deniedReason` 写的就是这两个词 —— 同一问在 `crashConverged` 与决断口 410 体上只有一个成因词(本型不再自带一份字面量)。
|
|
205
214
|
* wire 值闭集登记在 `trace/self-minted-wire.ts` 的 `crashConvergedCause`(`satisfies Record<CrashConvergedCause, …>` 双向绑定 ⇒ 加词不登记 = tsc 红,
|
|
206
215
|
* wire 变更清单从那张表 diff —— F3)。 */
|
|
207
|
-
export type CrashConvergedCause =
|
|
216
|
+
export type CrashConvergedCause = HolderGoneReason;
|
|
208
217
|
/** 决议点入参(`settle()` 公共咽喉挂线;五臂全过,不漏窗到期/断连/取消)。 */
|
|
209
218
|
export interface AuditSettleInput {
|
|
210
219
|
approvalId: string;
|
|
@@ -287,7 +296,11 @@ export interface CrashConvergedRow {
|
|
|
287
296
|
/** 铸造时刻(键名照 `livePending[].ts`)。 */
|
|
288
297
|
ts: number;
|
|
289
298
|
expiresAtMs: number;
|
|
290
|
-
/**
|
|
299
|
+
/**
|
|
300
|
+
* 收敛行的判别常量:这张卡**已不可再决**(wire 形不变)。⚠️ 协议上场的部署上,同一问在 ask 账里是 `VOID` + `deniedReason` = 本行 `cause`
|
|
301
|
+
* (决断口 410 `gone.ask` 读的是那一行)—— 「能不能决」两面一致,但终态的**名字**这里是 `denied`、ask 行是 `VOID`:这是登记在案的残留,
|
|
302
|
+
* 不是语义(改这个值会让把它当闭集判别式读的消费端整行丢弃,须与消费端同窗改)。
|
|
303
|
+
*/
|
|
291
304
|
decision: "denied";
|
|
292
305
|
/** 成因注(闭集 {@link CrashConvergedCause}):`crashed_before_park` = 进程崩了;`shutdown_before_park`(S-929)= 腿被**有序关停**截断
|
|
293
306
|
* (判据见头注「收敛行的 `cause`」)。两者的 `orphanState` / `resumeSafe` 判据相同。 */
|
|
@@ -420,7 +433,7 @@ export declare class FileApprovalAskAuditStore implements ApprovalAskAuditSink,
|
|
|
420
433
|
*/
|
|
421
434
|
recordLateSettle(input: AuditLateSettleInput): void;
|
|
422
435
|
/**
|
|
423
|
-
* boot
|
|
436
|
+
* boot 崩溃收敛器的**本店半场**(由 `approval-crash-convergence.ts` 的启动收敛在任何卡开始之前调一次;幂等跨 boot)。
|
|
424
437
|
* 判据 = 头注「boot 收敛判据」段(一处成文,不复述)。返回两分臂计数 + 坏行数供 boot 日志。
|
|
425
438
|
*/
|
|
426
439
|
convergeCrashedAsks(): {
|
|
@@ -429,6 +442,12 @@ export declare class FileApprovalAskAuditStore implements ApprovalAskAuditSink,
|
|
|
429
442
|
inconsistent: number;
|
|
430
443
|
corruptLines: number;
|
|
431
444
|
};
|
|
445
|
+
/**
|
|
446
|
+
* 一问的**成因词**(启动收敛的唯一判点,`approval-crash-convergence.ts` 读它写 ask 行的 `deniedReason`;本店收敛行的 `cause` 走同一只
|
|
447
|
+
* {@link causeOf})。前世账本里没有这只 `approvalId` 的铸造行(崩在 ask 账落行之后、本店铸造行之前,或本店那时不在)⇒ `crashed_before_park`:
|
|
448
|
+
* 没有有序关停的证据,就不说它是有序关停。判据读的是构造时冻结的前世账本 ⇒ 同一份账本跨几次启动恒得同一个词。
|
|
449
|
+
*/
|
|
450
|
+
causeFor(approvalId: string): CrashConvergedCause;
|
|
432
451
|
/** S-929 收敛行的成因注(判据见头注「收敛行的 `cause`」):组的最后一个事件行之后、收敛行(若已收敛)之前,有本 taskId 的
|
|
433
452
|
* 有序关停记号 ⇒ `shutdown_before_park`;否则 `crashed_before_park`。 */
|
|
434
453
|
private causeOf;
|
|
@@ -4,6 +4,7 @@ import { AppendLog, ensureDir } from "@sema-agent/core";
|
|
|
4
4
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
5
5
|
import { replayJsonlSkipping } from "./observability/corrupt-read-seat.js";
|
|
6
6
|
export const APPROVAL_ASK_AUDIT_DIR = "approval-ask-audit";
|
|
7
|
+
export const APPROVAL_ASK_AUDIT_LEDGER = "ledger.jsonl";
|
|
7
8
|
export const CRASH_CONVERGED_LIST_MAX = 200;
|
|
8
9
|
export const AUDIT_SHUTDOWN_CAUSE_WORDS = { shutdown_before_park: true };
|
|
9
10
|
export function buildCheckpointAnchorHash(token) {
|
|
@@ -190,7 +191,7 @@ export class FileApprovalAskAuditStore {
|
|
|
190
191
|
this.openLog = internals?.appendLog ?? ((p) => new AppendLog(p));
|
|
191
192
|
const dir = join(root, APPROVAL_ASK_AUDIT_DIR);
|
|
192
193
|
ensureDir(dir);
|
|
193
|
-
this.ledgerPath = join(dir,
|
|
194
|
+
this.ledgerPath = join(dir, APPROVAL_ASK_AUDIT_LEDGER);
|
|
194
195
|
this.replay();
|
|
195
196
|
}
|
|
196
197
|
corruptLines = 0;
|
|
@@ -375,6 +376,10 @@ export class FileApprovalAskAuditStore {
|
|
|
375
376
|
this.convergedRows.length = Math.min(this.convergedRows.length, CRASH_CONVERGED_LIST_MAX);
|
|
376
377
|
return { pending, decided, inconsistent, corruptLines: this.corruptLines };
|
|
377
378
|
}
|
|
379
|
+
causeFor(approvalId) {
|
|
380
|
+
const g = this.replayed.get(approvalId);
|
|
381
|
+
return g?.ask === undefined ? "crashed_before_park" : this.causeOf(g);
|
|
382
|
+
}
|
|
378
383
|
causeOf(g) {
|
|
379
384
|
const after = g.settleLine ?? g.askLine ?? 0;
|
|
380
385
|
const before = g.convergedLine ?? Number.POSITIVE_INFINITY;
|
package/dist/approval-card.d.ts
CHANGED
|
@@ -566,6 +566,7 @@ export declare const ApprovalCardSchema: z.ZodObject<{
|
|
|
566
566
|
requiresRealApproval: z.ZodBoolean;
|
|
567
567
|
}, z.core.$strict>;
|
|
568
568
|
governanceForced: z.ZodOptional<z.ZodLiteral<true>>;
|
|
569
|
+
rememberRefused: z.ZodOptional<z.ZodLiteral<true>>;
|
|
569
570
|
inputHasBidi: z.ZodOptional<z.ZodLiteral<true>>;
|
|
570
571
|
persistedRuleShadowed: z.ZodOptional<z.ZodString>;
|
|
571
572
|
ruleOffers: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
@@ -648,8 +649,8 @@ export declare const ApprovalCardSchema: z.ZodObject<{
|
|
|
648
649
|
}, z.core.$strict>>>;
|
|
649
650
|
personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
|
|
650
651
|
}, z.core.$strict>>;
|
|
651
|
-
askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>;
|
|
652
|
-
classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>>;
|
|
652
|
+
askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>;
|
|
653
|
+
classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>>;
|
|
653
654
|
fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
|
|
654
655
|
sourceTaskId: z.ZodOptional<z.ZodString>;
|
|
655
656
|
sourceAgentName: z.ZodOptional<z.ZodString>;
|
|
@@ -681,6 +682,7 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
|
|
|
681
682
|
requiresRealApproval: z.ZodBoolean;
|
|
682
683
|
}, z.core.$strict>;
|
|
683
684
|
governanceForced: z.ZodOptional<z.ZodLiteral<true>>;
|
|
685
|
+
rememberRefused: z.ZodOptional<z.ZodLiteral<true>>;
|
|
684
686
|
inputHasBidi: z.ZodOptional<z.ZodLiteral<true>>;
|
|
685
687
|
persistedRuleShadowed: z.ZodOptional<z.ZodString>;
|
|
686
688
|
ruleOffers: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
@@ -763,8 +765,8 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
|
|
|
763
765
|
}, z.core.$strict>>>;
|
|
764
766
|
personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
|
|
765
767
|
}, z.core.$strict>>;
|
|
766
|
-
askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>;
|
|
767
|
-
classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>>;
|
|
768
|
+
askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>;
|
|
769
|
+
classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>>;
|
|
768
770
|
fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
|
|
769
771
|
sourceTaskId: z.ZodOptional<z.ZodString>;
|
|
770
772
|
sourceAgentName: z.ZodOptional<z.ZodString>;
|
|
@@ -800,6 +802,8 @@ export interface ApprovalCardSource {
|
|
|
800
802
|
/** [ref]/[ref]:治理来源标 —— 与 wire 帧**同一份素材**(`ToolApprovalFrame` 结构上满足本接口),
|
|
801
803
|
* 于是 live 帧 / `card_json` / 重放帧三面同源,不是三处各判一遍。 */
|
|
802
804
|
governanceForced?: true;
|
|
805
|
+
/** 7.116.0 S4:「本会话放行」不会被记住(帧上同名键,见 `ApprovalCardSchema.rememberRefused`)。 */
|
|
806
|
+
rememberRefused?: true;
|
|
803
807
|
/** E-14:bidi 在场位 —— 与 wire 帧**同一份素材**(`ToolApprovalFrame` 结构上满足本接口)⇒ live 帧 /
|
|
804
808
|
* `approval_request` / `card_json` / 重放帧四面同源,不是四处各扫一遍。 */
|
|
805
809
|
inputHasBidi?: true;
|
package/dist/approval-card.js
CHANGED
|
@@ -354,6 +354,7 @@ export const ApprovalCardSchema = z
|
|
|
354
354
|
})
|
|
355
355
|
.strict(),
|
|
356
356
|
governanceForced: z.literal(true).optional(),
|
|
357
|
+
rememberRefused: z.literal(true).optional(),
|
|
357
358
|
inputHasBidi: z.literal(true).optional(),
|
|
358
359
|
persistedRuleShadowed: z.string().max(MAX_RULE_TEXT_CHARS).optional(),
|
|
359
360
|
ruleOffers: z.array(RuleOfferSchema).max(MAX_RULE_OFFERS).optional(),
|
|
@@ -427,6 +428,7 @@ export function buildApprovalCard(source, req, requiresRealApproval) {
|
|
|
427
428
|
requiresRealApproval,
|
|
428
429
|
},
|
|
429
430
|
...(source.governanceForced === true ? { governanceForced: true } : {}),
|
|
431
|
+
...(source.rememberRefused === true ? { rememberRefused: true } : {}),
|
|
430
432
|
...(source.inputHasBidi === true ? { inputHasBidi: true } : {}),
|
|
431
433
|
...(shadowedRule !== undefined && shadowedRule !== "" ? { persistedRuleShadowed: shadowedRule } : {}),
|
|
432
434
|
...(probeCause !== undefined ? { probeCause } : {}),
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ApprovalAskStore } from "./plugins/approval-ask-store-sql.js";
|
|
2
|
+
import { FileApprovalAskAuditStore } from "./approval-ask-audit-store.js";
|
|
3
|
+
import { type HolderGoneReason } from "./approval-deny-reasons.js";
|
|
4
|
+
import { type RunStoreReader } from "./user-input-ledger.js";
|
|
5
|
+
import type { Logger } from "./observability/logger.js";
|
|
6
|
+
/** 启动收敛的读数(进启动日志)。 */
|
|
7
|
+
export interface CrashConvergenceReport {
|
|
8
|
+
/** 审计半场:pending 臂(崩时人还没答)/ decided 臂(批了、执行窗内崩)/ 审计断档组 / 坏行 —— 语义见审计店头注。 */
|
|
9
|
+
readonly pending: number;
|
|
10
|
+
readonly decided: number;
|
|
11
|
+
readonly inconsistent: number;
|
|
12
|
+
readonly corruptLines: number;
|
|
13
|
+
/** ask 账半场:按成因词计的、本次收成 VOID 的前世 `STREAM_PENDING` 行数。 */
|
|
14
|
+
readonly asksVoided: Readonly<Record<HolderGoneReason, number>>;
|
|
15
|
+
/** 其中卡信封读不出、成因词是回落(`crashed_before_park`)的行数(同时记 fail-open tag)。 */
|
|
16
|
+
readonly causeUnreadable: number;
|
|
17
|
+
}
|
|
18
|
+
/** 本次收成 VOID 的行数合计 —— 由成因闭集派生(对 `asksVoided` 的每个词求和;闭集加词不需要改这里)。 */
|
|
19
|
+
export declare function totalAsksVoided(report: Pick<CrashConvergenceReport, "asksVoided">): number;
|
|
20
|
+
/** 本函数读写的那几口(窄形:测试可以直给一只内存店)。 */
|
|
21
|
+
export type CrashConvergenceAskPort = Pick<ApprovalAskStore, "listByState" | "transitionAsk">;
|
|
22
|
+
/** 审计店的两口(收敛半场 + 成因判点)。 */
|
|
23
|
+
export type CrashConvergenceAuditPort = Pick<FileApprovalAskAuditStore, "convergeCrashedAsks" | "causeFor">;
|
|
24
|
+
/**
|
|
25
|
+
* 启动收敛读哪一份**审计证据**:活的审计店在场就用它;不在场(这一世 `TOOL_APPROVAL_ENABLED` 关着 ⇒ 装配三合取不成立)而盘上有前世的审计账本
|
|
26
|
+
* ⇒ 开一只读它 —— 成因是盘上证据的函数,不随这一世的开关变(否则前世有序关停截断的问在这一世被写成 `crashed_before_park`,审批重新打开那天审计读面
|
|
27
|
+
* 对同一问列 `shutdown_before_park`,两本账永久分家)。盘上没有账本(审批从没开过)⇒ 不铸店、不建目录,证据缺席。
|
|
28
|
+
* 开出来的这一只只供收敛:不进协调器、不进 `crashConverged` 读面(那一面在审批关着时本就缺席)。
|
|
29
|
+
*/
|
|
30
|
+
export declare function openAuditEvidenceForConvergence(live: CrashConvergenceAuditPort | undefined, localDataRoot: string | undefined): {
|
|
31
|
+
audit: CrashConvergenceAuditPort | undefined;
|
|
32
|
+
release: () => void;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* 启动收敛(见文件头)。只在数据根独占的车道上调,且必须在本进程铸出第一问之前。幂等跨启动:收过的行已不是 `STREAM_PENDING`,
|
|
36
|
+
* 审计半场有自己的收敛锚;成因读冻结的前世审计账 ⇒ 半途崩溃再启,剩下的行得到与第一次相同的词。
|
|
37
|
+
*/
|
|
38
|
+
export declare function convergeCrashedAsksAtBoot(input: {
|
|
39
|
+
askStore: CrashConvergenceAskPort | undefined;
|
|
40
|
+
audit: CrashConvergenceAuditPort | undefined;
|
|
41
|
+
now?: () => number;
|
|
42
|
+
}): Promise<CrashConvergenceReport>;
|
|
43
|
+
/**
|
|
44
|
+
* **启动装配的这一步**(stage-07 调一次,孤儿 run 回收之后):车道判据 → 审计证据 → 收敛 → 启动日志。
|
|
45
|
+
* · 只在数据根独占的车道上跑 —— 车道事实与用户输入受理账本同一只 `writerGoneEvidenceFor`(local ⇒ `exclusive_data_root`);SQL 车道整步不做。
|
|
46
|
+
* · ask 店取 `backend.approvalAsk()`,**不看协议开关**:前世开着协议留下的行,今世关着也照收(否则重新打开协议那天它们又变得可决)。
|
|
47
|
+
* · 审计证据经 {@link openAuditEvidenceForConvergence}(这一世的审批开关不改变成因)。
|
|
48
|
+
* · 有任何收敛 / 坏行 ⇒ 一行 warn `approval_ask_audit_crash_converged`(零动作不打)。收敛写入抛错 ⇒ 抛出 ⇒ 拒启。
|
|
49
|
+
*/
|
|
50
|
+
export declare function runBootCrashConvergence(ctx: {
|
|
51
|
+
backend: {
|
|
52
|
+
readonly kind: "mysql" | "pg" | "local";
|
|
53
|
+
approvalAsk(): CrashConvergenceAskPort;
|
|
54
|
+
} | undefined;
|
|
55
|
+
runStore: RunStoreReader | undefined;
|
|
56
|
+
liveAudit: CrashConvergenceAuditPort | undefined;
|
|
57
|
+
localDataRoot: string | undefined;
|
|
58
|
+
logger: Pick<Logger, "warn">;
|
|
59
|
+
}): Promise<CrashConvergenceReport | undefined>;
|
|
60
|
+
//# sourceMappingURL=approval-crash-convergence.d.ts.map
|