@sema-agent/server 7.56.0 → 7.58.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 (121) hide show
  1. package/README.md +3 -2
  2. package/README.zh-CN.md +2 -2
  3. package/USAGE.md +101 -9
  4. package/dist/approval-ask-audit-store.d.ts +100 -1
  5. package/dist/approval-ask-audit-store.js +103 -2
  6. package/dist/approval-card.d.ts +148 -13
  7. package/dist/approval-card.js +82 -13
  8. package/dist/approval.d.ts +31 -0
  9. package/dist/approval.js +18 -0
  10. package/dist/auto-mode-face.d.ts +111 -0
  11. package/dist/auto-mode-face.js +99 -0
  12. package/dist/bench/s1/arms.js +5 -5
  13. package/dist/bench/s1/live-deps.js +11 -11
  14. package/dist/bench/s1/run-firm.js +3 -0
  15. package/dist/bench/s1/runner-ctx.d.ts +4 -0
  16. package/dist/bench/s1/runner-ctx.js +7 -0
  17. package/dist/boot/config-center.js +23 -1
  18. package/dist/boot/coordinators.js +13 -1
  19. package/dist/boot/parked-revive-gate.d.ts +7 -2
  20. package/dist/boot/parked-revive-gate.js +12 -1
  21. package/dist/boot/resolve-spec.d.ts +3 -0
  22. package/dist/boot/resolve-spec.js +3 -1
  23. package/dist/boot/runner-deps.d.ts +12 -0
  24. package/dist/boot/runner-deps.js +28 -3
  25. package/dist/boot/runtime-caps.d.ts +19 -9
  26. package/dist/boot/runtime-caps.js +49 -21
  27. package/dist/boot/session-shell-gate-registry.d.ts +39 -0
  28. package/dist/boot/session-shell-gate-registry.js +21 -0
  29. package/dist/boot/stores.js +12 -5
  30. package/dist/config-catalog.js +14 -5
  31. package/dist/config-center/http-client.js +7 -11
  32. package/dist/config-center/read-warnings.d.ts +20 -0
  33. package/dist/config-center/read-warnings.js +36 -0
  34. package/dist/config-provider.d.ts +3 -0
  35. package/dist/config-provider.js +4 -8
  36. package/dist/config-types.d.ts +41 -10
  37. package/dist/config.d.ts +21 -0
  38. package/dist/config.js +40 -5
  39. package/dist/hooks/hook-runner.js +8 -8
  40. package/dist/http/route-ctx.d.ts +75 -0
  41. package/dist/http/routes/a2a-serve.js +5 -3
  42. package/dist/http/routes/approvals-assistant.js +19 -6
  43. package/dist/http/routes/capabilities.js +21 -4
  44. package/dist/http/routes/memory-origin.d.ts +2 -2
  45. package/dist/http/routes/rules.js +3 -2
  46. package/dist/http/routes/runs.d.ts +0 -14
  47. package/dist/http/routes/runs.js +20 -8
  48. package/dist/http/routes/tasks.js +34 -8
  49. package/dist/http/routes/workflows.js +19 -4
  50. package/dist/http/server.d.ts +11 -1
  51. package/dist/http/server.js +215 -43
  52. package/dist/http/wire-types.d.ts +9 -4
  53. package/dist/leader/diffout.js +2 -1
  54. package/dist/leader/endpoint.js +45 -12
  55. package/dist/leader/fanout.js +6 -5
  56. package/dist/leader/leader.js +17 -14
  57. package/dist/leader/merge.js +17 -12
  58. package/dist/leader/planner.js +3 -2
  59. package/dist/leader/repair-oracle.js +6 -4
  60. package/dist/leader/repair-wire.js +5 -3
  61. package/dist/leader/wire.d.ts +30 -1
  62. package/dist/leader/wire.js +36 -19
  63. package/dist/main.js +43 -7
  64. package/dist/observability/err-text.d.ts +6 -0
  65. package/dist/observability/err-text.js +10 -0
  66. package/dist/observability/fail-open.d.ts +40 -0
  67. package/dist/observability/fail-open.js +19 -0
  68. package/dist/observability/metrics.js +2 -2
  69. package/dist/observability/run-terminal-log.d.ts +120 -0
  70. package/dist/observability/run-terminal-log.js +360 -0
  71. package/dist/orchestration/workflow-completion-inbox.d.ts +9 -0
  72. package/dist/orchestration/workflow-completion-inbox.js +8 -0
  73. package/dist/permission-rule-vocab.d.ts +40 -0
  74. package/dist/permission-rule-vocab.js +17 -0
  75. package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
  76. package/dist/plugins/checkpoint-store-sql.js +19 -3
  77. package/dist/plugins/file-run-store.d.ts +3 -34
  78. package/dist/plugins/file-run-store.js +19 -0
  79. package/dist/plugins/local-checkpoint-store.d.ts +10 -0
  80. package/dist/plugins/local-checkpoint-store.js +3 -0
  81. package/dist/plugins/mailbox-store-sql.d.ts +28 -9
  82. package/dist/plugins/mailbox-store-sql.js +72 -10
  83. package/dist/plugins/memory-run-store.d.ts +3 -15
  84. package/dist/plugins/memory-run-store.js +19 -0
  85. package/dist/plugins/permission-rule-store-file.js +8 -3
  86. package/dist/plugins/permission-rule-store-sql.js +16 -10
  87. package/dist/plugins/remote-scratchpad.js +3 -2
  88. package/dist/plugins/run-store-sql.d.ts +3 -42
  89. package/dist/plugins/run-store-sql.js +30 -1
  90. package/dist/plugins/store-backend.d.ts +1 -1
  91. package/dist/plugins/store-contracts.d.ts +66 -1
  92. package/dist/plugins/workflow-run-store-sql.d.ts +5 -0
  93. package/dist/plugins/workflow-run-store-sql.js +10 -2
  94. package/dist/rules-consent.js +20 -13
  95. package/dist/run-cancel-context.d.ts +13 -0
  96. package/dist/run-cancel-context.js +15 -0
  97. package/dist/run-local.js +2 -2
  98. package/dist/runs.d.ts +4 -1
  99. package/dist/runs.js +41 -10
  100. package/dist/runtime-caps-resolver.d.ts +133 -17
  101. package/dist/runtime-caps-resolver.js +41 -3
  102. package/dist/task-settings.d.ts +57 -3
  103. package/dist/task-settings.js +78 -5
  104. package/dist/task-workflow.d.ts +32 -2
  105. package/dist/task-workflow.js +8 -3
  106. package/dist/tool-approval.d.ts +26 -3
  107. package/dist/tool-approval.js +96 -13
  108. package/dist/trace/core-keyset-guard.d.ts +7 -8
  109. package/dist/trace/engine-notice-wire.d.ts +1 -1
  110. package/dist/trace/engine-notice-wire.js +2 -0
  111. package/dist/trace/ledger-sink.d.ts +13 -4
  112. package/dist/trace/ledger-sink.js +14 -6
  113. package/dist/trace/project.d.ts +58 -3
  114. package/dist/trace/project.js +33 -1
  115. package/dist/trace/redact.d.ts +22 -11
  116. package/dist/trace/redact.js +660 -21
  117. package/dist/trace/sema-provenance.d.ts +35 -0
  118. package/dist/trace/sema-provenance.js +14 -0
  119. package/dist/turn-activity.d.ts +32 -2
  120. package/dist/turn-activity.js +29 -4
  121. package/package.json +3 -3
