@sema-agent/server 7.117.0 → 7.119.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/MIGRATION.md +182 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +58 -28
  5. package/deploy/sema-up/README.md +2 -0
  6. package/deploy/sema-up/chart/templates/minio.yaml +2 -1
  7. package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
  8. package/deploy/sema-up/chart/templates/server.yaml +1 -0
  9. package/deploy/sema-up/chart/values.yaml +7 -1
  10. package/deploy/sema-up/cluster-up.sh +20 -8
  11. package/deploy/sema-up/kube-up.sh +29 -16
  12. package/deploy/sema-up/lib/buckets.sh +21 -0
  13. package/deploy/sema-up/lib/health.sh +33 -0
  14. package/deploy/sema-up/sema-up.sh +14 -9
  15. package/deploy/sema-up/smoke.sh +16 -6
  16. package/deploy/sema-up/stack/compose.yaml +1 -1
  17. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -1
  18. package/dist/approval-ask-audit-store.js +2 -7
  19. package/dist/approval-card.d.ts +40 -10
  20. package/dist/approval-card.js +11 -1
  21. package/dist/approval.d.ts +55 -17
  22. package/dist/approval.js +74 -33
  23. package/dist/audit.d.ts +12 -1
  24. package/dist/audit.js +62 -2
  25. package/dist/auto-mode-face.d.ts +2 -3
  26. package/dist/bake-runner/main.js +1 -6
  27. package/dist/bake-runner/protocol.d.ts +18 -0
  28. package/dist/bake-runner/protocol.js +22 -2
  29. package/dist/bake-runner/runner.d.ts +12 -8
  30. package/dist/bake-runner/runner.js +5 -9
  31. package/dist/boot/config-center.js +1 -1
  32. package/dist/boot/coordinators.js +4 -1
  33. package/dist/boot/device-lane.d.ts +3 -3
  34. package/dist/boot/device-lane.js +1 -2
  35. package/dist/boot/leader.d.ts +4 -4
  36. package/dist/boot/leader.js +1 -2
  37. package/dist/boot/parked-revive-gate.d.ts +4 -6
  38. package/dist/boot/parked-revive-gate.js +2 -6
  39. package/dist/boot/resolve-spec.js +9 -11
  40. package/dist/boot/runner-deps.js +2 -2
  41. package/dist/boot/stage-07-capability-layer.js +7 -3
  42. package/dist/boot/stage-10-http-server.js +3 -2
  43. package/dist/boot/stores.js +12 -7
  44. package/dist/capabilities/center-plugins.d.ts +2 -0
  45. package/dist/capabilities/center-plugins.js +24 -6
  46. package/dist/capabilities/code-review-council.js +8 -11
  47. package/dist/capabilities/hands-lane.d.ts +25 -0
  48. package/dist/capabilities/hands-lane.js +1 -1
  49. package/dist/capabilities/mcp-probe.d.ts +2 -2
  50. package/dist/capabilities/scenarios.d.ts +34 -7
  51. package/dist/capabilities/scenarios.js +33 -6
  52. package/dist/config-catalog.js +3 -3
  53. package/dist/config.d.ts +14 -18
  54. package/dist/config.js +15 -26
  55. package/dist/deployment-governance.d.ts +5 -7
  56. package/dist/deployment-governance.js +4 -5
  57. package/dist/host-decision.d.ts +114 -31
  58. package/dist/host-decision.js +39 -2
  59. package/dist/http/admission.js +12 -6
  60. package/dist/http/approval-sweeps.js +1 -1
  61. package/dist/http/mcp-dial-deadline.d.ts +35 -0
  62. package/dist/http/mcp-dial-deadline.js +21 -0
  63. package/dist/http/memory-compliance-failure-reply.d.ts +11 -5
  64. package/dist/http/memory-compliance-failure-reply.js +17 -8
  65. package/dist/http/memory-store-refusal-reply.d.ts +14 -11
  66. package/dist/http/memory-store-refusal-reply.js +16 -5
  67. package/dist/http/resume-legs.js +8 -3
  68. package/dist/http/routes/approvals-assistant.d.ts +25 -3
  69. package/dist/http/routes/approvals-assistant.js +27 -26
  70. package/dist/http/routes/capabilities.js +6 -3
  71. package/dist/http/routes/mcp-reconnect.d.ts +17 -0
  72. package/dist/http/routes/mcp-reconnect.js +6 -1
  73. package/dist/http/routes/memory-bundle.js +1 -1
  74. package/dist/http/routes/memory-compliance.js +1 -1
  75. package/dist/http/routes/memory-origin.d.ts +1 -1
  76. package/dist/http/routes/memory-origin.js +1 -1
  77. package/dist/http/routes/runs.js +47 -33
  78. package/dist/http/routes/sessions.d.ts +1 -1
  79. package/dist/http/routes/sessions.js +23 -22
  80. package/dist/http/routes/workflows.js +2 -0
  81. package/dist/http/wire-types.d.ts +17 -13
  82. package/dist/images/manifest.d.ts +7 -1
  83. package/dist/images/manifest.js +18 -16
  84. package/dist/memory-store-refusal.d.ts +38 -18
  85. package/dist/memory-store-refusal.js +25 -7
  86. package/dist/model-unreachable-notice.d.ts +1 -1
  87. package/dist/namespaced-rule-name.d.ts +46 -41
  88. package/dist/namespaced-rule-name.js +36 -22
  89. package/dist/observability/corrupt-read-seat.d.ts +4 -0
  90. package/dist/observability/corrupt-read-seat.js +1 -0
  91. package/dist/observability/fail-open.d.ts +19 -3
  92. package/dist/observability/fail-open.js +19 -3
  93. package/dist/operator-ask.d.ts +6 -2
  94. package/dist/orchestration/workflow-agent-session-index.d.ts +82 -59
  95. package/dist/orchestration/workflow-agent-session-index.js +80 -14
  96. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -0
  97. package/dist/orchestration/workflow-completion-inbox.js +39 -17
  98. package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
  99. package/dist/orchestration/workflow-notify-journal.js +7 -2
  100. package/dist/outbound-dispatcher.d.ts +29 -20
  101. package/dist/outbound-dispatcher.js +105 -16
  102. package/dist/parked-decide.d.ts +43 -48
  103. package/dist/parked-decide.js +54 -16
  104. package/dist/permission-rule-vocab.d.ts +4 -3
  105. package/dist/permission-rule-vocab.js +1 -1
  106. package/dist/plugins/approval-ask-store-sql.d.ts +17 -20
  107. package/dist/plugins/approval-ask-store-sql.js +0 -3
  108. package/dist/plugins/approval-exemption-store.d.ts +4 -0
  109. package/dist/plugins/approval-exemption-store.js +1 -0
  110. package/dist/plugins/blob-backend.d.ts +13 -0
  111. package/dist/plugins/blob-backend.js +26 -0
  112. package/dist/plugins/local-session-store.d.ts +78 -47
  113. package/dist/plugins/local-session-store.js +152 -122
  114. package/dist/plugins/one-time-migrations.d.ts +11 -0
  115. package/dist/plugins/one-time-migrations.js +128 -4
  116. package/dist/plugins/pg-pool.js +1 -1
  117. package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
  118. package/dist/plugins/snapshot-bucket-presence.js +40 -0
  119. package/dist/plugins/store-backend.d.ts +17 -6
  120. package/dist/plugins/store-backend.js +46 -16
  121. package/dist/plugins/store-open-wait.d.ts +33 -0
  122. package/dist/plugins/store-open-wait.js +73 -0
  123. package/dist/plugins/tidb-pool.js +1 -1
  124. package/dist/plugins/web-search.d.ts +20 -1
  125. package/dist/plugins/web-search.js +39 -10
  126. package/dist/plugins/workflow-run-store-sql.d.ts +7 -6
  127. package/dist/plugins/workflow-run-store-sql.js +5 -7
  128. package/dist/request-key-closure.js +1 -1
  129. package/dist/request-value-closure.d.ts +1 -1
  130. package/dist/rule-entry-list.d.ts +35 -46
  131. package/dist/rule-entry-list.js +18 -26
  132. package/dist/rules-consent.js +3 -0
  133. package/dist/run-local.js +5 -3
  134. package/dist/runs.d.ts +1 -1
  135. package/dist/security.d.ts +5 -3
  136. package/dist/security.js +4 -0
  137. package/dist/session-policy-wire.d.ts +13 -12
  138. package/dist/session-policy-wire.js +11 -11
  139. package/dist/spec-fields.d.ts +13 -11
  140. package/dist/spec-fields.js +4 -7
  141. package/dist/standing-answer-grant.d.ts +14 -2
  142. package/dist/standing-answer-grant.js +8 -1
  143. package/dist/store-posture.d.ts +14 -6
  144. package/dist/store-posture.js +7 -4
  145. package/dist/task-settings.d.ts +27 -55
  146. package/dist/task-settings.js +13 -21
  147. package/dist/tool-approval.d.ts +133 -138
  148. package/dist/tool-approval.js +175 -66
  149. package/dist/trace/core-keyset-guard.d.ts +5 -5
  150. package/dist/trace/redact.d.ts +6 -0
  151. package/dist/trace/redact.js +9 -9
  152. package/dist/trace/self-minted-wire.d.ts +16 -2
  153. package/dist/trace/self-minted-wire.js +7 -0
  154. package/dist/user-input-ledger.d.ts +30 -14
  155. package/dist/user-input-ledger.js +25 -14
  156. package/package.json +2 -2
  157. package/scripts/drain-check-local.mjs +74 -3
  158. package/scripts/drain-check.mysql.sql +43 -17
  159. package/scripts/drain-check.pg.sql +46 -20
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * **宿主的决策事实** —— 一条耐久 park 被结算时,本服务能如实陈述的那半件事:谁结束了这次等待,以及
3
- * 审批通道自报的归属。一个叶子模块(只依赖 `@sema-agent/core`),四个消费点共用一个定义。
3
+ * 审批通道自报的归属,以及决定体上陈述「谁决定的」那一只键(`decidedBy`)的闭集、读法与映射。一个叶子模块
4
+ * (运行期只依赖 `@sema-agent/core`;另一处 import 是纯类型的编译期门),全部决定口共用一个定义。
4
5
  *
