@sema-agent/server 7.124.0 → 7.125.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 (152) hide show
  1. package/MIGRATION.md +138 -0
  2. package/USAGE.md +46 -8
  3. package/dist/audit.d.ts +27 -5
  4. package/dist/audit.js +42 -8
  5. package/dist/boot/execution-env.d.ts +33 -1
  6. package/dist/boot/execution-env.js +31 -2
  7. package/dist/boot/resolve-spec.js +4 -3
  8. package/dist/boot/session-faces.d.ts +14 -1
  9. package/dist/boot/session-faces.js +6 -5
  10. package/dist/boot/stage-02-web-admin-pull.js +5 -1
  11. package/dist/boot/stage-05-execution-env.js +6 -2
  12. package/dist/boot/stage-09-leader.d.ts +1 -1
  13. package/dist/boot/stage-10-http-server.d.ts +1 -1
  14. package/dist/boot/web-admin.js +130 -140
  15. package/dist/capabilities/center-wiring.d.ts +2 -1
  16. package/dist/capabilities/composite-members.d.ts +1 -0
  17. package/dist/capabilities/composite-members.js +1 -0
  18. package/dist/capability-facts.d.ts +28 -3
  19. package/dist/capability-facts.js +20 -0
  20. package/dist/config-catalog.js +4 -1
  21. package/dist/config-d-migration.js +2 -1
  22. package/dist/config-lkg.d.ts +4 -1
  23. package/dist/config-lkg.js +16 -12
  24. package/dist/config-provider.d.ts +4 -1
  25. package/dist/config-provider.js +1 -0
  26. package/dist/config-types.d.ts +10 -3
  27. package/dist/config.js +44 -14
  28. package/dist/env-facts.d.ts +2 -2
  29. package/dist/env-facts.js +3 -2
  30. package/dist/fleet/fleet-bus.d.ts +24 -12
  31. package/dist/fleet/fleet-bus.js +4 -1
  32. package/dist/fleet/workspace-snapshot-residual.d.ts +10 -0
  33. package/dist/fleet/workspace-snapshot-residual.js +35 -0
  34. package/dist/hooks/hook-runner.d.ts +15 -27
  35. package/dist/hooks/hook-runner.js +19 -59
  36. package/dist/http/active-run-conflict.d.ts +3 -0
  37. package/dist/http/active-run-conflict.js +3 -0
  38. package/dist/http/admission.js +7 -7
  39. package/dist/http/approval-sweeps.js +2 -4
  40. package/dist/http/device-placement.js +1 -0
  41. package/dist/http/dispatch.js +1 -1
  42. package/dist/http/durable-run-leg.d.ts +20 -6
  43. package/dist/http/durable-run-leg.js +4 -4
  44. package/dist/http/idempotency.d.ts +83 -38
  45. package/dist/http/idempotency.js +38 -13
  46. package/dist/http/request-guards.d.ts +18 -0
  47. package/dist/http/request-guards.js +12 -3
  48. package/dist/http/resume-legs.js +7 -1
  49. package/dist/http/route-ctx.d.ts +3 -3
  50. package/dist/http/route-ctx.js +1 -1
  51. package/dist/http/routes/a2a-serve.js +27 -11
  52. package/dist/http/routes/capabilities.js +1 -0
  53. package/dist/http/routes/fleet.js +2 -0
  54. package/dist/http/routes/images.js +2 -2
  55. package/dist/http/routes/leader.d.ts +4 -0
  56. package/dist/http/routes/leader.js +13 -3
  57. package/dist/http/routes/observability.js +2 -0
  58. package/dist/http/routes/runs.d.ts +6 -2
  59. package/dist/http/routes/runs.js +71 -101
  60. package/dist/http/routes/session-sync.d.ts +3 -0
  61. package/dist/http/routes/session-sync.js +30 -1
  62. package/dist/http/routes/sessions-list.d.ts +11 -0
  63. package/dist/http/routes/sessions-list.js +6 -6
  64. package/dist/http/routes/sessions.d.ts +6 -1
  65. package/dist/http/routes/sessions.js +41 -10
  66. package/dist/http/routes/tasks.js +63 -21
  67. package/dist/http/run-read-face.d.ts +26 -0
  68. package/dist/http/run-read-face.js +67 -0
  69. package/dist/http/server.d.ts +4 -4
  70. package/dist/http/server.js +1 -2
  71. package/dist/http/submit-replay.d.ts +40 -0
  72. package/dist/http/submit-replay.js +22 -0
  73. package/dist/leader/endpoint.d.ts +10 -5
  74. package/dist/leader/endpoint.js +27 -5
  75. package/dist/observability/cost-quota.d.ts +51 -7
  76. package/dist/observability/cost-quota.js +35 -17
  77. package/dist/observability/fail-open.d.ts +1 -1
  78. package/dist/observability/fail-open.js +1 -1
  79. package/dist/observability/run-terminal.d.ts +5 -9
  80. package/dist/observability/run-terminal.js +2 -7
  81. package/dist/plugins/approval-ask-store-sql.js +1 -1
  82. package/dist/plugins/caching-session-store.d.ts +7 -2
  83. package/dist/plugins/caching-session-store.js +6 -0
  84. package/dist/plugins/device-store-sql.js +1 -1
  85. package/dist/plugins/ensure-index.js +22 -0
  86. package/dist/plugins/file-run-store.d.ts +24 -14
  87. package/dist/plugins/file-run-store.js +52 -12
  88. package/dist/plugins/leader-run-store-sql.d.ts +1 -11
  89. package/dist/plugins/leader-run-store-sql.js +1 -5
  90. package/dist/plugins/local-session-store.d.ts +35 -6
  91. package/dist/plugins/local-session-store.js +87 -18
  92. package/dist/plugins/memory-run-store.d.ts +13 -12
  93. package/dist/plugins/memory-run-store.js +25 -0
  94. package/dist/plugins/one-time-migrations.js +1 -1
  95. package/dist/plugins/pg-cost-quota.d.ts +1 -1
  96. package/dist/plugins/pg-cost-quota.js +2 -5
  97. package/dist/plugins/pg-pool.js +3 -0
  98. package/dist/plugins/pg-session-storage.d.ts +11 -1
  99. package/dist/plugins/pg-session-storage.js +44 -8
  100. package/dist/plugins/remote-env-k8s.d.ts +127 -12
  101. package/dist/plugins/remote-env-k8s.js +270 -27
  102. package/dist/plugins/run-store-sql.d.ts +26 -12
  103. package/dist/plugins/run-store-sql.js +50 -25
  104. package/dist/plugins/schema-consistency.d.ts +1 -13
  105. package/dist/plugins/schema-consistency.js +1 -41
  106. package/dist/plugins/schema-indexes.js +1 -0
  107. package/dist/plugins/schema-live-columns.d.ts +20 -0
  108. package/dist/plugins/schema-live-columns.js +42 -0
  109. package/dist/plugins/store-contracts.d.ts +40 -2
  110. package/dist/plugins/tidb-cost-quota.d.ts +1 -1
  111. package/dist/plugins/tidb-cost-quota.js +2 -5
  112. package/dist/plugins/tidb-pool.js +14 -3
  113. package/dist/plugins/tidb-session-store.d.ts +22 -2
  114. package/dist/plugins/tidb-session-store.js +33 -6
  115. package/dist/plugins/write-behind-counter.d.ts +12 -5
  116. package/dist/plugins/write-behind-counter.js +14 -7
  117. package/dist/prompts-domain-validate.js +3 -2
  118. package/dist/rules-consent.d.ts +2 -2
  119. package/dist/rules-consent.js +2 -2
  120. package/dist/run-local.js +31 -23
  121. package/dist/runs.d.ts +9 -0
  122. package/dist/runs.js +8 -0
  123. package/dist/runtime-governance.d.ts +3 -3
  124. package/dist/runtime-governance.js +2 -4
  125. package/dist/security.d.ts +19 -2
  126. package/dist/session-row.d.ts +53 -0
  127. package/dist/session-row.js +47 -0
  128. package/dist/session-sync.d.ts +6 -0
  129. package/dist/session-sync.js +5 -1
  130. package/dist/session-titler.d.ts +9 -5
  131. package/dist/session-titler.js +3 -1
  132. package/dist/spec-fields.js +2 -2
  133. package/dist/task-cwd.d.ts +0 -20
  134. package/dist/task-cwd.js +2 -10
  135. package/dist/tool-approval.d.ts +5 -7
  136. package/dist/trace/redact.d.ts +23 -50
  137. package/dist/trace/redact.js +58 -18
  138. package/dist/trace/self-minted-wire.d.ts +25 -13
  139. package/dist/trace/self-minted-wire.js +9 -4
  140. package/dist/web-admin-client/apply-effective.d.ts +10 -12
  141. package/dist/web-admin-client/apply-effective.js +2 -3
  142. package/dist/web-admin-client/candidate-gate.d.ts +69 -0
  143. package/dist/web-admin-client/candidate-gate.js +27 -0
  144. package/dist/web-admin-client/http-client.d.ts +2 -1
  145. package/dist/web-admin-client/http-client.js +2 -1
  146. package/dist/web-admin-client/pull-state.d.ts +6 -4
  147. package/dist/web-admin-client/read-warnings.d.ts +29 -5
  148. package/dist/web-admin-client/read-warnings.js +24 -4
  149. package/dist/web-admin-client/stage-limits.js +4 -1
  150. package/dist/workspace-space.d.ts +41 -0
  151. package/dist/workspace-space.js +5 -0
  152. package/package.json +3 -3
