@sema-agent/server 7.122.0 → 7.123.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 (69) hide show
  1. package/MIGRATION.md +77 -0
  2. package/README.md +5 -0
  3. package/README.zh-CN.md +3 -0
  4. package/USAGE.md +23 -5
  5. package/dist/audit.d.ts +1 -1
  6. package/dist/audit.js +52 -22
  7. package/dist/auto-mode-face.d.ts +12 -8
  8. package/dist/auto-mode-face.js +8 -7
  9. package/dist/boot/config-center.js +3 -0
  10. package/dist/config-catalog.js +1 -1
  11. package/dist/config-center/apply-effective.d.ts +1 -1
  12. package/dist/config-center/apply-effective.js +5 -3
  13. package/dist/config-center/http-client.js +3 -3
  14. package/dist/config-center/read-warnings.d.ts +56 -35
  15. package/dist/config-center/read-warnings.js +67 -19
  16. package/dist/config-center/types.d.ts +7 -8
  17. package/dist/config-lkg.d.ts +12 -9
  18. package/dist/config-lkg.js +38 -39
  19. package/dist/config-provider.d.ts +3 -2
  20. package/dist/config-provider.js +2 -2
  21. package/dist/config-types.d.ts +9 -1
  22. package/dist/config.d.ts +8 -8
  23. package/dist/config.js +69 -92
  24. package/dist/fleet/engine-signal.d.ts +54 -23
  25. package/dist/fleet/engine-signal.js +29 -5
  26. package/dist/fleet-client.d.ts +8 -6
  27. package/dist/fleet-client.js +4 -3
  28. package/dist/http/dispatch.js +5 -14
  29. package/dist/http/health-self-report.d.ts +3 -0
  30. package/dist/http/routes/capabilities.js +2 -1
  31. package/dist/http/routes/sessions.js +1 -4
  32. package/dist/http/stored-body-replay.d.ts +4 -3
  33. package/dist/http/stored-body-replay.js +2 -3
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.js +1 -0
  36. package/dist/leader/leader.d.ts +7 -6
  37. package/dist/leader/leader.js +3 -1
  38. package/dist/leader/run-entry.d.ts +2 -2
  39. package/dist/leader/run-entry.js +3 -3
  40. package/dist/leader/wire.d.ts +2 -2
  41. package/dist/model-entry-refusal.d.ts +18 -9
  42. package/dist/model-entry-refusal.js +19 -14
  43. package/dist/model-route-endpoint.d.ts +21 -1
  44. package/dist/model-route-endpoint.js +9 -3
  45. package/dist/model-route-gap.d.ts +103 -0
  46. package/dist/model-route-gap.js +80 -0
  47. package/dist/namespaced-rule-name.d.ts +16 -30
  48. package/dist/namespaced-rule-name.js +2 -20
  49. package/dist/observability/fail-open.d.ts +0 -4
  50. package/dist/observability/fail-open.js +0 -4
  51. package/dist/plugins/one-time-migrations.js +27 -0
  52. package/dist/plugins/session-policy-store-sql.js +1 -2
  53. package/dist/plugins/workflow-run-store-sql.d.ts +4 -2
  54. package/dist/rule-entry-list.d.ts +3 -13
  55. package/dist/rule-entry-list.js +1 -12
  56. package/dist/run-local.js +4 -4
  57. package/dist/runtime-governance.d.ts +31 -8
  58. package/dist/runtime-governance.js +7 -2
  59. package/dist/session-policy-wire.d.ts +47 -49
  60. package/dist/session-policy-wire.js +41 -36
  61. package/dist/session-sync.d.ts +5 -1
  62. package/dist/session-sync.js +2 -1
  63. package/dist/spec-fields.d.ts +4 -4
  64. package/dist/spec-fields.js +1 -4
  65. package/dist/task-settings.d.ts +4 -5
  66. package/dist/task-settings.js +5 -8
  67. package/dist/unattended-permission-mode.d.ts +3 -3
  68. package/dist/unattended-permission-mode.js +2 -2
  69. package/package.json +3 -3
@@ -25,7 +25,8 @@
25
25
  * 候选门整拒,而它在 1.10.0 上零告警零 domainErrors(亲验读数)。兼容臂的关窗观测归属主(settings-schema 的
26
26
  * `onWarning` 流),不借候选门这条带副作用的通道传信息。
27
27
  */
28
- import { type EffectiveReadWarning } from "@sema-agent/settings-schema";
28
+ import { z } from "zod";
29
+ import { type DroppedEntryIdentity, type DroppedEntryReasonCode, type EffectiveReadWarning } from "@sema-agent/settings-schema";
29
30
  /** 折叠产物:`scope` 决定消费方做什么 —— `"domain"` 进候选门(`domainErrors`),`"entry"` 进逐条丢弃面(`droppedEntries`)。 */