5
6
  * ## 为什么是「事实」而不是「结算词」(core 7.6.0 / [ref] S6-A,BREAKING)
6
7
  *
@@ -14,17 +15,15 @@
14
15
  * ⇒ 本仓的规则从「记住一张词×决定的相容表」变成「说出你知道的那两件事」。相容性也不再靠记忆:一次
15
16
  * sweep 不可能 `allow`,core pre-CAS 拒。
16
17
  */
17
- import { type AskOutcome, type ResumeOutcome } from "@sema-agent/core";
18
+ import { type AskOutcome, type HostDecider, type PermissionResult, type ResumeOutcome } from "@sema-agent/core";
18
19
  /**
19
20
  * 谁结束了这次等待,以及通道自报的归属。
20
- * · `decidedBy: "person"` —— 运维经审批通道做的决定(两条 HTTP `/decide` 腿);
21
+ * · `decidedBy: "person"` —— 一个人经审批通道做的决定(决定体缺席 `decidedBy` 即此);
21
22
  * · `decidedBy: "sla_timeout"` —— 宿主自己的 SLA 扫在行的 deadline 上判的(FACET A;**只有** sweep 能这么说,
22
- * 而 sweep 结构上不能 `allow`)。
23
- * · `decidedBy: "policy"`(core 7.26.2 / #1016)—— **这台部署自己的策略**拒的:答话的是机器不是人。调用方
24
- * 经决裁体的 `settledBy` 自报;core 据它给模型铸一句**策略句**(而不是「用户不同意,停下来等他」),
25
- * 并把结算记成 `policy_refused`(`who.party:"policy"`,core 7.39.0 #1265 起;此前 `"none"`)。`allow` 侧:core 7.39.0 起引擎收策略的
26
- * 放行(`policy_allowed`),但本仓的两条 decide 口**不开**这一形(`tool-approval.ts` 的 `SETTLED_BY_ONLY_ON_DENY` 与店列的同一条拒仍在 ——
27
- * 耐久车道上的机器放行是一件没裁过的新能力,不在 7.113.0 提货批内,候登)。
23
+ * 而 sweep 结构上不能 `allow`;wire 上永不受理,见 {@link APPROVAL_DECIDED_BY})。
24
+ * · `decidedBy: "policy"` —— **这台部署自己的策略**答的:答话的是机器不是人,`allow` 与 `deny` 两向都可以。
25
+ * 调用方经决定体的 `decidedBy` 陈述;core 据它铸 `policy_allowed` / `policy_refused`(`who.party:"policy"`),
26
+ * 拒的那一向给模型一句**策略句**(而不是「用户不同意,停下来等他」)。
28
27
  * · `approver` —— 通道自报的结算方标识串。**缺席永不读成**「没人批」或「有人批」,只意味着通道没报名字。
29
28
  */
30
29
  /**
@@ -33,9 +32,9 @@ import { type AskOutcome, type ResumeOutcome } from "@sema-agent/core";
33
32
  * · **铸**(本仓自己观察到的事实):只有 `"timeout"` —— 同步 `onAsk` 腿的等待窗归**宿主**所有(引擎不为
34
33
  * 一个它没排程的回调起表),所以「本仓的 TTL 窗走完了、没人答」这件事引擎结构上观察不到,不自报就
35
34
  * 永远被记成「一个人拒绝了」;
36
- * · **转**(调用方在回决体里自报的事实,core 7.26.2 / #1016 起):`settledBy:"policy"` = 答这张卡的是
37
- * **这台部署自己的策略**,不是人。本仓**零判据、原样过境**:词的拒绝逻辑归引擎(`resolveAsk` 对
38
- * 自报词表外的值是响亮 fail-closed deny),本仓抄一份词表的那天 core 加词就会被静默 400 掉。
35
+ * · **转**(调用方在决定体里陈述的事实):决定体的 `decidedBy:"policy"` 经 {@link liveSettledBy} 落成这一格的
36
+ * `"policy"` = 答这张卡的是**这台部署自己的策略**,不是人。wire 上的词只有 {@link APPROVAL_DECIDED_BY} 两个,
37
+ * 映射只有那一只函数 —— 这一格的「转」不再是一个原样过境的串。
39
38
  * 逐词(词表属主恒是 core):
40
39
  * · `"human"` —— 与「不铸这个键」逐字同义(core 缺席即按人处理);
41
40
  * · `"aborted"` —— core **明令拒收**宿主自报(那是引擎自家 fail-closed 端的词,宿主自报等于把自己的
@@ -48,27 +47,116 @@ import { type AskOutcome, type ResumeOutcome } from "@sema-agent/core";
48
47
  * 🔴 **S-558:为什么这一格住在本叶子模块**(此前是 `tool-approval.ts` 的模块私有型):来源位随决议
49
48
  * **落持久行**之后,店(`src/plugins/approval-ask-store-sql.ts` 的 `AskRow.settledBy` /
50
49
  * `DecideAskInput.settledBy`)与协调器都要说同一个型 —— 而店不能反向依赖协调器。本模块只依赖
51
- * `@sema-agent/core`,与同住的 {@link HostDecidedBy}(耐久腿的同位事实)恰好是一对:一条决议的来源
50
+ * `@sema-agent/core`,与同住的 {@link HostDecision}(耐久腿的同位事实)恰好是一对:一条决议的来源
52
51
  * 在两条腿上是同一件事,型也该在同一处。
53
52
  */
