@sema-agent/server 7.101.1 → 7.103.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 (113) hide show
  1. package/MIGRATION.md +84 -0
  2. package/README.md +4 -4
  3. package/README.zh-CN.md +4 -4
  4. package/USAGE.md +156 -9
  5. package/dist/approval-card.d.ts +35 -2
  6. package/dist/approval-card.js +19 -1
  7. package/dist/background-session-reap.d.ts +58 -0
  8. package/dist/background-session-reap.js +90 -0
  9. package/dist/bake-runner/main.d.ts +21 -3
  10. package/dist/bake-runner/main.js +18 -11
  11. package/dist/boot/config-center.d.ts +21 -4
  12. package/dist/boot/config-center.js +62 -19
  13. package/dist/boot/exit-boundary.js +17 -0
  14. package/dist/boot/runner-deps.js +1 -0
  15. package/dist/boot/runtime-caps.js +3 -2
  16. package/dist/boot/session-faces.js +3 -4
  17. package/dist/boot/shutdown.d.ts +2 -1
  18. package/dist/boot/shutdown.js +33 -12
  19. package/dist/boot/stage-01-config.js +2 -0
  20. package/dist/boot/stage-07-capability-layer.d.ts +1 -1
  21. package/dist/boot/stage-07-capability-layer.js +6 -4
  22. package/dist/boot/stage-08-reapers.d.ts +1 -1
  23. package/dist/boot/stage-09-leader.d.ts +1 -1
  24. package/dist/boot/stage-10-http-server.d.ts +1 -1
  25. package/dist/boot/stage-10-http-server.js +13 -4
  26. package/dist/brain.d.ts +28 -8
  27. package/dist/brain.js +20 -4
  28. package/dist/capabilities/builtin-tools.d.ts +14 -1
  29. package/dist/capabilities/builtin-tools.js +3 -0
  30. package/dist/capabilities/center-plugins.d.ts +9 -3
  31. package/dist/capabilities/center-plugins.js +5 -10
  32. package/dist/capabilities/scenarios.d.ts +1 -1
  33. package/dist/capabilities/scenarios.js +3 -3
  34. package/dist/center-credential-renewer.d.ts +76 -0
  35. package/dist/center-credential-renewer.js +359 -0
  36. package/dist/center-credential.d.ts +76 -22
  37. package/dist/center-credential.js +119 -43
  38. package/dist/config-catalog.js +17 -8
  39. package/dist/config-center/apply-effective.d.ts +16 -0
  40. package/dist/config-center/apply-effective.js +26 -21
  41. package/dist/config-center/center-request.d.ts +13 -0
  42. package/dist/config-center/center-request.js +23 -3
  43. package/dist/config-center/facade.d.ts +1 -1
  44. package/dist/config-center/http-client.js +2 -5
  45. package/dist/config-center/mcp-revocation.d.ts +2 -2
  46. package/dist/config-center/mcp-revocation.js +1 -1
  47. package/dist/config-center/read-warnings.d.ts +19 -48
  48. package/dist/config-center/read-warnings.js +7 -49
  49. package/dist/config-center/restart-signal.d.ts +8 -21
  50. package/dist/config-center/restart-signal.js +2 -12
  51. package/dist/config-center/types.d.ts +4 -2
  52. package/dist/config-provider.js +1 -48
  53. package/dist/config-types.d.ts +107 -17
  54. package/dist/config-types.js +7 -2
  55. package/dist/config.d.ts +7 -0
  56. package/dist/config.js +91 -29
  57. package/dist/device-store.d.ts +1 -1
  58. package/dist/env-name-allowlist-knobs.d.ts +21 -7
  59. package/dist/env-name-allowlist-knobs.js +7 -2
  60. package/dist/fleet-client.d.ts +27 -7
  61. package/dist/fleet-client.js +43 -12
  62. package/dist/fleet-lease.d.ts +1 -1
  63. package/dist/host-lsp-manager.d.ts +4 -3
  64. package/dist/host-lsp-manager.js +2 -6
  65. package/dist/http/approval-sweeps.js +5 -20
  66. package/dist/http/dispatch.js +24 -14
  67. package/dist/http/readiness.d.ts +87 -0
  68. package/dist/http/readiness.js +54 -0
  69. package/dist/http/resume-legs.js +5 -4
  70. package/dist/http/route-table.d.ts +7 -0
  71. package/dist/http/route-table.js +6 -0
  72. package/dist/http/routes/a2a-serve.js +6 -14
  73. package/dist/http/routes/approvals-assistant.js +1 -0
  74. package/dist/http/routes/background.d.ts +80 -0
  75. package/dist/http/routes/background.js +94 -0
  76. package/dist/http/routes/capabilities.js +2 -0
  77. package/dist/http/server.d.ts +25 -8
  78. package/dist/http/server.js +2 -2
  79. package/dist/leader/wire.d.ts +8 -1
  80. package/dist/leader/wire.js +4 -3
  81. package/dist/model-entry-refusal.d.ts +42 -19
  82. package/dist/model-entry-refusal.js +34 -19
  83. package/dist/model-route-endpoint.d.ts +10 -0
  84. package/dist/model-route-endpoint.js +6 -2
  85. package/dist/observability/fail-open.d.ts +2 -2
  86. package/dist/observability/fail-open.js +2 -2
  87. package/dist/observability/secret-env-scrub.d.ts +12 -0
  88. package/dist/observability/secret-env-scrub.js +12 -0
  89. package/dist/outbound-dispatcher.d.ts +125 -0
  90. package/dist/outbound-dispatcher.js +211 -0
  91. package/dist/parked-decide.d.ts +11 -5
  92. package/dist/parked-decide.js +4 -1
  93. package/dist/parked-revive-surface.d.ts +47 -0
  94. package/dist/parked-revive-surface.js +20 -0
  95. package/dist/plugins/background-shell-support.d.ts +20 -1
  96. package/dist/plugins/background-shell-support.js +26 -0
  97. package/dist/plugins/host-platform.d.ts +16 -1
  98. package/dist/plugins/host-platform.js +11 -0
  99. package/dist/plugins/remote-env-host.d.ts +14 -1
  100. package/dist/plugins/remote-env-host.js +15 -10
  101. package/dist/project-memory.js +3 -6
  102. package/dist/run-local.js +2 -0
  103. package/dist/server-secret-env.d.ts +97 -24
  104. package/dist/server-secret-env.js +37 -1
  105. package/dist/tool-approval.d.ts +30 -1
  106. package/dist/tool-approval.js +31 -8
  107. package/dist/trace/core-keyset-guard.d.ts +9 -4
  108. package/dist/trace/engine-notice-wire.d.ts +1 -1
  109. package/dist/trace/engine-notice-wire.js +2 -0
  110. package/dist/trace/project.js +13 -0
  111. package/dist/trace/redact.d.ts +11 -0
  112. package/dist/trace/redact.js +1 -0
  113. package/package.json +4 -3
@@ -3,7 +3,8 @@ import type { IncomingMessage } from "node:http";
3
3
  import { type Runner, type TaskSpec, type TaskResult, type WorkflowRunStore, type MemoryEntry, type MemoryExportBundle, type MemoryImportReport, type EntryProvenanceAccount, type MemoryErasureAttestation, type MemoryEntryOrigin, type OriginClearanceRow, type SessionMemoryStatus, type ConsolidationRunReceipt, type ConsolidationDriverRunRow, type SideQuerySpec, type SideQueryResult, type Brain, type EngineNotice } from "@sema-agent/core";
