@sema-agent/server 7.103.2 → 7.105.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 (204) hide show
  1. package/MIGRATION.md +378 -2
  2. package/README.md +6 -1
  3. package/README.zh-CN.md +5 -1
  4. package/USAGE.md +320 -16
  5. package/deploy/sema-up/README.md +57 -7
  6. package/deploy/sema-up/cluster-up.sh +18 -4
  7. package/deploy/sema-up/kube-up.sh +16 -0
  8. package/deploy/sema-up/lib/dataroot.sh +142 -0
  9. package/deploy/sema-up/lib/flagshape.sh +69 -0
  10. package/deploy/sema-up/lib/lanes.py +14 -4
  11. package/deploy/sema-up/lib/shell-access.sh +41 -0
  12. package/deploy/sema-up/preflight.sh +14 -1
  13. package/deploy/sema-up/sema-up.sh +134 -37
  14. package/deploy/sema-up/smoke.sh +5 -0
  15. package/dist/auto-mode-face.js +3 -4
  16. package/dist/boot/budget-tracing.js +1 -1
  17. package/dist/boot/config-center.d.ts +6 -2
  18. package/dist/boot/config-center.js +5 -59
  19. package/dist/boot/execution-env.d.ts +11 -0
  20. package/dist/boot/execution-env.js +14 -0
  21. package/dist/boot/execution-lane.d.ts +25 -0
  22. package/dist/boot/execution-lane.js +95 -0
  23. package/dist/boot/memory-consolidation.d.ts +185 -9
  24. package/dist/boot/memory-consolidation.js +192 -32
  25. package/dist/boot/resolve-spec.d.ts +2 -0
  26. package/dist/boot/resolve-spec.js +28 -24
  27. package/dist/boot/runner-deps.d.ts +6 -2
  28. package/dist/boot/runner-deps.js +6 -1
  29. package/dist/boot/session-faces.d.ts +5 -0
  30. package/dist/boot/session-faces.js +18 -2
  31. package/dist/boot/session-incarnation-seat.d.ts +41 -0
  32. package/dist/boot/session-incarnation-seat.js +69 -0
  33. package/dist/boot/stage-01-config.js +2 -2
  34. package/dist/boot/stage-02-registry-pull.js +3 -1
  35. package/dist/boot/stage-04-stores.d.ts +2 -0
  36. package/dist/boot/stage-04-stores.js +3 -1
  37. package/dist/boot/stage-05-execution-env.d.ts +4 -0
  38. package/dist/boot/stage-05-execution-env.js +2 -2
  39. package/dist/boot/stage-06-runners.d.ts +4 -1
  40. package/dist/boot/stage-06-runners.js +7 -14
  41. package/dist/boot/stage-07-capability-layer.d.ts +4 -1
  42. package/dist/boot/stage-08-reapers.d.ts +4 -1
  43. package/dist/boot/stage-09-leader.d.ts +6 -1
  44. package/dist/boot/stage-09-leader.js +2 -1
  45. package/dist/boot/stage-10-http-server.d.ts +6 -1
  46. package/dist/boot/stage-10-http-server.js +4 -1
  47. package/dist/brain.d.ts +1 -1
  48. package/dist/config-catalog.d.ts +16 -1
  49. package/dist/config-catalog.js +9 -9
  50. package/dist/config-center/restart-signal.d.ts +4 -4
  51. package/dist/config-invariants.d.ts +7 -1
  52. package/dist/config-invariants.js +37 -6
  53. package/dist/config-provider.d.ts +42 -10
  54. package/dist/config-provider.js +18 -16
  55. package/dist/config-types.d.ts +37 -16
  56. package/dist/config-types.js +1 -0
  57. package/dist/config.d.ts +28 -30
  58. package/dist/config.js +95 -77
  59. package/dist/env-facts.d.ts +13 -3
  60. package/dist/env-facts.js +15 -3
  61. package/dist/execution-lane-caps.d.ts +15 -0
  62. package/dist/execution-lane-caps.js +8 -0
  63. package/dist/file-history-capture.d.ts +42 -35
  64. package/dist/file-history-capture.js +15 -10
  65. package/dist/file-history-disclosure.d.ts +7 -31
  66. package/dist/file-history-disclosure.js +12 -0
  67. package/dist/fleet/fleet-bus.d.ts +5 -1
  68. package/dist/http/admission.d.ts +12 -5
  69. package/dist/http/admission.js +5 -1
  70. package/dist/http/dispatch.d.ts +18 -0
  71. package/dist/http/dispatch.js +19 -6
  72. package/dist/http/principal-gate.d.ts +22 -0
  73. package/dist/http/principal-gate.js +20 -0
  74. package/dist/http/resume-legs.d.ts +1 -1
  75. package/dist/http/resume-legs.js +9 -5
  76. package/dist/http/route-table.d.ts +15 -4
  77. package/dist/http/route-table.js +6 -5
  78. package/dist/http/routes/agents-roster.d.ts +7 -0
  79. package/dist/http/routes/agents-roster.js +6 -2
  80. package/dist/http/routes/approvals-assistant.d.ts +48 -19
  81. package/dist/http/routes/approvals-assistant.js +117 -50
  82. package/dist/http/routes/capabilities.js +3 -2
  83. package/dist/http/routes/config-catalog.js +1 -0
  84. package/dist/http/routes/fleet.d.ts +3 -2
  85. package/dist/http/routes/images.js +0 -1
  86. package/dist/http/routes/memory-consolidation.d.ts +3 -2
  87. package/dist/http/routes/memory-policy.js +3 -3
  88. package/dist/http/routes/rules.js +2 -2
  89. package/dist/http/routes/runs.js +29 -10
  90. package/dist/http/routes/session-sync.js +14 -15
  91. package/dist/http/routes/sessions-list.js +3 -2
  92. package/dist/http/routes/sessions.js +49 -25
  93. package/dist/http/routes/shared-memory.js +2 -2
  94. package/dist/http/routes/side-query.js +8 -1
  95. package/dist/http/routes/tasks.js +22 -19
  96. package/dist/http/routes/trace-usage.d.ts +2 -0
  97. package/dist/http/routes/trace-usage.js +8 -13
  98. package/dist/http/send.d.ts +12 -0
  99. package/dist/http/send.js +12 -1
  100. package/dist/http/server.d.ts +30 -8
  101. package/dist/http/server.js +11 -5
  102. package/dist/http/sse-lifecycle.d.ts +1 -1
  103. package/dist/http/sse-log.d.ts +12 -9
  104. package/dist/http/sse-log.js +1 -8
  105. package/dist/http/sync-submit-body.d.ts +43 -0
  106. package/dist/http/sync-submit-body.js +4 -0
  107. package/dist/http/wire-types.d.ts +23 -5
  108. package/dist/index.d.ts +1 -0
  109. package/dist/index.js +1 -0
  110. package/dist/leader/merge.d.ts +8 -3
  111. package/dist/memory-incident-sink.js +2 -1
  112. package/dist/model-select.d.ts +9 -0
  113. package/dist/model-select.js +5 -1
  114. package/dist/observability/corrupt-read-seat.d.ts +151 -16
  115. package/dist/observability/corrupt-read-seat.js +158 -15
  116. package/dist/observability/fail-open.d.ts +17 -1
  117. package/dist/observability/fail-open.js +17 -1
  118. package/dist/observability/metrics.js +1 -1
  119. package/dist/observability/run-terminal.d.ts +60 -10
  120. package/dist/observability/run-terminal.js +119 -9
  121. package/dist/parked-decide.d.ts +8 -3
  122. package/dist/parked-decide.js +56 -20
  123. package/dist/parked-revive-surface.d.ts +14 -7
  124. package/dist/parked-revive-surface.js +5 -1
  125. package/dist/plugins/approval-ask-store-file.d.ts +4 -1
  126. package/dist/plugins/approval-ask-store-file.js +9 -12
  127. package/dist/plugins/approval-ask-store-sql.d.ts +4 -0
  128. package/dist/plugins/approval-ask-store-sql.js +4 -1
  129. package/dist/plugins/approval-exemption-store.d.ts +21 -1
  130. package/dist/plugins/approval-exemption-store.js +36 -7
  131. package/dist/plugins/approval-nonce-store.d.ts +16 -2
  132. package/dist/plugins/approval-nonce-store.js +17 -7
  133. package/dist/plugins/caching-session-store.d.ts +5 -0
  134. package/dist/plugins/caching-session-store.js +3 -0
  135. package/dist/plugins/file-resume-anchor-store.d.ts +7 -2
  136. package/dist/plugins/file-resume-anchor-store.js +19 -5
  137. package/dist/plugins/file-run-store.d.ts +21 -8
  138. package/dist/plugins/file-run-store.js +60 -38
  139. package/dist/plugins/image-bake-store-sql.d.ts +0 -2
  140. package/dist/plugins/image-bake-store-sql.js +0 -5
  141. package/dist/plugins/local-session-store.d.ts +22 -0
  142. package/dist/plugins/local-session-store.js +74 -11
  143. package/dist/plugins/memory-run-store.d.ts +7 -5
  144. package/dist/plugins/memory-run-store.js +11 -4
  145. package/dist/plugins/one-time-migrations.d.ts +38 -1
  146. package/dist/plugins/one-time-migrations.js +143 -15
  147. package/dist/plugins/permission-rule-store-file.d.ts +9 -2
  148. package/dist/plugins/permission-rule-store-file.js +37 -51
  149. package/dist/plugins/pg-session-storage.d.ts +3 -0
  150. package/dist/plugins/pg-session-storage.js +19 -10
  151. package/dist/plugins/retention-store-sql.js +1 -0
  152. package/dist/plugins/run-store-sql.d.ts +15 -6
  153. package/dist/plugins/run-store-sql.js +15 -13
  154. package/dist/plugins/schema-indexes.js +1 -0
  155. package/dist/plugins/send-file-ledger.d.ts +5 -1
  156. package/dist/plugins/send-file-ledger.js +11 -6
  157. package/dist/plugins/session-event-payload.d.ts +14 -4
  158. package/dist/plugins/session-event-payload.js +5 -2
  159. package/dist/plugins/session-policy-store-sql.js +18 -5
  160. package/dist/plugins/store-backend.d.ts +13 -7
  161. package/dist/plugins/store-backend.js +10 -6
  162. package/dist/plugins/store-contracts.d.ts +7 -2
  163. package/dist/plugins/tidb-pool.js +12 -5
  164. package/dist/plugins/tidb-session-store.d.ts +6 -1
  165. package/dist/plugins/tidb-session-store.js +17 -10
  166. package/dist/project-identity.d.ts +4 -6
  167. package/dist/read-face-posture.d.ts +21 -0
  168. package/dist/read-face-posture.js +9 -0
  169. package/dist/request-key-closure.js +1 -1
  170. package/dist/request-value-closure.d.ts +72 -3
  171. package/dist/request-value-closure.js +12 -4
  172. package/dist/roster-bound-http-status.d.ts +6 -0
  173. package/dist/roster-bound-http-status.js +6 -0
  174. package/dist/rules-consent.js +0 -11
  175. package/dist/run-local.js +22 -27
  176. package/dist/runs.d.ts +22 -2
  177. package/dist/runs.js +29 -7
  178. package/dist/sealed-key.d.ts +31 -4
  179. package/dist/sealed-key.js +9 -0
  180. package/dist/security.d.ts +5 -1
  181. package/dist/session-children-retire.d.ts +92 -0
  182. package/dist/session-children-retire.js +85 -0
  183. package/dist/session-error-wire.d.ts +58 -0
  184. package/dist/session-error-wire.js +38 -0
  185. package/dist/session-incarnation.d.ts +61 -0
  186. package/dist/session-incarnation.js +43 -0
  187. package/dist/session-policy-wire.d.ts +98 -12
  188. package/dist/session-policy-wire.js +71 -8
  189. package/dist/session-records-skipped-notice.d.ts +20 -0
  190. package/dist/session-records-skipped-notice.js +27 -0
  191. package/dist/tool-approval.d.ts +65 -0
  192. package/dist/tool-approval.js +36 -9
  193. package/dist/trace/core-keyset-guard.d.ts +8 -7
  194. package/dist/trace/engine-notice-wire.d.ts +3 -1
  195. package/dist/trace/engine-notice-wire.js +2 -0
  196. package/dist/trace/project.d.ts +11 -6
  197. package/dist/trace/project.js +12 -3
  198. package/dist/trace/redact.d.ts +7 -0
  199. package/dist/trace/redact.js +153 -61
  200. package/dist/trace/wire-projection-faces.d.ts +10 -3
  201. package/dist/trace/wire-projection-faces.js +8 -2
  202. package/package.json +2 -2
  203. package/dist/plugins/memory-session-policy-store.d.ts +0 -46
  204. package/dist/plugins/memory-session-policy-store.js +0 -52