54
53
  export type HostSettledBy = NonNullable<Extract<AskOutcome, {
55
54
  allow: boolean;
56
55
  }>["settledBy"]>;
57
- /** 直接取 core `ResumeOutcome` policy_ask 臂的 `hostDecision` 型 —— 本仓不复述那几个词(第二铸点)。 */
56
+ /** 直接取 core `ResumeOutcome` policy_ask 臂的 `hostDecision` 型 —— 本仓不复述那几个词(第二铸点)。它的 `decidedBy` 就是 core 的 `HostDecider`
57
+ * (core 7.45.0 #1387:三个型位读同一只闭集)。**谁结束了这次等待**的闭集只有 core 包根那一只(`HOST_DECIDERS` / `HostDecider` / `isHostDecider`)。 */
58
58
  export type HostDecision = Extract<ResumeOutcome, {
59
59
  gate: "policy_ask";
60
60
  }>["hostDecision"];
61
- /** **谁结束了这次等待**的闭集,型从 core 的那一格派生(本仓永不抄词表:core 加/退一个词,读它的每一处
62
- * 编译期先红)。今天 = `"person"` | `"sla_timeout"` | `"policy"`。 */
63
- export type HostDecidedBy = HostDecision["decidedBy"];
64
61
  /**
65
- * 一次 decide 的事实,**一处成形**(两条 HTTP decide 腿 + 活卡 PARKED 赎回席共用)。
62
+ * ══ 决定体的出处位 `decidedBy`:一只闭集、一只读法、一只映射 ═══════════════════════════════════════════════
66
63
  *
67
- * `decidedBy` = 调用方**陈述**的决策者(缺席 ⇒ `"person"`,= 本面自古以来的形:一条不说话的 decide 就是
68
- * 一个人按的)。🔴 **本仓零判据**:词表的属主是 core,不认识的词由它 pre-CAS 响亮拒
69
- * (`checkpoint.invalid_outcome`,`hostDecision.decidedBy … is outside the closed set`)。在这里再判一次
70
- * 就是同一语义面的第二个判官(源头修复纪律 [ref]),而且本仓抄一份词表的那天 core 加第四个词就会被
71
- * 静默 400 掉 —— 那正是 #1016 的 `"policy"` 加进来时本可以发生的事。
64
+ * 规则一句话:**一个审批决定用一只键说是谁做的** —— `decidedBy ∈ {"person","policy"}`,缺席 = `"person"`;读它的只有
65
+ * {@link readDecidedBy}(三条工具审批决定口共读:live `POST /v1/tool-approvals/:id/respond`、耐久 ask 账
66
+ * `POST /v1/tasks/:id/asks/:askId/decision`、耐久 `POST /v1/approvals/:sessionId/decide`),把它交给引擎的只有
67
+ * {@link liveSettledBy}(同步座 `AskOutcome.settledBy`)与 {@link hostDecisionFacts}(停驻座 `hostDecision.decidedBy`)。
68
+ * · 词与决定动词**正交**:策略可以放行也可以拒(core 7.39.0 起引擎铸 `policy_allowed` / `policy_refused`)。唯一的例外是
69
+ * 引擎自己的那一条 —— 一张要人的卡(`requiresRealApproval`)不归策略答,见 {@link heldAskOf}(判官是 core 的 `policyCannotAnswer`)。
70
+ * · `"sla_timeout"` **永不从 wire 收**:那是「等待到点、没人答」,只有本服务自己的截止扫说得出来(`approval-sweeps.ts`
71
+ * 直接铸 `{decidedBy:"sla_timeout"}`,不经本读法)。调用方说它 = 替服务端伪证一件只有服务端看得见的事。
72
+ * · `plan_review` 不读这只键:引擎的 plan_review 结局形没有决定者位(core 的结局规整会把别的键整只重建掉),收下它就是
73
+ * 一句「已记」的空话。那一门照兄弟门字段那一族拒(`approvals-assistant.ts` 的回决门族派生),候 core 给座。
74
+ *
75
+ * 词表的来源:core 包根的 `HOST_DECIDERS` / `HostDecider` / `isHostDecider`(core 7.45.0 #1387;此前包根只有型,本仓手写一只闭集常量 + 两道
76
+ * 编译门双向咬它 —— 那份手抄随本版退役)。wire 闭集 = 那只词表去掉扫的那一个词,运行期值与型都从它派生,没有第二份词表。
77
+ */
78
+ /** 只有本服务自己的截止扫说得出的那一个词 —— core 词表里唯一不上 wire 的成员(`satisfies`:core 改名 / 退役它 ⇒ 这里编译红)。 */
79
+ declare const SWEEP_ONLY_DECIDER = "sla_timeout";
80
+ /** wire 上的出处词(决定体 `decidedBy` 的值域)= core 闭集去掉扫的那一个。 */
81
+ export type ApprovalDecidedBy = Exclude<HostDecider, typeof SWEEP_ONLY_DECIDER>;
82
+ /** wire 闭集的运行期值:core `HOST_DECIDERS`(#1387)去掉扫的那一个,顺序随 core。core 加一个词 ⇒ 它在这里自动在场(决定口的门照收;两只座的映射
83
+ * {@link liveSettledBy} 的穷举 `switch` 编译红,由人裁它在同步座上怎么落)。 */
84
+ export declare const APPROVAL_DECIDED_BY: readonly ApprovalDecidedBy[];
85
+ /** 闭集外的拒句(三条决定口同一句;码按各口既有约定,见 {@link readDecidedBy})。成员表派生自 {@link APPROVAL_DECIDED_BY}(词表一个来源;今天字节与 7.118.0 同)。 */
86
+ export declare const DECIDED_BY_OUTSIDE_CLOSED_SET: string;
87
+ /** 直连门上的拒句(与 `remember` / `updatedInput` / `checkpointToken` 同族:键不在决策证明的签名载荷里)。 */
88
+ export declare const DECIDED_BY_NOT_IN_PROOF = "decidedBy is not supported on a direct-door worker (not covered by the decision proof)";
89
+ /** 一张要人的卡上策略的放行(两条决定口同一句;判官 core `policyCannotAnswer`,问形见 {@link heldAskOf})。 */
90
+ export declare const POLICY_CANNOT_ALLOW_PERSON_ASK = "decidedBy \"policy\" cannot allow this ask \u2014 it demands a person's approval (risk.requiresRealApproval), and a policy's answer is not a person's; answer it as a person, or refuse it as the policy";
91
+ /**
92
+ * 退役键 `settledBy`(7.93.3–7.117.x 的「只配 deny」那一位)的拒句。三条决定口共用这一句,出句的铸点是 {@link retiredDecisionKeyFacts}(各口不直接引用本常量)。
93
+ * live `/respond` 的解析不是键闭集的(未知键一律忽略),而这只键不是随便一个未知键:它是调用方在**明确陈述**「这是策略答的」—— 忽略它就是把缺席律(person)
94
+ * 套在一个说了话的体上(旧壳的策略拒记成人拒;一张要人的卡上旧形的放行被读成人的放行,绕过「要人的卡不归策略答」那一判)。所以那条口
95
+ * 在裁决之前响亮拒它,与同一只 parser 里 `scope` 的拒同一条先例;耐久两口本来就在键闭集 / strict 体上拒它,句子改由同一铸点出。
96
+ */
97
+ export declare const SETTLED_BY_RETIRED = "settledBy is retired \u2014 send decidedBy: \"person\" | \"policy\" (who made this decision; absent = \"person\"); ignoring it would record a policy's answer as a person's";
98
+ /**
99
+ * 退役键 `settledBy` 的**拒绝事实** —— 三条工具审批决定口(live `POST /v1/tool-approvals/:id/respond`、耐久 ask 账
100
+ * `POST /v1/tasks/:taskId/asks/:askId/decision`、`POST /v1/approvals/:sessionId/decide`)对它的拒,**句**与**机读位**同出自这一处:
101
+ * · `error` = {@link SETTLED_BY_RETIRED}(三口同一句,没有哪一口自说一句 —— 此前耐久 ask 账说 strict 体的通用句、`/decide` 说键闭集句);
102
+ * · `extra` = `{ field: "settledBy" }`,直接作 `sendError` 的 `extra` / 并进 400 体。`field` 是 400 体上既有的那一把键(`request.field_invalid` 的
103
+ * `settings.permissions.*` / `settings.hooks` / 模型位同键同读法:点名这只拒落在请求体里哪个字段上),不另造码、不另造键。
104
+ * 码三口同为 `request.body_shape`;`/decide` 照旧另带 `unknownKeys` / `unsupportedKeys` 两张键表(退役键仍在 `unknownKeys` 里)。
105
+ * 判在场的口径只有一个:值不是 `undefined`(JSON 里的 `null` / `""` 也算在场 —— 退役键是一句**陈述**,不看它陈述了什么)。
106
+ * 用法(两种出口,各口都只**带**、不改):① 拒的成因就是退役键(live 解析器撞上它 / 耐久 ask 账 strict 解析失败而体里有它 / `/decide` 键闭集门)⇒ 句用 `error`、
107
+ * 体并 `extra`;② 体里同时还有别的毛病而先撞上的是那一个(live 解析器的 `note` / `persistRule` / 决定词,`/decide` 的根形门)⇒ 句仍说那个毛病、体照并 `extra` ——
108
+ * 每个口在自己的**失败出口**调同一只函数,而不是在某条判定的分支里调,否则「机读位在不在」就取决于哪句先撞上。
109
+ */
110
+ export declare const RETIRED_DECISION_KEY: "settledBy";
111
+ export interface RetiredDecisionKeyFacts {
112
+ readonly error: typeof SETTLED_BY_RETIRED;
113
+ readonly extra: {
114
+ readonly field: typeof RETIRED_DECISION_KEY;
115
+ };
116
+ }
117
+ export declare function retiredDecisionKeyFacts(body: unknown): RetiredDecisionKeyFacts | undefined;
118
+ /** {@link readDecidedBy} 的判决(调用点做穷举 `switch` + `never` 兜底,各按自己的码约定发出)。 */
119
+ export type DecidedByReading = {
120
+ kind: "accepted";
121
+ decidedBy: ApprovalDecidedBy;
122
+ } | {
123
+ kind: "not_in_proof";
124
+ } | {
125
+ kind: "outside_closed_set";
126
+ };
127
+ /**
128
+ * 决定体 `decidedBy` 的**唯一读法**。次序即契约:
129
+ * ① 缺席 ⇒ `"person"`(缺席律:一条不说话的决定就是一个人按的 —— 与修前逐字同形);
130
+ * ② 直连门在场且键在场 ⇒ `not_in_proof`(不论值:它不在签名载荷里,能改线上字节的一方可以把人翻成策略;
131
+ * 第二参给字面 `false` 的调用点 = 那条口没有决策证明,live `/respond` 即此);
132
+ * ③ 闭集内 ⇒ 那个词;其余一切(`"sla_timeout"`、结算词、别的串、非串、`null`)⇒ `outside_closed_set`。
133
+ */
134
+ export declare function readDecidedBy(raw: unknown, directDoorActive: false): Exclude<DecidedByReading, {
135
+ kind: "not_in_proof";
136
+ }>;
137
+ export declare function readDecidedBy(raw: unknown, directDoorActive: boolean): DecidedByReading;
138
+ /**
139
+ * **同步座的映射**(`AskOutcome.settledBy`;ask 账行的 `settled_by` 列存的也是这一格):`person` ⇒ 不铸键(引擎缺席即按人读,
140
+ * 与修前逐字同形 —— 人的放行仍是裸 `true`),`policy` ⇒ `"policy"`。总函数,无 cast。
141
+ */
142
+ export declare function liveSettledBy(decidedBy: ApprovalDecidedBy): Extract<HostSettledBy, "policy"> | undefined;
143
+ /**
144
+ * 引擎自己的那一条例外:**一张要人的卡不归策略答** —— 判官是 core 的 `policyCannotAnswer(question, decidedBy)`(#1395 包根导出;引擎的续跑入口、
145
+ * 执行带与同步座读同一条)。本仓**只在自己的副作用先于引擎落地的两条口**上于裁决之前问它(live `/respond`:会话放行 / 常驻规则 / 一条按 approve
146
+ * 回放的 DECIDED 行;耐久 ask 账:一条宣称 approve 的 DECIDED 行),并且只问**放行**那一向(与引擎续跑入口同:拒永远答得了)。耐久 `/decide` 不判:
147
+ * 引擎在 CAS 之前就拒(`checkpoint.invalid_outcome`,卡留 pending),本口的副作用全在引擎受理之后 —— 在那里再问一次就是第二个判官。
148
+ *
149
+ * 交给判官的「问」= {@link heldAskOf}:本仓两条口持有的都不是引擎的原物(活卡条目记的是铸条目那一次从 `AskRequest` 读定的那一位;耐久 ask 账行上是
150
+ * 卡面 `risk.requiresRealApproval`,读不出按要人读),以引擎的问形(core `HeldQuestion` 的 `ask` 决定臂)交过去 —— 「谁答不了」只在引擎那一处判。
151
+ */
152
+ export declare function heldAskOf(demandsAPerson: boolean): Extract<PermissionResult, {
153
+ action: "ask";
154
+ }>;
155
+ /**
156
+ * 一次 decide 的**停驻座事实**,一处成形(耐久 `/decide` 与活卡 PARKED 赎回席共用;截止扫直接铸 `{decidedBy:"sla_timeout"}`)。
157
+ *
158
+ * `decidedBy` = 已经过 {@link readDecidedBy} 的 wire 词:两个词在停驻座上**就是它自己**(`person` / `policy`,
159
+ * 两只词表的交集由上面的编译期门证),所以这里没有 cast,也没有缺席律(缺席已在读法里落成 `"person"`)。
72
160
  *
73
161
  * `principal` = 那条腿已经验过身份的决策者(缺席 = 匿名档的部署,通道确实没有名字可报)。
74
162
  * 归属串走 core 的 `screenApproverAttribution` **先筛后带**:core 对形不合的归属是 pre-CAS 响亮拒,
@@ -76,14 +164,9 @@ export type HostDecidedBy = HostDecision["decidedBy"];
76
164
  * 被改造过的串):归属是转录,不是可以「修一修」的东西 —— 修出来的名字既不是通道报的那个,也不是
77
165
  * 「通道没报」,两头都不真。
78
166
  *
79
- * 🔴 归属**不按词分岔**:core 7.38.x 的 `policy_refused` 结算记录上没有归属席(`who.party:"none"`),7.39.0 #1265 起有了
80
- * (`who.party:"policy"`,`approver?`)—— 无论 core 的记录怎么写,**本服务观察到的事实**不变:送出这次请求的那把已验证身份是真的,本仓的
167
+ * 🔴 归属**不按词分岔**:无论 core 的记录怎么写,**本服务观察到的事实**不变:送出这次请求的那把已验证身份是真的,本仓的
81
168
  * 迟到结算账本(`approval-ask-audit-store.ts`)记的就是它。按词删一格 = 给账本挖一个只在某个词上出现的洞。
82
- *
83
- * 🔴 **入参是 `string`,不是 `HostDecidedBy` —— 这一格刻意让类型说真话**(wire 边界强类型宪法):两条
84
- * decide 腿能证得出的只有「调用方给了个串」(形门在各自的口上),**证不出**它是词表成员;把参数声明成
85
- * 闭集就是让类型替一件本层从没做过的检查背书。⇒ 收 `string`,在**这一处**(本座的边界)做唯一一次 cast,
86
- * 而那次 cast 说的是「我把一个未判词的串放到只收词的座上,判词的是座的属主(core,pre-CAS 响亮拒)」。
87
169
  */