@@ -1,24 +1,42 @@
1
- import { applyMemoryOptOutGrant, applyObserverEnvOptIn, centerEntitlementSourceWired, createPrincipalEntitlementsClient, scopedTokenNeedsWorker, } from "../runtime-caps-resolver.js";
1
+ import { applyGovernedVerdictAbsenceWarning, applyLocalAutoModeDeny, applyMemoryOptOutGrant, applyObserverEnvOptIn, centerEntitlementSourceWired, createPrincipalEntitlementsClient, scopedTokenNeedsWorker, } from "../runtime-caps-resolver.js";
2
2
  export function assertMemoryCapturePolicyWirable(input) {
3
3
  if (input.policy !== "governed")
4
4
  return;
5
5
  if (input.grantSourceWired)
6
6
  return;
7
- if (input.centerEntitlementPresent === true)
7
+ if (input.centerEntitlementSourceWired === true)
8
8
  return;
9
9
  throw new Error(`MEMORY_CAPTURE_POLICY=governed requires a per-principal memory opt-out verdict source, and this deployment wires NONE ` +
10
10
  `— under "governed" core REFUSES every session memory-capture opt-out whose verdict is absent (fail-closed, the posture's ` +
11
11
  `own contract), so a governed worker without a source would refuse every \`memory.capture:"off"\` declaration and the ` +
12
- `refusal would read as an entitlement denial rather than the misconfiguration it is. The only verdict source in this ` +
13
- `version is the \`memory_optout_grant\` table on a SQL store backend (DB_BACKEND=mysql|pg; the table is created by the ` +
14
- `central ensureSchema and administered through /v1/admin/memory-optout). Wire a SQL backend, or set ` +
15
- `MEMORY_CAPTURE_POLICY=open (opt-outs take effect; only an explicit per-principal false denies) or ` +
12
+ `refusal would read as an entitlement denial rather than the misconfiguration it is. This version has TWO verdict sources ` +
13
+ `— wire either one: (1) the \`memory_optout_grant\` table on a SQL store backend (DB_BACKEND=mysql|pg; the table is ` +
14
+ `created by the central ensureSchema and administered through /v1/admin/memory-optout), or (2) a config center that ` +
15
+ `EMITS \`allowMemoryOptOut\` in each principal's entitlement caps (SEMA_REGISTRY_URL + SEMA_REGISTRY_TOKEN, ` +
16
+ `SEMA_REGISTRY_DRY_RUN off — dry-run observes without applying, so it is not a source; and note the key is OPTIONAL in ` +
17
+ `the entitlement schema: a center that never emits it leaves every verdict absent, i.e. every opt-out still refused). ` +
18
+ `Or set MEMORY_CAPTURE_POLICY=open (opt-outs take effect; only an explicit per-principal false denies) or ` +
16
19
  `MEMORY_CAPTURE_POLICY=capture-required (every opt-out declaration refused, no source consulted).`);
17
20
  }