@@ -1,11 +1,24 @@
1
- const RULE_FIELD_PRESENCE = {
2
- toolAllow: true,
3
- toolDeny: true,
4
- allowDirs: true,
5
- commandAllow: true,
6
- commandDeny: true,
1
+ import { PROTOCOL_TABLE, RETIRED_TOOL_NAMES, protocolOf } from "@sema-agent/core";
2
+ const RULE_FIELD_ROLE = {
3
+ toolAllow: "tool-name",
4
+ toolDeny: "tool-name",
5
+ allowDirs: "path",
6
+ commandAllow: "command",
7
+ commandDeny: "command",
7
8
  };
8
- export const SESSION_PERMISSION_RULE_FIELDS = Object.keys(RULE_FIELD_PRESENCE);
9
+ export const SESSION_PERMISSION_RULE_FIELDS = Object.keys(RULE_FIELD_ROLE);
10
+ const TOOL_NAME_RULE_FIELDS = SESSION_PERMISSION_RULE_FIELDS.filter((f) => RULE_FIELD_ROLE[f] === "tool-name");
11
+ export function legacyToolNameOf(name) {
12
+ const guidance = RETIRED_TOOL_NAMES.get(name);
13
+ if (guidance !== undefined)
14
+ return { why: "retired", guidance };
15
+ if (name.includes("__") && protocolOf(name) === undefined)
16
+ return { why: "unprefixed-namespace" };
17
+ return undefined;
18
+ }
19
+ function protocolToolNameShapes() {
20
+ return PROTOCOL_TABLE.map((ns) => `${ns.prefix}<peer>__<tool>`).join(" or ");
21
+ }
9
22
  export function readSessionPermissionRules(raw) {
10
23
  if (raw === null || typeof raw !== "object" || Array.isArray(raw))
11
24
  return { problem: { kind: "not-an-object" } };
@@ -21,6 +34,19 @@ export function readSessionPermissionRules(raw) {
21
34
  }
22
35
  return { rules };
23
36
  }
37
+ export function readWritableSessionPermissionRules(raw) {
38
+ const read = readSessionPermissionRules(raw);
39
+ if (read.problem !== undefined)
40
+ return read;
41
+ for (const field of TOOL_NAME_RULE_FIELDS) {
42
+ for (const name of read.rules[field] ?? []) {
43
+ const legacy = legacyToolNameOf(name);
44
+ if (legacy !== undefined)
45
+ return { problem: { kind: "legacy-tool-name", field, name, legacy } };
46
+ }
47
+ }
48
+ return read;
49
+ }
24
50
  export function readSessionRulesRecords(raw) {
25
51
  if (!Array.isArray(raw))
26
52
  return { problem: { kind: "not-an-array" } };
@@ -32,7 +58,7 @@ export function readSessionRulesRecords(raw) {
32
58
  const { principal, rules, rev } = e;
33
59
  if (principal !== undefined && typeof principal !== "string")
34
60
  return { problem: { kind: "principal-not-a-string", index } };
35
- const read = readSessionPermissionRules(rules);
61
+ const read = readWritableSessionPermissionRules(rules);
36
62
  if (read.problem !== undefined)
37
63
  return { problem: { kind: "rules", index, problem: read.problem } };
38
64
  const innerRev = rules?.rev;
@@ -45,12 +71,49 @@ export function describeSessionPermissionRulesProblem(p) {
45
71
  switch (p.kind) {
46
72
  case "not-an-object": return "rules must be a SessionPermissionRules object";
47
73
  case "field-not-string-array": return `${p.field} must be an array of strings`;
74
+ case "legacy-tool-name": return describeLegacyToolName(p.field, p.name, p.legacy);
48
75
  default: {
49
76
  const unhandled = p;
50
77
  throw new Error(`unhandled SessionPermissionRules problem: ${JSON.stringify(unhandled)}`);
51
78
  }
52
79
  }
53
80
  }
81
+ function describeLegacyToolName(field, name, legacy) {
82
+ switch (legacy.why) {
83
+ case "retired":
84
+ return `${field} names "${name}", a retired tool name (${legacy.guidance}); with that rule in place this session could not start another run, so nothing was written — use the current tool name`;
85
+ case "unprefixed-namespace":
86
+ return `${field} names "${name}", which contains "__" but no protocol prefix; with that rule in place this session could not start another run, so nothing was written — a protocol tool is named ${protocolToolNameShapes()}`;
87
+ default: {
88
+ const unhandled = legacy;
89
+ throw new Error(`unhandled legacy tool name arm: ${JSON.stringify(unhandled)}`);
90
+ }
91
+ }
92
+ }
93
+ export function legacyToolNameProblemOf(p) {
94
+ switch (p.kind) {
95
+ case "not-an-array":
96
+ case "record-not-an-object":
97
+ case "principal-not-a-string":
98
+ return undefined;
99
+ case "rules":
100
+ switch (p.problem.kind) {
101
+ case "not-an-object":
102
+ case "field-not-string-array":
103
+ return undefined;
104
+ case "legacy-tool-name":
105
+ return { field: p.problem.field, name: p.problem.name };
106
+ default: {
107
+ const unhandled = p.problem;
108
+ throw new Error(`unhandled SessionPermissionRules problem: ${JSON.stringify(unhandled)}`);
109
+ }
110
+ }
111
+ default: {
112
+ const unhandled = p;
113
+ throw new Error(`unhandled SessionRulesRecord problem: ${JSON.stringify(unhandled)}`);
114
+ }
115
+ }
116
+ }
54
117
  export function describeSessionRulesRecordProblem(p) {
55
118
  switch (p.kind) {
56
119
  case "not-an-array": return "must be an array of {principal, rules} records";
@@ -0,0 +1,20 @@
1
+ import type { FileStorageCorruptReadInfo, SessionRepo } from "@sema-agent/core";
2
+ import { type EngineNoticeRouter, type ServerNoticeCode } from "./trace/engine-notice-wire.js";
3
+ export declare const SESSION_RECORDS_SKIPPED: ServerNoticeCode;
4
+ /**
5
+ * 披露座上的「作用域在场则记」腿(装配点 = `plugins/store-backend.ts` 里喂给 `LocalSessionStore` 的那只
6
+ * `FileSessionRepo` 的 `onCorruptRead`)。只认 `kind:"replay"` 这一级(`record` 由它汇总,`file` 没有会话归属)。
7
+ * 作用域外 ⇒ 什么都不做(那一次披露照旧只进运维日志)。纯内存赋值,不抛。
8
+ */
9
+ export declare function noteReplayInSessionOpenScope(info: FileStorageCorruptReadInfo): void;
10
+ /** 一次打开的产物(core `SessionRepo.open` 的返回型,派生不手抄)。 */
11
+ type OpenedSession = Awaited<ReturnType<SessionRepo["open"]>>;
12
+ /**
13
+ * **唯一铸点**:为 `sessionId` 打开它自己的存档(`open` = 那一次 `repo.open(...)`),打开成功且这次回放跳过了记录 ⇒
14
+ * 数出幸存条目、铸一帧投出。打开失败原样上抛,不铸。
15
+ *
16
+ * 计数与投递都不抛(`getEntries()` 是存储内存条目的拷贝;`route()` 按口隔离、口的失败走 F 类留痕),所以这里没有降级臂。
17
+ */
18
+ export declare function openSessionForItself(sessionId: string, open: () => Promise<OpenedSession>, router?: EngineNoticeRouter): Promise<OpenedSession>;
19
+ export {};
20
+ //# sourceMappingURL=session-records-skipped-notice.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { defaultEngineNoticeRouter } from "./trace/engine-notice-wire.js";
3
+ export const SESSION_RECORDS_SKIPPED = "session.records_skipped";
4
+ const openScope = new AsyncLocalStorage();
5
+ export function noteReplayInSessionOpenScope(info) {
6
+ if (info.kind !== "replay")
7
+ return;
8
+ const scope = openScope.getStore();
9
+ if (scope !== undefined)
10
+ scope.dropped = info.dropped;
11
+ }
12
+ export async function openSessionForItself(sessionId, open, router = defaultEngineNoticeRouter) {
13
+ const scope = { sessionId };
14
+ const session = await openScope.run(scope, open);
15
+ const dropped = scope.dropped;
16
+ if (dropped !== undefined && dropped > 0) {
17
+ const entriesKept = (await session.getEntries()).length;
18
+ router.route({
19
+ code: SESSION_RECORDS_SKIPPED,
20
+ message: `Part of this session's saved history could not be read: ${dropped} record(s) were skipped when it was reopened, and this turn continues from the ${entriesKept} entries that remain.`,
21
+ detail: { dropped, entriesKept, sessionId },
22
+ sessionId,
23
+ });
24
+ }
25
+ return session;
26
+ }
27
+ //# sourceMappingURL=session-records-skipped-notice.js.map
@@ -717,7 +717,67 @@ export declare const PERSIST_RULE_BATCH_EXCLUSIVE_ERROR = "persistRule.batchOffe
717
717
  * `persistRule.rule` / `persistRule.edited` 三道既有形门**同族**(那三道在,只有本格没有 = 本格才是特例)。
718
718
  */
719
719
  export declare const SETTLED_BY_MUST_BE_STRING = "settledBy must be a string \u2014 the settlement source is one word from the engine's closed set, and this layer judges only the SHAPE (which word it is, the engine decides)";
720
+ /**
721
+ * #1016 / S-558 —— **live 座的那一次边界 cast,全仓只此一处**(耐久座的那一次在 `hostDecisionFacts` 里,
722
+ * 一座一次)。它说的是「我把一个**未判词**的串放到只收词的座上」:判词的是 core —— `resolveAsk` 对词表外
723
+ * 是 `approver_contract` 的响亮 fail-closed deny,不是静默当人拒(实测三形同判,特征钉在
724
+ * `test/tool-approval.test.ts` 的 #1016 组)。本层判过的只有**形**(是个串、只与 deny 同行,见
725
+ * {@link SETTLED_BY_MUST_BE_STRING} / {@link SETTLED_BY_DENY_ONLY}),而形不是词。
726
+ *
727
+ * 两个座位读它:这次回决的**即时结算**(`entry.settle` 的自报参)与**持久行**(`decideAsk` 的
728
+ * `settledBy` 列)。S-558 之前只有前者 —— 于是同一个事实在「当次」与「重放」上是两个答案。
729
+ * S-843:耐久 ask 回决口(`POST /v1/tasks/:taskId/asks/:askId/decision`)写的是**同一个座**(同一列,经
730
+ * `outcomeOfDecidedRow` 交给同一个 `AskOutcome.settledBy`),所以它也走这一只 —— cast 仍是一座一次。
731
+ */
732
+ export declare function respondSettledBy(parsed: Pick<ParsedApprovalResponse, "settledBy">): HostSettledBy | undefined;
720
733
  export declare const SETTLED_BY_DENY_ONLY = "settledBy is only meaningful with decision \"deny\" \u2014 only a person approves an action that EXECUTES, so a self-reported settlement source on an allow is a contradiction the engine refuses; the refusal would arrive too late to undo this door's own side effects (a session allow-all grant, a persisted rule, a DECIDED row that replays as a plain approval), so it is refused here, before the decision";
734
+ /**
735
+ * S-843 / 车QK F7 —— `settledBy` 的**宽**拒句(三条回决腿共用一份文本,与 {@link SETTLED_BY_DENY_ONLY} 同姿势)。句子逐字是 S-843 在
736
+ * 耐久 ask 回决口路由里写下的那一句(车QK F7 起搬到判官旁,路由不再自己写),尺 = 店的同一个常量({@link MAX_SETTLED_BY_CHARS})。
737
+ */
738
+ export declare const SETTLED_BY_TOO_WIDE = "settledBy must be 1-16 characters \u2014 the settlement source is one word from the engine's closed set and lands in a fixed-width column on the decision row";
739
+ /**
740
+ * S-843 —— 决议体上 `settledBy` 的判据,**一处成形**。三条 HTTP 回决腿共读:
741
+ * `POST /v1/approvals/:sessionId/decide`(耐久 park 腿)、`POST /v1/tasks/:taskId/asks/:askId/decision`(耐久 ask 腿)与
742
+ * live `POST /v1/tool-approvals/:id/respond`(经 {@link parseToolApprovalResponse};车QK F7 起 —— 此前那只 parser 手写了 ②③ 两臂、
743
+ * 没有 ④,是这条判据的第二个写者)。
744
+ *
745
+ * 次序即契约(①–③ = decide 腿修前那三道 if 的原序,逐字搬来,不是新判据):
746
+ * ① 直连门在场 ⇒ `not_in_proof` —— 键不在决策证明的签名载荷里(§6.3 同族第四条,理由在 approvals-assistant 的
747
+ * `DECIDE_NOT_IN_PROOF_KEYS` 注);
748
+ * ② 不是 deny ⇒ `deny_only` —— {@link SETTLED_BY_DENY_ONLY};
749
+ * ③ 不是串 ⇒ `not_string` —— {@link SETTLED_BY_MUST_BE_STRING};
750
+ * ④ 长度不在 1..{@link MAX_SETTLED_BY_CHARS} ⇒ `too_wide` —— {@link SETTLED_BY_TOO_WIDE}(车QK F7 / A3,7.104.0 合并复审)。
751
+ * 尺与店门口的形门同一只谓词({@link settledByFitsColumn}):`/respond` 与耐久 ask 口写的是**同一列**
752
+ * (`approval_ask.settled_by`,`VARCHAR(16)`),列宽是列的事实,不是某一条腿的事实。修前这一道只长在耐久 ask 口的路由里:
753
+ * `/respond` 上一只超宽 / 空串的来源位要到 CAS 里才被店门抛,落进 D5「店抖动不挡真实回决」臂 —— 行没决,决议照样交给了引擎,200。
754
+ * `/decide` 的来源位不落这一列(交给 core 的 checkpoint,core 在 CAS 之前按词表拒),这一道对它同样成立:core 的闭集词
755
+ * (`human` / `timeout` / `policy`)都远短于这把尺,尺外的串不可能是一个词 ⇒ 三条口对同一个体同判同句,不为一条腿开特例。
756
+ * 判的是**键在不在 / 配不配这个决议 / 形**,**从不判词**(词表属主是 core;三条腿的词都由引擎响亮拒)。
757
+ *
758
+ * 返回判决而不是直接回 400:各条腿在调用点按自己的码约定发出(错误文案冻结门按字面站点采集,码是 wire 契约);
759
+ * 调用点对判决做穷举 `switch` + `never` 兜底(加一臂 = 编译红,[ref])。`accepted` 臂带着已证形的串 —— 调用点拿它往下传,
760
+ * 不再对 `body.settledBy` 做第二次窄化。第三参给字面 `false` 的调用点(live `/respond`:那条口没有直连门证明)拿到的判决型里
761
+ * **没有** `not_in_proof` 臂(重载),不必为一条结构上不可达的臂写兜底。
762
+ */
763
+ export type SettledByBodyVerdict = {
764
+ kind: "absent";
765
+ } | {
766
+ kind: "accepted";
767
+ settledBy: string;
768
+ } | {
769
+ kind: "not_in_proof";
770
+ } | {
771
+ kind: "deny_only";
772
+ } | {
773
+ kind: "not_string";
774
+ } | {
775
+ kind: "too_wide";
776
+ };
777
+ export declare function judgeSettledByBody(settledBy: unknown, decision: string, directDoorActive: false): Exclude<SettledByBodyVerdict, {
778
+ kind: "not_in_proof";
779
+ }>;
780
+ export declare function judgeSettledByBody(settledBy: unknown, decision: string, directDoorActive: boolean): SettledByBodyVerdict;
721
781
  /** S-720(core 7.32.0 #1093)—— `readRootGrant` 只随 `decision:"allow"`(CC 卡的第三选项「Yes, and add directory」= 放行这一次
722
782
  * + 加目录;`allow_session` 不是那一个选项,`deny` 上它没有意义)。裁决之前响亮拒,不静默丢。 */
723
783
  export declare const READ_ROOT_GRANT_ALLOW_ONLY = "readRootGrant is only meaningful with decision \"allow\" \u2014 it is the card's \"Yes, and add directory\" answer (allow this call and add the card's readRootCandidate.dir to this run's read directories)";
@@ -737,6 +797,11 @@ export declare function parseToolApprovalResponse(body: unknown): {
737
797
  ok: false;
738
798
  error: string;
739
799
  };
800
+ /** [ref] 车2:`parseToolApprovalResponse` 的成功分支形——`respond()` 拆分出的私有收尾方法(`finishRespond`/
801
+ * `respondWithCas`)复用,避免裸内联匿名类型重复三处。 */
802
+ type ParsedApprovalResponse = Extract<ReturnType<typeof parseToolApprovalResponse>, {
803
+ ok: true;
804
+ }>;
740
805
  /**
741
806
  * #1016 —— wire 上**开给调用方**的那一个词,从 core 的两只闭集里取**交集**。
742
807
  *
@@ -9,7 +9,7 @@ import { createLogger } from "./observability/logger.js";
9
9
  import { recordFailOpen } from "./observability/fail-open.js";
10
10
  import { deriveAskId, deriveAskScopedBatchId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
11
11
  import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readReadRootCandidate, readRuleEvidence, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readClosedWordSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
12
- import { POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
12
+ import { MAX_SETTLED_BY_CHARS, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth, settledByFitsColumn } from "./plugins/approval-ask-store-sql.js";
13
13
  import { settleByPolicy } from "./approval-policy-settlement.js";
14
14
  import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
15
15
  import { governanceOriginOf } from "./runtime-governance.js";
@@ -107,10 +107,24 @@ export const PERSIST_RULE_EDITED_FLAG_ERROR = "persistRule.edited must be a bool
107
107
  export const PERSIST_RULE_BATCH_INDEX_ERROR = "persistRule.batchOfferIndex must be a non-negative integer when present";
108
108
  export const PERSIST_RULE_BATCH_EXCLUSIVE_ERROR = "persistRule.batchOfferIndex is mutually exclusive with persistRule.rule / persistRule.edited — send exactly one arm";
109
109
  export const SETTLED_BY_MUST_BE_STRING = "settledBy must be a string — the settlement source is one word from the engine's closed set, and this layer judges only the SHAPE (which word it is, the engine decides)";
110
- function respondSettledBy(parsed) {
110
+ export function respondSettledBy(parsed) {
111
111
  return parsed.settledBy;
112
112
  }
113
113
  export const SETTLED_BY_DENY_ONLY = 'settledBy is only meaningful with decision "deny" — only a person approves an action that EXECUTES, so a self-reported settlement source on an allow is a contradiction the engine refuses; the refusal would arrive too late to undo this door\'s own side effects (a session allow-all grant, a persisted rule, a DECIDED row that replays as a plain approval), so it is refused here, before the decision';
114
+ export const SETTLED_BY_TOO_WIDE = `settledBy must be 1-${MAX_SETTLED_BY_CHARS} characters — the settlement source is one word from the engine's closed set and lands in a fixed-width column on the decision row`;
115
+ export function judgeSettledByBody(settledBy, decision, directDoorActive) {
116
+ if (settledBy === undefined)
117
+ return { kind: "absent" };
118
+ if (directDoorActive)
119
+ return { kind: "not_in_proof" };
120
+ if (decision !== "deny")
121
+ return { kind: "deny_only" };
122
+ if (typeof settledBy !== "string")
123
+ return { kind: "not_string" };
124
+ if (!settledByFitsColumn(settledBy))
125
+ return { kind: "too_wide" };
126
+ return { kind: "accepted", settledBy };
127
+ }
114
128
  export const READ_ROOT_GRANT_ALLOW_ONLY = 'readRootGrant is only meaningful with decision "allow" — it is the card\'s "Yes, and add directory" answer (allow this call and add the card\'s readRootCandidate.dir to this run\'s read directories)';
115
129
  export const READ_ROOT_GRANT_SHAPE = `readRootGrant must be the card's readRootCandidate.dir sent back byte for byte — a non-empty string of at most ${READ_ROOT_CANDIDATE_DIR_MAX} characters`;
116
130
  export const PERSIST_RULE_SCOPE_REFUSED = "this endpoint does not accept a rule scope — the server mints it from where the approval happened (send neither `scope` nor `persistRule.scope`)";
@@ -157,12 +171,25 @@ export function parseToolApprovalResponse(body) {
157
171
  }
158
172
  note = rawNote;
159
173
  }
160
- const rawSettledBy = body.settledBy;
161
- if (rawSettledBy !== undefined && d !== "deny")
162
- return { ok: false, error: SETTLED_BY_DENY_ONLY };
163
- if (rawSettledBy !== undefined && typeof rawSettledBy !== "string")
164
- return { ok: false, error: SETTLED_BY_MUST_BE_STRING };
165
- const settledBy = rawSettledBy;
174
+ const settledByVerdict = judgeSettledByBody(body.settledBy, d, false);
175
+ let settledBy;
176
+ switch (settledByVerdict.kind) {
177
+ case "deny_only":
178
+ return { ok: false, error: SETTLED_BY_DENY_ONLY };
179
+ case "not_string":
180
+ return { ok: false, error: SETTLED_BY_MUST_BE_STRING };
181
+ case "too_wide":
182
+ return { ok: false, error: SETTLED_BY_TOO_WIDE };
183
+ case "absent":
184
+ break;
185
+ case "accepted":
186
+ settledBy = settledByVerdict.settledBy;
187
+ break;
188
+ default: {
189
+ const unreachable = settledByVerdict;
190
+ throw new Error(`unhandled settledBy verdict: ${JSON.stringify(unreachable)}`);
191
+ }
192
+ }
166
193
  const rawGrant = body.readRootGrant;
167
194
  if (rawGrant !== undefined && d !== "allow")
168
195
  return { ok: false, error: READ_ROOT_GRANT_ALLOW_ONLY };
@@ -175,7 +202,7 @@ export function parseToolApprovalResponse(body) {
175
202
  ...(d !== "deny" && u !== undefined ? { updatedInput: u } : {}),
176
203
  ...(persistRule !== undefined ? { persistRule } : {}),
177
204
  ...(note !== undefined ? { note } : {}),
178
- ...(rawSettledBy !== undefined ? { settledBy } : {}),
205
+ ...(settledBy !== undefined ? { settledBy } : {}),
179
206
  ...(readRootGrant !== undefined ? { readRootGrant } : {}),
180
207
  };
181
208
  }
@@ -59,8 +59,8 @@ type _GuardMailboxReverse = AssertAllKeysHandled<Exclude<MailboxProjected, keyof
59
59
  type MailboxAppendProjected = "from" | "content" | "sentAt" | "hopChain" | "peerMeta";
60
60
  type _GuardMailboxAppend = AssertAllKeysHandled<Exclude<keyof MailboxAppendMessage, MailboxAppendProjected>>;
61
61
  type _GuardMailboxAppendReverse = AssertAllKeysHandled<Exclude<MailboxAppendProjected, keyof MailboxAppendMessage>>;
62
- type WiringSectionProjected = "schemaVersion" | "leg" | "ask" | "question" | "interaction" | "elicit" | "parkLane" | "session" | "fleet" | "permissionRules" | "modelGate" | "autoMode" | "mcp" | "tools" | "hooks" | "lsp" | "hands" | "writeProtection" | "teamRole";
63
- type WiringSectionExcluded = "governance" | "configFingerprint" | "autoConsolidation" | "readDeny" | "peerPresence" | "fileHistory";
62
+ type WiringSectionProjected = "schemaVersion" | "leg" | "ask" | "question" | "interaction" | "elicit" | "parkLane" | "session" | "fleet" | "permissionRules" | "modelGate" | "autoMode" | "mcp" | "tools" | "hooks" | "lsp" | "hands" | "writeProtection" | "teamRole" | "fileHistory";
63
+ type WiringSectionExcluded = (typeof import("./wire-projection-faces.js").WIRE_PROJECTION_FACES)["wiring_manifest_tenant"]["excluded"][number];
64
64
  type _GuardWiringSections = AssertAllKeysHandled<Exclude<keyof WiringManifest, WiringSectionProjected | WiringSectionExcluded>>;
65
65
  type _GuardWiringSectionsReverse = AssertAllKeysHandled<Exclude<WiringSectionProjected | WiringSectionExcluded, keyof WiringManifest>>;
66
66
  type WiringSection<K extends keyof WiringManifest> = NonNullable<WiringManifest[K]>;
@@ -145,8 +145,8 @@ type ParkArmQueueExcluded = "kind" | "args" | "batchToolCallIds" | "completedCal
145
145
  type _GuardParkArmQueue = AssertAllKeysHandled<Exclude<ParkArmKeys, ParkArmQueueProjected | ParkArmQueueExcluded>>;
146
146
  type _GuardParkArmQueueReverse = AssertAllKeysHandled<Exclude<ParkArmQueueProjected | ParkArmQueueExcluded, ParkArmKeys>>;
147
147
  type _NoDoubleBookParkArmQueue = AssertAllKeysHandled<Extract<ParkArmQueueProjected, ParkArmQueueExcluded>>;
148
- type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "approverPosture" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "autoModeRequested" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools";
149
- type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars" | "peerPresence" | "fileHistory";
148
+ type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "approverPosture" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "autoModeRequested" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools" | "fileHistory";
149
+ type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars" | "peerPresence";
150
150
  type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
151
151
  type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
152
152
  type TaskResultForwarded = "taskId" | "runId" | "sessionId" | "terminal" | "model" | "effectiveReasoning" | "result" | "salvagedOutput" | "remoteEnvFailures" | "apiFailure" | "retryAfterMs" | "degraded" | "structuredOutput" | "rewindNotes" | "haltedOnUserRejection" | "haltedByUser" | "strandedHumanAnswers" | "pendingApproval" | "approvalWaitedMs" | "effectiveReadFace" | "effectiveReadDenyPatterns" | "effectiveWriteFace" | "effectiveMemoryScopes" | "stats" | "editedFiles" | "responseModel" | "compaction";
@@ -213,8 +213,9 @@ type LspOptionsUnwired = "onNotice" | "servers" | "spawn" | "readText" | "resolv
213
213
  type _GuardLspOptions = AssertAllKeysHandled<Exclude<keyof NodeLspManagerOptions, LspOptionsWired | LspOptionsUnwired>>;
214
214
  type _GuardLspOptionsReverse = AssertAllKeysHandled<Exclude<LspOptionsWired | LspOptionsUnwired, keyof NodeLspManagerOptions>>;
215
215
  type _NoDoubleBookLspOptions = AssertAllKeysHandled<Extract<LspOptionsWired, LspOptionsUnwired>>;
216
- type RunnerDepsDroppedPendingS724 = "fileHistoryCapture";
217
- type _GuardFacelessRunnerDepsReverse = AssertAllKeysHandled<Exclude<RunnerDepsDroppedPendingS724, keyof RunnerDeps>>;
216
+ /** ⑯ 账上记过、已接线的 `RunnerDeps` 席(S-724 起 `fileHistoryCapture`)。反向钉:core 改名 / 退役 ⇒ 红。 */
217
+ type RunnerDepsLedgerWired = "fileHistoryCapture";
218
+ type _GuardLedgerRunnerDepsReverse = AssertAllKeysHandled<Exclude<RunnerDepsLedgerWired, keyof RunnerDeps>>;
218
219
  export type CoreKeysetGuards = [
219
220
  _GuardNotification,
220
221
  _GuardNotificationReverse,
@@ -321,7 +322,7 @@ export type CoreKeysetGuards = [
321
322
  _GuardLspOptions,
322
323
  _NoDoubleBookLspOptions,
323
324
  _GuardLspOptionsReverse,
324
- _GuardFacelessRunnerDepsReverse
325
+ _GuardLedgerRunnerDepsReverse
325
326
  ];
326
327
  export {};
327
328
  //# sourceMappingURL=core-keyset-guard.d.ts.map
@@ -16,7 +16,7 @@
16
16
  * 同文件 G1 e2e 的全码扇出夹具、以及契约文档附录 D.3 那张表(含**小节标题里的码数** —— 那道门本批
17
17
  * 刚立,立完就在下一次加码时自己咬住了)。
18
18
  */
19
- export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "instructions.source_changed"];
19
+ export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "instructions.source_changed", "session.records_skipped"];
20
20
  export type EngineNoticeWireCode = (typeof ENGINE_NOTICE_WIRE_CODES)[number];
21
21
  /**
22
22
  * S-690 —— **本仓自铸**的通告码与它们的受众(码 / 铸点 / 受众三者的属主都是本仓)。
@@ -40,6 +40,8 @@ export declare const SERVER_NOTICE_AUDIENCE: {
40
40
  * `detail: { layers, remedy }` —— 层名(挂载面的直接子目录名)与那一句补救;条目名 / 内容不进。
41
41
  */
42
42
  readonly "memory.layer_locked_legacy": "operator";
43
+ /** S-612 —— 会话存档部分记录被跳过(唯一铸点 `src/session-records-skipped-notice.ts`);受众 user,理由见白名单键注。 */
44
+ readonly "session.records_skipped": "user";
43
45
  };
44
46
  export type ServerNoticeCode = keyof typeof SERVER_NOTICE_AUDIENCE;
45
47
  /** 该码是否本仓自铸(属主判据单点:门与投递面都读这一个)。 */
@@ -26,10 +26,12 @@ export const ENGINE_NOTICE_WIRE_CODES = [
26
26
  "approval.read_root_granted",
27
27
  "approval.read_root_grant_rejected",
28
28
  "instructions.source_changed",
29
+ "session.records_skipped",
29
30
  ];
30
31
  export const SERVER_NOTICE_AUDIENCE = {
31
32
  "instructions.source_changed": "user",
32
33
  "memory.layer_locked_legacy": "operator",
34
+ "session.records_skipped": "user",
33
35
  };
34
36
  export function isServerNoticeCode(code) {
35
37
  return Object.hasOwn(SERVER_NOTICE_AUDIENCE, code);
@@ -156,8 +156,9 @@ export declare function toolStartEventData(ev: {
156
156
  * 🔴 **筛子是一只总函数**(S-239,codex 对抗复审 r1 [medium] 亲核复现后修)—— 上面纪律②许诺的
157
157
  * 「坏形 ⇒ report + withhold,**不抛**」此前是靠**逐条枚举**坏形兑现的,枚举不到的那些照样抛:
158
158
  * `projectSettlement` 对 `note` / `who.approver` 先 `redactSecrets` 再截断,而 `redactSecrets` 会在
159
- * 病态输入上爆回溯栈(`RangeError`:≥2^23 单段那一形已由 S-219 的 `atLeast` 铸点闭掉,仍能抛的是 JSON 秘密字段臂的
160
- * 转义对循环,S-102/S-106 登记的既有上界,见 `redact.ts` 该行顶注),两条叶各能抛一次。
159
+ * 病态输入上爆回溯栈(`RangeError`:≥2^23 单段那一形已由 S-219 的 `atLeast` 铸点闭掉,JSON 秘密字段臂的转义对循环与
160
+ * PEM 栅栏臂的标签交替循环由 S-265 换成手写扫描器闭掉 —— 已知形全部闭掉,本段写的是「当时为什么要总函数化」),
161
+ * 两条叶各能抛一次。总函数化照留:它兑现的是「坏字节一律 withhold」这条**结构**,不依赖脱敏器今天抛不抛。
161
162
  *
162
163
  * 抛出去的代价按腿算,而且一条比一条重:写腿落账那处的 `.catch` 看不到**同步**投影期的异常;读腿上它
163
164
  * 直接逃出 `formatEvent` / 投影函数 —— 账本裸回放腿的那一次会当场断流(那一行不上帧、游标不前进、后面的
@@ -188,7 +189,7 @@ export declare function screenedGate(v: unknown): GateOutcome | undefined;
188
189
  * ## 读腿与写腿现在是**同一只**筛(7.74.0 起,零双读)
189
190
  * 7.72.0–7.73.0 期间读腿多一步「认本服务上一版写下的退役词旧拼法」(`unresolvable` → `ancestor_marked`),
190
191
  * 那是一条**长期双读臂**:同一份字节在写腿与读腿上有两种理解,而且没有到期日。7.74.0 把它换成**一次性
191
- * 迁移**(启动期把存量字节原地改名,`plugins/one-time-migrations.ts`,随 7.75.0 删)⇒ 读腿退回写腿那只
192
+ * 迁移**(启动期把存量字节原地改名,`plugins/one-time-migrations.ts` 的 M3;窗口见 `plugins/one-time-migrations.ts` 登记表)⇒ 读腿退回写腿那只
192
193
  * 严格筛子,集外词一律整条 withhold + 计数,不再有第二种读法。
193
194
  *
194
195
  * ## 与写腿脱敏汇点的关系(别再走回头路)
@@ -295,7 +296,9 @@ export declare function toolEndEventData(ev: {
295
296
  * **不等于** `tool_end` 的 duration)。
296
297
  *
297
298
  * ## 信任划线
298
- * · `toolCallId` / `toolName` —— 引擎铸的 id 与名册里的工具名(与 `tool_start` 逐字同源)⇒ verbatim。
299
+ * · `toolCallId` / `toolName` —— 调用 id(🔴 S-283 订正:**服务商**在 tool_use / tool_calls 里给的,引擎只在缺席时自铸兜底 ——
300
+ * 此前写「引擎铸」不实)与名册里的工具名(与 `tool_start` 逐字同源)⇒ verbatim。调用 id 这一族在**逐帧面**上的形状闸
301
+ * 不在 S-283 射程(它是全 wire 的 join 键,要闸得所有面一起闸,见 run-terminal `admitProviderEcho` 顶注与车QO 收车档)。
299
302
  * · `elapsedTimeSeconds` —— 引擎铸的整秒,单调、一 call 一个原点 ⇒ 有限数才铸键。
300
303
  * · `output` —— 🔴 **UNTRUSTED RAW**,core 逐字「exactly as `tool_end.output`: process output that may
301
304
  * carry secrets, paths or PII; core bounds only its SIZE, and a consumer MUST redact before persisting
@@ -925,8 +928,10 @@ export declare function contextUsageEventData(ev: {
925
928
  *
926
929
  * `responseModel`(core 7.29.0 #1029):**供应商自己**报的、回答这一 turn 的模型名,**只在**它 ≠ 请求所带 id
927
930
  * 时在场。缺席 = 「报了同名」或「没报」,**一个读法**(请求 id 读 `TaskResult.model`)。值是供应商铸的名字
928
- * (与 `apiFailure.requestId` 同类:不是模型写的自由文本)⇒ 逐字;非串 / 空串 ⇒ 缺席(core 的读律
929
- * `providerReportedModel` 对空串答「没报」,本仓不铸一个看起来合法的空名)。
931
+ * (与 `apiFailure.requestId` 同类:不是模型写的自由文本)⇒ 🔴 **S-283 起过 provider 回声的形状闸**
932
+ * ({@link admitProviderEcho}:长度 1–128 的可见 ASCII 原样,越界 ⇒ 不铸键 + 计数,与 core #1204 源头闸同一条;不过脱敏器),
933
+ * 与结果面的孪生 `TaskResult.responseModel` 同一只闸 —— 同一个名字在两张脸上不许两种待遇。非串 / 空串同样不铸键(空串在
934
+ * 那条谓词的长度下界之外;core 的读律 `providerReportedModel` 对空串也答「没报」)。
930
935
  */
931
936
  export declare function turnEndEventData(ev: {
932
937
  usage?: unknown;
@@ -1,4 +1,4 @@
1
- import { redactErrorMessage, redactModelText } from "../observability/run-terminal.js";
1
+ import { admitProviderEcho, redactErrorMessage, redactModelText } from "../observability/run-terminal.js";
2
2
  import { MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, screenGateOutcome, TEAM_ROLES, ToolRoster, ToolRosterDelta, WRITE_PROTECTION_TARGET_VIEWS } from "@sema-agent/core";
3
3
  import { Value } from "typebox/value";
4
4
  import { recordFailOpen } from "../observability/fail-open.js";
@@ -300,7 +300,7 @@ export function taskNotificationEventData(ev) {
300
300
  ? {
301
301
  apiFailure: {
302
302
  ...(typeof n.apiFailure.status === "number" ? { status: n.apiFailure.status } : {}),
303
- ...(typeof n.apiFailure.requestId === "string" ? { requestId: n.apiFailure.requestId } : {}),
303
+ ...(admitProviderEcho(n.apiFailure.requestId, "task_notification.apiFailure.requestId") ? { requestId: n.apiFailure.requestId } : {}),
304
304
  },
305
305
  }
306
306
  : {}),
@@ -554,10 +554,19 @@ export function wiringManifestEventData(ev) {
554
554
  return dropProjectedFace("wiring_manifest.teamRole is outside core TEAM_ROLES");
555
555
  return m.teamRole;
556
556
  })();
557
+ const fileHistory = (() => {
558
+ if (m.fileHistory === undefined)
559
+ return undefined;
560
+ const word = str(m.fileHistory);
561
+ if (word === undefined)
562
+ return dropProjectedFace("wiring_manifest.fileHistory is not a non-empty string");
563
+ return word;
564
+ })();
557
565
  return {
558
566
  ...(num(m.schemaVersion) !== undefined ? { schemaVersion: m.schemaVersion } : {}),
559
567
  ...(str(legIn.kind) !== undefined ? { leg: { kind: legIn.kind } } : {}),
560
568
  ...(teamRole !== undefined ? { teamRole } : {}),
569
+ ...(fileHistory !== undefined ? { fileHistory } : {}),
561
570
  ...(ask !== undefined ? { ask } : {}),
562
571
  ...(question !== undefined ? { question } : {}),
563
572
  ...(str(interactionIn.posture) !== undefined ? { interaction: { posture: interactionIn.posture } } : {}),
@@ -747,7 +756,7 @@ export function turnEndEventData(ev) {
747
756
  ...(ev.usage ? { usage: ev.usage } : {}),
748
757
  ...(ev.usageMissing === true ? { usageMissing: true } : {}),
749
758
  ...(typeof ev.stopReason === "string" ? { stopReason: ev.stopReason } : {}),
750
- ...(typeof ev.responseModel === "string" && ev.responseModel !== "" ? { responseModel: ev.responseModel } : {}),
759
+ ...(admitProviderEcho(ev.responseModel, "turn_end.responseModel") ? { responseModel: ev.responseModel } : {}),
751
760
  ...identityFields(ev),
752
761
  };
753
762
  }
@@ -72,6 +72,13 @@ declare const DISPLAY_SAFE_URL_TOKEN = "\u00ABredacted:url\u00BB";
72
72
  /** S-776:出网代理 URL 的回显尺(`outbound-dispatcher.ts` `displayProxyUrl`)形不干净时整只用**同一枚**占位 —— 单独一行导出,
73
73
  * 上面那行字面量保持 `const NAME = "…";` 形(`test/api-notice-text-freeze.test.ts` §6 的冻结扫描按这一形认替换 token)。 */
74
74
  export { DISPLAY_SAFE_URL_TOKEN };
75
+ /** 测试可见(S-265 真值表的两半):结构段里**表行只作声明、实现是手写扫描器**的臂 —— 声明正则的**新拷贝**
76
+ * (`lastIndex` 不与模块内那一枚共享)+ 扫描器在一份原文上产的候选跨度 `[起, 止)`。臂名是闭集 ⇒ 穷尽 `switch`
77
+ * (少一个 `case` = 缺返回 = 编译红)。 */
78
+ export declare function structuralScannerForTest(arm: "json-secret-field" | "pem-fence"): {
79
+ declared: RegExp;
80
+ spans: (text: string) => Array<[number, number]>;
81
+ };
75
82
  /** 测试可见:扫描器 indexOf 实际扫过的字节数(摊还线性 ⇒ ≤ 常数 × 输入长)。 */
76
83
  export declare function ppkScanBytesForTest(): number;
77
84
  export declare function resetPpkScanBytesForTest(): void;