88
- export declare function hostDecisionFacts(decidedBy: string | undefined, principal: string | undefined): HostDecision;
170
+ export declare function hostDecisionFacts(decidedBy: ApprovalDecidedBy, principal: string | undefined): HostDecision;
171
+ export {};
89
172
  //# sourceMappingURL=host-decision.d.ts.map
@@ -1,6 +1,43 @@
1
- import { screenApproverAttribution } from "@sema-agent/core";
1
+ import { HOST_DECIDERS, isHostDecider, screenApproverAttribution } from "@sema-agent/core";
2
+ const SWEEP_ONLY_DECIDER = "sla_timeout";
3
+ function isApprovalDecidedBy(v) {
4
+ return isHostDecider(v) && v !== SWEEP_ONLY_DECIDER;
5
+ }
6
+ export const APPROVAL_DECIDED_BY = Object.freeze(HOST_DECIDERS.filter(isApprovalDecidedBy));
7
+ export const DECIDED_BY_OUTSIDE_CLOSED_SET = `decidedBy must be ${APPROVAL_DECIDED_BY.map((w) => `"${w}"`).join(" or ")} when present — who made this decision: a person, or this deployment's own policy (absent = "person"); "${SWEEP_ONLY_DECIDER}" is never accepted from a caller (only this server's own deadline sweep says a wait ended with nobody answering)`;
8
+ export const DECIDED_BY_NOT_IN_PROOF = "decidedBy is not supported on a direct-door worker (not covered by the decision proof)";
9
+ export const POLICY_CANNOT_ALLOW_PERSON_ASK = 'decidedBy "policy" cannot allow this ask — it demands a person\'s approval (risk.requiresRealApproval), and a policy\'s answer is not a person\'s; answer it as a person, or refuse it as the policy';
10
+ export const SETTLED_BY_RETIRED = 'settledBy is retired — send decidedBy: "person" | "policy" (who made this decision; absent = "person"); ignoring it would record a policy\'s answer as a person\'s';
11
+ export const RETIRED_DECISION_KEY = "settledBy";
12
+ export function retiredDecisionKeyFacts(body) {
13
+ if (body === null || typeof body !== "object" || Array.isArray(body))
14
+ return undefined;
15
+ return body[RETIRED_DECISION_KEY] !== undefined ? { error: SETTLED_BY_RETIRED, extra: { field: RETIRED_DECISION_KEY } } : undefined;
16
+ }
17
+ export function readDecidedBy(raw, directDoorActive) {
18
+ if (raw === undefined)
19
+ return { kind: "accepted", decidedBy: "person" };
20
+ if (directDoorActive)
21
+ return { kind: "not_in_proof" };
22
+ return isApprovalDecidedBy(raw) ? { kind: "accepted", decidedBy: raw } : { kind: "outside_closed_set" };
23
+ }
24
+ export function liveSettledBy(decidedBy) {
25
+ switch (decidedBy) {
26
+ case "person":
27
+ return undefined;
28
+ case "policy":
29
+ return "policy";
30
+ default: {
31
+ const unreachable = decidedBy;
32
+ throw new Error(`liveSettledBy: unknown decidedBy ${String(unreachable)}`);
33
+ }
34
+ }
35
+ }
36
+ export function heldAskOf(demandsAPerson) {
37
+ return demandsAPerson ? { action: "ask", requiresRealApproval: true } : { action: "ask" };
38
+ }
2
39
  export function hostDecisionFacts(decidedBy, principal) {
3
40
  const screened = screenApproverAttribution(principal);
4
- return { decidedBy: (decidedBy ?? "person"), ...(screened.approver !== undefined ? { approver: screened.approver } : {}) };
41
+ return { decidedBy, ...(screened.approver !== undefined ? { approver: screened.approver } : {}) };
5
42
  }