30
31
  export type FoldedReadWarning = {
31
32
  readonly scope: "domain";
@@ -36,12 +37,16 @@ export type FoldedReadWarning = {
36
37
  readonly domain: string;
37
38
  readonly entry: string;
38
39
  readonly error: string;
40
+ readonly reasonCode: DroppedEntryReasonCode;
39
41
  };
40
- /** 逐条丢弃面的一行(两条读腿的 `droppedEntries` 同形):`entry` = 被丢那条的出门形坐标(`models.1`),`error` = 哪些字段、为什么。 */
42
+ /** 逐条丢弃面的一行(两条读腿的 `droppedEntries` 同形,`config_entry_dropped` 日志行读它):`entry` = 被丢那条的出门形坐标(`models.1`),
43
+ * `error` = 哪些字段、为什么(人读),`reasonCode` = 为什么丢的**闭集词**(S-1295:settings-schema 铸、原样过境 —— 目录行的
44
+ * `GET /v1/models.droppedEntries[].reasonCode` 就是这同一个词,取自同一次折叠的同一行)。 */
41
45
  export type DroppedConfigEntry = {
42
46
  readonly domain: string;
43
47
  readonly entry: string;
44
48
  readonly error: string;
49
+ readonly reasonCode: DroppedEntryReasonCode;
45
50
  };
46
51
  /**
47
52
  * zod 形的错误 → 一行可读的字段诊断(`contextWindow: expected number, received string; api: custom`)。
@@ -58,41 +63,42 @@ export declare function describeSchemaIssueFields(error: unknown): string[];
58
63
  /** null = 不入诊断面(`hosts-grandfathered` / `legacy-governance-lifted`:值已被收编接受,与本地店同口径)。 */
59
64
  export declare function foldEffectiveReadWarning(w: EffectiveReadWarning): FoldedReadWarning | null;
60
65
  /**
61
- * 🔴 S-712 —— 一条被 schema 逐条丢掉的**启用**、**有名**目录条目的身份(模型面墓碑的来源;载体 = `EffectiveConfig.models.droppedIdentities`,
62
- * 随 effective 走 —— 盘上 LKG 从同一个对象落盘,重启从 LKG 起服墓碑不丢)。
66
+ * 🔴 S-712 / S-1295 —— **被丢目录条目的载体行**(载体 = `EffectiveConfig.models.droppedCatalogEntries`,读腿铸,随 effective 走 —— 盘上 LKG
67
+ * 从同一个对象落盘,重启从 LKG 起服时它还在)。形的**唯一定义**(型由它推出;判据按它判盘上来的行)。
63
68
  *
64
- * 为什么要交出身份:条目被丢之后它的名字从目录里消失,而档位增广(core `expandTiers`)会把一个**空出来的**档位词 / CC 别名
65
- * (条目恰好叫 `sonnet` / `pro` …)重新铸到另一条条目上 —— 显式点它的请求与点名它的角色绑定就这样被静默换了目的地
66
- * (gpt-6 腿 F2 / F3,真复现)。身份进 `modelRefusals` 之后,不变量 `refused-names-not-minted` 才判得到它。
69
+ * **规则一条:读腿把 schema 逐条丢掉的每一条目录条目(`models` 域的 `["models", <下标>]`)记一行,写入时不挑;每个读者读自己要的那一部分。**
70
+ * · 墓碑判据(`model-entry-refusal.ts` `judgeCatalogEntries`)—— 有名(`identity.name`)且未停用(`identity.enabled !== false`)的行;
71
+ * · `GET /v1/models` 的 `droppedEntries`({@link buildDroppedEntriesWire})—— 每一行的下标、闭集词与可披露身份;
72
+ * · `config_entry_dropped` 日志行读的是这一行由之而来的那条折叠行({@link DroppedConfigEntry},同一次折叠、同一个词)。
73
+ * (修前 S-712 的载体 `droppedIdentities` 在写入时只收「有名且启用」的行 —— 那是墓碑读者的筛子,装在了写入处;同一件事于是没有
74
+ * 第二个读者可用。筛子回到读者那里,载体就只剩「记下发生了什么」这一条律。)
67
75
  *
68
- * 🔐 纯身份,不含值:`name` / `id` 取自告警的 `identity`(settings-schema 6.1.0:只取被丢条目原文里的字符串形,脱敏器会改动的串
69
- * 整键扣下),`fields` 是坏字段的**键路径**,`issues` 是 {@link describeSchemaIssues} 的一行(键名 + 类型词)—— 被丢条目的原值
70
- * (可能是一段贴错位置的凭据)绝不进载体、日志、LKG 与 400 拒因句。
76
+ * 🔐 不含值:`identity` 取告警自带的身份(settings-schema 只铸可披露的标识符 —— 秘密形 / 非标识符的串 ⇒ 该键缺席,三键都没有 ⇒ 整个缺席);
77
+ * `index` 是条目在**这次读到的那份目录文档** `models` 数组里的原下标(数,不是串);`fields` 是坏字段的键路径、`issues` 是
78
+ * {@link describeSchemaIssues} 的一行(键名 + 类型词)—— 后两者只供本机拒因句,不上 wire。
71
79
  */
72
- export interface DroppedModelIdentity {
73
- /** 出门形坐标(`models.1`),与同一次读的 `droppedEntries[].entry` 同一串。 */
74
- readonly entry: string;
75
- /** 条目写的 `name`(读腿只为有名的行铸载体)。载体上缺了它 = 载体形不对(LKG 载体被改 / 手写 effective)⇒ 模型面整组拒,
76
- * 判在 `model-entry-refusal.ts` `judgeCatalogEntries`。 */
77
- readonly name: string;
78
- readonly id?: string;
79
- readonly fields: readonly string[];
80
- readonly issues: string;
81
- }
80
+ export declare const DroppedCatalogEntryRow: z.ZodObject<{
81
+ index: z.ZodNumber;
82
+ reasonCode: z.ZodEnum<{
83
+ "entry-schema-invalid": "entry-schema-invalid";
84
+ "entry-key-invalid": "entry-key-invalid";
85
+ "entry-unreadable": "entry-unreadable";
86
+ }>;
87
+ identity: z.ZodOptional<z.ZodObject<{
88
+ name: z.ZodOptional<z.ZodString>;
89
+ id: z.ZodOptional<z.ZodString>;
90
+ enabled: z.ZodOptional<z.ZodBoolean>;
91
+ }, z.core.$strict>>;
92
+ fields: z.ZodArray<z.ZodString>;
93
+ issues: z.ZodString;
94
+ }, z.core.$strict>;
95
+ export type DroppedCatalogEntry = z.infer<typeof DroppedCatalogEntryRow>;
82
96
  /**
83
97
  * 两条读腿(远端 `http-client.ts` / 本地 `config-provider.ts`)共用的**分面单点**:折叠产物按 `scope` 分进候选门
84
- * (`domainErrors`)与逐条丢弃面(`droppedEntries`)。`switch` 穷举 —— 加一种 scope 不补臂 = 编译红,两腿不会各分各的。
98
+ * (`domainErrors`)与逐条丢弃面(`droppedEntries`);逐条丢弃里的**目录条目**同一拍铸进目录载体(`droppedCatalogEntries`,
99
+ * 一次遍历、一行折叠、一个词 —— 日志行与载体行不各算一次)。`switch` 穷举 —— 加一种 scope 不补臂 = 编译红,两腿不会各分各的。
85
100
  * 文案**未脱敏**:进日志流之前各腿在自己的产出边界上过 `redactConfigError`(与 `domainErrors` 同一道)。
86
- *
87
- * 🔴 S-712 身份规则(一条,数据源 = 告警自带的 `identity`,settings-schema 6.1.0 在**同一次解析**里从被丢条目原文取 —— 本仓不再回读原文档):
88
- * **`identity.name` 是字符串 ⇒ 有名;有名且 `identity.enabled !== false` ⇒ 墓碑(进载体);其余一律不进载体。**
89
- * · 有名而停用 ⇒ 停用 = 不在目录(与 7.100.0 同义);
90
- * · 没有字符串名 —— `identity` 在场而无 `name`,或 `identity` 整个缺席 ⇒ **无名可留**,与停用同处置。按 settings-schema 6.1.0 的铸点
91
- * (`config-fns.ts` `readEntryIdentity`),「没有名」有两个成因、刻意不分:原文 `name` 不是字符串(`null` / 数 / 整行不是对象),
92
- * 或是字符串但脱敏器会改动它(秘密形 —— 扣下不出门)。后者对本判据同样安全:档位增广只铸闭集词(`ultra` / `max` / `pro` /
93
- * `flash` / `lite`、CC 别名、`best`),没有一个是秘密形,一个被扣下的名字无从被铸出;它的代价只是显式点它的请求拿到「未知模型」
94
- * 而不是 `request.model_unavailable`(那个名字本来也不许进日志与拒因句)。
95
- * 坏行本身照样走 `droppedEntries`(坐标 + 字段,不带值)。
101
+ * 坏行本身照样走 `droppedEntries`(坐标 + 字段 + 词,不带值)。
96
102
  */
97
103
  export declare function splitFoldedReadWarnings(warnings: readonly EffectiveReadWarning[]): {
98
104
  domainErrors: Array<{
@@ -100,10 +106,25 @@ export declare function splitFoldedReadWarnings(warnings: readonly EffectiveRead
100
106
  error: string;
101
107
  }>;
102
108
  droppedEntries: DroppedConfigEntry[];
103
- droppedIdentities: DroppedModelIdentity[];
109
+ droppedCatalogEntries: DroppedCatalogEntry[];
104
110
  };
105
- /** S-712:把本次读交出的被丢身份挂上 effective 的模型域(两腿同一只;空 ⇒ 不挂键)。 */
106
- export declare function withDroppedIdentities<E extends {
111
+ /** 把本次读交出的目录载体挂上 effective 的模型域(两腿同一只;空 ⇒ 不挂键)。 */
112
+ export declare function withDroppedCatalogEntries<E extends {
107
113
  models?: object;
108
- }>(effective: E, droppedIdentities: readonly DroppedModelIdentity[]): E;
114
+ }>(effective: E, rows: readonly DroppedCatalogEntry[]): E;
115
+ /** `GET /v1/models` 顶层 `droppedEntries` 的一行(wire 形;契约 §A.1d「`GET /v1/models` 的 `droppedEntries`」)。 */
116
+ export interface DroppedEntryWireRow {
117
+ readonly index: number;
118
+ readonly reasonCode: DroppedEntryReasonCode;
119
+ readonly identity?: DroppedEntryIdentity;
120
+ }
121
+ /**
122
+ * S-1295 —— 本代目录的被丢条目(`config.droppedCatalogEntries`)→ `GET /v1/models` 的 `droppedEntries`。纯投影,**唯一**出口。
123
+ * · 词:settings-schema 的闭集原样过境({@link reasonCodeForWire} 穷举:包加词 / 删词 ⇒ 本仓编译红,逼一次「这个词能不能上 wire」的显式决定);
124
+ * · 身份:`name` / `id` 在出口再问一次包的 `isDisclosableIdentity`(唯一判据,import 不复制)—— 载体可能来自被手改的盘上 LKG,读腿铸的身份
125
+ * 本来就过了同一只判据;不可披露 ⇒ 该键不出门(不打码:打过码的串不再是身份);`enabled` 是布尔照带;三键都不剩 ⇒ `identity` 缺席;
126
+ * · 不投 `fields` / `issues`(人读的诊断句,不是契约),不投坐标串(下标是数)。
127
+ * 恒返回数组(零条 = `[]`):键恒在场,缺席只意味着更早的 server。
128
+ */
129
+ export declare function buildDroppedEntriesWire(rows: readonly DroppedCatalogEntry[]): DroppedEntryWireRow[];
109
130
  //# sourceMappingURL=read-warnings.d.ts.map
@@ -1,4 +1,5 @@
1
- import { formatConfigPath } from "@sema-agent/settings-schema";
1
+ import { z } from "zod";
2
+ import { DROPPED_ENTRY_REASON_CODES, formatConfigPath, isDisclosableIdentity, } from "@sema-agent/settings-schema";
2
3
  export function describeSchemaIssues(error) {
3
4
  const issues = schemaIssuesOf(error);
4
5
  if (issues === undefined)
@@ -55,7 +56,7 @@ export function foldEffectiveReadWarning(w) {
55
56
  case "domain-defaulted":
56
57
  return { scope: "domain", domain: w.domain, error: cap(errorText(w.error)) };
57
58
  case "entry-dropped":
58
- return { scope: "entry", domain: w.domain, entry: formatConfigPath(w.path), error: cap(`entry dropped by domain schema: ${describeSchemaIssues(w.error) ?? errorText(w.error)}`) };
59
+ return { scope: "entry", domain: w.domain, entry: formatConfigPath(w.path), error: cap(`entry dropped by domain schema: ${describeSchemaIssues(w.error) ?? errorText(w.error)}`), reasonCode: droppedReasonCodeOf(w) };
59
60
  case "unknown-keys-dropped":
60
61
  return { scope: "domain", domain: w.domain, error: keyList("unknown keys dropped by domain schema: ", w.keys, w.truncatedFrom) };
61
62
  case "unknown-keys-carried":
@@ -68,12 +69,34 @@ export function foldEffectiveReadWarning(w) {
68
69
  }
69
70
  }
70
71
  }
71
- function isModelEntryDrop(w) {
72
- return w.kind === "entry-dropped" && w.domain === "models" && w.path.length === 2 && w.path[0] === "models" && typeof w.path[1] === "number";
72
+ function droppedReasonCodeOf(w) {
73
+ if (w.reasonCode === undefined) {
74
+ throw new Error(`entry-dropped warning at ${formatConfigPath(w.path)} carries no reasonCode — settings-schema mints one on every dropped entry, so this warning did not come from the pinned package`);
75
+ }
76
+ return w.reasonCode;
77
+ }
78
+ export const DroppedCatalogEntryRow = z
79
+ .object({
80
+ index: z.number().int().nonnegative(),
81
+ reasonCode: z.enum(DROPPED_ENTRY_REASON_CODES),
82
+ identity: z.object({ name: z.string().optional(), id: z.string().optional(), enabled: z.boolean().optional() }).strict().optional(),
83
+ fields: z.array(z.string()),
84
+ issues: z.string(),
85
+ })
86
+ .strict();
87
+ function catalogIndexOf(w) {
88
+ return w.kind === "entry-dropped" && w.domain === "models" && w.path.length === 2 && w.path[0] === "models" && typeof w.path[1] === "number" ? w.path[1] : undefined;
89
+ }
90
+ function identityOf(i) {
91
+ if (i === undefined)
92
+ return undefined;
93
+ const out = { ...(i.name !== undefined ? { name: i.name } : {}), ...(i.id !== undefined ? { id: i.id } : {}), ...(i.enabled !== undefined ? { enabled: i.enabled } : {}) };
94
+ return Object.keys(out).length > 0 ? out : undefined;
73
95
  }
74
96
  export function splitFoldedReadWarnings(warnings) {
75
97
  const domainErrors = [];
76
98
  const droppedEntries = [];
99
+ const droppedCatalogEntries = [];
77
100
  for (const w of warnings) {
78
101
  const f = foldEffectiveReadWarning(w);
79
102
  if (f === null)
@@ -82,29 +105,54 @@ export function splitFoldedReadWarnings(warnings) {
82
105
  case "domain":
83
106
  domainErrors.push({ domain: f.domain, error: f.error });
84
107
  break;
85
- case "entry":
86
- droppedEntries.push({ domain: f.domain, entry: f.entry, error: f.error });
108
+ case "entry": {
109
+ droppedEntries.push({ domain: f.domain, entry: f.entry, error: f.error, reasonCode: f.reasonCode });
110
+ const index = catalogIndexOf(w);
111
+ if (index !== undefined && w.kind === "entry-dropped") {
112
+ const identity = identityOf(w.identity);
113
+ droppedCatalogEntries.push({ index, reasonCode: f.reasonCode, ...(identity !== undefined ? { identity } : {}), fields: describeSchemaIssueFields(w.error), issues: describeSchemaIssues(w.error) ?? "(schema issues unreadable)" });
114
+ }
87
115
  break;
116
+ }
88
117
  default: {
89
118
  const never = f;
90
119
  throw new Error(`unhandled folded read-warning scope: ${JSON.stringify(never)}`);
91
120
  }
92
121
  }
93
122
  }
94
- const droppedIdentities = [];
95
- for (const w of warnings) {
96
- if (!isModelEntryDrop(w))
97
- continue;
98
- const identity = w.identity;
99
- if (identity?.name === undefined || identity.enabled === false)
100
- continue;
101
- droppedIdentities.push({ entry: formatConfigPath(w.path), name: identity.name, ...(identity.id !== undefined ? { id: identity.id } : {}), fields: describeSchemaIssueFields(w.error), issues: describeSchemaIssues(w.error) ?? "(schema issues unreadable)" });
102
- }
103
- return { domainErrors, droppedEntries, droppedIdentities };
123
+ return { domainErrors, droppedEntries, droppedCatalogEntries };
104
124
  }
105
- export function withDroppedIdentities(effective, droppedIdentities) {
106
- if (droppedIdentities.length === 0 || effective.models === undefined)
125
+ export function withDroppedCatalogEntries(effective, rows) {
126
+ if (rows.length === 0 || effective.models === undefined)
107
127
  return effective;
108
- return { ...effective, models: { ...effective.models, droppedIdentities: [...droppedIdentities] } };
128
+ return { ...effective, models: { ...effective.models, droppedCatalogEntries: [...rows] } };
129
+ }
130
+ export function buildDroppedEntriesWire(rows) {
131
+ return rows.map((r) => {
132
+ const identity = disclosableIdentityOf(r.identity);
133
+ return { index: r.index, reasonCode: reasonCodeForWire(r.reasonCode), ...(identity !== undefined ? { identity } : {}) };
134
+ });
135
+ }
136
+ function reasonCodeForWire(code) {
137
+ switch (code) {
138
+ case "entry-schema-invalid":
139
+ case "entry-key-invalid":
140
+ case "entry-unreadable":
141
+ return code;
142
+ default: {
143
+ const never = code;
144
+ throw new Error(`unhandled DroppedEntryReasonCode: ${JSON.stringify(never)}`);
145
+ }
146
+ }
147
+ }
148
+ function disclosableIdentityOf(i) {
149
+ if (i === undefined)
150
+ return undefined;
151
+ const out = {
152
+ ...(i.name !== undefined && isDisclosableIdentity(i.name) ? { name: i.name } : {}),
153
+ ...(i.id !== undefined && isDisclosableIdentity(i.id) ? { id: i.id } : {}),
154
+ ...(i.enabled !== undefined ? { enabled: i.enabled } : {}),
155
+ };
156
+ return Object.keys(out).length > 0 ? out : undefined;
109
157
  }
110
158
  //# sourceMappingURL=read-warnings.js.map
@@ -8,7 +8,7 @@
8
8
  import type { ToolContentOrigin } from "@sema-agent/core";
9
9
  import type { CollabTemplateWire } from "../capabilities/collab-wire.js";
10
10
  import type { Autonomy, CommandRule } from "../runtime-governance.js";
11
- import type { DroppedModelIdentity } from "./read-warnings.js";
11
+ import type { DroppedCatalogEntry } from "./read-warnings.js";
12
12
  export interface CenterModel {
13
13
  name: string;
14
14
  id: string;
@@ -271,13 +271,12 @@ export interface EffectiveConfig {
271
271
  default?: string;
272
272
  tierGroups?: unknown;
273
273
  activeTierGroup?: string;
274
- /** 🔴 S-712 —— **本仓读腿铸的**载体(不是 wire 键:中心在模型域里写同名键会被 schema 当未知键剥掉并进候选门):这次读
275
- * 被 schema 逐条丢掉的**启用**、**有名**目录条目的身份(取 settings-schema 6.1.0 告警自带的 `identity`,见
276
- * `read-warnings.ts` `splitFoldedReadWarnings`)。模型面 stage 把它们铸成 `config.model_entry_invalid` 墓碑进 `modelRefusals`,
277
- * 不变量 `refused-names-not-minted` 据此判增广有没有把它们的名字铸到别的条目上。挂在 effective 上(而不是读结果旁边)是为了
278
- * **随 effective 落 LKG**:重启从 LKG 起服时墓碑还在。形见 {@link DroppedModelIdentity}(纯身份,不含值);盘上形不对
279
- * (不是列表 / 行缺字符串 `name`)⇒ 模型面整组拒。 */
280
- droppedIdentities?: DroppedModelIdentity[];
274
+ /** 🔴 S-712 / S-1295 —— **本仓读腿铸的**载体(不是 wire 键:中心在模型域里写同名键会被 schema 当未知键剥掉并进候选门):这次读
275
+ * 被 schema 逐条丢掉的**每一条**目录条目一行(下标 + settings-schema 闭集词 `reasonCode` + 告警自带的 `identity` + 字段诊断;形与律见
276
+ * `read-warnings.ts` {@link DroppedCatalogEntry})。读者各取所需:模型面 stage 把有名且启用的行铸成 `config.model_entry_invalid` 墓碑
277
+ * (不变量 `refused-names-not-minted` 据此判增广),每一行与 `models` 同 commit 进 `config.droppedCatalogEntries`(`GET /v1/models.droppedEntries` 的源)。
278
+ * 挂在 effective 上(而不是读结果旁边)是为了**随 effective 落 LKG**:重启从 LKG 起服时它还在。盘上形不对 ⇒ 模型面整组拒。 */
279
+ droppedCatalogEntries?: DroppedCatalogEntry[];
281
280
  };
282
281
  /** registry-core 0.7 collab domain (teams' successor) — consumed by the slice-1.5 projection
283
282
  * (projectCollabToWorkflows → registerCollabWorkflows). Both lanes carry it: the remote worker /effective view
@@ -1,6 +1,6 @@
1
- /** 落盘信封。`effective`/`etag` 逐字=当时的 fetchEffective 返回(JSON verbatim 面)。 */
1
+ /** 落盘信封。`effective`/`etag` 逐字=当时的 fetchEffective 返回(JSON verbatim 面)。`formatVersion` 的语义见头注(3 = 载荷的被丢目录条目记录是全的)。 */
2
2
  export interface PersistedLkg {
3
- formatVersion: 2;
3
+ formatVersion: 3;
4
4
  savedAt: string;
5
5
  worker: string;
6
6
  /** 拉到这份候选时所用凭证的身份(`center-credential.ts`;摘要形,不含凭证原文)。 */
@@ -30,13 +30,14 @@ export declare function lkgPathOverrideOf(raw: string | undefined): string | und
30
30
  * LKG(含凭据)实际所在位置的**读拒行** —— 一条规则:读拒从 LKG 落在哪派生,不写死位置。
31
31
  * · 没覆写 ⇒ LKG 落在配置根下(`config-lkg-<箱>.json`):引擎内建表的 `~/.sema/config-lkg-*.json` 行在家目录与声明的根(`agentConfigDir`,
32
32
  * 根经链接时引擎连目标一起守)两处守它 ⇒ 不另加行(加了就是同一个文件两只判据,`READ_DENY_BUILTIN_EXCLUDE` 删那一行也删不掉它);
33
- * · 覆写 ⇒ LKG 在配置根外,引擎那一行够不着 ⇒ 这里派生行:它的**两种拼法**各一行 —— 覆写写下的绝对路径,与文件真正落的地方(路径经符号链接的
34
- * 目录时,写进去的文件在链接目标下;只守拼写的话读真实路径就绕过了 —— 车内复审 [high])。两者相同 ⇒ 一行。大小写不敏感(与内建行同读法)。
35
- * 拒句与内建行同一句(`sensitive-path read deny list (pattern …)`)。
36
- * **行的射程**(引擎读拒表的读法,不是本函数选的):行是路径段串 —— 引擎去掉开头的 `/`、不锚到根,这串段在任意深度出现即拒(大小写不敏感),
37
- * 所以挡的不止 LKG 那一个文件:用户仓里同段串的路径(`/app/config-lkg.json` ⇒ `…/app/config-lkg.json`)一并读不到、Grep 结果里也不出现。
38
- * 挡多了优于不挡;引擎提供锚定单文件的行之前如此(随包文案 MIGRATION / 目录行同句)。
39
- * 读者:部署姿态座席族(`boot/runner-deps.ts` `buildDeploymentPostureSeats` 的 `readDenyPatterns` 席 = 运维的行 ∪ 本函数的行)—— 每次铸席现解一次链接。
33
+ * · 覆写 ⇒ LKG 在配置根外,引擎那一行够不着 ⇒ 这里派生**一行** = 覆写的绝对路径(`/` 开头;`lkgPathOverrideOf` 已把相对值解成绝对)。大小写不敏感
34
+ * (与内建行同读法)。拒句与内建行同一句(`sensitive-path read deny list (pattern …)`)。
35
+ * **行的射程**(引擎读拒表的读法,core 7.48.0 #1442):`/` 开头的行**根锚定** —— 只守那一个绝对位置,用户仓里同尾的路径
36
+ * (`/app/config-lkg.json` 之于 `…/repo/app/config-lkg.json`)读得到、Grep 结果里也在;路径经符号链接的目录时,引擎在每次准备时把这一行
37
+ * **沿链放置**到它的字面头真正通向的地方(写进去的文件落在链接目标下,目标路径同样被拒)。
38
+ * 7.120.0–7.122.x 的两处旧读法随之退役:行是尾段任意深度匹配(挡掉用户仓里同尾的路径);本仓另派生一条 realpath 落点行(座席铸一次、
39
+ * 解不开计 P-DEBT)—— 那是「这条路径通向哪」的第二个读者,引擎沿链放置之后它是双读。
40
+ * 读者:部署姿态座席族(`boot/runner-deps.ts` `buildDeploymentPostureSeats` 的 `readDenyPatterns` 席 = 运维的行 ∪ 本函数的行)。
40
41
  */
41
42
  export declare function lkgReadDenyRows(configLkgPath: string | undefined): {
42
43
  pattern: string;
@@ -49,9 +50,11 @@ export declare function saveLkg(path: string, worker: string | undefined, identi
49
50
  * 读盘上 LKG。返回 undefined 的所有情形都「安静地回 env 兜底」(调用方 warn 点名 reason):
50
51
  * 文件缺席/JSON 坏/信封形不对/worker 或凭证身份不匹配(防同名文件跨 worker / 跨账号串包;`CONFIG_LKG_PATH`
51
52
  * 单一路径时这是唯一的一道)/prompts 面复判失败。
53
+ * `migrated`:读回时做过一次性迁移({@link migrateV2Lkg})—— 调用方响亮打一行。
52
54
  */
53
55
  export declare function loadLkg(path: string, worker: string | undefined, identity: string): Promise<{
54
56
  lkg: PersistedLkg;
57
+ migrated?: string;
55
58
  } | {
56
59
  lkg?: undefined;
57
60
  reason: string;
@@ -1,10 +1,9 @@
1
- import { promises as fs, lstatSync, realpathSync } from "node:fs";
2
- import { basename, join, dirname, resolve } from "node:path";
1
+ import { promises as fs } from "node:fs";
2
+ import { join, dirname, resolve } from "node:path";
3
3
  import { randomBytes } from "node:crypto";
4
4
  import { validatePromptsDomain } from "./prompts-domain-validate.js";
5
5
  import { centerStateBin, pullTokenIdentity } from "./center-credential.js";
6
6
  import { configRootOf } from "./sema-config-dir.js";
7
- import { recordFailOpen } from "./observability/fail-open.js";
8
7
  export function defaultSkillCacheDir(config) {
9
8
  return join(configRootOf(config), "skill-cache");
10
9
  }
@@ -23,41 +22,10 @@ export function lkgPathOverrideOf(raw) {
23
22
  export function lkgReadDenyRows(configLkgPath) {
24
23
  if (configLkgPath === undefined)
25
24
  return [];
26
- return [...new Set([configLkgPath, landedPathOf(configLkgPath)])].map((pattern) => ({ pattern, caseSensitive: false }));
27
- }
28
- function landedPathOf(p) {
29
- const rest = [];
30
- let landed = p;
31
- let unresolved;
32
- for (let head = p;; head = dirname(head)) {
33
- try {
34
- landed = join(realpathSync(head), ...rest);
35
- break;
36
- }
37
- catch {
38
- if (unresolved === undefined && head !== p && segmentPresent(head))
39
- unresolved = head;
40
- if (dirname(head) === head)
41
- break;
42
- rest.unshift(basename(head));
43
- }
44
- }
45
- if (unresolved !== undefined)
46
- recordFailOpen("server.config-lkg.landed-path-unresolved", `segment=${unresolved}`);
47
- return landed;
48
- }
49
- function segmentPresent(p) {
50
- try {
51
- lstatSync(p);
52
- return true;
53
- }
54
- catch (e) {
55
- const code = e.code;
56
- return code !== "ENOENT" && code !== "ENOTDIR";
57
- }
25
+ return [{ pattern: configLkgPath, caseSensitive: false }];
58
26
  }
59
27
  export async function saveLkg(path, worker, identity, effective, etag) {
60
- const body = { formatVersion: 2, savedAt: new Date().toISOString(), worker: worker ?? "global", identity, ...(etag !== undefined ? { etag } : {}), effective };
28
+ const body = { formatVersion: 3, savedAt: new Date().toISOString(), worker: worker ?? "global", identity, ...(etag !== undefined ? { etag } : {}), effective };
61
29
  const tmp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
62
30
  await fs.mkdir(dirname(path), { recursive: true });
63
31
  await fs.writeFile(tmp, JSON.stringify(body), { mode: 0o600 });
@@ -84,14 +52,45 @@ export async function loadLkg(path, worker, identity) {
84
52
  const formatVersion = p.formatVersion;
85
53
  if (formatVersion === 1)
86
54
  return { reason: "pre-identity LKG (formatVersion 1) — not read back: it cannot prove whose credential fetched it (S-651); the next clean candidate rewrites it" };
87
- if (formatVersion !== 2)
55
+ if (formatVersion !== 2 && formatVersion !== 3)
88
56
  return { reason: `unknown formatVersion ${String(formatVersion)}` };
89
57
  if (typeof p.worker !== "string" || p.worker !== (worker ?? "global"))
90
58
  return { reason: `worker mismatch (file=${String(p.worker)})` };
91
59
  if (typeof p.identity !== "string" || p.identity !== identity)
92
60
  return { reason: "credential identity mismatch (the LKG was fetched under another credential identity — not read back)" };
93
61
  const problem = lkgPayloadProblem(p.effective, p.etag);
94
- return problem === undefined ? { lkg: p } : { reason: problem };
62
+ if (problem !== undefined)
63
+ return { reason: problem };
64
+ return formatVersion === 2 ? migrateV2Lkg(p) : { lkg: p };
65
+ }
66
+ const RETIRED_CATALOG_COORDINATE = /^models\.(0|[1-9][0-9]*)$/;
67
+ function migrateDroppedIdentityRow(row) {
68
+ if (row === null || typeof row !== "object")
69
+ return row;
70
+ const r = row;
71
+ const at = typeof r.entry === "string" ? RETIRED_CATALOG_COORDINATE.exec(r.entry) : null;
72
+ const fields = r.fields;
73
+ if (at === null || typeof r.name !== "string" || (r.id !== undefined && typeof r.id !== "string") || !Array.isArray(fields) || !fields.every((f) => typeof f === "string") || typeof r.issues !== "string")
74
+ return row;
75
+ return { index: Number(at[1]), reasonCode: "entry-schema-invalid", identity: { name: r.name, ...(typeof r.id === "string" ? { id: r.id } : {}) }, fields: [...fields], issues: r.issues };
76
+ }
77
+ function migrateV2Lkg(lkg) {
78
+ const { etag: _incompleteCarrierValidator, ...envelope } = lkg;
79
+ const models = lkg.effective.models;
80
+ const hasRetired = models !== undefined && models !== null && typeof models === "object" && Object.hasOwn(models, "droppedIdentities");
81
+ let effective = lkg.effective;
82
+ if (hasRetired) {
83
+ const { droppedIdentities: retired, ...rest } = models;
84
+ if (Object.hasOwn(rest, "droppedCatalogEntries")) {
85
+ return { reason: "both the retired dropped-entry carrier (models.droppedIdentities) and its successor (models.droppedCatalogEntries) are present — no writer produces that shape; not read back" };
86
+ }
87
+ effective = { ...lkg.effective, models: { ...rest, droppedCatalogEntries: Array.isArray(retired) ? retired.map(migrateDroppedIdentityRow) : retired } };
88
+ }
89
+ return {
90
+ lkg: { ...envelope, formatVersion: 3, effective },
91
+ migrated: "formatVersion 2 → 3 (one-time): this LKG was written before every dropped catalog entry was recorded, so its etag is not reused — the next pull re-reads the whole candidate and re-mints the full record" +
92
+ (hasRetired ? "; the retired carrier models.droppedIdentities was moved to models.droppedCatalogEntries" : ""),
93
+ };
95
94
  }
96
95
  function lkgPayloadProblem(effective, etag) {
97
96
  if (effective === null || typeof effective !== "object")
@@ -183,7 +182,7 @@ export async function migrateV1Lkg(o) {
183
182
  }
184
183
  }
185
184
  try {
186
- await saveLkg(o.binPath, o.worker, o.identity, v1.effective, typeof v1.etag === "string" ? v1.etag : undefined);
185
+ await saveLkg(o.binPath, o.worker, o.identity, v1.effective, undefined);
187
186
  }
188
187
  catch (err) {
189
188
  return refuse(`writing it into this credential identity's bin failed (${String(err)}) — the old file is left in place and the migration retries on the next LKG read`);
@@ -22,8 +22,9 @@ import type { ConfigProviderWord } from "./config-types.js";
22
22
  * 路由进日志/审计流的消费者必须自己先脱敏。 */
23
23
  /** `credentialIdentity`(S-651 / [ref] E-15):远程腿恒在场 = 这次拉取所用凭证的身份(分箱键来源);本地腿
24
24
  * 缺席(没有凭证 —— config.d 就在本机盘上)。 */
25
- /** `droppedEntries`(S-704 ②):schema 逐条丢掉的条目(坐标 + 字段诊断,已脱敏)—— **不**进候选门,调用方逐条响亮
26
- * 点名、其余照用;缺席 = 这次读零逐条丢弃(与 `domainErrors` 同一种缺席即无的形)。 */
25
+ /** `droppedEntries`(S-704 ②):schema 逐条丢掉的条目(坐标 + 字段诊断,已脱敏;S-1295 起每行带闭集词 `reasonCode`)—— **不**进候选门,
26
+ * 调用方逐条响亮点名、其余照用;缺席 = 这次读零逐条丢弃(与 `domainErrors` 同一种缺席即无的形)。其中的目录条目同一次折叠另记进
27
+ * `effective.models.droppedCatalogEntries`(`GET /v1/models.droppedEntries` 的源,与 `models` 同世代)。 */
27
28
  export type FetchEffectiveResult = {
28
29
  effective: EffectiveConfig;
29
30
  etag?: string;
@@ -1,6 +1,6 @@
1
1
  import { readMcpSourceLabel } from "./mcp-source-label.js";
2
2
  import { createHmac, randomBytes } from "node:crypto";
3
- import { splitFoldedReadWarnings, withDroppedIdentities } from "./config-center/read-warnings.js";
3
+ import { splitFoldedReadWarnings, withDroppedCatalogEntries } from "./config-center/read-warnings.js";
4
4
  import { FileConfigStore } from "@sema-agent/settings-schema/node";
5
5
  import { findSkillContent, skillContentHash, refIntegrityIssues, createSiblingResolver, formatConfigPath, REF_CHECKED_DOMAINS, } from "@sema-agent/settings-schema";
6
6
  import { redactSecrets } from "./trace/redact.js";
@@ -96,7 +96,7 @@ export class LocalConfigProvider {
96
96
  const droppedEntries = folded.droppedEntries.map((d) => ({ ...d, error: redactConfigError(d.error) }));
97
97
  const errors = [...domainErrors.map((de) => ({ domain: de.domain, error: redactConfigError(de.error) })), ...parseErrors, ...refErrors];
98
98
  return {
99
- effective: withDroppedIdentities(mapToServiceEffective(eff, eff.version), folded.droppedIdentities),
99
+ effective: withDroppedCatalogEntries(mapToServiceEffective(eff, eff.version), folded.droppedCatalogEntries),
100
100
  etag: tag,
101
101
  readWarnings,
102
102
  ...(errors.length > 0 ? { domainErrors: errors } : {}),
@@ -9,6 +9,7 @@ import type { ReadFaceSource } from "./read-face-posture.js";
9
9
  import type { PostureSource } from "./posture-source.js";
10
10
  import type { SealedCustodyPoison } from "./key-resolver.js";
11
11
  import type { ModelEntryRefusal } from "./model-entry-refusal.js";
12
+ import type { DroppedCatalogEntry } from "./config-center/read-warnings.js";
12
13
  import type { A2aServerSpec, CompliancePosture, CrossSessionDialogExpiry, CrossSessionInboundSetting, FileHistoryRetentionPolicy, LockedKey, McpServerSpec, Model, ModelRoles, RetentionPolicy, ShellGateDoctrine, WriteProtectedEntry } from "@sema-agent/core";
13
14
  import type { ApprovalHmacKey, PrincipalJwtKey } from "./auth-keys.js";
14
15
  import type { ElicitationThrottle } from "./elicitation.js";
@@ -572,6 +573,13 @@ export interface ServiceConfigFlat {
572
573
  * 「没这模型」。env 腿恒空;只由 applyEffective 与 `models` **同世代同 commit** 整表重赋值(非粘)。
573
574
  */
574
575
  modelRefusals: Readonly<Record<string, ModelEntryRefusal>>;
576
+ /**
577
+ * S-1295 —— **本世代目录里被读腿逐条丢掉的条目**(载体 `EffectiveConfig.models.droppedCatalogEntries` 经模型面判据判过形的行,保序;
578
+ * 下标 + settings-schema 闭集词 + 身份 + 字段诊断)。`GET /v1/models` 的 `droppedEntries` 唯一的源(出口投影 `buildDroppedEntriesWire`:
579
+ * 只投词与可披露身份)。env 腿恒空(没有目录读);只由 applyEffective 与 `models` **同世代同 commit** 整表重赋值(非粘)—— 模型面这一组
580
+ * 被拒时它与目录一起停在上一代。墓碑(`modelRefusals` 里的 `entry_invalid` 行)是同一份载体的另一种读法,不是第二份记录。
581
+ */
582
+ droppedCatalogEntries: readonly DroppedCatalogEntry[];
575
583
  /** 配额倍率表(name+id 双键→ModelEntry.quotaWeight):weight-at-burn 的运行时源——tracer 在
576
584
  * brain.call 记账点读它算 weightedTokens(lease 双轴+usage 批报)。registry apply IN-PLACE hot-apply;
577
585
  * env lane 恒空(无倍率概念,weight=1 兜底)。 */
@@ -2159,7 +2167,7 @@ export interface ServiceConfigFlat {
2159
2167
  /** 组:store(持久化)—— DB 引擎三态、session 后端、SQL coords、快照 BLOB / SendUserFile 对象存储。 */
2160
2168
  export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "sessionCacheTtlSec" | "storeProbeIntervalMs" | "storeLockWaitMs" | "dbBackend" | "dbBackendExplicit" | "localDataRoot" | "runStoreStrictHydrate" | "tidb" | "pg" | "dbQueryTimeoutMs" | "snapshotBlobStore" | "snapshotBlobSqlMaxBytes" | "snapshotBlobAllowSql" | "fileHistory" | "fileHistoryCapture" | "sendUserFile">;
2161
2169
  /** 组:modelPlane(模型面)—— 网关坐标、Anthropic 路线、韧性旋钮、主/廉价 model entry、role 表、降级梯。 */
2162
- export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "envModel" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelRefusals" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "atModelAllowlist" | "cascadeLadder" | "degrade">;
2170
+ export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "envModel" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelRefusals" | "droppedCatalogEntries" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "atModelAllowlist" | "cascadeLadder" | "degrade">;
2163
2171
  /** 组:approval(审批 / HITL 门)。`directDoorActive` 无 env 解析腿(装配层三域合取的产物),但语义上
2164
2172
  * 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
2165
2173
  export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalGrant" | "approvalHmacKeys" | "durableApproval" | "durableApprovalSource" | "streamAskWindowMsSource" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "writeProtectedPaths" | "manualModeShellGate" | "permissionsDisableAutoMode" | "autoModeClassifierFallback" | "autoModeClassifierTimeoutMs" | "crossSessionInbound" | "crossSessionDialogExpiry">;
package/dist/config.d.ts CHANGED
@@ -211,7 +211,7 @@ export declare function buildUnpricedCeilingNotice(models: Record<string, Model>
211
211
  fields: Record<string, string>;
212
212
  } | undefined;
213
213
  /** [ref] N8/N9 — the BOOLEAN member of this module's env-parser family (`env`/`env2`/`numEnv`/
214
- * `numEnvBounded`/`optFinitePositiveEnv`/`enumEnv`/`headerNameEnv`/`firstSetEnv`). Until now booleans were the
214
+ * `numEnvBounded`/`optFinitePositiveEnv`/`enumEnv`/`headerNameEnv`). Until now booleans were the
215
215
  * one type parsed by a hand-written literal comparison at every single site, and that is exactly why the same
216
216
  * `*_ENABLED` suffix grew FOUR incompatible polarities (opt-in `=== "true"`, opt-out `!== "false"`, the posture
217
217
  * tri-state, and negative `*_DISABLED` names): with no shared helper there was nowhere for the default to be
@@ -298,7 +298,7 @@ export declare function isRosterGateArmed(configCenter: ServiceConfig["configCen
298
298
  * - 够不着挂载工具的协议名:**对端段铸不出**(`mcp__github.com`、`mcp__github.com__*`、`mcp__*`、超长 / 归一后带连串下划线的服务器段、对端段为空的
299
299
  * `mcp__` / `mcp____x`)—— 前缀对,但引擎铸注册名时归一对端段,而规则对对端段逐字比,永不匹配。判官是引擎的 `judgeRuleToolName`(core 7.43.0 #1355 (b)
300
300
  * 的判词 `unmintable_segment`,句子带它的 guidance 与 1:1 现名);本仓 7.116.0–7.117.x 那只自比 `mintNamespacePrefix` 的对照随之退役。
301
- * 引擎判官放行、却一只工具都够不着的一形 —— **工具段为空**(`mcp__srv__`)—— 由本仓判(`namespaced-rule-name.ts`,会话规则写口两腿与 SQL 店写口同调)。
301
+ * **工具段为空**(`mcp__srv__`)core 7.48.0 #1400 起也是这一判词(7.116.0–7.122.x 由本仓自答,随之退役)。
302
302
  * ⚠️ 两段覆盖形 `mcp__<server>`(服务器段是自铸形)够得着(S-1047 收掉的分叉,见下)。
303
303
  * - **core 的文法编不出来的串**:判词取自 core 自己的 `validatePermissionRules`,射程与指引见 {@link uncompilableRuleGuidance}
304
304
  * (名表消费者只递得来名字里带反斜杠、单条 >512 字符、空串三种;规则编译器消费者递来的每一条内容形都走这里)。[ref]①([ref])补齐 ——
@@ -337,8 +337,8 @@ export interface UnmatchableToolName {
337
337
  * (S-177 / core 7.9.0 起)。引擎**真的按内容匹配** deny / ask 的内容形(`Bash(rm -r:*)` / `Edit(~/.sema/**)`),所以本仓不许再替它拒 ——
338
338
  * 判据整条交回 core 的 `validatePermissionRules`,按这张表自己的 behavior 问。core 7.42.0 起策略只说 deny / ask(allow 表不再是本消费者)。
339
339
  * (7.117.0 有过第三种 `"allow-layer"` —— 请求的 `settings.permissions.allow` 只判名字。7.118.0 / core 7.43.0 #1358 起引擎的 allow 层自己按名册读条目名并通告
340
- * (`permission.allowed_tool_not_granted`,`reason: "tool_name"` / `near_miss`)⇒ 本仓的名字判退役,allow 侧整只交引擎,只剩引擎读不到的那一形在
341
- * `task-settings.ts` 经 `rule-entry-list.ts` 拒。)
340
+ * (`permission.allowed_tool_not_granted`,`reason: "tool_name"` / `near_miss`)⇒ 本仓的名字判退役,allow 侧整只交引擎;7.116.0–7.122.x 另拒的「工具段为空」
341
+ * 一形随 core 7.48.0 #1400 也交引擎。)
342
342
  */
343
343
  export type RuleEntryConsumer = "tool-name-table" | "rule-policy";
344
344
  /**
@@ -369,10 +369,10 @@ export declare function unmatchableToolNamesRefusal(source: string, names: reado
369
369
  * 7.117.0(core 7.42.0)起这张表整份投 `RunnerDeps.allowedTools`(引擎 allow 层的部署成员,构造时读一次),判据是**引擎的读法**,与请求的两张
370
370
  * allow 表同一份(`rule-entry-list.ts`):
371
371
  * ① 值不是字符串表 ⇒ 拒(env 的 CSV 恒给字符串表;判在这里是为了「值一旦换了来源,第一道判仍在」);
372
- * ② 引擎读不到的那一形(工具段为空的协议名)⇒ 拒(与技能清单、请求 allow 桶同一只函数);
373
- * ③ 引擎读成空的条目(`compileAllowList(names, "allow", 名册 = 空集).inert` 非空 —— `*`、`mcp__*`、写路径 / 域名形、带空白或畸形的名字,以及
374
- * core 7.43.0 起条目名过引擎判官读成 `tool_name` 的退役名 / 无前缀短名 / 对端段铸不出)⇒ 拒,句子点名条目、引擎给的原因词(名字层带判词与现名)、
375
- * 该怎么写。请求的 allow 侧对 ③ 只收通告;运维这一侧拒启:这是运维自己的配置,写错了当场改,而引擎对读成空的部署条目只在每一跑发一条通告。
372
+ * ② 引擎读成空的条目(`compileAllowList(names, "allow", 名册 = 空集).inert` 非空 —— `*`、`mcp__*`、写路径 / 域名形、带空白或畸形的名字,以及
373
+ * core 7.43.0 起条目名过引擎判官读成 `tool_name` 的退役名 / 无前缀短名 / 对端段铸不出,core 7.48.0 #1400 起含工具段为空的 `mcp__<srv>__`;
374
+ * 7.116.0–7.122.x 那一形由本仓先拒,随之退役)⇒ 拒,句子点名条目、引擎给的原因词(名字层带判词与现名)、
375
+ * 该怎么写。请求的 allow 侧对 ② 只收通告;运维这一侧拒启:这是运维自己的配置,写错了当场改,而引擎对读成空的部署条目只在每一跑发一条通告。
376
376
  * 文法随引擎:工具名、协议名覆盖形、`Bash(...)` 命令规则、`Read(//dir/**)` 读目录都收(内容形里带逗号的写不进 CSV —— 这张表的分隔符就是逗号)。
377
377
  */
378
378
  export declare function approvalGrantRefusal(names: unknown): string | undefined;