@@ -1,55 +1,25 @@
1
1
  /**
2
- * Trace thinking-block redaction (service-side). Per core [R57]: the agent's reasoning/CoT is emitted on the
3
- * live TaskEvent stream but DELIBERATELY kept out of the metadata Tracer because CoT may contain reasoned-about
4
- * secrets (and is large / ToS-sensitive for some providers). Persisting it for the trace UI is the deployment's
5
- * call — so when we DO persist it, redact unambiguous secret formats first. Conservative on purpose: only clear
6
- * key/token shapes are redacted. ⚠️ It is NOT exhaustive: unrecognised secret formats (a free-text password,
7
- * a JWT) pass through, and the broad `Bearer <20+ chars>` rule can over-match legitimate CoT (false positives
8
- * tolerated — over-redacting trace text is safe). For sensitive deployments, disable thinking capture entirely
9
- * via TRACE_THINKING=false. This is a seam — deployments can extend the patterns.
2
+ * 🔴 **剥后视图 → 原文的坐标映射**(单视律的另一半:臂在剥后视图上认凭据,产物按**原文**坐标拼)。
10
3
  *
11
- * 🔴 **脱敏 ≠ 出站控制**:**工具结果送模主路径**不经过本模块 —— 发往模型的请求体是工具结果**原文**
12
- * (模型作为执行体必须见原文;出站改写等于静默改坏用户的真实数据)。⚠️ 这句边界只管这一条路径,别读成
13
- * 「本模块从不出现在模型输入路径上」——本模块同时被两条**反方向**调用复用:项目记忆片段注入
14
- * (`src/project-memory.ts`,git log/status 与指令文件摘录先脱敏、脱敏结果才拼进注入文本)与 hook
15
- * asyncRewake 的唤醒文本(`src/boot/resolve-spec.ts` 的 `wake` 回调,先脱敏再经 `stream.steer` 注入模型)。
16
- * 这两条路径是「先脱敏、脱敏后的结果才给模型看」,与工具结果路径的方向相反,不要混淆。要让内容不出机器,
17
- * 核对的是**该 run 实际选中的模型路由**——默认网关(`MODEL_GATEWAY_BASEURL`)只是其中一条:还有备用网关
18
- * (`MODEL_GATEWAY_FALLBACK_URLS`)、`provider:"anthropic"` 的独立直连路由(缺省根
19
- * `https://api.anthropic.com`,除非显式配 `ANTHROPIC_BASEURL`)、以及 web-admin 可对单个目录模型下发的
20
- * per-model `baseUrl` 覆盖(见 `src/brain.ts` 的路由装配与 `src/web-admin-client/apply-effective.ts:416`)。
21
- * 靠**读侧**敏感路径门与确保**全部**生效路由都指向自己控制的基础设施,不靠本模块、也不是设一根 env 就够。
4
+ * 前提是 core `stripFormatCharacters` 的契约:**逐码点删除** —— 无状态、不插、不换、不改序(本仓的契约钉在全码点空间上
5
+ * 逐个证它,`test/trace-redact.test.ts` 的输出回原文组)。删除按**原文**的码点切分做(`u` 档正则:高代理后紧跟低代理 =
6
+ * 一个码点两码元,其余每个码元 —— 含孤代理 —— 各是一个码点),所以本映射也按原文的这一份切分走,逐码点拿它的码元与
7
+ * 剥后视图当前位置的码元比:
8
+ * · 相同 ⇒ 这个码点留下了,它的每个码元记 `map[j] = i`;不同 ⇒ 它被删了,只推原文指针。
9
+ * · 为什么按码点而不按码元走:astral 格式字符(U+E0001、U+E0020–U+E007F 标签字符)是代理对,按码元走会让它的半截与
10
+ * emoji 代理对的半截错配。为什么按**原文**的切分而不按两边各自的切分:删除能把两枚孤代理**拼成**剥后视图里的一对
11
+ * (`<孤高><ZWSP><孤低>` ⇒ 一枚 😀),按剥后视图的切分比,原文那两枚孤代理永远对不上。
12
+ * · 唯一的二义:剥后视图里拼出来的那一对恰好与原文当前这一个被删的 astral 码点同码元(拼成的码点本身就是格式字符)。
13
+ * BMP 码点没有这一问(下一个保留码元恒不是 BMP 格式字符的值:留下的 BMP 码点不是格式字符,代理码元也不是),所以
14
+ * **只在 astral 码点码元相同时**问删除的属主一句「这一枚你删不删」—— 问的是 core 本身,本仓不多写一份格式字符谓词。
22
15
  *
23
- * 🔴 **射程自 7.75.1 起含「模型自由文本」全族**(B-106 / S-268):本模块此前在结果面只管诊断/披露文本,
24
- * 而**模型自己写的**那一份(`TaskResult.result` / `TaskResult.salvagedOutput` / 账本 `text` 行)整条逐字
25
- * 上 wire —— 工具结果送模路径给的是原文,模型把里面的凭据**逐字引用**进答复,那一份就绕过了本模块。
26
- * 现在三面与 CoT(账本 `reasoning` 行)走**同一只** `redactSecrets`、同一张替换串表:零第二只脱敏器、
27
- * 零 per-face 开关、零体量上限。**装载口只有一只** —— `observability/run-terminal.ts` 的
28
- * `redactModelText`(四座同调:结果两叶经 `redactTerminalResult`,账本两行经 `redactLedgerEventData`);
29
- * 它定的是**写回**判据(认出凭据形才改字节、抛则占位),不是第二张规则表。
16
+ * 返回 `Int32Array(normalized.length + 1)`:`map[j]` = 剥后视图第 `j` 个码元在原文里的下标,末位 `map[normalized.length]`
17
+ * = `text.length`。O(n) 一遍(只对 astral 码点多一次对 core 的询问);零第二只扫描器。
30
18
  *
31
- * 🔴 **只有一条施加路径:候选池**(S-223 v2 起;此前是「结构段 → 词法关键字段 → 池 → URL 收尾归一化」四步,
32
- * 每一步都把**上一步改写过的文本**喂给下一步)。四张臂表({@link STRUCTURAL_SECRET_PATTERNS} /
33
- * {@link KEYWORD_SECRET_PATTERNS} / {@link SHAPE_SECRET_PATTERNS} / {@link FRAGMENT_SECRET_PATTERNS})
34
- * 加三条体扫描器全是 {@link armPool} 的成员:全臂看**同一份未改写文本**收候选跨度 → 不动点 → 并集律解重叠
35
- * → **一次**替换,**臂序对输出无语义**(次序只定 `order` = 同起点同长时的稳定裁决)。
36
- * 左边界律因此有**三种**边界:非词内字符 ∨ 转义分隔单元尾 ∨ **一枚已收候选跨度的尾**(= 凭据尾)。
19
+ * 失配 = 剥后视图里有码元在原文剩余部分里配不上(原文先走完)⇒ 归一化不再是「只删码点」,core 改了契约 ⇒ **抛**。
20
+ * 不回退到剥后产物、不吞:装着的 core 上运行期不可达,换 core 时契约钉先红。
37
21
  */