6
43
  //# sourceMappingURL=host-decision.js.map
@@ -1,8 +1,8 @@
1
- import { MCP_IMAGE_MAX_BASE64, isThinkingLevel, expandTiers } from "@sema-agent/core";
1
+ import { APPROVAL_REACH, MCP_IMAGE_MAX_BASE64, isApprovalReach, isPermissionMode, isThinkingLevel, expandTiers } from "@sema-agent/core";
2
2
  import { isModelAllowlisted, matchCatalogModel, refusedCatalogEntry, selectableCatalog } from "../model-select.js";
3
3
  import { modelUnavailableMessage } from "../model-entry-refusal.js";
4
4
  import { HttpError } from "../security.js";
5
- import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, SETTINGS_PERMISSION_MODE_WORDS, isSettingsPermissionMode, parseTaskSettings, settingsPermissionBucketIssue, skillListingBudgetFractionOk, skillListingMaxDescOk, taskHooksHonored, taskSettingsKeyIssue } from "../task-settings.js";
5
+ import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, PERMISSION_MODE_WORDS, parseTaskSettings, settingsPermissionBucketIssue, skillListingBudgetFractionOk, skillListingMaxDescOk, taskHooksHonored, taskSettingsKeyIssue } from "../task-settings.js";
6
6
  import { taskBodyKeyIssue } from "../request-key-closure.js";
7
7
  import { FILE_HISTORY_DISABLED_NO_RESTORE, fileHistoryRestoreConflict, inheritEnvFieldIssue, singleMemberOffFieldIssue } from "../request-value-closure.js";
8
8
  import * as facts from "../capability-facts.js";
@@ -14,6 +14,7 @@ import { verifyRoundsFromBody } from "./verify-rounds.js";
14
14
  import { skillCarriesRetiredManifest } from "./retired-skill-manifest.js";
15
15
  import { resolveDevicePlacementIntent } from "./device-placement.js";
16
16
  import { sendError, httpErrorCode } from "./send.js";
17
+ const APPROVAL_REACH_WORDS = APPROVAL_REACH.map((word) => JSON.stringify(word)).join(" / ");
17
18
  export const MAX_SKILL_CONTENT_CHARS = 1_048_576;
18
19
  export const MAX_SYSTEM_PROMPT_CHARS = 16_384;
19
20
  export const MAX_OUTPUT_SCHEMA_CHARS = 32_768;
@@ -470,6 +471,11 @@ export function createAdmission(ctx) {
470
471
  sendError(res, 400, "request.field_invalid", "interactiveTools must be a boolean");
471
472
  return null;
472
473
  }