21
+ function memoryVerdictAbsenceEffect(reason) {
22
+ switch (reason) {
23
+ case "anonymous_lane":
24
+ return 'an ANONYMOUS/system run (no principal) resolved no allowMemoryOptOut — the center entitlement source is per-principal and is never consulted without an identity, so on a center-only deployment the principal-less lane can NEVER obtain a verdict and core refuses every anonymous memory.capture:"off" (memory.capture_optout_denied). Only the local memory_optout_grant table answers for anonymous runs (its deployment-default row). One-shot per process.';
25
+ case "center_verdict_absent":
26
+ return 'governed posture, center-only verdict source, and this resolve produced NO allowMemoryOptOut for a NAMED principal (the center omitted the optional entitlement key, or the caps fetch degraded fail-closed) ⇒ core refuses that principal\'s memory.capture:"off" declarations (memory.capture_optout_denied). One-shot per process.';
27
+ }
28
+ }
18
29
  export function createRuntimeCaps(ctx) {
19
30
  const { config, logger } = ctx;
20
- assertMemoryCapturePolicyWirable({ policy: config.memoryCapturePolicy, grantSourceWired: ctx.memoryOptOutGrant !== undefined, centerEntitlementPresent: Boolean(config.configCenter && !config.configCenter.dryRun) });
21
- if (config.configCenter && !config.configCenter.dryRun && scopedTokenNeedsWorker(config.configCenter.token, config.configCenter.worker)) {
31
+ const grantSourceWired = ctx.memoryOptOutGrant !== undefined;
32
+ assertMemoryCapturePolicyWirable({ policy: config.memoryCapturePolicy, grantSourceWired, centerEntitlementSourceWired: centerEntitlementSourceWired(config) });
33
+ const governedCenterOnlySource = config.memoryCapturePolicy === "governed" && !grantSourceWired && centerEntitlementSourceWired(config);
34
+ if (governedCenterOnlySource) {
35
+ logger.warn("memory_capture_governed_center_only_source", {
36
+ reason: "MEMORY_CAPTURE_POLICY=governed with NO local memory_optout_grant table: the only verdict source is the config center, and `allowMemoryOptOut` is an OPTIONAL entitlement key — for any principal whose caps omit it the verdict is ABSENT and core refuses that session's memory.capture:\"off\" (memory.capture_optout_denied, fail-closed). ANONYMOUS/system runs are worse off still: the center source is per-principal and is never consulted without an identity, so on this shape a principal-less run can never obtain a verdict at all. Verify the center emits allowMemoryOptOut for every principal, or wire a SQL backend (DB_BACKEND=mysql|pg) so the grant table answers (its deployment-default row also covers anonymous runs).",
37
+ });
38
+ }
39
+ if (centerEntitlementSourceWired(config) && config.configCenter && scopedTokenNeedsWorker(config.configCenter.token, config.configCenter.worker)) {
22
40
  logger.warn("runtime_caps_scoped_token_no_worker", {
23
41
  reason: "SEMA_REGISTRY_TOKEN is a worker-scoped wpt_ token but SEMA_REGISTRY_WORKER is unset → center 403s per-principal caps → ALL workflow self-orchestration will be fail-closed denied. Set SEMA_REGISTRY_WORKER (the orchestrator normally injects it) or use the full SERVICE_PULL_TOKEN.",
24
42
  });
@@ -32,18 +50,28 @@ export function createRuntimeCaps(ctx) {
32
50
  })
33
51
  : undefined;
34
52
  const centerRuntimeCapsResolver = principalCaps?.resolveRuntimeCaps;
35
- const withObservers = applyObserverEnvOptIn(centerRuntimeCapsResolver, {
36
- experimentalObserverAgents: config.experimentalObserverAgents,
37
- requirePrincipal: config.requirePrincipal,
38
- warn: (msg, fields) => logger.warn(msg, fields),
39
- });
40
- const runtimeCapsResolver = applyMemoryOptOutGrant(withObservers, ctx.memoryOptOutGrant, {
41
- onFault: (err, principal) => logger.warn("memory_optout_grant_resolve_failed", {
42
- principal,
43
- err: String(err),
44
- effect: "allowMemoryOptOut left ABSENT for this resolve (never coined as false): open posture ⇒ the opt-out takes effect; governed ⇒ core refuses the run (verdict absent)",
45
- }),
46
- });
47
- return { principalCaps, centerRuntimeCapsResolver, runtimeCapsResolver };
53
+ const compose = (base, observability) => {
54
+ const withObservers = applyObserverEnvOptIn(base, {
55
+ experimentalObserverAgents: config.experimentalObserverAgents,
56
+ requirePrincipal: config.requirePrincipal,
57
+ ...(observability ? { warn: (msg, fields) => logger.warn(msg, fields) } : {}),
58
+ });
59
+ const withMemoryGrant = applyMemoryOptOutGrant(withObservers, ctx.memoryOptOutGrant, {
60
+ onFault: (err, principal) => logger.warn("memory_optout_grant_resolve_failed", {
61
+ principal,
62
+ err: String(err),
63
+ ...(observability ? {} : { face: "capabilities-read" }),
64
+ effect: "allowMemoryOptOut left ABSENT for this resolve (never coined as false): open posture ⇒ the opt-out takes effect; governed ⇒ core refuses the run (verdict absent)",
65
+ }),
66
+ });
67
+ const withLocalAutoModeDeny = applyLocalAutoModeDeny(withMemoryGrant, config.permissionsDisableAutoMode);
68
+ return applyGovernedVerdictAbsenceWarning(withLocalAutoModeDeny, {
69
+ enabled: observability && governedCenterOnlySource,
70
+ warn: (reason, principal) => logger.warn("memory_capture_governed_verdict_absent", { principal, reason, effect: memoryVerdictAbsenceEffect(reason) }),
71
+ });
72
+ };
73
+ const runtimeCapsResolver = compose(centerRuntimeCapsResolver, true);
74
+ const runtimeCapsPeekResolver = compose(principalCaps?.peekRuntimeCaps, false);
75
+ return { principalCaps, centerRuntimeCapsResolver, runtimeCapsResolver, runtimeCapsPeekResolver };
48
76
  }
49
77
  //# sourceMappingURL=runtime-caps.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * [ref] 件⑤(core PM 三仓扫查 [ref] server 条 ③,并入 [ref])—— 「本 run 是否**显式**声明了 bypassPermissions」的
3
+ * per-session 登记簿,供 `boot/runner-deps.ts` 的 `onError(phase:"config")` 汇点给 core 的 `shell-gate-off`
4
+ * 忠告定级。
5
+ *
6
+ * ## 为什么需要它
7
+ * core 在「shell 门 doctrine=off ∧ 挂了真可写 Bash」时经 `onError(phase:"config", classification:"shell-gate-off")`
8
+ * 发一条忠告(`prepare-hands-readface.js`),但它分不清 off 是**调用方显式选的**(server 把 `permissionMode:
9
+ * "bypassPermissions"` 翻成 `shellGate:"off"`,[ref] 表)还是**没表态落到 core 默认**(无壳直连部署,真值得
10
+ * 一条 warn)。这正是真实用户提案里「bypass 下照打 shell gate doctrine is off 告警」的根:正常态被当异常打。
11
+ * 汇点只拿到 `ctx.sessionId`,而生效模式是 server 在 resolve-spec 阶段④ 单点算出的(`effectivePermissionMode`),
12
+ * 所以由 resolve-spec 在算出 effMode 的那一行登记、汇点按 sessionId 回查。
13
+ *
14
+ * ## 键
15
+ * `auth.sessionId`:authorizer 在服务路径上**恒**铸 session id(`security.ts` `uuidv7()`,core 的无 sessionId
16
+ * 分支在服务路径不可达),且 spec.sessionId = 同一值 ⇒ core 回调的 `ctx.sessionId` 与登记键同源。
17
+ * 缺 authorizer 的裸装配(测试桩)拿不到键 ⇒ 不登记 ⇒ 汇点按「未声明」处理 = 仍 warn(退化方向=多一条告警,
18
+ * 不是少一条)。
19
+ *
20
+ * ## 已知界(codex r2 [medium],登记不修:属主在 core 回调契约)
21
+ * 键是 session 而非**执行**:同 session 并发第二次提交在 resolveSpec 后才被 409 拒,其登记已覆盖前一次——若被拒的
22
+ * 那次是 bypass 而在跑的那次不是,且忠告恰在这一窗内发出,则一条本该 warn 的 `shell-gate-off` 被降成 info(仍整行
23
+ * 落日志、字段齐全,执法零变)。窗 = 在跑 run 的 resolveSpec 到其 prepare 期忠告之间(毫秒级,且要同 session 并发
24
+ * 提交 + 模式相反)。执行唯一的归因需要 core 回调 `ctx` 带 taskId / shellGate 出处(已作 core 侧诉求登记),
25
+ * 到货后本簿退役。
26
+ *
27
+ * ## 界
28
+ * 有界 Map(插入序淘汰,默认 10k 条,与 caps 缓存同量级):session id 是开集,长命 worker 不能无界累积。
29
+ * 淘汰掉的旧会话再来忠告时同样回落「未声明」⇒ warn(同一退化方向)。每次 resolve 覆写(同 session 后一轮改
30
+ * 模式时以最近一轮为准;续跑四条 rebuild 腿都经 resolveSpec ⇒ 同律登记)。
31
+ */
32
+ export interface SessionShellGateRegistry {
33
+ /** resolve-spec 阶段④:登记本 session 这一轮是否显式声明 bypassPermissions(= server 翻成 shellGate:"off")。 */
34
+ note(sessionId: string | undefined, explicitOff: boolean): void;
35
+ /** runner-deps 汇点:本 session 最近一轮是否显式声明了 off。未登记/已淘汰 ⇒ false(退化=照 warn)。 */
36
+ explicitlyOff(sessionId: string): boolean;
37
+ }
38
+ export declare function createSessionShellGateRegistry(cap?: number): SessionShellGateRegistry;
39
+ //# sourceMappingURL=session-shell-gate-registry.d.ts.map
@@ -0,0 +1,21 @@
1
+ export function createSessionShellGateRegistry(cap = 10_000) {
2
+ const off = new Map();
3
+ return {
4
+ note(sessionId, explicitOff) {
5
+ if (sessionId === undefined)
6
+ return;
7
+ if (off.has(sessionId))
8
+ off.delete(sessionId);
9
+ else if (off.size >= cap) {
10
+ const oldest = off.keys().next();
11
+ if (!oldest.done)
12
+ off.delete(oldest.value);
13
+ }
14
+ off.set(sessionId, explicitOff);
15
+ },
16
+ explicitlyOff(sessionId) {
17
+ return off.get(sessionId) === true;
18
+ },
19
+ };
20
+ }
21
+ //# sourceMappingURL=session-shell-gate-registry.js.map
@@ -1,5 +1,5 @@
1
1
  import { join } from "node:path";
2
- import { FileBackgroundAgentStore, FileMailboxStore, FileRosterStore, FileUsageWindowStore, InMemoryUsageWindowStore } from "@sema-agent/core";
2
+ import { FileBackgroundAgentStore, FileMailboxStore, FileRosterStore, FileUsageWindowStore, InMemoryUsageWindowStore, mailboxCrossProcessMountVerdict } from "@sema-agent/core";
3
3
  import { createMemorySyncRunner, createMemorySyncTransport } from "../memory-sync-client.js";
4
4
  import { memoryEmbedderFor } from "../plugins/memory-embedder.js";
5
5
  import { ensurePgMemoryEmbedderMetaSchema, reconcileEmbedderFingerprint } from "../plugins/memory-embedder-fingerprint.js";
@@ -299,19 +299,26 @@ export async function openStores(ctx) {
299
299
  const pgPool = backend?.pgPool?.();
300
300
  if (pgPool) {
301
301
  await ensurePgMailboxSchema(async (text, params) => pgPool.query(text, params));
302
- mailboxStore = new PgMailboxStore(pgPool);
302
+ mailboxStore = new PgMailboxStore(pgPool, logger);
303
303
  }
304
304
  else if (mysqlPool) {
305
305
  await ensureTiDBMailboxSchema(mysqlPool);
306
- mailboxStore = new TiDBMailboxStore(mysqlPool);
306
+ mailboxStore = new TiDBMailboxStore(mysqlPool, logger);
307
307
  }
308
308
  else if (backend?.kind === "local") {
309
309
  mailboxStore = new FileMailboxStore(config.localDataRoot ?? localRoot, {
310
310
  onCorruptRead: (info) => logger.warn("mailbox_corrupt_read", { path: info.path, reason: info.reason }),
311
311
  });
312
312
  }
313
- if (mailboxStore)
314
- logger.info("mailbox_store_enabled", { backend: pgPool ? "pg" : mysqlPool ? "tidb" : "file" });
313
+ if (mailboxStore) {
314
+ const laneVerdict = mailboxCrossProcessMountVerdict(mailboxStore);
315
+ logger.info("mailbox_store_enabled", {
316
+ backend: pgPool ? "pg" : mysqlPool ? "tidb" : "file",
317
+ crossProcessSafe: mailboxStore.crossProcessSafe,
318
+ peerLaneMountable: laneVerdict.ok,
319
+ ...(laneVerdict.ok ? {} : { peerLaneRefusal: laneVerdict.code }),
320
+ });
321
+ }
315
322
  }
316
323
  let usageWindowStore;
317
324
  if (config.usageWindows) {
@@ -254,7 +254,7 @@ export const CONFIG_CATALOG = [
254
254
  r("MODEL_COST_CACHE_WRITE", "modelPlane", "number", "主模型 cache-write 单价", { staticDefault: "0", danger: BILL, resolve: (c) => c.model.cost?.cacheWrite ?? null, center: cModels("models[].cost.cacheWrite") }),
255
255
  r("MODEL_COST_INPUT", "modelPlane", "number", "主模型 input 单价(NaN 会让成本天花板整条消失 ⇒ 坏值拒启)", { staticDefault: "0", danger: BILL, resolve: (c) => c.model.cost?.input ?? null, center: cModels("models[].cost.input") }),
256
256
  r("MODEL_COST_OUTPUT", "modelPlane", "number", "主模型 output 单价", { staticDefault: "0", danger: BILL, resolve: (c) => c.model.cost?.output ?? null, center: cModels("models[].cost.output") }),
257
- r("MODEL_DEFAULT_THINKING", "modelPlane", "enum", "主模型默认思考档(llm-core ThinkingLevel,无 off)", { enumValues: ["minimal", "low", "medium", "high", "max"] }),
257
+ r("MODEL_DEFAULT_THINKING", "modelPlane", "enum", "主模型默认思考档(llm-core ThinkingLevel;off=不设模型级默认;未知词拒启)", { enumValues: ["minimal", "low", "medium", "high", "xhigh", "max", "off"], derivedDefaultNote: "缺席/off ⇒ 不铸 defaultThinking(逐任务档由 spec>role 决定)", resolve: (c) => c.model.defaultThinking ?? null }),
258
258
  r("MODEL_DEGRADE_AT_COST_FRACTION", "modelPlane", "number", "预算降级触发比(spent/budget;>1=实际别降级)", { staticDefault: "0.7", danger: BILL }),
259
259
  r("MODEL_DEGRADE_ON", "modelPlane", "csv", "反应式降级触发词表(core DegradeReason 全集;拼错拒启)", { center: { domain: "models", path: "degrade", centerKey: "models", precedence: "center-wins", timing: "restart", restartSlice: "degrade-route" } }),
260
260
  r("MODEL_DEGRADE_REACTIVE", "modelPlane", "boolean", "反应式降级开关(degrade-route 重启片只在它开时活)", { staticDefault: "false", center: { domain: "models", path: "degrade", centerKey: "models", precedence: "center-wins", timing: "restart", restartSlice: "degrade-route" } }),
@@ -271,7 +271,15 @@ export const CONFIG_CATALOG = [
271
271
  r("MODEL_PRESENCE_PENALTY", "modelPlane", "number", "presence_penalty 直通"),
272
272
  r("MODEL_PROVIDER", "modelPlane", "string", "主模型 provider(路由选 brain)", { derivedDefaultNote: "缺席:ANTHROPIC base+cred 同在 ⇒ anthropic,否则 gateway", resolve: (c) => c.model.provider, center: cModels("models[].provider") }),
273
273
  r("MODEL_REASONING", "modelPlane", "boolean", "主模型 reasoning 声明", { staticDefault: "true", resolve: (c) => c.model.reasoning ?? null, center: cModels("models[].reasoning") }),
274
- r("MODEL_REASONING_EFFORT_LEVELS", "modelPlane", "csv", "openai 端点接受的思考档集(高档 clamp-down 不 422;anthropic 腿不需要)"),
274
+ r("MODEL_REASONING_EFFORT_LEVELS", "modelPlane", "csv", "openai 端点接受的思考档集(六档子集;高档 clamp-down 不 422;anthropic 腿不铸此键;未知词拒启)", {
275
+ derivedDefaultNote: "缺席 ⇒ 不铸 compat,core 走保守梯(minimal|low|medium|high),xhigh/max 被 clamp 下来",
276
+ resolve: (c) => {
277
+ const compat = c.model.compat;
278
+ if (compat === undefined || !("reasoningEffortLevels" in compat))
279
+ return null;
280
+ return compat.reasoningEffortLevels ?? null;
281
+ },
282
+ }),
275
283
  r("MODEL_ROUTER_ID", "modelPlane", "string", "leader 路由分类模型覆写", { derivedDefaultNote: "缺席 ⇒ MODEL_CHEAP_ID,再退 default", danger: BILL }),
276
284
  r("MODEL_VERIFIER", "modelPlane", "string", "verify 门的异构 verifier(须命中目录;typo 回 default 并 boot warn=自评自批)", { derivedDefaultNote: "缺席/不在目录 ⇒ default(=implementer,去相关性失效警告)" }),
277
285
  r("MODEL_VISION", "modelPlane", "boolean", "主模型 vision 声明(false 让 model_no_vision 预检真拦)", { staticDefault: "true", resolve: (c) => (Array.isArray(c.model.input) ? c.model.input.includes("image") : true), center: cModels("models[].vision") }),
@@ -286,6 +294,7 @@ export const CONFIG_CATALOG = [
286
294
  r("OTEL_EXPORTER_OTLP_HEADERS", "observability", "secret", "OTLP 出口头(k=v CSV;常载鉴权头 ⇒ 按凭据处置)", { danger: SEC }),
287
295
  r("OTEL_EXPORT_INTERVAL_MS", "observability", "number", "OTLP 批报周期(ms)", { staticDefault: "15000" }),
288
296
  r("OTEL_SERVICE_NAME", "observability", "string", "OTLP service.name", { staticDefault: "sema-server" }),
297
+ r("PERMISSIONS_DISABLE_AUTO_MODE", "approval", "boolean", "auto 模式本地 org deny(CC/center/settings 键名 permissions.disableAutoMode 的 env 腿;tighten-only:每 principal caps.autoMode 折 false,center 授予翻不回;未设=不动 caps;词表外拒启)", { staticDefault: "false", danger: SEC, configKey: "permissionsDisableAutoMode", center: { domain: "permissions", path: "disableAutoMode", centerKey: "permissions.disableAutoMode", precedence: "env-wins", timing: "inert" } }),
289
298
  r("PERMISSION_RULES_ENABLED", "approval", "boolean", "持久化权限规则车道总开关(撤销面已建 ⇒ 默认 ON)", { staticDefault: "true", danger: SEC, configKey: "permissionRulesEnabled" }),
290
299
  r("PG_DATABASE", "store", "string", "PostgreSQL 库名", { danger: DATA }),
291
300
  r("PG_HOST", "store", "string", "PostgreSQL 主机(在场即算 SQL 信号)", { danger: DATA }),
@@ -361,7 +370,7 @@ export const CONFIG_CATALOG = [
361
370
  r("SERVICE_AUTH_TOKEN", "auth", "secret", "共享 service token(写面门;缺席且未开 ALLOW_UNAUTHED_WRITES ⇒ 拒提交)", { danger: SEC }),
362
371
  r("SERVICE_AUTH_TOKENS", "auth", "secret", "per-system 凭据表(tok=system CSV;source 从凭据派生,绝不自报)", { danger: SEC }),
363
372
  r("SESSION_AUTO_TITLE", "orchestration", "boolean", "首次提交自动铸 session 标题(一次 cheap 调用)", { staticDefault: "true", danger: BILL, configKey: "sessionAutoTitle" }),
364
- r("SESSION_BACKEND", "store", "enum", "session 店引擎(auto=探到 DB 用 DB,探不到回内存)", { enumValues: ["memory", "mysql", "auto"], danger: DATA, derivedDefaultNote: "缺席:显式 DB_BACKEND=mysql|pg ⇒ mysql,否则 memory", configKey: "sessionBackend", resolve: (c) => c.sessionBackend }),
373
+ r("SESSION_BACKEND", "store", "enum", "session 店引擎(auto=探到 DB 用 DB,探不到回内存;回显=boot 解析后的真引擎)", { enumValues: ["memory", "mysql", "auto"], danger: DATA, derivedDefaultNote: "缺席:显式 DB_BACKEND=mysql|pg ⇒ mysql,否则 memory", configKey: "sessionBackend", resolve: (c) => (c.sessionBackend === "tidb" ? "mysql" : c.sessionBackend) }),
365
374
  r("SESSION_CACHE_TTL_SEC", "store", "number", "session 读缓存 TTL(秒;0=关)", { staticDefault: "300", configKey: "sessionCacheTtlSec" }),
366
375
  r("SESSION_EVENTS_HOT_MS", "limitsHttp", "number", "session SSE 订阅热探针间隔(ms)", { derivedDefaultNote: "缺席 ⇒ 200" }),
367
376
  r("SESSION_EVENTS_MAX_CONNS", "limitsHttp", "number", "session SSE 并发连接帽", { derivedDefaultNote: "缺席 ⇒ 256" }),
@@ -391,12 +400,12 @@ export const CONFIG_CATALOG = [
391
400
  r("STREAM_APPROVAL_RECONCILE_BATCH", "approval", "number", "对账收敛器每 tick 每段行数帽(整数)", { staticDefault: "200" }),
392
401
  r("STREAM_APPROVAL_REPLAY_MAX", "approval", "number", "开流 preamble 重放帽", { staticDefault: "50" }),
393
402
  r("STREAM_ASK_WINDOW_MARGIN_MS", "approval", "number", "审批窗长三元安全余量(ms)", { staticDefault: "10000", configKey: "streamAskWindowMarginMs" }),
394
- r("STREAM_ASK_WINDOW_MS", "approval", "number", "流内 ask 窗长(ms)", { staticDefault: "300000" }),
403
+ r("STREAM_ASK_WINDOW_MS", "approval", "number", "活卡审批窗长(ms;所有腿生效,与流内协议上不上场无关;0=关窗⇒不发卡恒走无人值守终局;负值与超 Node 定时器上界 2147483647 的值拒启)", { staticDefault: "300000" }),
395
404
  r("SYNC_IMPORT_LEASE_STALE_SEC", "memory", "number", "session-sync staged import 租约陈旧阈(秒)", { staticDefault: "600", configKey: "syncImportLeaseStaleSec" }),
396
405
  r("TASK_MAX_OUTPUT_TOKENS_MAX", "limitsHttp", "number", "调用方自报 limits.maxOutputTokens 的运营方封顶(Math.min 钳显式请求值;坏值=封顶不生效+响亮 warn)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 不封顶" }),
397
406
  r("TASK_MAX_TURNS_MAX", "limitsHttp", "number", "调用方自报 limits.maxTurns 的运营方封顶(同族三旋钮;只钳显式请求值,不是全队默认限额)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 不封顶" }),
398
407
  r("TASK_TIMEOUT_MAX_SEC", "limitsHttp", "number", "调用方自报 limits.timeoutSec 的运营方封顶(同族三旋钮)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 不封顶" }),
399
- r("TASK_TIMEOUT_SEC", "limitsHttp", "number", "任务墙钟超时(秒;0=关)", { staticDefault: "0" }),
408
+ r("TASK_TIMEOUT_SEC", "limitsHttp", "number", "任务墙钟超时(秒);0/缺席=按 tenancy(单用户无墙;多租户 2400s、大任务 3600s);正值**只抬不降**(Math.max 基值);负值拒启", { staticDefault: "0", derivedDefaultNote: "0/缺席 ⇒ 单用户无墙、多租户 2400/3600s;要低于基值的配速请用 body.limits.maxWalltimeMs" }),
400
409
  r("TOOL_APPROVAL_ENABLED", "approval", "boolean", "活体工具审批 HITL(onAsk 装配;关 ⇒ core resolveAsk 走 headless 缺省)", { danger: SEC, derivedDefaultNote: "posture 三态:显式词恒赢,缺席 = 单用户(REQUIRE_PRINCIPAL≠true)⇒ on / 多租户 ⇒ off", configKey: "toolApprovalEnabled" }),
401
410
  r("TOOL_DEFER_LONGTAIL", "orchestration", "boolean", "长尾一方工具 defer 呈现(实验)", { staticDefault: "false", configKey: "toolDeferLongtail" }),
402
411
  r("TOOL_RESULT_TTL_SEC", "limitsHttp", "number", "工具结果店 TTL(秒)", { staticDefault: "86400", danger: DATA, configKey: "toolResultTtlSec", center: cLimits("toolResultTtlSec") }),
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readEffectiveWire, PrincipalCapsWire } from "@sema-agent/settings-schema";
3
3
  import { centerPromptsFromEffective } from "../prompts-domain-validate.js";
4
+ import { foldEffectiveReadWarning } from "./read-warnings.js";
4
5
  export async function fetchEffective(baseUrl, token, etag, fetchImpl = fetch, worker) {
5
6
  const scheme = new URL(baseUrl).protocol;
6
7
  if (scheme !== "http:" && scheme !== "https:")
@@ -21,17 +22,12 @@ export async function fetchEffective(baseUrl, token, etag, fetchImpl = fetch, wo
21
22
  delete wire.prompts;
22
23
  else
23
24
  wire.prompts = centerPrompts;
24
- const domainErrors = warnings
25
- .map((w) => w.kind === "domain-defaulted"
26
- ? { domain: w.domain, error: (w.error instanceof Error ? w.error.message : String(w.error)).slice(0, 600) }
27
- : w.kind === "unknown-keys-dropped"
28
- ? { domain: w.domain, error: `unknown keys dropped by domain schema: ${w.keys.join(", ")}`.slice(0, 600) }
29
- : w.kind === "unknown-keys-carried"
30
- ? { domain: w.domain, error: `unknown keys carried to consumer (spelling?): ${w.keys.join(", ")}`.slice(0, 600) }
31
- : w.kind === "unread-config-file"
32
- ? { domain: w.domain ?? "config.d", error: `unread config file ${w.file} (${w.why})`.slice(0, 600) }
33
- : undefined)
34
- .filter((r) => r !== undefined);
25
+ const domainErrors = [];
26
+ for (const w of warnings) {
27
+ const f = foldEffectiveReadWarning(w);
28
+ if (f !== null)
29
+ domainErrors.push(f);
30
+ }
35
31
  const effective = wire;
36
32
  return { effective, etag: res.headers.get("etag") ?? undefined, ...(domainErrors.length > 0 ? { domainErrors } : {}) };
37
33
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `EffectiveReadWarning` → 候选门口径的**单点**折叠(本地腿 `config-provider.ts` 与远程腿 `http-client.ts` 共用;
3
+ * 两腿各抄一份映射正是漂移的成因——远程腿曾漏掉一次改动,版本超前的 center 多一个字段就整份远程 refresh 整拒)。
4
+ *
5
+ * **律只有一条:未知键 = 候选门错误,零特例。** 「别的写者按契约放的扩展键」(壳写在 models 条目上的 `sema*`)
6
+ * 不是本函数要分辨的事——那由 settings-schema 的 `EXTENSION_NAMESPACES` 在**声明处**说清(登记的键既不点名也不计数,
7
+ * 1.5.0 起);到了这里的每一个键都是「写了没生效」的拼错或越界,与 domain-defaulted 同级。此前本仓在下游按 kind/
8
+ * 按域/按键路径闭集/按 64 满额四层猜意图,每层都被下一轮复审打穿——意图只能在声明处说,消费方不猜。
9
+ *
10
+ * 截断:schema 的键列表有界(64),超界带 `truncatedFrom`(真实总数)——文案如实带上,消费方永远不把有界列表当穷举。
11
+ * 词表是闭集:`switch` 穷举 `kind`,schema 加新 kind 这里编译红,不会静默漏臂。
12
+ */
13
+ import type { EffectiveReadWarning } from "@sema-agent/settings-schema";
14
+ export type FoldedReadWarning = {
15
+ readonly domain: string;
16
+ readonly error: string;
17
+ };
18
+ /** null = 不入诊断面(`hosts-grandfathered`:值已被收编接受,与本地店同口径)。 */
19
+ export declare function foldEffectiveReadWarning(w: EffectiveReadWarning): FoldedReadWarning | null;
20
+ //# sourceMappingURL=read-warnings.d.ts.map
@@ -0,0 +1,36 @@
1
+ const MAX_ERROR_CHARS = 600;
2
+ const cap = (s) => s.slice(0, MAX_ERROR_CHARS);
3
+ const keyList = (prefix, keys, truncatedFrom) => {
4
+ const total = truncatedFrom ?? keys.length;
5
+ const budget = MAX_ERROR_CHARS - prefix.length;
6
+ const shown = [];
7
+ let used = 0;
8
+ const suffixFor = (n) => (n < total ? ` (+${total - n} more, list truncated)` : "");
9
+ for (const k of keys) {
10
+ const piece = (shown.length > 0 ? ", " : "") + k;
11
+ if (used + piece.length + suffixFor(shown.length + 1).length > budget)
12
+ break;
13
+ shown.push(k);
14
+ used += piece.length;
15
+ }
16
+ return `${prefix}${shown.join(", ")}${suffixFor(shown.length)}`;
17
+ };
18
+ export function foldEffectiveReadWarning(w) {
19
+ switch (w.kind) {
20
+ case "hosts-grandfathered":
21
+ return null;
22
+ case "domain-defaulted":
23
+ return { domain: w.domain, error: cap(w.error instanceof Error ? w.error.message : String(w.error)) };
24
+ case "unknown-keys-dropped":
25
+ return { domain: w.domain, error: keyList("unknown keys dropped by domain schema: ", w.keys, w.truncatedFrom) };
26
+ case "unknown-keys-carried":
27
+ return { domain: w.domain, error: keyList("unknown keys carried to consumer (spelling?): ", w.keys, w.truncatedFrom) };
28
+ case "unread-config-file":
29
+ return { domain: w.domain ?? "config.d", error: cap(`unread config file ${w.file} (${w.why})`) };
30
+ default: {
31
+ const never = w;
32
+ throw new Error(`unhandled EffectiveReadWarning kind: ${JSON.stringify(never)}`);
33
+ }
34
+ }
35
+ }
36
+ //# sourceMappingURL=read-warnings.js.map
@@ -7,6 +7,9 @@ import { fetchEffective as remoteFetchEffective, fetchSkillContent as remoteFetc
7
7
  * 连坐全包回落 env——坏域按该域 schema default 落+错误单列,好域照常生效;caller(main.ts)对每条打
8
8
  * per-domain warn 点名坏域。gate 域(governance/rosters/…)坏文件 tolerant 仍 throw(READ 容错不放宽
9
9
  * gate fail-loud,registry-core 半场精化)——那条路走既有的整包 catch。 */
10
+ /** 壳写在 `config.d/models.json` 条目上的装饰键(`sema*`)自 settings-schema 1.5.0 起是**声明过的扩展命名空间**
11
+ * (`EXTENSION_NAMESPACES`):schema 照旧 strip、但不再报「未知键」,所以本函数收到的每一个未知键都是拼错/越界 ⇒ 候选门错误。
12
+ * 此前把这类键当「通知」放行的整套下游机制(域豁免/键路径闭集/满额特判/单独的通知面)已全部撤回——意图在声明处说,消费方不猜。 */
10
13
  export type FetchEffectiveResult = {
11
14
  effective: EffectiveConfig;
12
15
  etag?: string;
@@ -1,4 +1,5 @@
1
1
  import { createHmac, randomBytes } from "node:crypto";
2
+ import { foldEffectiveReadWarning } from "./config-center/read-warnings.js";
2
3
  import { FileConfigStore } from "@sema-agent/settings-schema/node";
3
4
  import { findSkillContent, skillContentHash, refIntegrityIssues, siblingResolver, } from "@sema-agent/settings-schema";
4
5
  import { redactSecrets } from "./trace/redact.js";
@@ -62,14 +63,9 @@ export class LocalConfigProvider {
62
63
  const store = this.#injectedStore ??
63
64
  new FileConfigStore(this.root, {
64
65
  onWarning: (w) => {
65
- if (w.kind === "domain-defaulted")
66
- parseWarnings.push({ domain: w.domain, error: w.error instanceof Error ? w.error.message : String(w.error) });
67
- else if (w.kind === "unknown-keys-dropped")
68
- parseWarnings.push({ domain: w.domain, error: `unknown keys dropped by domain schema: ${w.keys.join(", ")}` });
69
- else if (w.kind === "unknown-keys-carried")
70
- parseWarnings.push({ domain: w.domain, error: `unknown keys carried to consumer (spelling?): ${w.keys.join(", ")}` });
71
- else if (w.kind === "unread-config-file")
72
- parseWarnings.push({ domain: w.domain ?? "config.d", error: `unread config file ${w.file} (${w.why})` });
66
+ const f = foldEffectiveReadWarning(w);
67
+ if (f !== null)
68
+ parseWarnings.push(f);
73
69
  },
74
70
  });
75
71
  const { effective: eff, domainErrors } = await store.getEffective({ tolerant: true });
@@ -145,16 +145,36 @@ export interface A2aServeConfig {
145
145
  * the default base ref) live here so image-api injects only VETTED, server-fixed values into the build.sh argv —
146
146
  * operator free-text never reaches the runner. */
147
147
  /** [ref] 车3([ref] 流内审批协议)的配置面。总开关默认 **true**(clay 裁 2026-08-08,[ref] 翻真验证后;
148
- * 树上已生效,发布线落在 7.4.0 之后的下一个发布);四个从属旋钮只在开关为真时生效
148
+ * 树上已生效,发布线落在 7.4.0 之后的下一个发布);从属旋钮**除 `windowMs` 外**只在开关为真时生效
149
+ * ([ref]:`STREAM_ASK_WINDOW_MS` 是活卡窗长,与协议上不上场解耦 —— 逐条辖域见各字段域注)
149
150
  * (语义与「谁需要 / 谁被伤 / 什么补偿」三问见 `ServiceConfigFlat.streamApproval` 的注)。 */
150
151
  export interface StreamApprovalConfig {
151
152
  /** 协议总开关。`STREAM_APPROVAL_ENABLED`,默认 **true**(clay 裁 2026-08-08,[ref] 翻真验证后;7.5.0 起)。显式 false ⇒ 全链逐字 7.3.0 前行为(唯一干净还原键)。
152
153
  * 翻真要等回决端点(车4)与对账收敛器(车5)到位——在此之前是个不完整协议(有卡无正规回决口、
153
154
  * 有 PARKING 无收敛器、跨副本回决无唤醒路径)。 */
154
155
  enabled: boolean;
155
- /** 开关为真时作为 `ToolApprovalCoordinator` 的 `ttlMs`([ref] §3.3 的「配置窗默认」)。
156
- * `STREAM_ASK_WINDOW_MS`,默认 **300000**(同裁与 sync 腿既有 5min 活卡窗对齐)。`0` = 运维显式关窗 ⇒ 恒 park(§3.3 窗=0 语义)
157
- * 开关为假 ⇒ 本值不参与,窗保持既有 `DEFAULT_APPROVAL_TTL_MS`(5min)。 */
156
+ /** `ToolApprovalCoordinator` 的 `ttlMs` —— **一只活卡等人多久**([ref] §3.3 的「配置窗默认」)。
157
+ * `STREAM_ASK_WINDOW_MS`,默认 **300000**(同裁与 sync 腿既有 5min 活卡窗对齐)。
158
+ *
159
+ * 🔴 **辖域([ref] 起):无条件** —— 本值在**每一条**呈活卡的腿上生效,与流内协议上不上场无关
160
+ * (`DB_BACKEND=local` / 无 park 设施 / `STREAM_APPROVAL_ENABLED=false` 的部署同样按它计窗)。
161
+ * 旧文「开关为假 ⇒ 本值不参与,窗保持既有 `DEFAULT_APPROVAL_TTL_MS`(5min)」是**已修的病**,不是
162
+ * 设计:那让一根部署级旋钮的生效与否挂在装配在场性上,运维配了它、没有任何一行说它没生效
163
+ * (clay 令:部署级旋钮禁挂在场性派生腿)。缺省值与那个常量同为 300000 ⇒ **没配过这根 env 的部署
164
+ * 字节不变**;协议上不上场改变的是**持久层与协议帧**(ask 行 / 重放 / 迟到赎回),不是窗长。
165
+ *
166
+ * `0` = 运维显式关窗 ⇒ **不发任何审批卡**,每只受门 ask 在铸造时刻直接走 `unattendedAskOutcome()`
167
+ * (§3.3 窗=0 语义;[ref] 起两条车道同义 —— 此前 `0` 在协议不上场的腿上被静默读成「5min 活卡」)。
168
+ * 该形有 park 设施 ⇒ 全落 durable park(经 `POST /v1/approvals/:sessionId/decide` 兑现);无 park 设施
169
+ * 或 `UNATTENDED_APPROVAL_POLICY=deny` ⇒ core fail-closed 拒 = **受门工具全关**,故 boot 期有一行
170
+ * `stream_ask_window_zero` 点名(`boot/coordinators.ts`)。
171
+ * **坏值拒启两侧**([ref]):负值(`-1` 此前静默落成「立即到期」并白发一张没人赢得了的卡)与
172
+ * **超过 Node 定时器上界 `2147483647`(2^31−1 ms ≈ 24.8 天)的值**(codex r1-[high]:`setTimeout` 把更大的
173
+ * delay 静默折成 1ms ⇒ 想调长反而立即到期;跨旋钮门只管 `窗 + adhoc宽限 < orphanTtl`,而 orphanTtl 域上界
174
+ * 90 天,所以 30 天的窗此前合法通过)—— 两侧都点名拒启,不夹取。
175
+ *
176
+ * 窗到期的**去向**不由本键决定(`UNATTENDED_APPROVAL_POLICY` + park 设施在场性决定),本键只决定
177
+ * 「等多久」。悬挂 ask(零活流铸造,[ref] 案A)走的是 `unreachedTtlMs`,不是本键。 */
158
178
  windowMs: number;
159
179
  /** 开流重放(§5)每次最多投几张未决卡——**读面**的帽,超出只投最新的并记一次 warn,开流不失败。
160
180
  * `STREAM_APPROVAL_REPLAY_MAX`,默认 **50**。 */
@@ -512,10 +532,12 @@ export interface ServiceConfigFlat {
512
532
  * `open` 的故障极性**相反**,一个被静默折成缺省的拼写错误会让合规部署在故障窗里放行留存必需用户的 opt-out。
513
533
  * core 侧 `prepareConfigDoors` 另有同判门(`config.memory_capture_policy`),两道同向不冲突。
514
534
  *
515
- * 🔴 **`governed` 的半配置拒启**:该姿态要求本部署有 per-principal verdict 源。本批唯一源 = SQL 后端上的
516
- * `memory_optout_grant` 表(S-2;center `EntitlementRuntimeCaps.allowMemoryOptOut` 是 settings-schema
517
- * 的独立小件,到货前不算源)。governed 而无源 ⇒ 每个 opt-out 声明都被 core 拒且没人知道为什么 ⇒
535
+ * 🔴 **`governed` 的半配置拒启**:该姿态要求本部署有 per-principal verdict 源。源**两条**(析取,[ref] 起):
536
+ * SQL 后端上的 `memory_optout_grant` 表(S-2),或非 dry-run 的 config center `EntitlementRuntimeCaps.allowMemoryOptOut`
537
+ * (settings-schema 1.4.0 到货)。两源皆无 ⇒ 每个 opt-out 声明都被 core 拒且没人知道为什么 ⇒
518
538
  * `boot/runtime-caps.ts` 的 `assertMemoryCapturePolicyWirable` 在启动期拒启(与 consolidation 阀门 D1 同姿态)。
539
+ * ⚠️ [ref]:center 那一臂只证「源在场」,不证 center 会**发**这个可选键 —— center-only 的部署起动后仍可能次次拒,
540
+ * 两处 warn 把它响亮化(`memory_capture_governed_center_only_source` / `memory_capture_governed_verdict_absent`)。
519
541
  *
520
542
  * ⚠️ **部署席 ONLY**:TaskSpec 无同名键;不冻进 checkpoint(resume 腿跟当前部署配置走,core 成文)。
521
543
  * 运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.capturePolicy`(`null`=本部署未设)。 */
@@ -1270,6 +1292,13 @@ export interface ServiceConfigFlat {
1270
1292
  * other value (incl. `"off"`) is treated as unset — this knob has no "off" value to opt BACK OUT with once a
1271
1293
  * session's baseline already tightened elsewhere (tightenTaskSpec would reject that as a loosen anyway). */
1272
1294
  manualModeShellGate?: "always" | "classify";
1295
+ /** [ref] ③(黑板 [ref]/[ref],core [ref] 合修)—— CC `permissions.disableAutoMode` 的本地镜像
1296
+ * (`PERMISSIONS_DISABLE_AUTO_MODE`,布尔词表旋钮,opt-in 缺省 false):本部署对**每个** principal 把
1297
+ * `RuntimeCaps.autoMode` 折成 `false` = org deny 位(core [ref] 三态谓词 `runtimeCaps?.autoMode !== false`
1298
+ * 的拒臂)。**tighten-only**:center 授予 `true` 翻不回;未设 = 不动 caps(无 center 的部署 caps 仍缺席 ⇒
1299
+ * 「缺席=不阻」⇒ 壳发 `permissionMode:"auto"` 即武装)。词表外的值 boot 拒启(安全轴旋钮不许静默回默认)。
1300
+ * 合成点 = `boot/runtime-caps.ts` 最外层(applyLocalAutoModeDeny);读面 = `/v1/capabilities.permissionModeAuto`。 */
1301
+ permissionsDisableAutoMode: boolean;
1273
1302
  /** [ref]④ defer face (EXPERIMENTAL, default OFF): mark the default scenario's LONG-TAIL first-party tools
1274
1303
  * (today = SelectEnvironment only) `defer:true` (core tool-disclosure: the model sees a placeholder until it
1275
1304
  * ToolSearch-loads the schema). Assessment says the win is marginal for us (spec.tools total ≈4.2k tokens, of
@@ -1400,8 +1429,10 @@ export interface ServiceConfigFlat {
1400
1429
  * `DEFAULT_APPROVAL_TTL_MS` 对齐——「开协议」不再顺带把窗砍短)。⚠️ 版本坐标:翻转**已在树上生效**,
1401
1430
  * 发布线上落在 7.4.0 之后的下一个发布,所以一台自报 7.4.0 的**已发布** worker 仍是 OFF,别只按
1402
1431
  * `/health` 的版本号推默认。显式 `STREAM_APPROVAL_ENABLED=false` 是**唯一干净还原键** ⇒ 全链逐字回到
1403
- * 翻转前行为:不发 `approval_request` 帧、不落 `approval_ask` 行、不起收敛器腿、窗 = 既有
1404
- * `DEFAULT_APPROVAL_TTL_MS`(5min)、`askStore` 不注入协调器。
1432
+ * 翻转前行为:不发 `approval_request` 帧、不落 `approval_ask` 行、不起收敛器腿、`askStore` 不注入
1433
+ * 协调器。⚠️ **窗不在这份还原清单里了([ref])**:`STREAM_ASK_WINDOW_MS` 自此在关协议的部署上照样
1434
+ * 生效(缺省 300000 == `DEFAULT_APPROVAL_TTL_MS` ⇒ 没配过它的部署仍然字节不变),理由见
1435
+ * `StreamApprovalConfig.windowMs` 的辖域段。
1405
1436
  *
1406
1437
  * **旋钮为什么存在(2026-08-08 翻转前的成文前提,已被上面的裁定取代,留作沿革)**:那一版默认 false +
1407
1438
  * 60s 窗,三问记的是那个世界——
@@ -1597,7 +1628,7 @@ export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "ses
1597
1628
  export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "atModelAllowlist" | "cascadeLadder" | "degrade">;
1598
1629
  /** 组:approval(审批 / HITL 门)。`directDoorActive` 无 env 解析腿(装配层三域合取的产物),但语义上
1599
1630
  * 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
1600
- export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "manualModeShellGate">;
1631
+ export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "manualModeShellGate" | "permissionsDisableAutoMode">;
1601
1632
  /** 组:memory(记忆面 + TOC 同步腿)。 */
1602
1633
  export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memoryPersistenceCapable" | "memorySync" | "memoryEmbedder" | "memoryOrgAdmissionMode" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "memoryConsolidationDriver" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
1603
1634
  /** 组:auth(鉴权 / 身份 / 治理棒)。`commandPolicy` 只有 sema-registry 腿(无 env 标量形),故 env 解析
package/dist/config.d.ts CHANGED
@@ -169,6 +169,27 @@ export declare const hostExecSpoolEnabled: () => boolean;
169
169
  * a loud CONFIG_NOTICES line ("removed-1m"); the center lane's roster culture is explicit declaration.
170
170
  * Shared by the env lane (main + cheap models) and the center lane (sema-registry toModel). */
171
171
  export declare function applyAutoCompactWindow(m: Model, explicit?: number): "explicit" | "sonnet5" | "removed-1m" | "removed";
172
+ /**
173
+ * [ref] 件⑤ —— 思考档两键的**闭集词表**与拒启判据(`MODEL_DEFAULT_THINKING` / `MODEL_REASONING_EFFORT_LEVELS`)。
174
+ *
175
+ * 改前两键都走 `isThinkingLevel(...)` **过滤**:拼错的词被**静默丢掉**,而两根旋钮的失败方向恰好都是
176
+ * 「把运维的意图反向执行」——
177
+ * · `MODEL_DEFAULT_THINKING=hight` ⇒ `defaultThinking` 不铸键 ⇒ 全部任务回落到「无模型级默认档」,
178
+ * 运维以为整台机器抬到了 high;
179
+ * · `MODEL_REASONING_EFFORT_LEVELS=med,high` ⇒ 只剩 `["high"]`;写成 `med` 一个词则整键落空、
180
+ * `compat` 不铸 ⇒ 回落 core 的保守梯(`minimal|low|medium|high`),`xhigh`/`max` 被 clamp 下来。
181
+ * 运维配这根旋钮的**全部用途**就是告诉引擎「本端点吃得下更高档」,静默落空 = 高档永远上不去。
182
+ * 两条都是 `MODEL_DEGRADE_ON` / `TASK_TIMEOUT_SEC` 那条族律的同形([ref] A 档:坏值拒启,不静默折默认),
183
+ * 所以改成 boot 响亮拒 —— 与 `MODEL_DEGRADE_ON` 的 `parseDegradeOn` 逐字同款(点名坏词 + 列合法词)。
184
+ *
185
+ * 词表用 `Record<ThinkingLevel, true>` 铸 = 与 llm-core 锁步:上游加一档而这里不补 ⇒ **编译红**
186
+ * (`MODEL_DEGRADE_ON` 的 `Set<DegradeReason>` 同款纪律);目录行的 `enumValues` 由本表派生,
187
+ * 「文档写五档、代码收六档」那种分叉在类型层就不可能发生。
188
+ */
189
+ type ModelThinkingLevel = NonNullable<Model["defaultThinking"]>;
190
+ /** 六档闭集(有序:低→高,取自上表的书写序),目录行 `enumValues` 与两条拒因文案的单一真源。
191
+ * (`CATALOG_RESTART_SLICES = Object.keys(RESTART_SLICE_SET)` 同款派生式。) */
192
+ export declare const THINKING_LEVELS: readonly ModelThinkingLevel[];
172
193
  /**
173
194
  * 一条工具名**永不匹配任何现役工具**的三种拼法,及其修正指引。审批/权限名单是 server 侧 ToolPolicy 的
174
195
  * RAW 逐字比对(canonicalToolName 折叠面已随 core 5.0.0 [ref] 退役),所以写错的名字不会报错、只会静默