38
- /** 本模块替换 token 的**闭形**正则源(`«redacted»` / `«redacted:aws-key»` / `«redacted:gh-token:partial»` …):
39
- * URL-userinfo 臂用它把「用户名位上已经是本模块 token」整个当一个用户名单元吃掉。模块加载时对
40
- * {@link REDACTION_TOKENS} 逐个校验闭形(表加了新 token 形而这里没跟 ⇒ 加载即抛,fail-closed)。 */
41
- import { stripFormatCharacters } from "@sema-agent/core";
42
- /**
43
- * `redactSecrets` 的**归一化前置**(S-103 ①):把全部 `\p{Cf}`(零宽空格 / 词连接符 / BOM / 软连字符 / ZWJ / ZWNJ …)
44
- * 剥掉。实现单一属主是 core 的 `stripFormatCharacters`(`@sema-agent/core`,7.3.1 起导出,正是为「先扫描后渲染」这一序
45
- * 开的口),本模块只**转出**它,不抄第二份 —— 两份实现漂了就等于两个「什么是格式字符」的定义。
46
- *
47
- * 🔴 **需要判断「这次脱敏到底有没有认出凭据形」的调用方,比较基准是它、不是原文**:
48
- * `redactSecrets(x) !== x` 在本版里对**任何**含 Cf 的文本都为真(输出是剥后文本),拿它当谓词会把一条带 emoji ZWJ
49
- * 的普通 `git commit` 判成「含凭据形」;正确的谓词是 `redactSecrets(x) !== normalizeForRedaction(x)`。
50
- * 现役消费方:`src/tool-approval.ts` 的 `durableRuleMaterial`(规则车道素材闸)与语料的 FP=0 格。
51
- */
52
- export declare const normalizeForRedaction: typeof stripFormatCharacters;
22
+ export declare function mapNormalizedToOriginal(text: string, normalized: string): Int32Array;
53
23
  export declare const REDACTION_TOKEN_RE_SRC: string;