4
4
  import type { TaskRequestBody } from "./wire-types.js";
5
5
  import type { ServiceConfig } from "../config-types.js";
6
- import { type RestartSignal, type SessionMirrorRuling } from "../config-center/facade.js";
6
+ import { type SessionMirrorRuling } from "../config-center/facade.js";
7
+ import type { RestartSignal } from "@sema-agent/settings-schema/fleet";
7
8
  import { type OwnerAwareSessionStore } from "../security.js";
8
9
  import type { RunStore, ImageIndex, ImageBake, CheckpointStoreFull, ResumeAnchorStore, ApprovalExemptionStore, ApprovalNonceStore, ServiceSessionPolicyStore, ServiceFileHistoryStore, ToolResultStoreFull, StoreBackend } from "../plugins/store-backend.js";
9
10
  import type { RuleConsentLane } from "../rules-consent.js";
@@ -549,7 +550,9 @@ export interface ServiceSeamDeps {
549
550
  };
550
551
  };
551
552
  }) => void;
552
- parkedReviveTool?: import("@sema-agent/core").ToolSpec;
553
+ /** #1110(core 7.32.0):parked 赎回面工厂(按行解析驱动哪只 Agent 工具 + 同出一张列表的继承座;属主 `parked-revive-surface.ts`)。
554
+ * 缺席 = 这台部署没有 park 底座(checkpointStore / backgroundAgentStore 任一缺席),bg 赎回臂不接线。 */
555
+ parkedReviveSurface?: import("../parked-revive-surface.js").ParkedReviveSurfaceFactory;
553
556
  /** 可选第三件:revive args 的 `subagent_type` roster 成员集([ref] Q1 成员性规则)。当前生产装配
554
557
  * **恒缺席**(boot 内建 default 无自定义 agents roster,per-request 的 spec.agents 不进 boot 工具)
555
558
  * = 恒省略 subagent_type,typed 子代按 general-purpose 形复活(保真度 caveat 已报 core,[ref]
@@ -562,7 +565,7 @@ export interface ServiceSeamDeps {
562
565
  * `rebuildInheritedGate` 头注。 */
563
566
  parkedReviveInheritedGate?: (row: import("@sema-agent/core").BackgroundAgentRecord, identity: {
564
567
  principal: string | undefined;
565
- }) => Promise<unknown>;
568
+ }) => Promise<import("../boot/parked-revive-gate.js").RebuiltInheritedGate | undefined>;
566
569
  }
567
570
  /** **可观测面**:日志/指标/累加器。全体 fail-open——观测失败绝不改变业务面行为。 */