474
+ const approvalReach = body.approvalReach;
475
+ if (approvalReach !== undefined && !isApprovalReach(approvalReach)) {
476
+ sendError(res, 400, "request.field_invalid", `approvalReach must be ${APPROVAL_REACH_WORDS} when present (the run declares that nobody on it can be asked to approve; omit the key otherwise)`);
477
+ return null;
478
+ }
473
479
  if (body.oneShot !== undefined && typeof body.oneShot !== "boolean") {
474
480
  sendError(res, 400, "request.field_invalid", "oneShot must be a boolean");
475
481
  return null;
@@ -518,8 +524,8 @@ export function createAdmission(ctx) {
518
524
  }
519
525
  {
520
526
  const v = body.permissionMode;
521
- if (v !== undefined && !isSettingsPermissionMode(v)) {
522
- sendError(res, 400, "request.field_invalid", `permissionMode must be one of ${SETTINGS_PERMISSION_MODE_WORDS}`);
527
+ if (v !== undefined && !isPermissionMode(v)) {
528
+ sendError(res, 400, "request.field_invalid", `permissionMode must be one of ${PERMISSION_MODE_WORDS}`);
523
529
  return null;
524
530
  }
525
531
  }
@@ -529,8 +535,8 @@ export function createAdmission(ctx) {
529
535
  const p = s.permissions;
530
536
  if (p !== null && typeof p === "object" && !Array.isArray(p)) {
531
537
  const m = p.defaultMode;
532
- if (m !== undefined && !isSettingsPermissionMode(m)) {
533
- sendError(res, 400, "request.field_invalid", `settings.permissions.defaultMode must be one of ${SETTINGS_PERMISSION_MODE_WORDS}`);
538
+ if (m !== undefined && !isPermissionMode(m)) {
539
+ sendError(res, 400, "request.field_invalid", `settings.permissions.defaultMode must be one of ${PERMISSION_MODE_WORDS}`);
534
540
  return null;
535
541
  }
536
542
  const d = p.disableAutoMode;
@@ -150,7 +150,7 @@ export function createApprovalSweeps(ctx) {
150
150
  ...(req.gateBoundInputHash !== null ? { boundInputHash: req.gateBoundInputHash } : {}),
151
151
  ...(req.updatedInput !== undefined ? { updatedInput: req.updatedInput } : {}),
152
152
  };
153
- const out = await resumeCheckpoint(cp.sessionId, req.decision, req.note, hostDecisionFacts(req.settledBy, req.principal), req.httpReq, undefined, binding, undefined, true, { principal: req.principal, explicitOperator: false });
153
+ const out = await resumeCheckpoint(cp.sessionId, req.decision, req.note, hostDecisionFacts(req.decidedBy, req.principal), req.httpReq, undefined, binding, undefined, true, { principal: req.principal, explicitOperator: false });
154
154
  return { status: out.status, body: out.body };
155
155
  }
156
156
  return { denyExpiredApprovals, redeemParkedAsk };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * S-431 —— 一条 HTTP 请求愿意等**一次远端 MCP 拨号**多久:**一只数、一只 helper**,两口共读。
3
+ *
4
+ * 同一类动作(连一台远端 MCP 服务器 + 列它的工具)在本仓有两张 HTTP 面:
5
+ * · `GET /v1/sessions/:id/mcp` 面板(`routes/sessions.ts`)—— 物化部署缺省场景的申报,到点答 200 `degraded:true`;
6
+ * · `POST /v1/sessions/:id/mcp/reconnect`(`routes/mcp-reconnect.ts`)—— 在飞 run 上重拨一台,到点答 503。
7
+ * 修前面板有期限、reconnect 是裸 `await`(S-431):同一件事两个读法,一台拨不通 / 半开的远端能把后者挂很久。
8
+ * 两口各自**到点答什么**不同(面板是只读的部署面,有一个诚实的空形;reconnect 是一次交易,没有结果就没有
9
+ * 一个能当 `outcome` 的词)—— 但**等多久**是一个事实,所以只有一个数。
10
+ *
11
+ * 🔴 **这是常量,不是旋钮**:没有 env、不进 config-catalog。要调窗就改这一处,两口一起动。
12
+ * (底层拨号自己的上界是 core 的启动期钟 —— env `MCP_TIMEOUT`,未设落 SDK 缺省 —— 与本数无关:本数只界
13
+ * **HTTP 应答**,不界拨号;见 {@link raceDeadline}。)
14
+ *
15
+ * helper 收的是期限**参数**而不是自己读上面那个常量:调用点把 {@link MCP_DIAL_DEADLINE_MS} 显式交下来,
16
+ * `test/mcp-reconnect-deadline.test.ts` 的静态钉就是「每个远端 MCP 拨号调用点都把它交给 `raceDeadline`」。
17
+ */
18
+ /** 一次远端 MCP 拨号的 HTTP 应答期限(毫秒)。面板与 reconnect 共读;**全仓只有这一处写它**。 */
19
+ export declare const MCP_DIAL_DEADLINE_MS = 10000;
20
+ /** `work` 没在期限内落定。**具名型**:调用腿据它分诊,不比对错误文案。 */
21
+ export declare class DeadlineExceeded extends Error {
22
+ readonly boundMs: number;
23
+ constructor(boundMs: number);
24
+ }
25
+ /**
26
+ * 让**应答**不再等 `work`:`work` 先落定就原样交出(resolve / reject 都不变形),期限先到就 reject
27
+ * {@link DeadlineExceeded}。
28
+ *
29
+ * 🔴 **只界应答,不取消 `work`**:本函数不持有任何取消把手 —— core 的 `TaskStream.reconnectMcpServer` 与
30
+ * `materializeMcpTools` 都不收 `AbortSignal`(d.ts 逐字),所以期限到点后底层拨号**继续跑**,落定时
31
+ * 才释放它占的连接 / 子进程。调用点必须把这句话交代给调用方(reconnect 的 503 文案、契约 §G.7c)。
32
+ * 迟到的落定(尤其迟到的 reject)挂一条吞掉,免得没人 await 它时变成未处理拒绝。
33
+ */
34
+ export declare function raceDeadline<T>(work: Promise<T>, deadlineMs: number): Promise<T>;
35
+ //# sourceMappingURL=mcp-dial-deadline.d.ts.map
@@ -0,0 +1,21 @@
1
+ export const MCP_DIAL_DEADLINE_MS = 10_000;
2
+ export class DeadlineExceeded extends Error {
3
+ boundMs;
4
+ constructor(boundMs) {
5
+ super(`did not settle within ${boundMs}ms`);
6
+ this.boundMs = boundMs;
7
+ this.name = "DeadlineExceeded";
8
+ }
9
+ }
10
+ export function raceDeadline(work, deadlineMs) {
11
+ let timer;
12
+ const deadline = new Promise((_resolve, reject) => {
13
+ timer = setTimeout(() => reject(new DeadlineExceeded(deadlineMs)), deadlineMs);
14
+ });
15
+ work.catch(() => undefined);
16
+ return Promise.race([work, deadline]).finally(() => {
17
+ if (timer !== undefined)
18
+ clearTimeout(timer);
19
+ });
20
+ }
21
+ //# sourceMappingURL=mcp-dial-deadline.js.map
@@ -10,21 +10,24 @@
10
10
  *
11
11
  * ## 规则(三层,与 operator 面既有层序逐字同序)
12
12
  * ① core 的**本口专属**码(闭集穷举 `switch`,见 {@link sentCoreCode})—— 码与状态两面同;文案按受众(下一节,一条规则);
13
- * ② `memory.target_outside_store`(core 7.31.1 #1094 跨面 store 状态码)—— 409;
13
+ * ② **店形拒**两码 `memory.target_outside_store`(core 7.31.1 #1094)/ `memory.scope_home_not_directory`(core 7.44.0 #1368)—— 跨面 store 状态码,409、码原样
14
+ * (判据 `memory-store-refusal.ts` 一只;运维行 `memory_store_form_refused { code, path?, form? }` 两受众同一只铸点,路径只在那里);
14
15
  * ③ `ControlPlaneCorruptError` —— typed 500 `internal.memory_control_plane_corrupt`(fail-closed 信号,一码两义见下);
15
16
  * 其余 ⇒ `false`(未分类,调用方发它自己那句 500 且**不回显**内部文本)。
16
17
  *
17
18
  * ## 文案按受众分(码与状态不分)—— **一条规则,三层同施,不按码挑**(F9,7.108.0 合并复审 opus 腿 O-9)
18
- * core 原句是写给运维的诊断材料:② ③ 点名**宿主路径**(store 根、链解析到哪 / 控制面账本文件),① 的 `selector_mismatch` 带**首个请求的
19
+ * core 原句是写给运维的诊断材料:③ 点名**宿主路径**(控制面账本文件;② 自 core 7.44.0 起句子不带路径、路径在 `detail.path` ⇒ 运维行),① 的 `selector_mismatch` 带**首个请求的
19
20
  * 选择子哈希**(`pinned set <selectHash>`,无盐 sha256 —— operator 选择子可字典还原,首个请求未必是这位会话所有者发的):
20
21
  * · operator 受众 ⇒ 原句上 wire(过了 operator 门,这些材料对这个受众不是泄露);
21
22
  * · 会话所有者受众 ⇒ **一律**一句固定指路(每码一句,只说「怎么办」),原句整句交 `withheld` 落运维 warn —— 不逐码判断哪句安全
22
23
  * (逐码挑 = core 改一句文案就可能静默漏出一样新东西)。② ③ 的固定句**两形都真**:
23
- * ② 链先于请求在场 ⇒ 提交点之前拒、什么都没做;提交点之后才换上的链 ⇒ 已删的几条算数、journal 留盘(codex 一审 C1);
24
+ * ② 链先于请求在场 ⇒ 提交点之前拒、什么都没做;提交点之后才换上的链 ⇒ 已删的几条算数、journal 留盘(codex 一审 C1);scope home 不是真目录那一句
25
+ * 同样不许诺零删除(home 被换掉的时刻本层判不出),只指路「运维放回真目录后用同一个 requestId 重发」;
24
26
  * ③ 一码两义 —— 操作前账本读不出 ⇒ 什么都没发生;落定后残留枚举失败 ⇒ 抹除已提交、重放收敛(core 原话)。两句都不许诺零删除,
25
27
  * 都指路「运维修好后用同一个 requestId 重发」(两义下这句都对)。本层**不**嗅 core 文案分辨前后(拿上游自由文本当判别式是第二真源)。
26
28
  */
27
29
  import type { ServerResponse } from "node:http";
30
+ import type { Logger } from "../observability/logger.js";
28
31
  /**
29
32
  * 已分类拒 → HTTP 状态的**唯一**码表 = 自铸 wire 值登记处的 `sessionMemoryEraseStatus` 集(F3:wire 变更清单从同一张表 diff,状态改一处、
30
33
  * 清单跟着列)。两面同读(S-930:码与状态只判一次)。键写错 ⇒ tsc 红(索引的是 `as const` 表的键)。
@@ -36,6 +39,9 @@ export type MemoryComplianceAudience = {
36
39
  readonly kind: "session_owner";
37
40
  readonly withheld: (coreMessage: string) => void;
38
41
  };
39
- /** 按上面三层应答;返回 `true` = 已应答,`false` = 未分类(一个字节都没写,调用方发它自己那句 500 并落它自己的运维 warn)。 */
40
- export declare function sentMemoryComplianceFailure(res: ServerResponse, err: unknown, audience: MemoryComplianceAudience): boolean;
42
+ /**
43
+ * 按上面三层应答;返回 `true` = 已应答,`false` = 未分类(一个字节都没写,调用方发它自己那句 500 并落它自己的运维 warn)。
44
+ * `logger` = 店形拒那一条运维行的去处(两受众同一只;缺席 ⇒ 不落)。
45
+ */
46
+ export declare function sentMemoryComplianceFailure(res: ServerResponse, err: unknown, audience: MemoryComplianceAudience, logger: Pick<Logger, "warn"> | undefined): boolean;
41
47
  //# sourceMappingURL=memory-compliance-failure-reply.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { ControlPlaneCorruptError } from "@sema-agent/core";
2
- import { targetOutsideStoreOf } from "../memory-store-refusal.js";
2
+ import { logStoreFormRefusal, storeFormRefusalOf } from "../memory-store-refusal.js";
3
3
  import { sentMemoryStoreRefusal } from "./memory-store-refusal-reply.js";
4
4
  import { sendError } from "./send.js";
5
5
  import { assertNever } from "./route-ctx.js";
@@ -38,19 +38,28 @@ function codeOf(err) {
38
38
  const { code } = err;
39
39
  return typeof code === "string" ? code : undefined;
40
40
  }
41
- export function sentMemoryComplianceFailure(res, err, audience) {
41
+ export function sentMemoryComplianceFailure(res, err, audience, logger) {
42
42
  const coreCode = codeOf(err);
43
43
  if (coreCode !== undefined && sentCoreCode(res, coreCode, err instanceof Error ? err.message : String(err), audience))
44
44
  return true;
45
- const outside = targetOutsideStoreOf(err);
46
- if (outside !== undefined) {
45
+ const storeForm = storeFormRefusalOf(err);
46
+ if (storeForm !== undefined) {
47
47
  switch (audience.kind) {
48
48
  case "operator":
49
- return sentMemoryStoreRefusal(res, err);
49
+ return sentMemoryStoreRefusal(res, err, logger);
50
50
  case "session_owner":
51
- audience.withheld(outside.message);
52
- sendError(res, 409, "memory.target_outside_store", "session memory erasure refused — a directory in this deployment's memory store leads outside it (a link), so this erasure did not complete (a refusal before its commit point changed nothing; one after it keeps the deletions already made and a journal that finishes the rest once the link is removed); ask the operator to remove the link, then resend with the same requestId");
53
- return true;
51
+ logStoreFormRefusal(logger, storeForm);
52
+ audience.withheld(storeForm.message);
53
+ switch (storeForm.code) {
54
+ case "memory.target_outside_store":
55
+ sendError(res, 409, "memory.target_outside_store", "session memory erasure refused — a directory in this deployment's memory store leads outside it (a link), so this erasure did not complete (a refusal before its commit point changed nothing; one after it keeps the deletions already made and a journal that finishes the rest once the link is removed); ask the operator to remove the link, then resend with the same requestId");
56
+ return true;
57
+ case "memory.scope_home_not_directory":
58
+ sendError(res, 409, "memory.scope_home_not_directory", "session memory erasure refused — a scope directory in this deployment's memory store is not a real directory (it was replaced by a link or a file), so this erasure did not complete; ask the operator to put the real directory back, then resend with the same requestId");
59
+ return true;
60
+ default:
61
+ return assertNever(storeForm.code);
62
+ }
54
63
  default:
55
64
  return assertNever(audience);
56
65
  }
@@ -1,24 +1,27 @@
1
1
  /**
2
- * core 7.31.1 #1094 `memory.target_outside_store` 的 **operator 面 HTTP 映射** —— 唯一一处字面量(判据在
3
- * `src/memory-store-refusal.ts`)。挂在每一个触 store 树的 operator 记忆口的已分类收尾上:
2
+ * **店形拒**两码(`memory.target_outside_store` / `memory.scope_home_not_directory`,判据在 `src/memory-store-refusal.ts`)的
3
+ * **operator 面 HTTP 映射** + 两受众共用的**运维行** —— 唯一一处码字面量。挂在每一个触 store 树的 operator 记忆口的已分类收尾上:
4
4
  * `routes/memory-compliance.ts`(出处 / 抹除;经 `http/memory-compliance-failure-reply.ts` 的 operator 受众臂)· `routes/memory-origin.ts`(外源列表 / 清标;**不含**只读控制面的清标账列表 ——
5
5
  * core 的 wire 表逐字:只碰控制面的面不判,给它挂一条永远收不到的码路径正是附录 A 活触发门要防的形)·
6
- * `routes/memory-bundle.ts`(导出 / 导入)。会话所有者的自助抹除口**不走这里**(S-930 起经 `http/memory-compliance-failure-reply.ts` 的会话所有者受众臂:同码、固定文案)。
6
+ * `routes/memory-bundle.ts`(导出 / 导入)。会话所有者的自助抹除口**不走这里的 wire 句**(经 `http/memory-compliance-failure-reply.ts` 的会话所有者受众臂:同码、固定文案),
7
+ * 但运维行({@link logStoreFormRefusal},铸点在 `src/memory-store-refusal.ts`)两受众同一只。
7
8
  * `POST /v1/admin/memory/consolidation/run` 也不走这里:那口把 core 的**任何**有码拒一律收成 409 `memory.consolidation_refused`
8
- * (码进文案,附录 A 该行),本码已在那条臂上,不再给它第二个码。
9
+ * (码进文案,附录 A 该行),这一族已在那条臂上,不再给它第二个码。
9
10
  *
10
- * ## 为什么 409
11
- * core 的读法:operator 要处置的拒(去掉 store 里那只通到外面的目录链),不是可重试的 IO 故障,也不是对调用方的裁决。与
11
+ * ## 为什么 409(两码同一条)
12
+ * core 的读法:operator 要处置的盘形(去掉 store 里那只通到外面的目录链 / 把 scope home 放回真目录),不是可重试的 IO 故障,也不是对调用方的裁决。与
12
13
  * `memory.erasure_census_incomplete` 同一类(**当前 store 状态**下这次做不了;状态修好后重发即改判)⇒ 409,不 500(服务器没坏)、
13
14
  * 不 4xx 请求族(请求没写错)。抹除面:链先于请求在场 ⇒ core 在提交点之前拒,零删除、零 journal(真 core 格钉在
14
15
  * `test/memory-compliance-http.test.ts`);链在提交点之后才换上 ⇒ 已删的几条算数、journal 留盘 —— core 原句会带
15
16
  * `after the commit point; the journal is kept`,本层原样转发、不嗅它分类(codex 一审 C1)。
16
17
  *
17
- * ## 文案原样
18
- * core 的诊断句点名动作、路径与解析到哪 —— 那是运维找到那只链的唯一材料,与本族其它 core 码同一条「码与文案都不改写」纪律。
19
- * 只对 operator 面这么做:这几口都过了 operator 门,宿主路径对这个受众不是泄露。
18
+ * ## 文案原样,路径进运维行
19
+ * operator 面上 wire 的是 core 原句(码领头;core 7.44.0 起句子里**没有**宿主路径 —— 路径在 `detail.path`)。路径不上 wire(两受众同,裁定:运维腿的
20
+ * 句子不带服务器路径,路径只进日志):每次应答这一族,{@link logStoreFormRefusal} 落一条运维 warn `memory_store_form_refused { code, path?, form? }` ——
21
+ * 运维找到那只链 / 那只坏 home 的唯一材料。
20
22
  */
21
23
  import type { ServerResponse } from "node:http";
22
- /** `err` 是 `memory.target_outside_store` ⇒ 409 带码 + core 原句,返回 true(已应答);否则 false,一个字节都不写。 */
23
- export declare function sentMemoryStoreRefusal(res: ServerResponse, err: unknown): boolean;
24
+ import type { Logger } from "../observability/logger.js";
25
+ /** `err` 是店形拒 ⇒ 409 带码 + core 原句 + 运维行,返回 true(已应答);否则 false,一个字节都不写。 */
26
+ export declare function sentMemoryStoreRefusal(res: ServerResponse, err: unknown, logger: Pick<Logger, "warn"> | undefined): boolean;
24
27
  //# sourceMappingURL=memory-store-refusal-reply.d.ts.map
@@ -1,10 +1,21 @@
1
- import { targetOutsideStoreOf } from "../memory-store-refusal.js";
1
+ import { logStoreFormRefusal, storeFormRefusalOf } from "../memory-store-refusal.js";
2
2
  import { sendError } from "./send.js";
3
- export function sentMemoryStoreRefusal(res, err) {
4
- const refusal = targetOutsideStoreOf(err);
3
+ export function sentMemoryStoreRefusal(res, err, logger) {
4
+ const refusal = storeFormRefusalOf(err);
5
5
  if (refusal === undefined)
6
6
  return false;
7
- sendError(res, 409, "memory.target_outside_store", refusal.message);
8
- return true;
7
+ logStoreFormRefusal(logger, refusal);
8
+ switch (refusal.code) {
9
+ case "memory.target_outside_store":
10
+ sendError(res, 409, "memory.target_outside_store", refusal.message);
11
+ return true;
12
+ case "memory.scope_home_not_directory":
13
+ sendError(res, 409, "memory.scope_home_not_directory", refusal.message);
14
+ return true;
15
+ default: {
16
+ const unhandled = refusal.code;
17
+ throw new Error(`unhandled store-form refusal word: ${String(unhandled)}`);
18
+ }
19
+ }
9
20
  }
10
21
  //# sourceMappingURL=memory-store-refusal-reply.js.map
@@ -1,7 +1,7 @@
1
1
  import { redactErrorMessage, redactHead } from "../observability/run-terminal.js";
2
2
  import { createHash } from "node:crypto";
3
3
  import { uuidv7, resumeWithVerification, CheckpointError, defaultTaskRegistry, taskNotificationDedupKey, validatePendingSteer, GLOBAL_USAGE_KEY, PAUSE_REGISTRY } from "@sema-agent/core";
4
- import { decideParkedAgent, denyWorkflowParksUnreadable, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
4
+ import { decideParkedAgent, denyWorkflowParkForked, denyWorkflowParksUnreadable, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
5
5
  const sessionFaceUnavailable = async (sessionId) => {
6
6
  throw new Error(`parked decide cannot verify which occupant session ${sessionId} belongs to: this deployment's session store has no sessionFaceOf read (every built-in store has one) — refusing rather than resuming unverified`);
7
7
  };
@@ -15,7 +15,7 @@ import { recordResumeResourceWindow } from "../resource-window.js";
15
15
  import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
16
16
  import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, isFleetAgentTerminalNotification } from "../fleet/fleet-bus.js";
17
17
  import { defaultSubagentTailBus } from "../fleet/subagent-tail-bus.js";
18
- import { APPROVAL_GATE_KINDS, createApprovalCardEmitter, isApprovalGateKind, parkRowStandingAnswerFacts, resolveApprovalLeg, resolveStreamApprovalGate, standingAnswerMayRetire } from "../tool-approval.js";
18
+ import { APPROVAL_GATE_KINDS, createApprovalCardEmitter, isApprovalGateKind, parkRowStandingAnswerFacts, resolveApprovalLeg, resolveStreamApprovalGate, standingAnswerDurablyRecordable } from "../tool-approval.js";
19
19
  import { buildCheckpointAnchorHash } from "../approval-ask-audit-store.js";
20
20
  import { TERMINAL_PARK_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
21
21
  import { withPrincipal } from "../observability/principal-context.js";
@@ -41,6 +41,7 @@ import { buildActiveRunConflict, resumeEntryForGate } from "./active-run-conflic
41
41
  import { commitPlacement, devicePlacementErrorBody } from "./device-placement.js";
42
42
  import { commitSessionInheritEnv } from "./session-inherit-env.js";
43
43
  import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
44
+ import { deploymentListAsks } from "../approval.js";
44
45
  import { readResumeCtx, resumeCtxCorruptAnswer } from "./resume-ctx-read.js";
45
46
  import { resumeBlockedByPolicyAnswer, storedBodyReplayAnswer, storedBodyReplayLogFields, storedBodyReplayRefusal } from "./checkpoint-ctx.js";
46
47
  import { httpErrorCode } from "./send.js";
@@ -274,7 +275,7 @@ export function createResumeLegs(ctx) {
274
275
  const rememberableTool = paForGrant !== undefined &&
275
276
  paForGrant.toolName &&
276
277
  paForGrant.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
277
- standingAnswerMayRetire(parkRowStandingAnswerFacts(paForGrant, cp.gate, deps.config))
278
+ standingAnswerDurablyRecordable(parkRowStandingAnswerFacts(paForGrant, cp.gate, deps.config, deploymentListAsks(deps.config, { toolName: paForGrant.toolName })))
278
279
  ? paForGrant.toolName
279
280
  : null;
280
281
  const boundGrant = onResumeCommitted
@@ -345,6 +346,10 @@ export function createResumeLegs(ctx) {
345
346
  void ctxPromise.catch(() => { });
346
347
  return denyWorkflowParksUnreadable(origin);
347
348
  }
349
+ if (origin?.kind === "workflow_park_forked") {
350
+ void ctxPromise.catch(() => { });
351
+ return denyWorkflowParkForked(origin);
352
+ }
348
353
  if (origin?.kind === "workflow") {
349
354
  void ctxPromise.catch(() => { });
350
355
  const plan = planWorkflowParkedDecide(origin, parkedReq);