@sema-agent/server 7.124.1 → 7.125.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +138 -2
- package/USAGE.md +46 -8
- package/dist/audit.d.ts +27 -5
- package/dist/audit.js +42 -8
- package/dist/boot/execution-env.d.ts +33 -1
- package/dist/boot/execution-env.js +31 -2
- package/dist/boot/resolve-spec.js +4 -3
- package/dist/boot/session-faces.d.ts +14 -1
- package/dist/boot/session-faces.js +6 -5
- package/dist/boot/stage-02-web-admin-pull.js +5 -1
- package/dist/boot/stage-05-execution-env.js +6 -2
- package/dist/boot/stage-09-leader.d.ts +1 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/web-admin.js +130 -140
- package/dist/capabilities/center-wiring.d.ts +2 -1
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/capability-facts.d.ts +28 -3
- package/dist/capability-facts.js +20 -0
- package/dist/config-catalog.js +4 -1
- package/dist/config-d-migration.js +2 -1
- package/dist/config-lkg.d.ts +4 -1
- package/dist/config-lkg.js +16 -12
- package/dist/config-provider.d.ts +4 -1
- package/dist/config-provider.js +1 -0
- package/dist/config-types.d.ts +10 -3
- package/dist/config.js +44 -14
- package/dist/env-facts.d.ts +2 -2
- package/dist/env-facts.js +3 -2
- package/dist/fleet/fleet-bus.d.ts +24 -12
- package/dist/fleet/fleet-bus.js +4 -1
- package/dist/fleet/workspace-snapshot-residual.d.ts +10 -0
- package/dist/fleet/workspace-snapshot-residual.js +35 -0
- package/dist/hooks/hook-runner.d.ts +15 -27
- package/dist/hooks/hook-runner.js +19 -59
- package/dist/http/active-run-conflict.d.ts +3 -0
- package/dist/http/active-run-conflict.js +3 -0
- package/dist/http/admission.js +7 -7
- package/dist/http/approval-sweeps.js +2 -4
- package/dist/http/device-placement.js +1 -0
- package/dist/http/dispatch.js +1 -1
- package/dist/http/durable-run-leg.d.ts +20 -6
- package/dist/http/durable-run-leg.js +4 -4
- package/dist/http/idempotency.d.ts +83 -38
- package/dist/http/idempotency.js +38 -13
- package/dist/http/request-guards.d.ts +18 -0
- package/dist/http/request-guards.js +12 -3
- package/dist/http/resume-legs.js +7 -1
- package/dist/http/route-ctx.d.ts +3 -3
- package/dist/http/route-ctx.js +1 -1
- package/dist/http/routes/a2a-serve.js +27 -11
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/fleet.js +2 -0
- package/dist/http/routes/images.js +2 -2
- package/dist/http/routes/leader.d.ts +4 -0
- package/dist/http/routes/leader.js +13 -3
- package/dist/http/routes/observability.js +2 -0
- package/dist/http/routes/runs.d.ts +6 -2
- package/dist/http/routes/runs.js +71 -101
- package/dist/http/routes/session-sync.d.ts +3 -0
- package/dist/http/routes/session-sync.js +30 -1
- package/dist/http/routes/sessions-list.d.ts +11 -0
- package/dist/http/routes/sessions-list.js +6 -6
- package/dist/http/routes/sessions.d.ts +6 -1
- package/dist/http/routes/sessions.js +41 -10
- package/dist/http/routes/tasks.js +63 -21
- package/dist/http/run-read-face.d.ts +26 -0
- package/dist/http/run-read-face.js +67 -0
- package/dist/http/server.d.ts +4 -4
- package/dist/http/server.js +1 -2
- package/dist/http/submit-replay.d.ts +40 -0
- package/dist/http/submit-replay.js +22 -0
- package/dist/leader/endpoint.d.ts +10 -5
- package/dist/leader/endpoint.js +27 -5
- package/dist/observability/cost-quota.d.ts +51 -7
- package/dist/observability/cost-quota.js +35 -17
- package/dist/observability/fail-open.d.ts +1 -1
- package/dist/observability/fail-open.js +1 -1
- package/dist/observability/run-terminal.d.ts +5 -9
- package/dist/observability/run-terminal.js +2 -7
- package/dist/plugins/approval-ask-store-sql.js +1 -1
- package/dist/plugins/caching-session-store.d.ts +7 -2
- package/dist/plugins/caching-session-store.js +6 -0
- package/dist/plugins/device-store-sql.js +1 -1
- package/dist/plugins/ensure-index.js +22 -0
- package/dist/plugins/file-run-store.d.ts +24 -14
- package/dist/plugins/file-run-store.js +52 -12
- package/dist/plugins/leader-run-store-sql.d.ts +1 -11
- package/dist/plugins/leader-run-store-sql.js +1 -5
- package/dist/plugins/local-session-store.d.ts +35 -6
- package/dist/plugins/local-session-store.js +87 -18
- package/dist/plugins/memory-run-store.d.ts +13 -12
- package/dist/plugins/memory-run-store.js +25 -0
- package/dist/plugins/one-time-migrations.js +1 -1
- package/dist/plugins/pg-cost-quota.d.ts +1 -1
- package/dist/plugins/pg-cost-quota.js +2 -5
- package/dist/plugins/pg-pool.js +3 -0
- package/dist/plugins/pg-session-storage.d.ts +11 -1
- package/dist/plugins/pg-session-storage.js +44 -8
- package/dist/plugins/remote-env-k8s.d.ts +127 -12
- package/dist/plugins/remote-env-k8s.js +270 -27
- package/dist/plugins/run-store-sql.d.ts +26 -12
- package/dist/plugins/run-store-sql.js +50 -25
- package/dist/plugins/schema-consistency.d.ts +1 -13
- package/dist/plugins/schema-consistency.js +1 -41
- package/dist/plugins/schema-indexes.js +1 -0
- package/dist/plugins/schema-live-columns.d.ts +20 -0
- package/dist/plugins/schema-live-columns.js +42 -0
- package/dist/plugins/store-contracts.d.ts +40 -2
- package/dist/plugins/tidb-cost-quota.d.ts +1 -1
- package/dist/plugins/tidb-cost-quota.js +2 -5
- package/dist/plugins/tidb-pool.js +14 -3
- package/dist/plugins/tidb-session-store.d.ts +22 -2
- package/dist/plugins/tidb-session-store.js +33 -6
- package/dist/plugins/write-behind-counter.d.ts +12 -5
- package/dist/plugins/write-behind-counter.js +14 -7
- package/dist/prompts-domain-validate.js +3 -2
- package/dist/rules-consent.d.ts +2 -2
- package/dist/rules-consent.js +2 -2
- package/dist/run-local.js +31 -23
- package/dist/runs.d.ts +9 -0
- package/dist/runs.js +8 -0
- package/dist/runtime-governance.d.ts +3 -3
- package/dist/runtime-governance.js +2 -4
- package/dist/security.d.ts +19 -2
- package/dist/session-row.d.ts +53 -0
- package/dist/session-row.js +47 -0
- package/dist/session-sync.d.ts +6 -0
- package/dist/session-sync.js +5 -1
- package/dist/session-titler.d.ts +9 -5
- package/dist/session-titler.js +3 -1
- package/dist/spec-fields.js +2 -2
- package/dist/task-cwd.d.ts +0 -20
- package/dist/task-cwd.js +2 -10
- package/dist/tool-approval.d.ts +5 -7
- package/dist/trace/redact.d.ts +23 -50
- package/dist/trace/redact.js +42 -9
- package/dist/trace/self-minted-wire.d.ts +25 -0
- package/dist/trace/self-minted-wire.js +9 -0
- package/dist/web-admin-client/apply-effective.d.ts +10 -12
- package/dist/web-admin-client/apply-effective.js +2 -3
- package/dist/web-admin-client/candidate-gate.d.ts +69 -0
- package/dist/web-admin-client/candidate-gate.js +27 -0
- package/dist/web-admin-client/http-client.d.ts +2 -1
- package/dist/web-admin-client/http-client.js +2 -1
- package/dist/web-admin-client/pull-state.d.ts +6 -4
- package/dist/web-admin-client/read-warnings.d.ts +29 -5
- package/dist/web-admin-client/read-warnings.js +24 -4
- package/dist/web-admin-client/stage-limits.js +4 -1
- package/dist/workspace-space.d.ts +41 -0
- package/dist/workspace-space.js +5 -0
- package/package.json +3 -3
package/dist/trace/redact.d.ts
CHANGED
|
@@ -1,55 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
-
*
|
|
24
|
-
*
|
|
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
|
-
*
|
|
32
|
-
*
|
|
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
|
-
|
|
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`
|
|
128
|
-
*
|
|
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}
|
|
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}。
|
package/dist/trace/redact.js
CHANGED
|
@@ -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
|
-
|
|
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) => {
|
|
@@ -251,8 +277,9 @@ let headScreenHits = 0;
|
|
|
251
277
|
let fullYCalls = 0;
|
|
252
278
|
let forensicLevels = 0;
|
|
253
279
|
let ssh2ScanBytes = 0;
|
|
280
|
+
let mappedUnits = 0;
|
|
254
281
|
export function armPoolBudgetForTest() {
|
|
255
|
-
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 };
|
|
256
283
|
}
|
|
257
284
|
export function resetArmPoolBudgetForTest() {
|
|
258
285
|
poolSpanCount = 0;
|
|
@@ -261,6 +288,7 @@ export function resetArmPoolBudgetForTest() {
|
|
|
261
288
|
fullYCalls = 0;
|
|
262
289
|
forensicLevels = 0;
|
|
263
290
|
ssh2ScanBytes = 0;
|
|
291
|
+
mappedUnits = 0;
|
|
264
292
|
}
|
|
265
293
|
const unitBit = (t, c) => (t[c >>> 5] >>> (c & 31)) & 1;
|
|
266
294
|
let codeUnitDomain;
|
|
@@ -1740,9 +1768,8 @@ const armPool = (() => {
|
|
|
1740
1768
|
}
|
|
1741
1769
|
poolSpanCount += spans.length;
|
|
1742
1770
|
const kept = resolveSpanOverlaps(spans);
|
|
1743
|
-
const out = applySpans(text, kept).out;
|
|
1744
1771
|
if (!wantCount || kept.length === 0)
|
|
1745
|
-
return {
|
|
1772
|
+
return { hits: NO_HITS, kept };
|
|
1746
1773
|
const tokenOf = (sp) => members[sp.order].token;
|
|
1747
1774
|
const strong = spans.filter((sp) => armOfToken(tokenOf(sp)) === "full").sort((a, b) => a.start - b.start);
|
|
1748
1775
|
const byArmedToken = new Map();
|
|
@@ -1762,7 +1789,7 @@ const armPool = (() => {
|
|
|
1762
1789
|
else
|
|
1763
1790
|
cur.count += 1;
|
|
1764
1791
|
}
|
|
1765
|
-
return {
|
|
1792
|
+
return { hits: [...byArmedToken.values()].map(({ token, arm, count }) => ({ label: labelOfToken(token), count, arm })), kept };
|
|
1766
1793
|
};
|
|
1767
1794
|
})();
|
|
1768
1795
|
let onRedaction;
|
|
@@ -1790,7 +1817,14 @@ function runRedaction(text, observe, json, wantHits, scope) {
|
|
|
1790
1817
|
forgetEscapedSeparatorTails();
|
|
1791
1818
|
if (observe)
|
|
1792
1819
|
reportHits(r.hits);
|
|
1793
|
-
|
|
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 }));
|
|
1794
1828
|
}
|
|
1795
1829
|
export function redactSecrets(text, opts) {
|
|
1796
1830
|
return runRedaction(text, opts?.observe ?? true, opts?.json === true, false, "free-text").text;
|
|
@@ -1930,13 +1964,12 @@ if (!REDACTION_TOKENS.has(WITHHELD_PATH_SEGMENT_TOKEN)) {
|
|
|
1930
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`);
|
|
1931
1965
|
}
|
|
1932
1966
|
function shapeLawCredentialInQueryOrFragment(canonical) {
|
|
1933
|
-
const
|
|
1934
|
-
const boundary = normalized.indexOf("?");
|
|
1967
|
+
const boundary = canonical.indexOf("?");
|
|
1935
1968
|
if (boundary < 0)
|
|
1936
1969
|
return false;
|
|
1937
1970
|
if (runRedaction(canonical, false, false, false, "free-text").spans.some((sp) => sp.end > boundary))
|
|
1938
1971
|
return true;
|
|
1939
|
-
for (const unit of
|
|
1972
|
+
for (const unit of canonical.slice(boundary + 1).split("&").flatMap((sg) => {
|
|
1940
1973
|
const eq = sg.indexOf("=");
|
|
1941
1974
|
return eq === -1 ? [sg] : [sg.slice(0, eq), sg.slice(eq + 1)];
|
|
1942
1975
|
})) {
|
|
@@ -364,6 +364,31 @@ export declare const SELF_MINTED_WIRE: {
|
|
|
364
364
|
};
|
|
365
365
|
};
|
|
366
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
|
+
};
|
|
367
392
|
/** 本仓在出口对 core 值做的变换(值变、键不变)。 */
|
|
368
393
|
readonly wireValueRule: {
|
|
369
394
|
readonly at: "出口值变换(<端点>#<键> → 变换)";
|
|
@@ -127,6 +127,15 @@ export const SELF_MINTED_WIRE = {
|
|
|
127
127
|
unknown: { attr: "-", since: "7.121.0" },
|
|
128
128
|
},
|
|
129
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
|
+
},
|
|
130
139
|
wireValueRule: {
|
|
131
140
|
at: "出口值变换(<端点>#<键> → 变换)",
|
|
132
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 腿)
|
|
86
|
-
* 盘上 LKG 候选)、才让它的 ETag 做条件请求(§9.5-2 + codex R2-F1)
|
|
87
|
-
* prompts
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* LKG
|
|
91
|
-
*
|
|
92
|
-
*
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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 的已应用代与整套落地的那一代对不上(
|
|
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;
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
*
|
|
13
13
|
* **归族判据**(下游两个消费者:候选门 = 非空 ⇒ 这棵树整拒、保 LKG;逐条丢弃面 = 响亮点名、其余照用):每一种告警都是
|
|
14
14
|
* 「写了没生效」,按**失效的颗粒**分:丢的是**一条条目**(`entry-dropped`)⇒ `scope:"entry"` —— 这条条目单独不在,同域其余条目照用,
|
|
15
|
-
* 候选**不**因它整拒;丢的是一个域 / 一组键 /
|
|
15
|
+
* 候选**不**因它整拒;丢的是一个域 / 一组键 / 一份**读的**域文件 ⇒ `scope:"domain"` —— 候选门错误。`config.d` 里**读腿根本不读**的文件
|
|
16
|
+
* (`unread-config-file`:名字不是域,或是域但不便携 —— `workers.json` / `hosts.json` / 任意非域 `*.json`)⇒ `scope:"unread"` —— 不是
|
|
17
|
+
* 「读的域判形失败」,候选判官只收读腿读的域;它与逐条丢弃同一「点名不判拒」律(调用方一份文件一行 `config_unread_file`,其余照用)。(settings-schema 11 起读路径上零抬升臂:
|
|
16
18
|
* 此前「值已被收编接受」的两种告警 `hosts-grandfathered` / `legacy-governance-lifted` 随抬升臂一起退役,旧形由一次性迁移改写,
|
|
17
19
|
* 读侧遇旧形一律拒 —— 没有「不入诊断面」的那一族了。)
|
|
18
20
|
*
|
|
@@ -24,7 +26,8 @@
|
|
|
24
26
|
*/
|
|
25
27
|
import { z } from "zod";
|
|
26
28
|
import { type DroppedEntryIdentity, type DroppedEntryReasonCode, type EffectiveReadWarning } from "@sema-agent/settings-schema";
|
|
27
|
-
/** 折叠产物:`scope` 决定消费方做什么 —— `"domain"` 进候选门(`domainErrors`),`"entry"` 进逐条丢弃面(`droppedEntries`)
|
|
29
|
+
/** 折叠产物:`scope` 决定消费方做什么 —— `"domain"` 进候选门(`domainErrors`),`"entry"` 进逐条丢弃面(`droppedEntries`),
|
|
30
|
+
* `"unread"` 进读腿不读的文件面(`unreadFiles`;点名不判拒)。 */
|
|
28
31
|
export type FoldedReadWarning = {
|
|
29
32
|
readonly scope: "domain";
|
|
30
33
|
readonly domain: string;
|
|
@@ -35,7 +38,17 @@ export type FoldedReadWarning = {
|
|
|
35
38
|
readonly entry: string;
|
|
36
39
|
readonly error: string;
|
|
37
40
|
readonly reasonCode: DroppedEntryReasonCode;
|
|
41
|
+
} | ({
|
|
42
|
+
readonly scope: "unread";
|
|
43
|
+
} & UnreadConfigFile);
|
|
44
|
+
/** `config.d` 里读腿不读的一份文件(本地读腿才有;`why` / `domain` 是契约告警的原值)。 */
|
|
45
|
+
export type UnreadConfigFile = {
|
|
46
|
+
readonly file: string;
|
|
47
|
+
readonly why: "not-a-domain" | "non-portable-domain";
|
|
48
|
+
readonly domain?: string;
|
|
38
49
|
};
|
|
50
|
+
/** `config_unread_file` 一行的体(纯数据;起服 / 刷新 / 迟到起服与 `run-local` 同一体):`dir` = 本地读腿的配置根。 */
|
|
51
|
+
export declare function buildUnreadConfigFileFields(f: UnreadConfigFile, dir: string | undefined): Record<string, unknown>;
|
|
39
52
|
/** 逐条丢弃面的一行(两条读腿的 `droppedEntries` 同形,`config_entry_dropped` 日志行读它):`entry` = 被丢那条的出门形坐标(`models.1`),
|
|
40
53
|
* `error` = 哪些字段、为什么(人读),`reasonCode` = 为什么丢的**闭集词**(S-1295:settings-schema 铸、原样过境 —— 目录行的
|
|
41
54
|
* `GET /v1/models.droppedEntries[].reasonCode` 就是这同一个词,取自同一次折叠的同一行)。 */
|
|
@@ -60,13 +73,23 @@ export declare function describeSchemaIssueFields(error: unknown): string[];
|
|
|
60
73
|
/** 契约句子里**值**的位置在本仓诊断面上的写法(一个洞,不回显值;句子的固定词 —— 迁移指路、词表出路、字段名 —— 原样)。 */
|
|
61
74
|
export declare const SCHEMA_OPERAND_HOLE = "\u00ABvalue\u00BB";
|
|
62
75
|
/**
|
|
63
|
-
* 整域失效(`domain-defaulted`)的诊断文案:逐条 issue `坐标: 句子`,句子读 {@link issueSentenceOf}(与逐条丢弃面同一只)
|
|
64
|
-
* 操作数换成 {@link SCHEMA_OPERAND_HOLE}
|
|
76
|
+
* 整域失效(`domain-defaulted`)的诊断文案:逐条 issue `坐标: 句子`,句子读 {@link issueSentenceOf}(与逐条丢弃面同一只):custom issue 的结构化句子重铸、
|
|
77
|
+
* 操作数换成 {@link SCHEMA_OPERAND_HOLE};zod 内建码(含墓碑字段的 `invalid_type`)读 message 原样。读不出 issues 形 ⇒ 退回 {@link errorText}。
|
|
65
78
|
* 为什么不直接用 `error.message`:zod 4 的 message 是整份 issues 的 JSON —— 本地腿出口的引号段指纹(`redactConfigError`)会把它从第一个
|
|
66
79
|
* 引号到最后一个引号整段吞掉,契约拒句里点名的迁移 id(「旧形读写两面响亮拒、拒句点名迁移 id」)就到不了运维眼前。
|
|
67
80
|
*/
|
|
68
81
|
export declare function describeDomainIssues(error: unknown): string;
|
|
69
|
-
/** 一条读告警 → 它的诊断面(每一种都进:见文件头「归族判据」)。 */
|
|
82
|
+
/** 一条读告警 → 它的诊断面(每一种都进:见文件头「归族判据」)。按 kind 的重载只是让调用方拿到窄型(`unread` 一面没有 `error` 句)。 */
|
|
83
|
+
export declare function foldEffectiveReadWarning(w: Extract<EffectiveReadWarning, {
|
|
84
|
+
kind: "unread-config-file";
|
|
85
|
+
}>): Extract<FoldedReadWarning, {
|
|
86
|
+
scope: "unread";
|
|
87
|
+
}>;
|
|
88
|
+
export declare function foldEffectiveReadWarning(w: Exclude<EffectiveReadWarning, {
|
|
89
|
+
kind: "unread-config-file";
|
|
90
|
+
}>): Exclude<FoldedReadWarning, {
|
|
91
|
+
scope: "unread";
|
|
92
|
+
}>;
|
|
70
93
|
export declare function foldEffectiveReadWarning(w: EffectiveReadWarning): FoldedReadWarning;
|
|
71
94
|
/**
|
|
72
95
|
* 🔴 S-712 / S-1295 —— **被丢目录条目的载体行**(载体 = `EffectiveConfig.models.droppedCatalogEntries`,读腿铸,随 effective 走 —— 盘上 LKG
|
|
@@ -114,6 +137,7 @@ export declare function splitFoldedReadWarnings(warnings: readonly EffectiveRead
|
|
|
114
137
|
}>;
|
|
115
138
|
droppedEntries: DroppedConfigEntry[];
|
|
116
139
|
droppedCatalogEntries: DroppedCatalogEntry[];
|
|
140
|
+
unreadFiles: UnreadConfigFile[];
|
|
117
141
|
};
|
|
118
142
|
/** 把本次读交出的目录载体挂上 effective 的模型域(两腿同一只;空 ⇒ 不挂键)。 */
|
|
119
143
|
export declare function withDroppedCatalogEntries<E extends {
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { DROPPED_ENTRY_REASON_CODES, formatConfigPath, isDisclosableIdentity, renderQuotedSentence, } from "@sema-agent/settings-schema";
|
|
3
|
+
export function buildUnreadConfigFileFields(f, dir) {
|
|
4
|
+
return {
|
|
5
|
+
...(dir !== undefined ? { dir } : {}),
|
|
6
|
+
file: f.file,
|
|
7
|
+
why: f.why,
|
|
8
|
+
...(f.domain !== undefined ? { domain: f.domain } : {}),
|
|
9
|
+
note: "config.d holds a file the local read never reads (not a domain, or a domain that is not portable) — it is not applied and does not block the candidate; move it out of config.d if it is stray",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
3
12
|
export function describeSchemaIssues(error) {
|
|
4
13
|
const issues = schemaIssuesOf(error);
|
|
5
14
|
if (issues === undefined)
|
|
@@ -13,8 +22,13 @@ function issueCoordinateOf(raw) {
|
|
|
13
22
|
return Array.isArray(p) && p.length > 0 ? formatConfigPath(p) : undefined;
|
|
14
23
|
}
|
|
15
24
|
function issueSentenceOf(raw) {
|
|
16
|
-
const
|
|
17
|
-
|
|
25
|
+
const issue = typeof raw === "object" && raw !== null ? raw : {};
|
|
26
|
+
if (issue.code === "custom")
|
|
27
|
+
return contractSentenceOf(raw) ?? sentencelessCustomIssue(issueCoordinateOf(raw));
|
|
28
|
+
return typeof issue.message === "string" ? issue.message : "(unreadable issue)";
|
|
29
|
+
}
|
|
30
|
+
function sentencelessCustomIssue(at) {
|
|
31
|
+
return `(issue code "custom" at ${at ?? "(entry)"} carries no contract sentence — its message is withheld)`;
|
|
18
32
|
}
|
|
19
33
|
function contractSentenceOf(raw) {
|
|
20
34
|
const params = (typeof raw === "object" && raw !== null ? raw.params : undefined);
|
|
@@ -74,7 +88,7 @@ export function foldEffectiveReadWarning(w) {
|
|
|
74
88
|
case "unknown-keys-carried":
|
|
75
89
|
return { scope: "domain", domain: w.domain, error: keyList("unknown keys carried to consumer (spelling?): ", w.keys, w.truncatedFrom) };
|
|
76
90
|
case "unread-config-file":
|
|
77
|
-
return { scope: "
|
|
91
|
+
return { scope: "unread", file: w.file, why: w.why, ...(w.domain !== undefined ? { domain: w.domain } : {}) };
|
|
78
92
|
default: {
|
|
79
93
|
const never = w;
|
|
80
94
|
throw new Error(`unhandled EffectiveReadWarning kind: ${JSON.stringify(never)}`);
|
|
@@ -109,6 +123,7 @@ export function splitFoldedReadWarnings(warnings) {
|
|
|
109
123
|
const domainErrors = [];
|
|
110
124
|
const droppedEntries = [];
|
|
111
125
|
const droppedCatalogEntries = [];
|
|
126
|
+
const unreadFiles = [];
|
|
112
127
|
for (const w of warnings) {
|
|
113
128
|
const f = foldEffectiveReadWarning(w);
|
|
114
129
|
switch (f.scope) {
|
|
@@ -124,13 +139,18 @@ export function splitFoldedReadWarnings(warnings) {
|
|
|
124
139
|
}
|
|
125
140
|
break;
|
|
126
141
|
}
|
|
142
|
+
case "unread": {
|
|
143
|
+
const { scope: _scope, ...file } = f;
|
|
144
|
+
unreadFiles.push(file);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
127
147
|
default: {
|
|
128
148
|
const never = f;
|
|
129
149
|
throw new Error(`unhandled folded read-warning scope: ${JSON.stringify(never)}`);
|
|
130
150
|
}
|
|
131
151
|
}
|
|
132
152
|
}
|
|
133
|
-
return { domainErrors, droppedEntries, droppedCatalogEntries };
|
|
153
|
+
return { domainErrors, droppedEntries, droppedCatalogEntries, unreadFiles };
|
|
134
154
|
}
|
|
135
155
|
export function withDroppedCatalogEntries(effective, rows) {
|
|
136
156
|
if (rows.length === 0 || effective.models === undefined)
|