568
571
  export interface ServiceObservabilityDeps {
@@ -640,7 +643,8 @@ export interface ServiceDeploymentDeps {
640
643
  * respond/subagent) stay open so the runs we are waiting on can still finish/interact.
641
644
  * • `inflight` is ASSIGNED BY createServer (a live union of this replica's in-flight legs: durable bg/resume
642
645
  * `inflightRuns` + live sync/resume streams `steerableRuns`) — main.ts polls it to know when drain is done.
643
- * • /health carries `draining:true` (k8s readiness 摘流信号) + `version` (build self-description).
646
+ * • /health carries `draining:true` + `version` (build self-description); draining is a readiness reason
647
+ * (`http/readiness.ts`) ⇒ `/readyz` 503 + `/health` `ready:false` (the k8s readiness 摘流信号).
644
648
  * • [ref]:`attachedStreams` 同样 ASSIGNED BY createServer —— 本副本当前打开着的 SSE 流条数
645
649
  * (= 引擎附着租约集的大小)。消费方是 `boot/engine-lease.ts` 的自退判据;与 `inflight` 同族,
646
650
  * 同一个共享盒子,不另开第二条 main↔server 的通道。
@@ -654,21 +658,32 @@ export interface ServiceDeploymentDeps {
654
658
  attachedStreams?: () => number;
655
659
  lastAttachEndedAt?: () => number;
656
660
  };
657
- /** boot ready 门(b):false = registry 部署无显式 env 模型且首次 effective pull 尚未落 roster
658
- * (worker 只有占位模型)。计费提交 503 + /health 加性 `ready:false`。absent = 恒 ready(env 模型在/非
659
- * registry 部署),现有部署零影响。main.ts 在 applyEffective 落模型后翻 true(one-way latch)。 */
661
+ /** boot ready 门(b):false = 布防(`CONFIG_REQUIRE_ROSTER=true`)且首次 effective pull 尚未落 roster
662
+ * (worker 只有占位模型)。就绪原因 `model_roster_pending`(`http/readiness.ts`):计费提交 503 + `/readyz` 503 +
663
+ * /health 加性 `ready:false`。absent = 恒 ready(未布防 / 非 registry 部署),现有部署零影响。config-center 的
664
+ * markRosterLanded 在落模型后翻 true(one-way latch)。 */
660
665
  modelReady?: () => boolean;
661
666
  /** Sema-registry restart signal: the refresh loop sets this when a pull carries restart-to-apply
662
667
  * changes (skills/mcp/a2a/scenarios/… baked at boot — the `RestartSlice` closed set) that DIFFER from this process's boot config.
663
668
  * Surfaced at GET /health as `restartRequired` + `restart{reasons,version,since}` so the orchestrator can
664
669
  * auto rolling-restart the worker. Change-detected (not presence) — absent when nothing baked-at-boot
665
- * changed, so polling it never causes a restart loop. */
670
+ * changed, so polling it never causes a restart loop. S-741:the fleet heartbeat reads THIS getter too
671
+ * (`boot/stage-10-http-server.ts` → `restart: () => deployment.restartState?.()`), so `/health.restart` and the
672
+ * announcement's `restart` are the same object — one mint point, two exits. */
666
673
  restartState?: () => RestartSignal | undefined;
667
674
  /** [ref] 世代账([ref] §2):本副本的配置收敛状态 —— `/health` 取三键
668
675
  * (configTargetVersion / configAppliedVersion / configApplyStaleMs),`GET /v1/diagnostics/wiring` 取整份
669
676
  * (按组的 appliedVersion / deferredKeys+铸因 / lastRejected 消毒形)。缺席 = 这个进程没有配置管道
670
677
  * (纯 env 部署 / 测试夹具)⇒ 两个面的形状逐字不变(additive)。 */
671
678
  configApplyState?: () => import("../config-center/apply-ledger.js").ConfigApplyLedgerView | undefined;
679
+ /** S-698:中心凭证健康位(`/health.centerCredential`)—— 凭证来源的健康态 + 续签器状态(`center-credential-renewer.ts`
680
+ * `buildCenterCredentialHealthView` 铸形,只有闭集词 / 时刻 / 计数,不带路径与令牌)。缺席 / 返回 undefined = 健康或没有中心
681
+ * ⇒ `/health` 形状逐字不变(additive)。 */
682
+ centerCredentialState?: () => import("../center-credential-renewer.js").CenterCredentialHealthView | undefined;
683
+ /** S-768:模型拒因信号(`/health.modelRefusals`,settings-schema 6.2.0 `ModelRefusalsSignal` 形:名 → 码 + 组拒码)。fleet 心跳读**这同一只**
684
+ * (`boot/stage-10-http-server.ts` → `modelRefusals: () => deployment.modelRefusalsState?.()`)—— 一处铸、两出口。缺席 / 返回 undefined =
685
+ * 没有可报的拒或没有配置管道 ⇒ `/health` 形状逐字不变(additive)。 */
686
+ modelRefusalsState?: () => import("@sema-agent/settings-schema").ModelRefusalsSignal | undefined;
672
687
  /** [ref] 追加件:`POST /v1/admin/config/refresh` 的实现面 —— 触发**一次既有**的 refresh 拍(在飞则 join)。
673
688
  * 缺席 / 回 `"not-wired"` = 本部署没有配置管道 ⇒ 该口诚实 4xx(绝不吞成 200)。 */
674
689
  configRefreshNow?: () => Promise<"triggered" | "not-wired" | "failed">;
@@ -798,6 +813,8 @@ export declare function flattenServiceDeps(deps: ServiceDeps): FlatServiceDeps;
798
813
  /**
799
814
  * Managed-Agents-style HTTP/SSE service surface.
800
815
  * GET /health
816
+ * GET /readyz → readiness probe: 200 {ready:true} / 503 {ready:false, reasons} (S-739; same predicate
817
+ * as /health.ready, the billable-submit 503s and the fleet heartbeat — http/readiness.ts)
801
818
  * POST /v1/tasks → run to completion, TaskResult JSON (synchronous)
802
819
  * POST /v1/tasks/stream → SSE of TaskEvent, live tokens (synchronous, same instance)
803
820
  * POST /v1/runs → async: 202 {taskId, sessionId, status}, runs in background (S1)
@@ -24,7 +24,7 @@ import { createRequestGuards } from "./request-guards.js";
24
24
  import { createAdmission, MAX_SKILL_CONTENT_CHARS, MAX_SYSTEM_PROMPT_CHARS, MAX_OUTPUT_SCHEMA_CHARS, validateUserSkills } from "./admission.js";
25
25
  import { createResumeLegs } from "./resume-legs.js";
26
26
  import { createDispatch, hasServiceAuth } from "./dispatch.js";
27
- import { routeLabel, isBillableSubmitPath, isCredentialGatedRewrite, isMethodDispatchedSubmitPath } from "./route-table.js";
27
+ import { routeLabel, isBillableSubmitPath, isCredentialGatedRewrite, isMethodDispatchedSubmitPath, isProbePath } from "./route-table.js";
28
28
  import { createApprovalSweeps } from "./approval-sweeps.js";
29
29
  export { routeLabel, isBillableSubmitPath, isCredentialGatedRewrite, isMethodDispatchedSubmitPath, hasServiceAuth };
30
30
  export { MAX_SKILL_CONTENT_CHARS, MAX_SYSTEM_PROMPT_CHARS, MAX_OUTPUT_SCHEMA_CHARS, validateUserSkills };
@@ -49,7 +49,7 @@ export function createHttpServer(rawDeps) {
49
49
  deps.drainState.inflight = () => new Set([...inflightRuns.keys(), ...steerableRuns.keys()]).size + counters.uncountedBillableInflight + counters.admittedInflight + (deps.leaderEndpoint?.inflight?.() ?? 0);
50
50
  let lastBillableActivityAt = performance.now();
51
51
  const noteActivity = (url) => {
52
- if (url === "/health" || url.startsWith("/metrics"))
52
+ if (isProbePath(url))
53
53
  return;
54
54
  lastBillableActivityAt = performance.now();
55
55
  };
@@ -264,7 +264,14 @@ export declare const LEADER_PUSH_LOCAL_TIMEOUT_MS = 30000;
264
264
  /** Coordinator(control plane,持有推送凭据)的 git push 闭包——从 `createLeaderRunner` 抽出为独立工厂,
265
265
  * 仅依赖 durableRemote/targetRef/git 身份(不依赖 wire 内其余状态),因此可以脱离完整的
266
266
  * plan→provision→fan-out→merge 管线单测(那条管线需要真 brain + E2B/k8s env)。行为与抽出前逐字相同,
267
- * 唯一新增是六条 execFileSync 各自的 `timeout`。 */
267
+ * 唯一新增是六条 execFileSync 各自的 `timeout`。
268
+ *
269
+ * 7.103.0(S-684 残余):六条 git 子进程的 env 一律 = `gitSubprocessEnv({ isolateConfig: false })` + `LC_ALL=C`(修前:
270
+ * `{ ...process.env, LC_ALL: "C" }`,两条 `git config` 连 env 都不交 ⇒ 整份服务 env 进 git 与它拉起的 helper / hook)。
271
+ * **不隔离宿主 git 配置**(读后定):写凭据是控制面宿主的(「Coordinator's creds」,`durableRemote` 由受信调用方给出,
272
+ * 代码里从不带凭据),而宿主侧能供凭据的形 —— 全局配置里的 credential helper(自带存储)/ `url.<…>.insteadOf` 改写 /
273
+ * `core.sshCommand` —— 都住在全局 git 配置里,隔离会把它们一起切掉;URL 里带凭据的形隔离与否都能用,所以隔离只会打断前者。
274
+ * 仍然剥密 + `GIT_TERMINAL_PROMPT=0`:helper 看得到的只剩非密钥 env,凭据缺席 ⇒ 响亮失败而不是挂在 prompt 上。 */
268
275
  export declare function createCoordinatorPush(durableRemote: string, targetRef: string | undefined, ident: {
269
276
  name: string;
270
277
  email: string;
@@ -8,6 +8,7 @@ import { Runner, combinePolicies, createDurableQuestionPolicy, QUESTION_AWAITS_R
8
8
  import { RemoteContainerExecutionEnv } from "../plugins/remote-env-e2b.js";
9
9
  import { E2B_SANDBOX_HOME } from "../execution-lane-caps.js";
10
10
  import { createDurableAskPolicy } from "../approval.js";
11
+ import { gitSubprocessEnv } from "../server-secret-env.js";
11
12
  import { presignS3Url } from "../plugins/s3-presign.js";
12
13
  import { buildUploadScript, uploadStepSuffix, fetchUploadedDiff, withStaging } from "./diffup.js";
13
14
  import { pullDiff, safeRev } from "./diffout.js";
@@ -100,11 +101,11 @@ export const LEADER_PUSH_LOCAL_TIMEOUT_MS = 30_000;
100
101
  export function createCoordinatorPush(durableRemote, targetRef, ident) {
101
102
  return async (integratedPatch, baseSha) => {
102
103
  const dir = mkdtempSync(join(tmpdir(), "leader-coord-"));
103
- const gitEnv = { ...process.env, LC_ALL: "C" };
104
+ const gitEnv = { ...gitSubprocessEnv({ site: "server.leader.git-push", isolateConfig: false }), LC_ALL: "C" };
104
105
  try {
105
106
  execFileSync("git", ["clone", "-q", durableRemote, dir], { env: gitEnv, timeout: LEADER_PUSH_NETWORK_TIMEOUT_MS });
106
- execFileSync("git", ["-C", dir, "config", "user.name", ident.name], { timeout: LEADER_PUSH_LOCAL_TIMEOUT_MS });
107
- execFileSync("git", ["-C", dir, "config", "user.email", ident.email], { timeout: LEADER_PUSH_LOCAL_TIMEOUT_MS });
107
+ execFileSync("git", ["-C", dir, "config", "user.name", ident.name], { env: gitEnv, timeout: LEADER_PUSH_LOCAL_TIMEOUT_MS });
108
+ execFileSync("git", ["-C", dir, "config", "user.email", ident.email], { env: gitEnv, timeout: LEADER_PUSH_LOCAL_TIMEOUT_MS });
108
109
  writeFileSync(join(dir, ".leader.patch"), integratedPatch);
109
110
  execFileSync("git", ["-C", dir, "am", "--3way", ".leader.patch"], { env: gitEnv, timeout: LEADER_PUSH_LOCAL_TIMEOUT_MS });
110
111
  rmSync(join(dir, ".leader.patch"));
@@ -1,15 +1,20 @@
1
- /** 不可用的成因 —— **闭集**(加一个成因 ⇒ {@link MODEL_ENTRY_REFUSAL_CODES} 与 {@link refusalEventOf} 编译红)。 */
2
- export type ModelEntryRefusalReason = "no_brain" | "entry_invalid";
3
- /** 机读码(拼法随 `config.*` 家族,按**规则**命名):出现在拒因句的方括号里、warn 字段里、`modelRefusals` 里。 */
4
- export declare const MODEL_ENTRY_REFUSAL_CODES: {
5
- readonly no_brain: "config.model_no_brain";
6
- readonly entry_invalid: "config.model_entry_invalid";
1
+ import { type ModelEntryRefusalCode, type ModelPlaneRefusalCode, type ModelRefusalsSignal } from "@sema-agent/settings-schema";
2
+ /**
3
+ * 条目拒因码 → 本仓铸它的成因 —— **以码为键**(S-768):码闭集的属主是 settings-schema(`MODEL_ENTRY_REFUSAL_CODES`,6.2.0 起;此前本仓
4
+ * 自持一份同名码表,撞名已消灭、零别名)。那边加一个条目码 ⇒ 本表缺键 ⇒ 本仓 tsc 红(只拿 `satisfies` 约束码值证明的是「子集」,
5
+ * 不是「相等」;以码为键才证相等)。成因闭集由本表派生,{@link refusalEventOf} 对它穷举 —— 加成因同样编译红。
6
+ * 码的拼法随 `config.*` 家族,按**规则**命名:出现在拒因句的方括号里、warn 字段里、`modelRefusals` 里。
7
+ */
8
+ export declare const ENTRY_REFUSAL_REASON_OF: {
9
+ readonly "config.model_no_brain": "no_brain";
10
+ readonly "config.model_entry_invalid": "entry_invalid";
7
11
  };
8
- export type ModelEntryRefusalCode = (typeof MODEL_ENTRY_REFUSAL_CODES)[ModelEntryRefusalReason];
9
- /** 「这张表没有缺省模型」的机读码(S-705;boot 拒启与热路径模型面组拒同一个码)。 */
12
+ /** 不可用的成因 —— **闭集**,由 {@link ENTRY_REFUSAL_REASON_OF} 派生。 */
13
+ export type ModelEntryRefusalReason = (typeof ENTRY_REFUSAL_REASON_OF)[ModelEntryRefusalCode];
14
+ /** 「这张表没有缺省模型」的机读码(S-705;boot 拒启与热路径模型面组拒同一个码;组拒码闭集属主 = settings-schema)。 */
10
15
  export declare const DEFAULT_MODEL_MISSING_CODE = "config.default_model_missing";
11
- /** 🔴 S-712「被拒的名字被档位 / 别名增广铸到了别的条目上(或读不出被丢条目的名字,无从证明没有)」的机读码
12
- * (不变量 `refused-names-not-minted`;热路径 / boot 首次 apply / dry-run 同一个码)。 */
16
+ /** 🔴 S-712「被拒的名字被档位 / 别名增广铸到了别的条目上(或被丢身份载体形不对、读不出名字,无从证明没有)」的机读码
17
+ * (不变量 `refused-names-not-minted`;热路径 / boot 首次 apply / dry-run 同一个码;组拒码闭集属主 = settings-schema)。 */
13
18
  export declare const REFUSED_NAME_MINTED_CODE = "config.model_refused_name_minted";
14
19
  /**
15
20
  * 一条不可用条目的**事实**(纯数据;`config.modelRefusals[<name>]` 的值)。
@@ -47,23 +52,41 @@ export interface CatalogEntryLike {
47
52
  * 坐标,不取它的输出(输出会补缺省、剥扩展键,那是读腿的事)。
48
53
  */
49
54
  export declare function judgeCatalogEntry(m: CatalogEntryLike, ctx: CatalogEntryVerdictCtx): ModelEntryRefusal | undefined;
50
- /** 一批已启用条目的判决:可用的(保序)、不可用的(按 catalog name)、以及身份证明不了的被丢条目(坐标)。 */
55
+ /** 一批已启用条目的判决:可用的(保序)、不可用的(按 catalog name)、以及形不对的被丢身份载体行(坐标)。 */
51
56
  export interface CatalogEntriesVerdict<M extends CatalogEntryLike> {
52
57
  readonly usable: readonly M[];
58
+ /** 键 = catalog name(**每一个**名,含秘密形 / 空串 / `__proto__`:引擎自己的判决保持完整 —— S-712 不变量与选择面的「让出」都读它;
59
+ * 名筛只在出口,见 {@link buildModelRefusalsSignal})。键以自有数据属性写入(`__proto__` 不走原型 setter)。 */
53
60
  readonly refused: Readonly<Record<string, ModelEntryRefusal>>;
54
- /** S-712:读腿逐条丢掉、而身份证明不了的条目坐标(`models.3`;载体上不带 `name` 的那些 —— 读腿只在原文档对不上本次解析时
55
- * 这样交出,见 `read-warnings.ts` `DroppedModelIdentity`)。非空 ⇒ 不变量 `refused-names-not-minted` 无从成立 ⇒ 模型面组拒。 */
56
- readonly unreadable: readonly string[];
61
+ /** S-712:被丢身份**载体形不对**的行坐标(`models.3`;载体不是列表时是一个固定标记)。读腿只为有名的被丢条目铸载体行
62
+ * (`read-warnings.ts` `DroppedModelIdentity`,`name` 必在),所以这一形只来自绕过读腿的输入被改过(盘上 LKG 被手改 / 手写
63
+ * effective)。非空 ⇒ 读不出的名字无从证明它不被增广铸出 ⇒ 不变量 `refused-names-not-minted` 不成立 ⇒ 模型面组拒(fail-closed)。 */
64
+ readonly malformed: readonly string[];
57
65
  }
58
66
  /**
59
67
  * 批量形(模型面 stage 与 dry-run 共用这一只 —— 报数与真 apply 永不撕裂)。
60
68
  * S-712:`dropped` = 读腿交出的被丢条目身份(`EffectiveConfig.models.droppedIdentities`)—— 它们与逐条判据判出的 `entry_invalid`
61
- * 是**同一件事**(条目不合 `ModelEntry` 形),只是在第一道边界就被丢了;带名字的铸墓碑,不带名字的(读腿证明不了身份)进
62
- * `unreadable`。没有字符串名的坏行读腿已按停用处置、根本不进载体(7.101.0 验修轮 V-2:没有名字就没有可保留的名字)。
63
- * 入参按 `unknown` 判形:载体可能来自盘上 LKG(本仓写的,但文件可被改),形不对的一律按证明不了处置(fail-closed)。
69
+ * 是**同一件事**(条目不合 `ModelEntry` 形),只是在第一道边界就被丢了;每一行铸一块墓碑(拒因 provider 词空:告警身份不带它)。
70
+ * 入参按 `unknown` 判形:载体可能来自盘上 LKG(本仓写的,但文件可被改)。**规则一条**:载体要么缺席,要么是一列带字符串 `name`
71
+ * 的行;其余一律「载体形不对」进 `malformed`(fail-closed)—— 读腿自己从不铸这种形。
64
72
  * 与一条可用 / 已判不可用条目同名时不覆盖(可用条目仍可用;同名撞车由不变量 `refused-names-not-minted` 响亮拒)。
65
73
  */
66
74
  export declare function judgeCatalogEntries<M extends CatalogEntryLike>(enabled: readonly M[], ctx: CatalogEntryVerdictCtx, dropped?: unknown): CatalogEntriesVerdict<M>;
75
+ /** S-768:一次 apply 对「最新交到手的那份候选目录」的模型拒因判决(纯数据;`ApplyReport.modelRefusals`)。 */
76
+ export interface ModelRefusalVerdict {
77
+ /** 那份候选的逐条判决(键 = catalog name,**完整**,未过名筛 —— 名筛在出口 {@link buildModelRefusalsSignal})。 */
78
+ readonly entries: Readonly<Record<string, ModelEntryRefusal>>;
79
+ /** 那份候选的模型面组拒码(组拒时在场;一个候选恰一个组拒判决)。 */
80
+ readonly plane?: ModelPlaneRefusalCode;
81
+ }
82
+ /**
83
+ * S-768 —— 判决 → `/health.modelRefusals` 与 fleet 心跳 `modelRefusals` 的 wire 形(settings-schema `ModelRefusalsSignal`,纯投影):
84
+ * 名 → 码,**值只码**(拒因事实里的上游 id / provider 词 / 坏字段键 / 出路句都不上 wire —— `entry_invalid` 行是没人验过的行,id / provider
85
+ * 可能就是坏行原样;句子是给本机运维的散文,不是契约)。**名筛在这里、只在这里**:键过 settings-schema `isReportableRefusalName`(唯一判据,
86
+ * import 不复制)—— 秘密形 / 空串 / `__proto__` 这类出不了门的名不进键、不计数;引擎内部的判决不受影响(本机日志的写名行为与基线同)。
87
+ * 逐条空且无组拒 ⇒ `undefined`(键缺席:「没有」只有一种拼法)。
88
+ */
89
+ export declare function buildModelRefusalsSignal(v: ModelRefusalVerdict | undefined): ModelRefusalsSignal | undefined;
67
90
  /** 成因 → 它那一条 warn 的事件名(**穷举**)。两个名字是票面点名的既有/新事件,运维的告警规则按名挂。 */
68
91
  export declare function refusalEventOf(reason: ModelEntryRefusalReason): "sema_registry_model_no_brain" | "sema_registry_model_entry_invalid";
69
92
  /** warn 字段(不含值):`field` = 第一处坏字段(告警规则最常按它分组),`fields` = 全部。 */
@@ -125,6 +148,6 @@ export interface RefusedNameView {
125
148
  export declare function mintedRefusedNames(view: RefusedNameView): MintedRefusedName[];
126
149
  /** 不变量 `refused-names-not-minted` 的拒因句(`undefined` = 成立)。warn 与世代账 / 拒因同一句;逐对明细另走 {@link mintedRefusedNames}。 */
127
150
  export declare function refusedNameMintedRefusal(view: RefusedNameView): string | undefined;
128
- /** 身份证明不了的被丢条目的拒因句(同一不变量同一个码:证明不了的名字无从证明它不被增广铸出)。 */
129
- export declare function unreadableDroppedRefusal(entries: readonly string[]): string;
151
+ /** 被丢身份载体形不对的拒因句(同一不变量同一个码:读不出的名字无从证明它不被增广铸出)。 */
152
+ export declare function malformedDroppedCarrierRefusal(entries: readonly string[]): string;
130
153
  //# sourceMappingURL=model-entry-refusal.d.ts.map
@@ -1,12 +1,15 @@
1
1
  import { CC_MODEL_TIER_ALIASES, DEFAULT_TIER_ORDER, expandTiers } from "@sema-agent/core";
2
- import { ModelEntry } from "@sema-agent/settings-schema";
2
+ import { ModelEntry, isReportableRefusalName } from "@sema-agent/settings-schema";
3
3
  import { catalogBrainLane } from "./model-provider.js";
4
4
  import { describeSchemaIssueFields, describeSchemaIssues } from "./config-center/read-warnings.js";
5
5
  import { DEFAULT_MODEL_REF } from "./model-select.js";
6
- export const MODEL_ENTRY_REFUSAL_CODES = {
7
- no_brain: "config.model_no_brain",
8
- entry_invalid: "config.model_entry_invalid",
6
+ export const ENTRY_REFUSAL_REASON_OF = {
7
+ "config.model_no_brain": "no_brain",
8
+ "config.model_entry_invalid": "entry_invalid",
9
9
  };
10
+ function refusalCause(code) {
11
+ return { reason: ENTRY_REFUSAL_REASON_OF[code], code };
12
+ }
10
13
  export const DEFAULT_MODEL_MISSING_CODE = "config.default_model_missing";
11
14
  export const REFUSED_NAME_MINTED_CODE = "config.model_refused_name_minted";
12
15
  function laneMount(lane, ctx) {
@@ -27,8 +30,7 @@ export function judgeCatalogEntry(m, ctx) {
27
30
  const mount = laneMount(lane, ctx);
28
31
  if (!mount.mounted) {
29
32
  return {
30
- reason: "no_brain",
31
- code: MODEL_ENTRY_REFUSAL_CODES.no_brain,
33
+ ...refusalCause("config.model_no_brain"),
32
34
  id: m.id,
33
35
  provider: m.provider,
34
36
  fields: [],
@@ -41,8 +43,7 @@ export function judgeCatalogEntry(m, ctx) {
41
43
  }
42
44
  function entryInvalidRefusal(id, provider, fields, issues) {
43
45
  return {
44
- reason: "entry_invalid",
45
- code: MODEL_ENTRY_REFUSAL_CODES.entry_invalid,
46
+ ...refusalCause("config.model_entry_invalid"),
46
47
  id,
47
48
  provider,
48
49
  fields: [...fields],
@@ -50,30 +51,44 @@ function entryInvalidRefusal(id, provider, fields, issues) {
50
51
  "deployment's catalog on its own (the other entries keep serving) instead of being served with a value nothing validated; fix the entry to bring it back",
51
52
  };
52
53
  }
54
+ const MALFORMED_CARRIER_NOT_A_LIST = "(droppedIdentities is not a list)";
53
55
  export function judgeCatalogEntries(enabled, ctx, dropped) {
54
56
  const usable = [];
55
57
  const refused = {};
58
+ const mint = (name, r) => {
59
+ Object.defineProperty(refused, name, { value: r, enumerable: true, writable: true, configurable: true });
60
+ };
56
61
  for (const m of enabled) {
57
62
  const r = judgeCatalogEntry(m, ctx);
58
63
  if (r === undefined)
59
64
  usable.push(m);
60
65
  else
61
- refused[String(m.name)] = r;
66
+ mint(String(m.name), r);
62
67
  }
63
- const unreadable = [];
64
- for (const d of Array.isArray(dropped) ? dropped : []) {
68
+ const rows = dropped === undefined ? [] : Array.isArray(dropped) ? dropped : undefined;
69
+ if (rows === undefined)
70
+ return { usable, refused, malformed: [MALFORMED_CARRIER_NOT_A_LIST] };
71
+ const malformed = [];
72
+ for (const d of rows) {
65
73
  const e = (typeof d === "object" && d !== null ? d : {});
66
- const where = typeof e.entry === "string" ? e.entry : "(unknown coordinate)";
67
74
  if (typeof e.name !== "string") {
68
- unreadable.push(where);
75
+ malformed.push(typeof e.entry === "string" ? e.entry : "(unknown coordinate)");
69
76
  continue;
70
77
  }
71
78
  if (Object.hasOwn(refused, e.name))
72
79
  continue;
73
80
  const fields = Array.isArray(e.fields) ? e.fields.filter((f) => typeof f === "string") : [];
74
- refused[e.name] = entryInvalidRefusal(typeof e.id === "string" ? e.id : "", typeof e.provider === "string" ? e.provider : "", fields, typeof e.issues === "string" ? e.issues : fields.join(", "));
81
+ mint(e.name, entryInvalidRefusal(typeof e.id === "string" ? e.id : "", "", fields, typeof e.issues === "string" ? e.issues : fields.join(", ")));
75
82
  }
76
- return { usable, refused, unreadable };
83
+ return { usable, refused, malformed };
84
+ }
85
+ export function buildModelRefusalsSignal(v) {
86
+ if (v === undefined)
87
+ return undefined;
88
+ const entries = Object.fromEntries(Object.entries(v.entries).flatMap(([name, r]) => (isReportableRefusalName(name) ? [[name, r.code]] : [])));
89
+ if (Object.keys(entries).length === 0 && v.plane === undefined)
90
+ return undefined;
91
+ return v.plane === undefined ? { entries } : { entries, plane: v.plane };
77
92
  }
78
93
  export function refusalEventOf(reason) {
79
94
  switch (reason) {
@@ -149,9 +164,9 @@ export function refusedNameMintedRefusal(view) {
149
164
  "an explicit pick of that name (or a role bound to it) would silently run on the other entry's endpoint and account; " +
150
165
  "rename the unavailable entry, fix it (see its own warn), or rebind / drop the tier binding that mints the name.");
151
166
  }
152
- export function unreadableDroppedRefusal(entries) {
153
- return (`catalog entries dropped by the settings-schema read could not be identified [${REFUSED_NAME_MINTED_CODE}]: ${entries.join(", ")} — ` +
154
- "the raw document could not be matched to the snapshot that was parsed (e.g. the local file changed between two reads), so the candidate cannot prove the tier/alias expansion does not re-mint their names; " +
155
- "the next read re-judges (fix the named entries if this persists).");
167
+ export function malformedDroppedCarrierRefusal(entries) {
168
+ return (`the dropped-entry identity carrier of this candidate is malformed [${REFUSED_NAME_MINTED_CODE}]: ${entries.join(", ")} — ` +
169
+ "every row the settings-schema read writes there carries a string name, so this carrier was edited outside the read legs (e.g. a hand-edited last-known-good file); " +
170
+ "the candidate cannot prove the tier/alias expansion does not re-mint the unnamed entries, so its model plane is refused — restore or delete that carrier (a fresh read of the source re-mints it).");
156
171
  }
157
172
  //# sourceMappingURL=model-entry-refusal.js.map
@@ -79,4 +79,14 @@ export interface ModelRouteEndpointView {
79
79
  * 与别的键指同一只对象时不重复点名;只有它一个键时(手搓夹具)照样点名 `default`。
80
80
  */
81
81
  export declare function modelRouteEndpointRefusal(view: ModelRouteEndpointView): string | undefined;
82
+ /** S-768:部署级判决的结构化形 —— 码与拒因句由**同一次**判决给出(一个不变量 id 两个码,组拒码在判决那刻当数据带出,不从句子里抠)。 */
83
+ export interface ModelRouteEndpointVerdict {
84
+ readonly code: typeof GATEWAY_FALLBACK_WITHOUT_PRIMARY_CODE | typeof MODEL_ROUTE_ENDPOINT_MISSING_CODE;
85
+ readonly reason: string;
86
+ }
87
+ /**
88
+ * **判据属主**(S-768 起的结构化形;{@link modelRouteEndpointRefusal} 是它的句子投影):`undefined` = 通过。判序即 {@link modelRouteEndpointRefusal}
89
+ * 头注所列 —— ① 备用无主先说,② 逐条目解析不到端点全部点名。加一枝 = 在这里同时给出码与句(返回型强制两者同在)。
90
+ */
91
+ export declare function judgeModelRouteEndpoint(view: ModelRouteEndpointView): ModelRouteEndpointVerdict | undefined;
82
92
  //# sourceMappingURL=model-route-endpoint.d.ts.map
@@ -14,13 +14,17 @@ export function resolveOpenaiRouteEndpoint(routeName, routeBaseUrl, gatewayBaseU
14
14
  return baseUrl !== undefined ? { ok: true, baseUrl } : { ok: false, refusal: routeEndpointMissingRefusal([`"${routeName}"`]) };
15
15
  }
16
16
  export function modelRouteEndpointRefusal(view) {
17
+ return judgeModelRouteEndpoint(view)?.reason;
18
+ }
19
+ export function judgeModelRouteEndpoint(view) {
17
20
  const gateway = declared(view.gatewayBaseUrl);
18
21
  if (gateway === undefined && view.gatewayFallbackUrls.length > 0) {
19
- return (`env MODEL_GATEWAY_FALLBACK_URLS is set but MODEL_GATEWAY_BASEURL is not [${GATEWAY_FALLBACK_WITHOUT_PRIMARY_CODE}] — ` +
22
+ const reason = (`env MODEL_GATEWAY_FALLBACK_URLS is set but MODEL_GATEWAY_BASEURL is not [${GATEWAY_FALLBACK_WITHOUT_PRIMARY_CODE}] — ` +
20
23
  "the fallback URLs are mirrors of the primary gateway (same provider, same account, same key scope), tried only " +
21
24
  "after the primary fails; with no primary there is nothing to fail over from, and MODEL_GATEWAY_BASEURL has no " +
22
25
  `factory default. Set MODEL_GATEWAY_BASEURL to the primary gateway (for one on this machine: ${LOCAL_GATEWAY_HINT}), ` +
23
26
  "or unset MODEL_GATEWAY_FALLBACK_URLS.");
27
+ return { code: GATEWAY_FALLBACK_WITHOUT_PRIMARY_CODE, reason };
24
28
  }
25
29
  const anthropicMounted = view.anthropic !== undefined;
26
30
  const entries = Object.entries(view.models ?? {});
@@ -36,7 +40,7 @@ export function modelRouteEndpointRefusal(view) {
36
40
  ? `"${name}" (provider "anthropic", but no Anthropic route is mounted — ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN are unset — so it falls to the openai-compatible lane)`
37
41
  : `"${name}"`);
38
42
  }
39
- return unresolved.length > 0 ? routeEndpointMissingRefusal(unresolved) : undefined;
43
+ return unresolved.length > 0 ? { code: MODEL_ROUTE_ENDPOINT_MISSING_CODE, reason: routeEndpointMissingRefusal(unresolved) } : undefined;
40
44
  }
41
45
  function routeEndpointMissingRefusal(quotedNames) {
42
46
  const one = quotedNames.length === 1;
@@ -123,7 +123,7 @@ export declare const FAIL_OPEN_TAGS: {
123
123
  };
124
124
  readonly "server.approvals.park-row-blob-unreadable": {
125
125
  readonly cls: "F";
126
- readonly note: "S-700(`plugins/checkpoint-store-sql.ts` 的 `readParkRowFactsCell`):运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint` 列里寄存行的 **`pendingAction` 子树**(NP-3 / 7.100.0 起引擎内只取这一棵、整只快照不出库;只为 park 行事实 `requiresRealApproval` / `ruleOffersAbsence` / `mandated`(S-663 二段,core #1079)而读 —— 事实位的真源是 `approval-card.ts` 的 `ParkRowFacts`)**在场却不是一只对象**(JSON `null` 字面 / 串 / 数 —— 手改 / 迁移脚本改过)。子树**缺席**(SQL NULL:成员不在,或整只文档不是对象 —— 后者 `get()` 照旧拒;MariaDB 文本承载上的非 JSON 字节同样答 SQL NULL)⇒ 事实缺席、不计数。放行的最坏后果 = **这一行**的 park 行事实(`ParkRowFacts` 各位)缺席(展示/分诊材料;core 定性 display metadata,resume 的带子读 gate 自己的位),队列其余部分照常 —— 而这一行本来就 resume 不了(`get()` 对同一格拒)。不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 blob 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空),与同一 `rows.map` 里 `rule_suggestions` / `risk_descriptor` 两格同族同判。故 F 类;tag 与那两格**分开计**(哪一格在坏要读得出来)。计数非零 = 这一行的 decide 同样读不动(`get()` 对同一格拒),去按 session 查那一行。";
126
+ readonly note: "S-700(`plugins/checkpoint-store-sql.ts` 的 `readParkRowFactsCell`):运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint` 列里寄存行的 **`pendingAction` 子树**(NP-3 / 7.100.0 起引擎内只取这一棵、整只快照不出库;只为 park 行事实 `requiresRealApproval` / `ruleOffersAbsence` / `mandated`(S-663 二段,core #1079)/ `mandate`(S-721)/ `origin`(S-755)而读 —— 事实位的真源是 `approval-card.ts` 的 `ParkRowFacts`)**在场却不是一只对象**(JSON `null` 字面 / 串 / 数 —— 手改 / 迁移脚本改过)。子树**缺席**(SQL NULL:成员不在,或整只文档不是对象 —— 后者 `get()` 照旧拒;MariaDB 文本承载上的非 JSON 字节同样答 SQL NULL)⇒ 事实缺席、不计数。放行的最坏后果 = **这一行**的 park 行事实(`ParkRowFacts` 各位)缺席(展示/分诊材料;core 定性 display metadata,resume 的带子读 gate 自己的位),队列其余部分照常 —— 而这一行本来就 resume 不了(`get()` 对同一格拒)。不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 blob 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空),与同一 `rows.map` 里 `rule_suggestions` / `risk_descriptor` 两格同族同判。故 F 类;tag 与那两格**分开计**(哪一格在坏要读得出来)。计数非零 = 这一行的 decide 同样读不动(`get()` 对同一格拒),去按 session 查那一行。";
127
127
  };
128
128
  readonly "server.sql-json.checkpoint.pending-steer-queue-lossy": {
129
129
  readonly cls: "F";
@@ -287,7 +287,7 @@ export declare const FAIL_OPEN_TAGS: {
287
287
  };
288
288
  readonly "server.approval-card.closed-word-out-of-set": {
289
289
  readonly cls: "F";
290
- readonly note: "S-249(`approval-card.ts` 的边界窄读族;今天的唯一成员是 `readRuleOffersAbsence`):活卡帧 / `card_json` / (S-700 起)运维队列行(`parkRowFacts` 读寄存行上的 park 孪生座;队列是热读面 ⇒ 同一行每读一次计一次)的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40);数 / 布尔等原始值原样,对象 / 数组 / 函数 / null 只报形名(`<object>` / `<array>` / `<function>` / `<null>`,S-700 codex r1:格式化从不调用值自己的转换方法 —— 持久层里一格 `{\"toString\":null}` 曾让诊断本身抛出、打掉整只队列)。";
290
+ readonly note: "S-249(`approval-card.ts` 的边界窄读族;今天两个成员:`readRuleOffersAbsence`,与 S-721 起的 `readClosedWordSeat` —— 一座一只 core 成员筛,座是参数:`mandate`(core 7.32.0 #1092,活卡帧 / 活挂起行 / 运维队列行同一只,闭集筛 = core `isPersistedRuleMandate`)与 S-755 起的 `origin`(运维队列行读 park 行孪生座,闭集筛 = core `isAskOrigin`,与 inbox 行 core 投影处同一只筛);detail 前缀 `ruleOffersAbsence=` / `mandate=` / `origin=` 判别是哪一座):活卡帧 / `card_json` / (S-700 起)运维队列行(`parkRowFacts` 读寄存行上的 park 孪生座;队列是热读面 ⇒ 同一行每读一次计一次)的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40);数 / 布尔等原始值原样,对象 / 数组 / 函数 / null 只报形名(`<object>` / `<array>` / `<function>` / `<null>`,S-700 codex r1:格式化从不调用值自己的转换方法 —— 持久层里一格 `{\"toString\":null}` 曾让诊断本身抛出、打掉整只队列)。";
291
291
  };
292
292
  readonly "server.pg-pool.idle-client-error": {
293
293
  readonly cls: "F";
@@ -114,7 +114,7 @@ export const FAIL_OPEN_TAGS = {
114
114
  },
115
115
  "server.approvals.park-row-blob-unreadable": {
116
116
  cls: "F",
117
- note: "S-700(`plugins/checkpoint-store-sql.ts` 的 `readParkRowFactsCell`):运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint` 列里寄存行的 **`pendingAction` 子树**(NP-3 / 7.100.0 起引擎内只取这一棵、整只快照不出库;只为 park 行事实 `requiresRealApproval` / `ruleOffersAbsence` / `mandated`(S-663 二段,core #1079)而读 —— 事实位的真源是 `approval-card.ts` 的 `ParkRowFacts`)**在场却不是一只对象**(JSON `null` 字面 / 串 / 数 —— 手改 / 迁移脚本改过)。子树**缺席**(SQL NULL:成员不在,或整只文档不是对象 —— 后者 `get()` 照旧拒;MariaDB 文本承载上的非 JSON 字节同样答 SQL NULL)⇒ 事实缺席、不计数。放行的最坏后果 = **这一行**的 park 行事实(`ParkRowFacts` 各位)缺席(展示/分诊材料;core 定性 display metadata,resume 的带子读 gate 自己的位),队列其余部分照常 —— 而这一行本来就 resume 不了(`get()` 对同一格拒)。不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 blob 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空),与同一 `rows.map` 里 `rule_suggestions` / `risk_descriptor` 两格同族同判。故 F 类;tag 与那两格**分开计**(哪一格在坏要读得出来)。计数非零 = 这一行的 decide 同样读不动(`get()` 对同一格拒),去按 session 查那一行。",
117
+ note: "S-700(`plugins/checkpoint-store-sql.ts` 的 `readParkRowFactsCell`):运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint` 列里寄存行的 **`pendingAction` 子树**(NP-3 / 7.100.0 起引擎内只取这一棵、整只快照不出库;只为 park 行事实 `requiresRealApproval` / `ruleOffersAbsence` / `mandated`(S-663 二段,core #1079)/ `mandate`(S-721)/ `origin`(S-755)而读 —— 事实位的真源是 `approval-card.ts` 的 `ParkRowFacts`)**在场却不是一只对象**(JSON `null` 字面 / 串 / 数 —— 手改 / 迁移脚本改过)。子树**缺席**(SQL NULL:成员不在,或整只文档不是对象 —— 后者 `get()` 照旧拒;MariaDB 文本承载上的非 JSON 字节同样答 SQL NULL)⇒ 事实缺席、不计数。放行的最坏后果 = **这一行**的 park 行事实(`ParkRowFacts` 各位)缺席(展示/分诊材料;core 定性 display metadata,resume 的带子读 gate 自己的位),队列其余部分照常 —— 而这一行本来就 resume 不了(`get()` 对同一格拒)。不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 blob 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空),与同一 `rows.map` 里 `rule_suggestions` / `risk_descriptor` 两格同族同判。故 F 类;tag 与那两格**分开计**(哪一格在坏要读得出来)。计数非零 = 这一行的 decide 同样读不动(`get()` 对同一格拒),去按 session 查那一行。",
118
118
  },
119
119
  "server.sql-json.checkpoint.pending-steer-queue-lossy": {
120
120
  cls: "F",
@@ -260,7 +260,7 @@ export const FAIL_OPEN_TAGS = {
260
260
  "server.redact.ppk-eat-to-end": { cls: "F", note: "S-107 第三十二轮重扫:PuTTY .ppk 容器扫描器(redact.ts redactPpkContainers)在「无合格终止 / 收口后同容器范围再见私钥字段 / 收口后到下一版本头之间夹非空白」三形下从头**吃到输入末尾**(fail-closed 取证丢弃)——终态 errorMessage 座上=整条错误文案变成 «redacted:private-key»,与 redactions_applied_total{private-key} 的诚实替换同形不可分,故要有自己的计数/探针;影子扫不计;计数=多少次整段吃尾。" },
261
261
  "server.approval-card.closed-word-out-of-set": {
262
262
  cls: "F",
263
- note: "S-249(`approval-card.ts` 的边界窄读族;今天的唯一成员是 `readRuleOffersAbsence`):活卡帧 / `card_json` / (S-700 起)运维队列行(`parkRowFacts` 读寄存行上的 park 孪生座;队列是热读面 ⇒ 同一行每读一次计一次)的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40);数 / 布尔等原始值原样,对象 / 数组 / 函数 / null 只报形名(`<object>` / `<array>` / `<function>` / `<null>`,S-700 codex r1:格式化从不调用值自己的转换方法 —— 持久层里一格 `{\"toString\":null}` 曾让诊断本身抛出、打掉整只队列)。",
263
+ note: "S-249(`approval-card.ts` 的边界窄读族;今天两个成员:`readRuleOffersAbsence`,与 S-721 起的 `readClosedWordSeat` —— 一座一只 core 成员筛,座是参数:`mandate`(core 7.32.0 #1092,活卡帧 / 活挂起行 / 运维队列行同一只,闭集筛 = core `isPersistedRuleMandate`)与 S-755 起的 `origin`(运维队列行读 park 行孪生座,闭集筛 = core `isAskOrigin`,与 inbox 行 core 投影处同一只筛);detail 前缀 `ruleOffersAbsence=` / `mandate=` / `origin=` 判别是哪一座):活卡帧 / `card_json` / (S-700 起)运维队列行(`parkRowFacts` 读寄存行上的 park 孪生座;队列是热读面 ⇒ 同一行每读一次计一次)的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40);数 / 布尔等原始值原样,对象 / 数组 / 函数 / null 只报形名(`<object>` / `<array>` / `<function>` / `<null>`,S-700 codex r1:格式化从不调用值自己的转换方法 —— 持久层里一格 `{\"toString\":null}` 曾让诊断本身抛出、打掉整只队列)。",
264
264
  },
265
265
  "server.pg-pool.idle-client-error": {
266
266
  cls: "F",
@@ -27,6 +27,18 @@ export declare const SECRET_ENV_SCRUB_SITES: {
27
27
  readonly note: "宿主车道本地语言服务器子进程 env(core 7.30.1 #1073 的 `NodeLspManagerOptions.env`)—— 语言服务器会执行仓里的代码(构建脚本/插件/工作区配置),一次泄漏等于把本进程的凭据交给仓";
28
28
  readonly remedy: `LSP_ENV_ALLOW=NAME,NAME \u70B9\u540D\u653E\u884C(\u539F\u6837\u4EA4\u7ED9\u8BED\u8A00\u670D\u52A1\u5668,\u90E8\u7F72\u65B9\u663E\u5F0F\u627F\u62C5;${string});\u503C\u4E0D\u8FDB\u65E5\u5FD7`;
29
29
  };
30
+ readonly "server.center-plugins.git-clone": {
31
+ readonly note: "center 下发插件的 git clone / fetch 子进程 env —— 克隆的是 center 声明里的第三方仓,git 与它拉起的 transport / askpass 以本进程身份运行";
32
+ readonly remedy: null;
33
+ };
34
+ readonly "server.leader.git-push": {
35
+ readonly note: "leader 控制面把集成结果推回 durable remote 的 git 子进程 env —— 这条腿保留宿主 git 配置(写凭据可能走宿主 credential helper),helper / hook 以本进程身份运行";
36
+ readonly remedy: null;
37
+ };
38
+ readonly "server.bake-runner.spawn-env": {
39
+ readonly note: "bake-runner(构建宿主守护进程)交给 build.sh 与主机命令(git fetch / docker push / docker image inspect / df)的 env —— 配方脚本在构建宿主上以 runner 身份运行,一次泄漏等于把 runner 的 BAKE_RUNNER_TOKEN 交给配方";
40
+ readonly remedy: "BAKE_ENV_ALLOW=NAME,NAME 点名放行(原样交给 build.sh 与主机命令,部署方显式承担;bake-runner 进程的旋钮,没有多租户姿态);值不进日志";
41
+ };
30
42
  };
31
43
  /** 词表键推导的闭集类型。 */
32
44
  export type SecretEnvScrubSite = keyof typeof SECRET_ENV_SCRUB_SITES;
@@ -13,6 +13,18 @@ export const SECRET_ENV_SCRUB_SITES = {
13
13
  note: "宿主车道本地语言服务器子进程 env(core 7.30.1 #1073 的 `NodeLspManagerOptions.env`)—— 语言服务器会执行仓里的代码(构建脚本/插件/工作区配置),一次泄漏等于把本进程的凭据交给仓",
14
14
  remedy: `LSP_ENV_ALLOW=NAME,NAME 点名放行(原样交给语言服务器,部署方显式承担;${describeAllowlistPostures()});值不进日志`,
15
15
  },
16
+ "server.center-plugins.git-clone": {
17
+ note: "center 下发插件的 git clone / fetch 子进程 env —— 克隆的是 center 声明里的第三方仓,git 与它拉起的 transport / askpass 以本进程身份运行",
18
+ remedy: null,
19
+ },
20
+ "server.leader.git-push": {
21
+ note: "leader 控制面把集成结果推回 durable remote 的 git 子进程 env —— 这条腿保留宿主 git 配置(写凭据可能走宿主 credential helper),helper / hook 以本进程身份运行",
22
+ remedy: null,
23
+ },
24
+ "server.bake-runner.spawn-env": {
25
+ note: "bake-runner(构建宿主守护进程)交给 build.sh 与主机命令(git fetch / docker push / docker image inspect / df)的 env —— 配方脚本在构建宿主上以 runner 身份运行,一次泄漏等于把 runner 的 BAKE_RUNNER_TOKEN 交给配方",
26
+ remedy: "BAKE_ENV_ALLOW=NAME,NAME 点名放行(原样交给 build.sh 与主机命令,部署方显式承担;bake-runner 进程的旋钮,没有多租户姿态);值不进日志",
27
+ },
16
28
  };
17
29
  const SCRUB_COUNTER = "secret_env_scrubbed_total";
18
30
  const DISCLOSED_CAP = 512;