54
24
  export declare function armPoolBudgetForTest(): {
55
25
  spanCount: number;
@@ -60,6 +30,7 @@ export declare function armPoolBudgetForTest(): {
60
30
  boundaryArmCount: number;
61
31
  forensicLevels: number;
62
32
  ssh2ScanBytes: number;
33
+ mappedUnits: number;
63
34
  };
64
35
  export declare function resetArmPoolBudgetForTest(): void;
65
36
  /** 本模块会写进文本的全部替换 token(`«…»` 字面),从三张表现算 —— 表改这里自动跟,不另抄一份。
@@ -124,8 +95,9 @@ export declare function redactSecrets(text: string, opts?: {
124
95
  * 出口开在脱敏器**自己**身上是硬约束:全仓只该有一个「什么算凭据」的属主,消费方**不许**按 token 字面
125
96
  * (`includes(":partial»")`)或另跑一遍正则去反推 —— 那就是第二只脱敏器。
126
97
  *
127
- * `arms` 是**这一遍真命中的档位集合**:空集 = 一条臂都没命中(文本可能仍被归一化剥了 `\p{Cf}`,
128
- * 所以「`text !== 入参`」与「`arms` 非空」不是一回事,后者才是凭据形的证据)。
98
+ * `arms` 是**这一遍真命中的档位集合**。「`text !== 入参`」与「`arms` 非空」仍不是一回事(S-1435 起理由换了):
99
+ * 前者 ⇔ 至少一条跨度改写了字节(强弱两档都算 —— 格式字符不再改字节);后者 = 至少一条臂命中,**含**字节没变的那种
100
+ * (重扫已脱敏文本时,臂命中自己产出的记号、换回同一枚记号)。凭据形的证据是档位:规则素材闸问的是 `arms.has("full")`。
129
101
  */
130
102
  export declare function redactSecretsWithArms(text: string, opts?: {
131
103
  observe?: boolean;
@@ -138,7 +110,8 @@ export declare function redactSecretsWithArms(text: string, opts?: {
138
110
  * 🧪 **跨度出口 —— 只给测试**(车 UH 脱敏基线门 `test/redaction-baseline-gate.test.ts`:判「必隐字节**每一个**都被盖住」,
139
111
  * 而不是「必隐串整串不在」—— 后者对截断式脱敏(值的前缀被洗、尾段留着)恒判已藏,gpt-6 腿 r1-F1 [high])。
140
112
  *
141
- * 同一只 {@link runRedaction}、同一遍扫描,原样交出池已经算好的 `kept` 跨度(S-497 那一份;坐标在**归一化视图**上),
113
+ * 同一只 {@link runRedaction}、同一遍扫描,交出池算好的 `kept` 跨度映回**原文**之后的那一份(S-497 那一份;坐标在原文上,
114
+ * 与产物同一套 —— S-1435 起;此前在剥后视图上),
142
115
  * 不是第二只脱敏器、不改任何判据、不喂观察者(`observe:false`,与影子重扫同口);`opts.json` 与 {@link redactSecrets} 的同名选项
143
116
  * 逐字同义(JSON 模式,`redactedPreview` 走的那一条)。生产代码不调用它:
144
117
  * 问「有没有凭据形」的消费方走 {@link redactSecretsWithArms}。
@@ -1,7 +1,33 @@
1
1
  import { displayUrlOf, stripFormatCharacters } from "@sema-agent/core";
2
2
  import { isHierarchicalUrl } from "@sema-agent/settings-schema";
3
3
  import { recordFailOpen } from "../observability/fail-open.js";
4
- export const normalizeForRedaction = stripFormatCharacters;
4
+ const normalizeForRedaction = stripFormatCharacters;
5
+ export function mapNormalizedToOriginal(text, normalized) {
6
+ mappedUnits += normalized.length;
7
+ const map = new Int32Array(normalized.length + 1);
8
+ let i = 0;
9
+ for (let j = 0; j < normalized.length;) {
10
+ if (i >= text.length) {
11
+ throw new Error(`redact: the normalized view is not a code-point subsequence of the original (at normalized index ${j} of ${normalized.length}) — format-character stripping must only delete code points; refusing to map redaction spans back`);
12
+ }
13
+ const unit = text.charCodeAt(i);
14
+ if (unit >= 0xd800 && unit <= 0xdbff && (text.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {
15
+ if (unit === normalized.charCodeAt(j) && text.charCodeAt(i + 1) === normalized.charCodeAt(j + 1) && normalizeForRedaction(text.slice(i, i + 2)) !== "") {
16
+ map[j] = i;
17
+ map[j + 1] = i + 1;
18
+ j += 2;
19
+ }
20
+ i += 2;
21
+ }
22
+ else {
23
+ if (unit === normalized.charCodeAt(j))
24
+ map[j++] = i;
25
+ i += 1;
26
+ }
27
+ }
28
+ map[normalized.length] = text.length;
29
+ return map;
30
+ }
5
31
  export const REDACTION_TOKEN_RE_SRC = String.raw `«redacted(?::[a-z-]{1,16}){0,2}»`;
6
32
  const ARM_VALUE_CLASSES = [];
7
33
  const atLeast = (cls, min) => {
@@ -210,19 +236,24 @@ function forgetEscapedSeparatorTails() {
210
236
  escapedTailsText = undefined;
211
237
  escapedTailsValue = undefined;
212
238
  }
239
+ const UUID_SHAPE = "[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}";
240
+ const UUID_ALPHABET = "0123456789abcdefABCDEF-";
241
+ const notWholeUuid = (valueClass) => new RegExp(`^${valueClass}+$`).test(UUID_ALPHABET) ? `(?!${UUID_SHAPE}(?!${valueClass}))` : "";
213
242
  const VALUE_ARM_STICKY = new Map();
214
243
  const VALUE_ARM_BODIES = [];
215
244
  const valueArm = (prefixSrc, valueClass, min, evidence) => {
216
- const body = `${prefixSrc}${twoStrongAhead(valueClass)}${atLeast(valueClass, min)}`;
217
- VALUE_ARM_BODIES.push(body);
218
- const g = (() => {
245
+ const tail = `${twoStrongAhead(valueClass)}${atLeast(valueClass, min)}`;
246
+ const [body, g] = (() => {
219
247
  switch (evidence) {
220
- case "self-evident":
221
- return new RegExp(body, "g");
248
+ case "self-evident": {
249
+ const b = `${prefixSrc}${notWholeUuid(valueClass)}${tail}`;
250
+ return [b, new RegExp(b, "g")];
251
+ }
222
252
  case "needs-left-boundary": {
223
- const re = new RegExp(`(?<!${WORD_INTERIOR_CLASS})${body}`, "g");
224
- VALUE_ARM_STICKY.set(re, new RegExp(body, "y"));
225
- return re;
253
+ const b = `${prefixSrc}${tail}`;
254
+ const re = new RegExp(`(?<!${WORD_INTERIOR_CLASS})${b}`, "g");
255
+ VALUE_ARM_STICKY.set(re, new RegExp(b, "y"));
256
+ return [b, re];
226
257
  }
227
258
  default: {
228
259
  const unhandled = evidence;
@@ -230,6 +261,7 @@ const valueArm = (prefixSrc, valueClass, min, evidence) => {
230
261
  }
231
262
  }
232
263
  })();
264
+ VALUE_ARM_BODIES.push(body);
233
265
  return g;
234
266
  };
235
267
  let anyValueArm;
@@ -245,8 +277,9 @@ let headScreenHits = 0;
245
277
  let fullYCalls = 0;
246
278
  let forensicLevels = 0;
247
279
  let ssh2ScanBytes = 0;
280
+ let mappedUnits = 0;
248
281
  export function armPoolBudgetForTest() {
249
- return { spanCount: poolSpanCount, coveredPositions, headScreenHits, fullYCalls, armCount: poolArmCount, boundaryArmCount: poolBoundaryArmCount, forensicLevels, ssh2ScanBytes };
282
+ return { spanCount: poolSpanCount, coveredPositions, headScreenHits, fullYCalls, armCount: poolArmCount, boundaryArmCount: poolBoundaryArmCount, forensicLevels, ssh2ScanBytes, mappedUnits };
250
283
  }
251
284
  export function resetArmPoolBudgetForTest() {
252
285
  poolSpanCount = 0;
@@ -255,6 +288,7 @@ export function resetArmPoolBudgetForTest() {
255
288
  fullYCalls = 0;
256
289
  forensicLevels = 0;
257
290
  ssh2ScanBytes = 0;
291
+ mappedUnits = 0;
258
292
  }
259
293
  const unitBit = (t, c) => (t[c >>> 5] >>> (c & 31)) & 1;
260
294
  let codeUnitDomain;
@@ -628,6 +662,7 @@ const KEYWORD_SECRET_PATTERNS = [
628
662
  [keywordArm("Basic", "[A-Za-z0-9+/]", 16, "={0,2}"), "Basic «redacted»"],
629
663
  [keywordArm("[Tt]oken", "[A-Za-z0-9._~+/-]", 20), "token «redacted»"],
630
664
  ];
665
+ const SK_FULL_ARM_MIN = 20;
631
666
  const SHAPE_SECRET_PATTERNS = [
632
667
  [new RegExp(String.raw `(?:ghp|gho|ghu|ghs|ghr)_${atLeast("[A-Za-z0-9]", 20)}`, "g"), "«redacted:gh-token»"],
633
668
  [new RegExp(String.raw `github_pat_[A-Za-z0-9]{22}_${atLeast("[A-Za-z0-9]", 59)}`, "g"), "«redacted:gh-token»"],
@@ -636,7 +671,7 @@ const SHAPE_SECRET_PATTERNS = [
636
671
  [new RegExp(String.raw `ASIA${atLeast("[0-9A-Z]", 16)}`, "g"), "«redacted:aws-key»"],
637
672
  [new RegExp(String.raw `(?:gl(?:pat|dt|rt|rtr|cbt|ptt|oas|ft|imt|agent|wt|soat|ffct)-${atLeast("[A-Za-z0-9_-]", 20)}|(?:sk|rk)_(?:live|test|org)_${atLeast("[A-Za-z0-9]", 24)}|pypi-${atLeast("[A-Za-z0-9_-]", 85)})`, "g"), "«redacted:key»"],
638
673
  [new RegExp(String.raw `xapp-\d-${atLeast("[A-Za-z0-9-]", 20)}`, "g"), "«redacted:slack-token»"],
639
- [valueArm("sk-", "[A-Za-z0-9_-]", 16, "self-evident"), "«redacted:key»"],
674
+ [valueArm("sk-", "[A-Za-z0-9_-]", SK_FULL_ARM_MIN, "self-evident"), "«redacted:key»"],
640
675
  ];
641
676
  const FULL_ARM_PATTERNS = [...STRUCTURAL_SECRET_PATTERNS, ...KEYWORD_SECRET_PATTERNS, ...SHAPE_SECRET_PATTERNS];
642
677
  const PEM_HEADER_IDENT_MAX = 31;
@@ -1733,9 +1768,8 @@ const armPool = (() => {
1733
1768
  }
1734
1769
  poolSpanCount += spans.length;
1735
1770
  const kept = resolveSpanOverlaps(spans);
1736
- const out = applySpans(text, kept).out;
1737
1771
  if (!wantCount || kept.length === 0)
1738
- return { out, hits: NO_HITS, kept };
1772
+ return { hits: NO_HITS, kept };
1739
1773
  const tokenOf = (sp) => members[sp.order].token;
1740
1774
  const strong = spans.filter((sp) => armOfToken(tokenOf(sp)) === "full").sort((a, b) => a.start - b.start);
1741
1775
  const byArmedToken = new Map();
@@ -1755,7 +1789,7 @@ const armPool = (() => {
1755
1789
  else
1756
1790
  cur.count += 1;
1757
1791
  }
1758
- return { out, hits: [...byArmedToken.values()].map(({ token, arm, count }) => ({ label: labelOfToken(token), count, arm })), kept };
1792
+ return { hits: [...byArmedToken.values()].map(({ token, arm, count }) => ({ label: labelOfToken(token), count, arm })), kept };
1759
1793
  };
1760
1794
  })();
1761
1795
  let onRedaction;
@@ -1783,7 +1817,14 @@ function runRedaction(text, observe, json, wantHits, scope) {
1783
1817
  forgetEscapedSeparatorTails();
1784
1818
  if (observe)
1785
1819
  reportHits(r.hits);
1786
- return { text: r.out, hits: r.hits, spans: r.kept };
1820
+ const spans = spansInOriginal(text, normalized, r.kept);
1821
+ return { text: applySpans(text, spans).out, hits: r.hits, spans };
1822
+ }
1823
+ function spansInOriginal(text, normalized, kept) {
1824
+ if (kept.length === 0 || normalized === text)
1825
+ return kept;
1826
+ const map = mapNormalizedToOriginal(text, normalized);
1827
+ return kept.map((s) => ({ ...s, start: map[s.start], end: map[s.end - 1] + 1 }));
1787
1828
  }
1788
1829
  export function redactSecrets(text, opts) {
1789
1830
  return runRedaction(text, opts?.observe ?? true, opts?.json === true, false, "free-text").text;
@@ -1923,13 +1964,12 @@ if (!REDACTION_TOKENS.has(WITHHELD_PATH_SEGMENT_TOKEN)) {
1923
1964
  throw new Error(`redact: ${WITHHELD_PATH_SEGMENT_TOKEN} is no longer produced by the arm tables — the path-segment filler must stay a closed-set member`);
1924
1965
  }
1925
1966
  function shapeLawCredentialInQueryOrFragment(canonical) {
1926
- const normalized = normalizeForRedaction(canonical);
1927
- const boundary = normalized.indexOf("?");
1967
+ const boundary = canonical.indexOf("?");
1928
1968
  if (boundary < 0)
1929
1969
  return false;
1930
1970
  if (runRedaction(canonical, false, false, false, "free-text").spans.some((sp) => sp.end > boundary))
1931
1971
  return true;
1932
- for (const unit of normalized.slice(boundary + 1).split("&").flatMap((sg) => {
1972
+ for (const unit of canonical.slice(boundary + 1).split("&").flatMap((sg) => {
1933
1973
  const eq = sg.indexOf("=");
1934
1974
  return eq === -1 ? [sg] : [sg.slice(0, eq), sg.slice(eq + 1)];
1935
1975
  })) {
@@ -234,19 +234,6 @@ export declare const SELF_MINTED_WIRE: {
234
234
  since: string;
235
235
  };
236
236
  };
237
- /** 7.124.0 退役:被具体成因词取代的两个旧词(7.40.0 起在 wire 上;从未进过登记表,清单靠本格记 removed)。 */
238
- readonly retired: {
239
- readonly voided: {
240
- readonly attr: "-";
241
- readonly since: "7.40.0";
242
- readonly until: "7.124.0";
243
- };
244
- readonly denied: {
245
- readonly attr: "-";
246
- readonly since: "7.40.0";
247
- readonly until: "7.124.0";
248
- };
249
- };
250
237
  };
251
238
  /**
252
239
  * 活卡结局帧 `tool_approval_complete` 的 additive `cause`(7.124.0):**非人结局在场、人答缺席**。值 = 审计 settle 行 `cause` 的同一个词(一个事实一个读法);
@@ -377,6 +364,31 @@ export declare const SELF_MINTED_WIRE: {
377
364
  };
378
365
  };
379
366
  };
367
+ /**
368
+ * 提交幂等(契约 §0.7:幂等 = 资源行上的唯一键)本版新上 wire 的面:同步腿读行重放体的 `replay` 键、两只按键读的读口、
369
+ * `Idempotency-Key` 命中一条读不到 / 不是这次 taskId 所指的行时的 409。行为由 `test/submit-idempotency-one-ledger.test.ts` 钉。
370
+ */
371
+ readonly submitIdempotency: {
372
+ readonly at: "提交幂等(<端点>#<面> → 应答状态)";
373
+ readonly members: {
374
+ readonly "POST /v1/tasks#replay": {
375
+ readonly attr: 200;
376
+ readonly since: "7.125.0";
377
+ };
378
+ readonly "GET /v1/runs?idempotencyKey": {
379
+ readonly attr: 200;
380
+ readonly since: "7.125.0";
381
+ };
382
+ readonly "GET /v1/leader?idempotencyKey": {
383
+ readonly attr: 200;
384
+ readonly since: "7.125.0";
385
+ };
386
+ readonly "POST /v1/runs|/v1/tasks Idempotency-Key#conflict.run_exists": {
387
+ readonly attr: 409;
388
+ readonly since: "7.125.0";
389
+ };
390
+ };
391
+ };
380
392
  /** 本仓在出口对 core 值做的变换(值变、键不变)。 */
381
393
  readonly wireValueRule: {
382
394
  readonly at: "出口值变换(<端点>#<键> → 变换)";
@@ -77,10 +77,6 @@ export const SELF_MINTED_WIRE = {
77
77
  shutdown_before_park: { attr: "-", since: "7.124.0" },
78
78
  unknown_or_other_replica: { attr: "-", since: "7.40.0" },
79
79
  },
80
- retired: {
81
- voided: { attr: "-", since: "7.40.0", until: "7.124.0" },
82
- denied: { attr: "-", since: "7.40.0", until: "7.124.0" },
83
- },
84
80
  },
85
81
  toolApprovalCompleteCause: {
86
82
  at: "tool_approval_complete frame cause",
@@ -131,6 +127,15 @@ export const SELF_MINTED_WIRE = {
131
127
  unknown: { attr: "-", since: "7.121.0" },
132
128
  },
133
129
  },
130
+ submitIdempotency: {
131
+ at: "提交幂等(<端点>#<面> → 应答状态)",
132
+ members: {
133
+ "POST /v1/tasks#replay": { attr: 200, since: "7.125.0" },
134
+ "GET /v1/runs?idempotencyKey": { attr: 200, since: "7.125.0" },
135
+ "GET /v1/leader?idempotencyKey": { attr: 200, since: "7.125.0" },
136
+ "POST /v1/runs|/v1/tasks Idempotency-Key#conflict.run_exists": { attr: 409, since: "7.125.0" },
137
+ },
138
+ },
134
139
  wireValueRule: {
135
140
  at: "出口值变换(<端点>#<键> → 变换)",
136
141
  members: {
@@ -12,6 +12,7 @@ import type { Logger } from "../observability/logger.js";
12
12
  import type { EffectiveConfig } from "./types.js";
13
13
  import { type ModelRefusalVerdict } from "../model-entry-refusal.js";
14
14
  import { type ConfigApplyReport } from "./apply-ledger.js";
15
+ import { type ConfigCandidateRead } from "./candidate-gate.js";
15
16
  /** Apply center config OVER the env-derived config (mutates `config`). Empty domains keep env defaults. */
16
17
  /** Replace a record's contents IN PLACE (clear keys, copy source) so holders of the SAME reference see the
17
18
  * change. This is what makes models/roles/pricing HOT-apply: core's Runner reads
@@ -82,19 +83,16 @@ export type ModelPlaneOf = Pick<EffectiveConfig, ModelPlaneDomain>;
82
83
  */
83
84
  export declare function withModelPlaneOf<E extends EffectiveConfig>(target: E, source: ModelPlaneOf): E;
84
85
  /**
85
- * E-1(7.103.1 修复轮,opus 腿):boot 读到的这一代**整套落地**了吗 —— 只有整套落地的那一代才可以成为 LKG(`latestEffective` /
86
- * 盘上 LKG 候选)、才让它的 ETag 做条件请求(§9.5-2 + codex R2-F1)。三件同判,缺一不算:读腿没报坏域(`domainErrors`)、
87
- * prompts 面过候选门同一只判形、apply 报告没有被整拒的组(`fullyApplied`;报告缺席 = 没 apply,dry-run 只判前两件)。
88
- * boot 的两条落地路径(候选拉取、整份 LKG 读回)与 refresh / 迟到 boot 两腿的 apply 之后都调这一只,禁在调用点各写一份(此前候选路径三件、LKG 路径只抄了一件,
89
- * refresh / 迟到 boot 两腿各内联了第三件)。
90
- * LKG 读回路径上前两件在上游已有门(`loadLkg` 的载荷复判拒坏 prompts;读回不产 `domainErrors`),这里同判是同一条规则的同一只谓词,
91
- * 不是第二道门。refresh / 迟到 boot 两腿的候选门在 apply **之前**整拒坏域与坏 prompts(拒的是整份候选,不只是 LKG 推进),所以到 apply 之后那两腿调本谓词时
92
- * 前两件恒真、实判的是 `fullyApplied` 一件 —— 调它是为了「干净落地」只有这一个读法,不是再加一道门。
86
+ * E-1(7.103.1 修复轮,opus 腿):读到的这一代**整套落地**了吗 —— 只有整套落地的那一代才可以成为 LKG(`latestEffective` /
87
+ * 盘上 LKG 候选)、才让它的 ETag 做条件请求(§9.5-2 + codex R2-F1)。两件同判,缺一不算:候选判官判它 `clean`(形:读腿没报坏域、
88
+ * prompts 面判形过 —— 判据只在 `judgeConfigCandidate` 一处)、apply 报告没有被整拒的组(落地:`fullyApplied`;报告缺席 = 没 apply)。
89
+ * 分工:判官判**形**,本谓词判**落地**;形那一半读判官,不另写一份。起服的两条落地路径(候选拉取、整份 LKG 读回)与刷新 / 迟到起服两腿的
90
+ * apply 之后都调这一只,禁在调用点各写一份。
91
+ * 起服 / 刷新 / 迟到起服三条候选腿都在 apply **之前**经判官整拒坏形的候选(一个域都不应用),LKG 读回路径上形那一半在上游已有门(`loadLkg` 的载荷复判
92
+ * 拒坏 prompts;读回不产 `domainErrors`)—— 所以到 apply 之后调本谓词时形那一半恒真、实判的是 `fullyApplied` 一件;调它是为了「干净落地」只有这一个读法,
93
+ * 不是再加一道门。
93
94
  */
94
- export declare function fullyLandedForLkg(read: {
95
- readonly effective: EffectiveConfig;
96
- readonly domainErrors?: readonly unknown[];
97
- }, report: ApplyReport | undefined): boolean;
95
+ export declare function fullyLandedForLkg(read: ConfigCandidateRead, report: ApplyReport | undefined): boolean;
98
96
  /**
99
97
  * S-768:组拒码 → 铸它的那条模型面不变量 —— **以码为键**(组拒码闭集的属主是 settings-schema `MODEL_PLANE_REFUSAL_CODES`;那边加一个码
100
98
  * ⇒ 本表缺键 ⇒ 本仓 tsc 红)。模型面 stage 的组拒按**码**铸(码是判决那刻的数据),不变量 id 由本表派生 —— 一个 id 两个码
@@ -15,7 +15,7 @@ import { DEFAULT_MODEL_MISSING_CODE, REFUSED_NAME_MINTED_CODE, judgeCatalogEntri
15
15
  import { judgeModelRouteEndpoint } from "../model-route-endpoint.js";
16
16
  import { gateIntentUnserviceableReason } from "../approval.js";
17
17
  import { refusedGroups } from "./apply-ledger.js";
18
- import { validatePromptsDomain } from "../prompts-domain-validate.js";
18
+ import { judgeConfigCandidate } from "./candidate-gate.js";
19
19
  const charsPerTokenOf = (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 ? v : undefined);
20
20
  function envDerivedModel(config) {
21
21
  const m = config.envModel;
@@ -197,8 +197,7 @@ function takeModelPlaneDomain(out, source, d) {
197
197
  out[d] = v;
198
198
  }
199
199
  export function fullyLandedForLkg(read, report) {
200
- const prompts = read.effective.prompts;
201
- return (read.domainErrors ?? []).length === 0 && (prompts === undefined || validatePromptsDomain(prompts).ok) && report?.fullyApplied !== false;
200
+ return judgeConfigCandidate(read).verdict === "clean" && report?.fullyApplied !== false;
202
201
  }
203
202
  function modelRefusalVerdictOf(group) {
204
203
  const entries = group.refused ?? {};
@@ -0,0 +1,69 @@
1
+ /**
2
+ * 配置候选的**唯一判官**:一份候选(中心有效视图,或本地 `config.d` 读出的那份)能不能收 —— 只有这一种判法。
3
+ *
4
+ * 判据(两件,缺一即拒):读腿交出的 `domainErrors`(某个域读不懂 / 拼错键 / 坏文件:那个域已落 schema 缺省)与 prompts 面的判形
5
+ * (`validatePromptsDomain`;坏形的 pack 会让 core 在每个任务的装配期抛)。任一不过 ⇒ `rejected`:**整份候选不收**,一个域都不应用。
6
+ * 读腿的逐条丢弃(`droppedEntries`)不在这里 —— 颗粒是条目的失效留在条目上,候选照收(调用方响亮点名)。
7
+ *
8
+ * 调用面(全部只调这一只,不内联第二份):
9
+ * · 起服(候选拉到之后、应用之前)—— `rejected` ⇒ 与「候选拉取失败」同一条回落链(候选 → 盘上 LKG → env);
10
+ * · dry-run 起服 —— `clean` 才推进校验子;
11
+ * · 刷新拍 —— `rejected` ⇒ 活配置不动、校验子不进、持续可见到修复;
12
+ * · 迟到起服(超窗的那一拉到货)—— 同刷新;
13
+ * · `run-local` 一次性入口 —— `rejected` ⇒ env(没有 LKG、没有刷新环);
14
+ * · `fullyLandedForLkg`(E-1)的形那一半 —— E-1 判的是「整套落地」= 形(本判官)∧ 应用报告里没有被整拒的组。
15
+ * 拒判的运维行 `config_candidate_rejected` 的体只在 {@link buildCandidateRejectedFields} 一处铸(各入口同一体,`leg` 说是哪一拉)。
16
+ * 分工:**判官判形**(候选本身能不能收);**E-1 判落地**(收下之后,装配期按组被拒的那一组 —— 例:模型面组拒(凭证 / 网关事实)
17
+ * 是另一个问题,不是第二个判官)。
18
+ *
19
+ * `domains` 排序去重:收集器的顺序没有承诺,它是拒行去重键的一部分(同一份候选、同一组坏域只响一次)。`errors` 原样逐条
20
+ * (读腿的诊断在前、prompts 在后)—— 那是可操作的诊断,不是键。纯函数:不改入参,不读环境。
21
+ *
22
+ * **判官交值**:`clean` 判决带着 prompts 面的已判形值(`prompts`;候选不带 prompts 键 ⇒ `undefined`)。采纳 prompts 的各处(起服 / 刷新 /
23
+ * 迟到起服 / LKG 起服 / `run-local`)直接用这一份值,不再自己判第二遍 —— `validatePromptsDomain` 在 src 里只有本判官一个调用点
24
+ * (盘上 LKG 的读回复判也经本判官,`config-lkg.ts`)。
25
+ */
26
+ import { type PromptsDomainFaces } from "../prompts-domain-validate.js";
27
+ import type { EffectiveConfig } from "./types.js";
28
+ /** 读腿交出的一条域级诊断(与 `FetchEffectiveResult.domainErrors` 同形;error 文案已在产出边界脱敏)。 */
29
+ export interface CandidateDomainError {
30
+ readonly domain: string;
31
+ readonly error: string;
32
+ }
33
+ /** 判官的输入:一份读出来的候选。 */
34
+ export interface ConfigCandidateRead {
35
+ readonly effective: EffectiveConfig;
36
+ readonly domainErrors?: readonly CandidateDomainError[];
37
+ }
38
+ export type ConfigCandidateVerdict =
39
+ /** `prompts` = 候选 prompts 面的已判形值(候选不带 prompts 键 ⇒ `undefined` = 撤回中心包、内置提示词服务)。 */
40
+ {
41
+ readonly verdict: "clean";
42
+ readonly prompts: PromptsDomainFaces | undefined;
43
+ } | {
44
+ readonly verdict: "rejected";
45
+ readonly domains: readonly string[];
46
+ readonly errors: readonly CandidateDomainError[];
47
+ };
48
+ export declare function judgeConfigCandidate(r: ConfigCandidateRead): ConfigCandidateVerdict;
49
+ /** `config_candidate_rejected` 的 `leg`(闭集):被判官整拒的候选是**哪一拉**的 —— 起服那一拉 / 起服超窗、到货才判的那一拉 / 刷新拍的一拉 /
50
+ * `run-local` 一次性入口读的那一份。 */
51
+ export type CandidateLeg = "boot" | "late-boot" | "refresh" | "run-local";
52
+ /**
53
+ * `config_candidate_rejected` 一行的体(纯数据,各入口同一体):`domains` 是排序去重的域集逗号串(也是去重键的一半),`errors` 原样,
54
+ * 读腿逐条丢弃的条目与读腿不读的文件随行(被整拒的拍不再逐条单发),`lkgVersion` = 此刻在服务的最后一份整套落地的候选(没有 ⇒ `"(env)"`),
55
+ * `dir` = 本地读腿的配置根(远程读腿缺席;读腿的域诊断只带配置坐标,拒行据 dir + 域点得出文件)。
56
+ */
57
+ export declare function buildCandidateRejectedFields(r: {
58
+ readonly effective: EffectiveConfig;
59
+ readonly droppedEntries?: readonly unknown[];
60
+ readonly unreadFiles?: readonly unknown[];
61
+ }, verdict: Extract<ConfigCandidateVerdict, {
62
+ verdict: "rejected";
63
+ }>, leg: CandidateLeg, ctx: {
64
+ readonly lkgVersion: number | "(env)";
65
+ readonly dir?: string;
66
+ }): Record<string, unknown>;
67
+ /** `config_domain_invalid` 逐域坐标行的 note(各入口同一句):这一行说「哪一域、为什么坏」,判决只有 `config_candidate_rejected` 一行。 */
68
+ export declare const CONFIG_DOMAIN_INVALID_NOTE = "this domain failed its schema \u2014 the candidate judge rejects the whole candidate (see config_candidate_rejected); fix the named file / center data";
69
+ //# sourceMappingURL=candidate-gate.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { validatePromptsDomain } from "../prompts-domain-validate.js";
2
+ export function judgeConfigCandidate(r) {
3
+ const prompts = r.effective.prompts;
4
+ const promptsGate = prompts === undefined ? undefined : validatePromptsDomain(prompts);
5
+ const errors = [
6
+ ...(r.domainErrors ?? []).map((de) => ({ domain: de.domain, error: de.error })),
7
+ ...(promptsGate !== undefined && !promptsGate.ok ? [{ domain: "prompts", error: promptsGate.error }] : []),
8
+ ];
9
+ if (errors.length === 0)
10
+ return { verdict: "clean", prompts: promptsGate?.ok ? promptsGate.value : undefined };
11
+ return { verdict: "rejected", domains: [...new Set(errors.map((e) => e.domain))].sort(), errors };
12
+ }
13
+ export function buildCandidateRejectedFields(r, verdict, leg, ctx) {
14
+ return {
15
+ version: r.effective.version,
16
+ domains: verdict.domains.join(","),
17
+ leg,
18
+ ...(ctx.dir !== undefined ? { dir: ctx.dir } : {}),
19
+ errors: verdict.errors,
20
+ ...(r.droppedEntries !== undefined ? { droppedEntries: r.droppedEntries } : {}),
21
+ ...(r.unreadFiles !== undefined ? { unreadFiles: r.unreadFiles } : {}),
22
+ lkgVersion: ctx.lkgVersion,
23
+ note: "candidate rejected whole (§9.5-5) — live config keeps the last-known-good; fix the named files to unblock",
24
+ };
25
+ }
26
+ export const CONFIG_DOMAIN_INVALID_NOTE = "this domain failed its schema — the candidate judge rejects the whole candidate (see config_candidate_rejected); fix the named file / center data";
27
+ //# sourceMappingURL=candidate-gate.js.map
@@ -1,7 +1,7 @@
1
1
  import { type EffectiveReadWarning, type EntitlementRuntimeCaps } from "@sema-agent/settings-schema";
2
2
  import type { ScenarioRuling } from "../capabilities/scenarios.js";
3
3
  import type { EffectiveConfig, ExecutionRuling } from "./types.js";
4
- import { type DroppedConfigEntry } from "./read-warnings.js";
4
+ import { type DroppedConfigEntry, type UnreadConfigFile } from "./read-warnings.js";
5
5
  import { type CenterEndpoint } from "./center-request.js";
6
6
  /** GET the effective config (Bearer + ETag). null = 304 (unchanged). Throws on transport/HTTP error, and on a
7
7
  * payload that is not an effective config at all(非对象 / version 非有限数 / gate 域坏形——[ref] 裁B)。
@@ -23,6 +23,7 @@ export declare function fetchEffective(center: CenterEndpoint, etag: string | un
23
23
  error: string;
24
24
  }>;
25
25
  droppedEntries?: DroppedConfigEntry[];
26
+ unreadFiles?: UnreadConfigFile[];
26
27
  readWarnings: readonly EffectiveReadWarning[];
27
28
  credentialIdentity: string;
28
29
  } | null>;
@@ -18,7 +18,7 @@ export async function fetchEffective(center, etag, fetchImpl = fetch, worker) {
18
18
  delete wire.prompts;
19
19
  else
20
20
  wire.prompts = centerPrompts;
21
- const { domainErrors, droppedEntries, droppedCatalogEntries } = splitFoldedReadWarnings(warnings);
21
+ const { domainErrors, droppedEntries, droppedCatalogEntries, unreadFiles } = splitFoldedReadWarnings(warnings);
22
22
  const effective = withDroppedCatalogEntries(wire, droppedCatalogEntries);
23
23
  return {
24
24
  effective,
@@ -27,6 +27,7 @@ export async function fetchEffective(center, etag, fetchImpl = fetch, worker) {
27
27
  credentialIdentity: identity,
28
28
  ...(domainErrors.length > 0 ? { domainErrors } : {}),
29
29
  ...(droppedEntries.length > 0 ? { droppedEntries } : {}),
30
+ ...(unreadFiles.length > 0 ? { unreadFiles } : {}),
30
31
  };
31
32
  }
32
33
  export async function fetchPrincipalCaps(center, principal, etag, fetchImpl = fetch, worker) {
@@ -6,8 +6,9 @@
6
6
  */
7
7
  /**
8
8
  * 最近一次**实拉**的结局 —— 新状态,运行时只此一枚。闭集:
9
- * · `none` —— 本进程还没有一次中心实拉落地(LKG 起服 / env 兜底起服 / 拉取还在飞 / 配置源是本地目录 `CONFIG_PROVIDER=local`);
10
- * · `ok` —— 最近一拍拉取 + 处理完成(boot 实拉、迟到 boot 到货、refresh 拍任一;304 与被候选门整拒的拍也算:中心答了);
9
+ * · `none` —— 本进程还没有一次中心实拉落地(拉取失败 / 超窗后的 LKG 起服或 env 兜底起服 / 拉取还在飞 / 配置源是本地目录 `CONFIG_PROVIDER=local`);
10
+ * · `ok` —— 最近一拍拉取 + 处理完成(boot 实拉、迟到 boot 到货、refresh 拍任一;304 与被候选判官整拒的拍也算:中心答了 ——
11
+ * 起服遇拒而读回 LKG 起服也是 `ok`,区别于拉取失败后的 LKG 起服);
11
12
  * · `failed` —— 最近一个 refresh 拍抛了(中心超时 / 不可达 / 读不了 / 应用抛错 —— 与 `POST /v1/admin/config/refresh` 的 `"failed"` 同一判)。
12
13
  */
13
14
  export declare const CENTER_PULL_STATES: readonly ["none", "ok", "failed"];
@@ -18,8 +19,9 @@ export interface CenterPullView {
18
19
  /**
19
20
  * 生效修订号 —— 与 `/health.configAppliedVersion` **同一只** ledger 视图取值(同一事实的第三个面:`/health` 与
20
21
  * `GET /v1/diagnostics/wiring` 是运维面,caps 是调用方面);`updatedAt` 取最近一份整套落地的候选(`latestEffective`)的
21
- * `updatedAt`。`null` = 还没有一代真落地,或 ledger 的已应用代与整套落地的那一代对不上(部分应用窗 / boot 带域错的候选)——
22
- * 给不出一对同代的 `{version, updatedAt}` 就不拼一对(拼出来的是两代各取一半)。
22
+ * `updatedAt`。`null` = 还没有一代真落地,或 ledger 的已应用代与整套落地的那一代对不上(某组被拒的部分应用 —— 那一代不算已应用、
23
+ * 也不进整套落地)—— 给不出一对同代的 `{version, updatedAt}` 就不拼一对(拼出来的是两代各取一半)。被候选判官整拒的候选不进这两侧
24
+ * 任何一侧(只推进 target):起服遇拒读回 LKG ⇒ 两侧都是 LKG 那一代,配得上一对。
23
25
  */
24
26
  readonly effectiveRevision: {
25
27
  readonly version: number;