@sema-agent/server 7.98.0 → 7.100.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 (153) hide show
  1. package/MIGRATION.md +19 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +86 -60
  5. package/deploy/sema-up/lib/envf.sh +110 -42
  6. package/deploy/sema-up/sema-up.sh +76 -34
  7. package/dist/approval-ask-machine.d.ts +10 -0
  8. package/dist/approval-ask-machine.js +3 -0
  9. package/dist/approval-card.d.ts +74 -1
  10. package/dist/approval-card.js +41 -8
  11. package/dist/approval.d.ts +44 -20
  12. package/dist/approval.js +14 -6
  13. package/dist/boot/config-center.d.ts +16 -8
  14. package/dist/boot/config-center.js +206 -103
  15. package/dist/boot/execution-env.d.ts +1 -2
  16. package/dist/boot/execution-env.js +2 -2
  17. package/dist/boot/leader.js +12 -9
  18. package/dist/boot/memory-consolidation.d.ts +38 -0
  19. package/dist/boot/memory-consolidation.js +35 -0
  20. package/dist/boot/org-memory.d.ts +2 -1
  21. package/dist/boot/org-memory.js +9 -5
  22. package/dist/boot/resolve-spec.d.ts +0 -1
  23. package/dist/boot/resolve-spec.js +4 -3
  24. package/dist/boot/runner-deps.d.ts +2 -2
  25. package/dist/boot/runtime-caps.js +4 -4
  26. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  27. package/dist/boot/stage-06-runners.d.ts +2 -2
  28. package/dist/boot/stage-06-runners.js +3 -3
  29. package/dist/boot/stage-07-capability-layer.d.ts +2 -3
  30. package/dist/boot/stage-07-capability-layer.js +6 -6
  31. package/dist/boot/stage-08-reapers.d.ts +3 -4
  32. package/dist/boot/stage-08-reapers.js +2 -2
  33. package/dist/boot/stage-09-leader.d.ts +2 -3
  34. package/dist/boot/stage-10-http-server.d.ts +2 -3
  35. package/dist/boot/stage-10-http-server.js +2 -2
  36. package/dist/capabilities/center-plugins.d.ts +14 -2
  37. package/dist/capabilities/center-plugins.js +13 -5
  38. package/dist/capabilities/center-prompts.d.ts +3 -0
  39. package/dist/capabilities/center-prompts.js +1 -1
  40. package/dist/capabilities/hands-lane.js +2 -1
  41. package/dist/capabilities/scenarios.js +18 -5
  42. package/dist/capabilities/tool-defer.d.ts +2 -2
  43. package/dist/center-credential.d.ts +112 -0
  44. package/dist/center-credential.js +131 -0
  45. package/dist/config-catalog.d.ts +2 -2
  46. package/dist/config-catalog.js +15 -15
  47. package/dist/config-center/apply-effective.d.ts +34 -39
  48. package/dist/config-center/apply-effective.js +100 -64
  49. package/dist/config-center/apply-ledger.d.ts +35 -16
  50. package/dist/config-center/apply-ledger.js +25 -18
  51. package/dist/config-center/center-request.d.ts +60 -0
  52. package/dist/config-center/center-request.js +71 -0
  53. package/dist/config-center/facade.d.ts +11 -5
  54. package/dist/config-center/facade.js +1 -1
  55. package/dist/config-center/hot-keys-registry.d.ts +11 -4
  56. package/dist/config-center/hot-keys-registry.js +11 -7
  57. package/dist/config-center/http-client.d.ts +17 -9
  58. package/dist/config-center/http-client.js +27 -36
  59. package/dist/config-center/restart-signal.d.ts +21 -14
  60. package/dist/config-center/restart-signal.js +16 -22
  61. package/dist/config-center/skills-mcp.d.ts +4 -1
  62. package/dist/config-center/skills-mcp.js +2 -2
  63. package/dist/config-center/types.d.ts +3 -3
  64. package/dist/config-invariants.d.ts +2 -2
  65. package/dist/config-invariants.js +0 -8
  66. package/dist/config-lkg.d.ts +47 -8
  67. package/dist/config-lkg.js +107 -20
  68. package/dist/config-provider.d.ts +8 -7
  69. package/dist/config-provider.js +5 -5
  70. package/dist/config-types.d.ts +30 -11
  71. package/dist/config.js +11 -13
  72. package/dist/cross-session-settings.js +1 -1
  73. package/dist/fleet-client.d.ts +8 -8
  74. package/dist/fleet-client.js +7 -7
  75. package/dist/fleet-lease.d.ts +11 -11
  76. package/dist/fleet-lease.js +17 -13
  77. package/dist/host-lsp-manager.d.ts +29 -0
  78. package/dist/host-lsp-manager.js +14 -0
  79. package/dist/hosted-posture.d.ts +49 -93
  80. package/dist/hosted-posture.js +1 -25
  81. package/dist/http/active-run-conflict.d.ts +2 -12
  82. package/dist/http/active-run-conflict.js +1 -6
  83. package/dist/http/admission.js +8 -0
  84. package/dist/http/resume-legs.d.ts +1 -1
  85. package/dist/http/resume-legs.js +14 -10
  86. package/dist/http/route-ctx.d.ts +2 -2
  87. package/dist/http/routes/approvals-assistant.d.ts +1 -1
  88. package/dist/http/routes/approvals-assistant.js +2 -2
  89. package/dist/http/routes/memory-compliance.js +5 -0
  90. package/dist/http/routes/memory-origin.js +5 -0
  91. package/dist/http/routes/sessions.js +6 -0
  92. package/dist/http/routes/tasks.js +7 -6
  93. package/dist/http/server.d.ts +6 -5
  94. package/dist/http/wire-types.d.ts +6 -3
  95. package/dist/leader/wire.d.ts +20 -12
  96. package/dist/leader/wire.js +7 -6
  97. package/dist/memory-layer-preflight.d.ts +46 -0
  98. package/dist/memory-layer-preflight.js +121 -0
  99. package/dist/memory-operator-faces.js +17 -1
  100. package/dist/observability/corrupt-read-seat.d.ts +21 -4
  101. package/dist/observability/corrupt-read-seat.js +31 -0
  102. package/dist/observability/fail-open.d.ts +8 -4
  103. package/dist/observability/fail-open.js +8 -4
  104. package/dist/observability/run-terminal.js +1 -0
  105. package/dist/observability/secret-env-scrub.d.ts +3 -26
  106. package/dist/observability/secret-env-scrub.js +3 -2
  107. package/dist/orchestration/workflow-completion-inbox.d.ts +31 -23
  108. package/dist/orchestration/workflow-completion-inbox.js +8 -3
  109. package/dist/plugins/approval-ask-store-file.d.ts +20 -2
  110. package/dist/plugins/approval-ask-store-file.js +21 -3
  111. package/dist/plugins/approval-ask-store-memory.d.ts +2 -0
  112. package/dist/plugins/approval-ask-store-memory.js +39 -13
  113. package/dist/plugins/approval-ask-store-sql.d.ts +24 -5
  114. package/dist/plugins/approval-ask-store-sql.js +27 -3
  115. package/dist/plugins/checkpoint-store-sql.d.ts +9 -3
  116. package/dist/plugins/checkpoint-store-sql.js +19 -2
  117. package/dist/plugins/local-checkpoint-store.js +2 -0
  118. package/dist/plugins/mailbox-store-sql.d.ts +7 -5
  119. package/dist/plugins/mailbox-store-sql.js +6 -4
  120. package/dist/plugins/permission-rule-store-file.js +3 -2
  121. package/dist/plugins/remote-env-host.d.ts +3 -1
  122. package/dist/plugins/remote-env-host.js +3 -2
  123. package/dist/plugins/sql-json-column.d.ts +16 -3
  124. package/dist/plugins/sql-json-column.js +15 -4
  125. package/dist/plugins/web-search.d.ts +42 -22
  126. package/dist/plugins/web-search.js +141 -49
  127. package/dist/project-memory.d.ts +28 -1
  128. package/dist/project-memory.js +22 -22
  129. package/dist/run-local.js +6 -7
  130. package/dist/runs.js +6 -5
  131. package/dist/runtime-caps-resolver.d.ts +8 -8
  132. package/dist/runtime-caps-resolver.js +16 -14
  133. package/dist/runtime-governance.d.ts +50 -3
  134. package/dist/runtime-governance.js +5 -0
  135. package/dist/sealed-key.js +3 -7
  136. package/dist/server-secret-env.d.ts +43 -0
  137. package/dist/server-secret-env.js +18 -0
  138. package/dist/task-settings.d.ts +1 -1
  139. package/dist/tool-approval.d.ts +144 -9
  140. package/dist/tool-approval.js +74 -18
  141. package/dist/trace/core-keyset-guard.d.ts +5 -5
  142. package/dist/trace/ledger-sink.js +2 -2
  143. package/dist/trace/project.d.ts +13 -0
  144. package/dist/trace/project.js +22 -5
  145. package/dist/trace/projection-drop.d.ts +2 -0
  146. package/dist/trace/projection-drop.js +6 -0
  147. package/dist/trace/sema-provenance.d.ts +4 -1
  148. package/dist/trace/sema-provenance.js +1 -1
  149. package/dist/trace/task-notification-facets.d.ts +65 -0
  150. package/dist/trace/task-notification-facets.js +31 -0
  151. package/dist/trace/wire-projection-faces.d.ts +3 -3
  152. package/dist/trace/wire-projection-faces.js +3 -3
  153. package/package.json +5 -4
@@ -55,7 +55,7 @@ export async function applyCatalogToSource(source, next, prev, log, phase) {
55
55
  log.warn("center_prompts_catalog_apply_failed", { phase, code: r.code, reason: r.reason, note: "validate passed but the source apply failed (disk/state) — declaration axis and built-ins unaffected; candidate unchanged; the transition retries on the next refresh tick" });
56
56
  return false;
57
57
  }
58
- if (prev?.catalog || source.current?.() !== undefined) {
58
+ if (prev?.catalog || source.current?.() !== undefined || source.persistedActive?.() === true) {
59
59
  await source.applyDisabled(`${phase}:catalog-absent`);
60
60
  log.info("center_prompts_catalog_disabled", { phase, note: "catalog axis withdrawn — core candidate cleared (does not revive offline)" });
61
61
  }
@@ -18,8 +18,9 @@ export const HANDS_BAND_TOOL_NAMES = Object.freeze([
18
18
  "Monitor",
19
19
  "EnterWorktree",
20
20
  "ExitWorktree",
21
+ "ListAgents",
21
22
  ].filter((n, i, all) => !RETIRED_TOOL_NAMES.has(n) && all.indexOf(n) === i));
22
- export const HANDS_DUAL_USE_TOOL_NAMES = Object.freeze(["TaskOutput", "TaskStop", "SendMessage", "AgentTranscript"]);
23
+ export const HANDS_DUAL_USE_TOOL_NAMES = Object.freeze(["TaskOutput", "TaskStop", "SendMessage", "AgentTranscript", "ListAgents"]);
23
24
  export const HANDS_EXCLUSIVE_TOOL_NAMES = Object.freeze(HANDS_BAND_TOOL_NAMES.filter((n) => !HANDS_DUAL_USE_TOOL_NAMES.includes(n)));
24
25
  export function handsDenyPolicy() {
25
26
  return createAllowDenyPolicy({ deny: [...HANDS_EXCLUSIVE_TOOL_NAMES] });
@@ -5,7 +5,7 @@ import { parseRepo, repoToolsFor } from "./repo-tools.js";
5
5
  import { createCouncilTool, clampRounds } from "./code-review-council.js";
6
6
  import { stablePrompt } from "./prompt.js";
7
7
  import { createDiscussionTools, discussionCoordinatorPrompt, getTeam, teamNames } from "./team.js";
8
- import { createWebSearchBackend, webSearchConfigFromSettings } from "../plugins/web-search.js";
8
+ import { createWebSearchBackend, judgeRequestWebSearch } from "../plugins/web-search.js";
9
9
  import { skillsForScenario } from "./skills.js";
10
10
  import { pickHandsRunner } from "./hands-lane.js";
11
11
  export { SEMA_IDENTITY } from "./prompts/identity.js";
@@ -31,12 +31,25 @@ export function codePromptProvider(brand) {
31
31
  function subRunnerFor(deps, hands) {
32
32
  return pickHandsRunner(hands, { full: deps.subRunner, handsless: deps.handslessSubRunner });
33
33
  }
34
+ function requestWebSearchBackend(verdict, deployment, logger) {
35
+ switch (verdict.kind) {
36
+ case "absent":
37
+ return deployment;
38
+ case "honored":
39
+ return createWebSearchBackend(verdict.config);
40
+ case "malformed":
41
+ logger?.warn("web_search_settings_unusable", {
42
+ issue: verdict.issue,
43
+ note: "this request carries a settings.webSearch block that is not well-formed (stored before the submit-time check, or assembled without it); WebSearch is not mounted for this call rather than sending the searches to a different backend",
44
+ });
45
+ return undefined;
46
+ }
47
+ }
34
48
  export function buildScenarios(deps) {
35
49
  const defaultScenario = (req, _principal, ctx) => {
36
- const reqWebSearch = deps.requirePrincipal !== true
37
- ? webSearchConfigFromSettings(req?.settings?.webSearch)
38
- : undefined;
39
- const webSearch = reqWebSearch ? createWebSearchBackend(reqWebSearch) : deps.webSearch;
50
+ const webSearch = requestWebSearchBackend(deps.requirePrincipal !== true
51
+ ? judgeRequestWebSearch(req?.settings?.webSearch)
52
+ : { kind: "absent" }, deps.webSearch, deps.logger);
40
53
  return {
41
54
  tools: [
42
55
  nowTool(),
@@ -18,8 +18,8 @@
18
18
  * 在本仓缺省场景下那会连 Agent 与 Task 四件一起卷进候选(与 CC 姿态相反),所以我们**刻意不开**;
19
19
  * 🔴 该判断在 core 5.40.0 / 7.15.0 上**逐字仍成立**(亲验):两次收窄动的都是「候选集是谁」,而
20
20
  * Agent / Task 四件是 **caller spec 工具**,收窄后照样在候选池里 —— 不开 auto 的理由没有被消解。
21
- * 我方**从不接线**这个座:全树零 `RunnerDeps.deferMode` 赋值(`deferModelPlane` 是 config-center 的
22
- * 同形近名,与本轴无关)。机器钉见 test/tool-defer.test.ts 的 [ref] 件⑤ 格 —— 这条「零接线」正是本仓
21
+ * 我方**从不接线**这个座:全树零 `RunnerDeps.deferMode` 赋值(config-center 曾有同形近名 `deferModelPlane`,
22
+ * 与本轴无关,已于 S-668 波 0 退役)。机器钉见 test/tool-defer.test.ts 的 [ref] 件⑤ 格 —— 这条「零接线」正是本仓
23
23
  * 在两个 core 世代之间行为逐字不变的**全部**依据,所以它值一根钉而不只是一句注。
24
24
  * - per-tool `ToolSpec.defer:true` 是本 helper 施加的那一位。**7.15.0 起它是一次「进候选集」的声明,
25
25
  * 不再是「一定占位」的保证** —— 长尾集单独一只(下面实测 ≈842 字符)远在阈值之下,于是这根旋钮在
@@ -0,0 +1,112 @@
1
+ /** 凭证种类闭集。新增一个种类 = 本文件与 `config-center/center-request.ts` 的穷举 switch 当场编译红。 */
2
+ export type CenterCredentialKind = "pull-token" | "user-jwt" | "none";
3
+ /** 凭证在哪里声明(env 形带值 —— 只在本模块内流转)。 */
4
+ export type CenterCredentialDeclaration = {
5
+ readonly from: "env";
6
+ readonly token: string;
7
+ } | {
8
+ readonly from: "file";
9
+ readonly path: string;
10
+ } | {
11
+ readonly from: "absent";
12
+ };
13
+ /** 声明形的**非秘密**投影(进日志 / 拒因 / 未来的 caps 读面):env 形不带值。 */
14
+ export type CenterCredentialOrigin = {
15
+ readonly from: "env";
16
+ } | {
17
+ readonly from: "file";
18
+ readonly path: string;
19
+ } | {
20
+ readonly from: "absent";
21
+ };
22
+ /** 一次取值的结果。`ok:false` 只有文件形会出现(env / absent 形的取值恒成功)。 */
23
+ export type CenterCredentialRead = {
24
+ readonly ok: true;
25
+ readonly kind: "pull-token";
26
+ readonly bearer: string;
27
+ readonly identity: string;
28
+ readonly workerScoped: boolean;
29
+ } | {
30
+ readonly ok: true;
31
+ readonly kind: "user-jwt";
32
+ readonly bearer: string;
33
+ readonly identity: string;
34
+ } | {
35
+ readonly ok: true;
36
+ readonly kind: "none";
37
+ readonly identity: string;
38
+ } | {
39
+ readonly ok: false;
40
+ readonly reason: string;
41
+ };
42
+ /** 健康态:最近一次取值的结果(caps `leaseHealth` 之类的读面读它,不自己再判)。 */
43
+ export type CenterCredentialHealth = {
44
+ readonly state: "ok";
45
+ } | {
46
+ readonly state: "unreadable";
47
+ readonly reason: string;
48
+ readonly since: number;
49
+ readonly failures: number;
50
+ };
51
+ export interface CenterCredentialSource {
52
+ /** 声明形(非秘密):`{from:"file", path}` 进日志与拒因用。 */
53
+ readonly origin: CenterCredentialOrigin;
54
+ /** 装配判据:声明了凭证(env 值非空 / 文件路径在场)。**按声明装,不按内容装** —— 文件此刻不可读也算声明了。 */
55
+ readonly declared: boolean;
56
+ /** 每次出站请求调用一次;文件形每次都读文件。 */
57
+ read(): CenterCredentialRead;
58
+ health(): CenterCredentialHealth;
59
+ }
60
+ /** 凭证文件此刻不可读 / 为空。中心请求装配点(`center-request.ts`)取值失败时抛它,各消费方按自己的
61
+ * 失败臂处置(拉配置保 LKG、caps fail-closed、租约走「鉴权失败」响亮臂 …),**不发一个空 bearer 出去**。 */
62
+ export declare class CenterCredentialUnavailableError extends Error {
63
+ readonly reason: string;
64
+ constructor(reason: string);
65
+ }
66
+ export declare const CENTER_TOKEN_ENV = "SEMA_REGISTRY_TOKEN";
67
+ export declare const CENTER_TOKEN_FILE_ENV = "SEMA_REGISTRY_TOKEN_FILE";
68
+ /**
69
+ * 读两个声明键。两个都非空 ⇒ throw(boot 拒启,拒因点名二选一)。空串 = 未设(与 7.98.0 之前
70
+ * `SEMA_REGISTRY_TOKEN || ""` 的读法一致)。文件路径在这里解析成绝对路径(之后 cwd 变了也读同一个文件)。
71
+ */
72
+ export declare function readCenterCredentialDeclaration(env?: NodeJS.ProcessEnv): CenterCredentialDeclaration;
73
+ /** 拉取令牌形的身份 = worker(拉取令牌代表 worker,不是人)。种类判别与 formatVersion 1 LKG 的一次性迁移
74
+ * (`config-lkg.ts` `migrateV1Lkg`:v1 信封只记 worker,只有这一形的身份推得出来)共用这一只。 */
75
+ export declare function pullTokenIdentity(worker: string | undefined): string;
76
+ /**
77
+ * 凭证来源工厂(活对象:文件形每次取值读盘并记健康态)。`worker` = `SEMA_REGISTRY_WORKER`(拉取令牌形的身份)。
78
+ * `readFile` 只为测试注入。
79
+ */
80
+ export declare function createCenterCredentialSource(declaration: CenterCredentialDeclaration, worker: string | undefined, deps?: {
81
+ readFile?: (path: string) => string;
82
+ now?: () => number;
83
+ }): CenterCredentialSource;
84
+ /** 需要作用域 worker 的拉取令牌(`wpt_<self>`)却没配 `SEMA_REGISTRY_WORKER` ⇒ 中心对 per-principal caps
85
+ * 逐次 403(caps fail-closed 拒全部 workflow)。boot 期一次性诊断用;判据只读本模块的种类判别。 */
86
+ export declare function scopedTokenNeedsWorker(read: CenterCredentialRead, worker: string | undefined): boolean;
87
+ /**
88
+ * [ref] E-15 —— 按「worker + 凭证身份」分箱的**唯一键形**(LKG 文件名、prompt 活动状态目录共用)。
89
+ * 两段都取摘要:worker 名 / 身份任意字节都成单段路径,无 dot-segment,无撞形(codex-5 当年给 prompt 状态
90
+ * 目录定的姿势,本批推广到 LKG)。
91
+ */
92
+ export declare function centerStateBin(worker: string | undefined, identity: string): string;
93
+ /** 此刻凭证的身份(每次现读);文件形此刻不可读 ⇒ `undefined`(身份未知)。「我现在是谁」只有这一只读法。 */
94
+ export declare function credentialIdentityNow(source: CenterCredentialSource): string | undefined;
95
+ /**
96
+ * [ref] E-15 ② —— 一次中心往返的答复**仍属于此刻的凭证**:发出它所用的身份 = 此刻的身份。
97
+ * 拉配置(`boot/config-center.ts` `pullEffective`)与两条 `?principal=` 视图(`config-center/http-client.ts`)
98
+ * 都只问这一只;不成立 ⇒ 答复丢弃,当这次拉取失败(各走各的失败臂)。
99
+ */
100
+ export declare function answerStillCurrent(source: CenterCredentialSource, usedIdentity: string | undefined): usedIdentity is string;
101
+ /**
102
+ * [ref] E-15 · 内存面 —— 身份绑定的进程内状态(per-principal caps 缓存与在飞表、org 记忆目录)的**唯一分箱规则**,
103
+ * 与盘上的 {@link centerStateBin} 同一条规则的两面(7.99.0 合并复审 F2:此前两处缓存只按 principal 键)。
104
+ * · 箱 = 取箱那一刻的凭证身份(惰建)。凭证此刻不可读 ⇒ 「身份未知」箱。
105
+ * · 每个箱只经**钉在它身份上**的凭证来源对中心说话(`make` 拿到的那只):此刻的凭证已换了人 / 不可读 ⇒ 这一箱的
106
+ * 请求不发(取值失败 ⇒ `CenterCredentialUnavailableError`,消费方走自己的失败臂)。身份未知箱因此从不收任何
107
+ * 中心答复;缓存 / 在飞 / 条件请求的校验子全在箱里,换了人结构上够不着上一个人的。
108
+ * · 请求发出之后才换人:答复到达时由 {@link answerStillCurrent} 判丢弃(请求装配侧,见 http-client.ts)。
109
+ * 旧身份的箱不淘汰(量级 = 进程生命期内出现过的身份数):换回来的身份在 TTL 内照常命中它自己的缓存。
110
+ */
111
+ export declare function createIdentityBins<T>(source: CenterCredentialSource, make: (pinned: CenterCredentialSource) => T): () => T;
112
+ //# sourceMappingURL=center-credential.d.ts.map
@@ -0,0 +1,131 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { createHash } from "node:crypto";
3
+ import { resolve as resolvePath } from "node:path";
4
+ import { looksLikeJwt } from "@sema-agent/settings-schema/api/auth-bridge";
5
+ export class CenterCredentialUnavailableError extends Error {
6
+ reason;
7
+ constructor(reason) {
8
+ super(`center credential unavailable: ${reason}`);
9
+ this.reason = reason;
10
+ this.name = "CenterCredentialUnavailableError";
11
+ }
12
+ }
13
+ export const CENTER_TOKEN_ENV = "SEMA_REGISTRY_TOKEN";
14
+ export const CENTER_TOKEN_FILE_ENV = "SEMA_REGISTRY_TOKEN_FILE";
15
+ export function readCenterCredentialDeclaration(env = process.env) {
16
+ const token = env.SEMA_REGISTRY_TOKEN ?? "";
17
+ const file = env.SEMA_REGISTRY_TOKEN_FILE ?? "";
18
+ if (token !== "" && file !== "") {
19
+ throw new Error(`${CENTER_TOKEN_ENV} and ${CENTER_TOKEN_FILE_ENV} are both set — pick exactly one: ${CENTER_TOKEN_ENV} carries the credential value, ` +
20
+ `${CENTER_TOKEN_FILE_ENV} names a file re-read on every request (rotation without restart). Two writers on one credential is a guess, not a config.`);
21
+ }
22
+ if (file !== "")
23
+ return { from: "file", path: resolvePath(file) };
24
+ if (token !== "")
25
+ return { from: "env", token };
26
+ return { from: "absent" };
27
+ }
28
+ const digest16 = (s) => createHash("sha256").update(s, "utf8").digest("hex").slice(0, 16);
29
+ function jwtSubject(bearer) {
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(Buffer.from(bearer.split(".")[1] ?? "", "base64url").toString("utf8"));
33
+ }
34
+ catch (err) {
35
+ return { missing: `payload is not JSON (${err instanceof Error ? err.name : "parse error"})` };
36
+ }
37
+ if (parsed === null || typeof parsed !== "object" || !("sub" in parsed))
38
+ return { missing: "no sub claim" };
39
+ return typeof parsed.sub === "string" && parsed.sub !== "" ? { sub: parsed.sub } : { missing: "sub is not a non-empty string" };
40
+ }
41
+ function classify(bearer, worker) {
42
+ if (looksLikeJwt(bearer)) {
43
+ const s = jwtSubject(bearer);
44
+ return { ok: true, kind: "user-jwt", bearer, identity: "sub" in s ? `user-jwt:${digest16(s.sub)}` : `user-jwt-opaque:${digest16(bearer)}` };
45
+ }
46
+ return { ok: true, kind: "pull-token", bearer, identity: pullTokenIdentity(worker), workerScoped: bearer.startsWith("wpt_") };
47
+ }
48
+ export function pullTokenIdentity(worker) {
49
+ return `pull-token:${worker ?? "global"}`;
50
+ }
51
+ const NONE = { ok: true, kind: "none", identity: "none" };
52
+ export function createCenterCredentialSource(declaration, worker, deps = {}) {
53
+ switch (declaration.from) {
54
+ case "absent":
55
+ return { origin: declaration, declared: false, read: () => NONE, health: () => ({ state: "ok" }) };
56
+ case "env": {
57
+ const fixed = classify(declaration.token, worker);
58
+ return { origin: { from: "env" }, declared: true, read: () => fixed, health: () => ({ state: "ok" }) };
59
+ }
60
+ case "file": {
61
+ const readFile = deps.readFile ?? ((p) => readFileSync(p, "utf8"));
62
+ const now = deps.now ?? Date.now;
63
+ let health = { state: "ok" };
64
+ const fail = (reason) => {
65
+ health = health.state === "unreadable" ? { ...health, reason, failures: health.failures + 1 } : { state: "unreadable", reason, since: now(), failures: 1 };
66
+ return { ok: false, reason };
67
+ };
68
+ return {
69
+ origin: declaration,
70
+ declared: true,
71
+ read() {
72
+ let text;
73
+ try {
74
+ text = readFile(declaration.path);
75
+ }
76
+ catch (err) {
77
+ const code = err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : "read error";
78
+ return fail(`${CENTER_TOKEN_FILE_ENV} ${declaration.path} is unreadable (${code})`);
79
+ }
80
+ const bearer = text.trim();
81
+ if (bearer === "")
82
+ return fail(`${CENTER_TOKEN_FILE_ENV} ${declaration.path} is empty`);
83
+ health = { state: "ok" };
84
+ return classify(bearer, worker);
85
+ },
86
+ health: () => health,
87
+ };
88
+ }
89
+ }
90
+ }
91
+ export function scopedTokenNeedsWorker(read, worker) {
92
+ return read.ok && read.kind === "pull-token" && read.workerScoped && !worker;
93
+ }
94
+ export function centerStateBin(worker, identity) {
95
+ return `${worker === undefined ? "global" : `worker-${digest16(worker)}`}.id-${digest16(identity)}`;
96
+ }
97
+ export function credentialIdentityNow(source) {
98
+ const r = source.read();
99
+ return r.ok ? r.identity : undefined;
100
+ }
101
+ export function answerStillCurrent(source, usedIdentity) {
102
+ return usedIdentity !== undefined && credentialIdentityNow(source) === usedIdentity;
103
+ }
104
+ export function createIdentityBins(source, make) {
105
+ const bins = new Map();
106
+ return () => {
107
+ const identity = credentialIdentityNow(source);
108
+ if (bins.has(identity))
109
+ return bins.get(identity);
110
+ const made = make(pinCredential(source, identity));
111
+ bins.set(identity, made);
112
+ return made;
113
+ };
114
+ }
115
+ function pinCredential(source, identity) {
116
+ return {
117
+ origin: source.origin,
118
+ declared: source.declared,
119
+ read() {
120
+ const r = source.read();
121
+ if (!r.ok)
122
+ return r;
123
+ if (identity === undefined) {
124
+ return { ok: false, reason: "this state bin was opened while the center credential was unreadable (identity unknown) — it never talks to the center; the next call runs in the current identity's bin" };
125
+ }
126
+ return r.identity === identity ? r : { ok: false, reason: "center credential identity changed — this request belongs to the previous identity's state bin and is not sent" };
127
+ },
128
+ health: () => source.health(),
129
+ };
130
+ }
131
+ //# sourceMappingURL=center-credential.js.map
@@ -10,14 +10,14 @@ export type CatalogDangerAxis = (typeof CATALOG_DANGER_AXES)[number];
10
10
  /** 域 = 配置九组(类型锁步:组集变了这里跟着红)。 */
11
11
  export type CatalogDomain = keyof ServiceConfigGroups;
12
12
  export declare const CATALOG_RESTART_SLICES: readonly RestartSlice[];
13
- /** center 联接座(spec 层)。`models-plane` 是内部时机词:build 时按 tier 表在场性实算成 hot/restart。 */
13
+ /** center 联接座(spec 层)。 */
14
14
  export interface CatalogCenterSeatSpec {
15
15
  readonly domain: string;
16
16
  readonly path: string;
17
17
  /** `centerManagedKeys`(config-center 在管键集)里查「在管」用的键;models 面统一 "models"。 */
18
18
  readonly centerKey: string;
19
19
  readonly precedence: "center-wins" | "env-wins";
20
- readonly timing: "hot" | "restart" | "inert" | "models-plane";
20
+ readonly timing: "hot" | "restart" | "inert";
21
21
  readonly restartSlice?: RestartSlice;
22
22
  readonly restartReason?: string;
23
23
  }
@@ -1,7 +1,7 @@
1
1
  import { CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES } from "@sema-agent/core";
2
2
  import { THINKING_FORMATS } from "./model-compat.js";
3
3
  import { RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS } from "./config-types.js";
4
- import { webSearchConfigFromEnv, WEB_SEARCH_PROVIDERS } from "./plugins/web-search.js";
4
+ import { webSearchProviderFromEnv, WEB_SEARCH_PROVIDERS } from "./plugins/web-search.js";
5
5
  import { AUTONOMY_MODES } from "./runtime-governance.js";
6
6
  import { REMOTE_EXEC_PROVIDER_WORDS } from "./execution-lane-caps.js";
7
7
  import { CENTER_READ_FACE_LIVE_KEYS } from "./config-center/read-face.js";
@@ -15,10 +15,9 @@ const RESTART_SLICE_SET = {
15
15
  mcp: true,
16
16
  a2a: true,
17
17
  scenarios: true,
18
- "runtime-gates": true,
19
- "models-tiers": true,
20
18
  "degrade-route": true,
21
19
  "read-face": true,
20
+ "memory-consolidation": true,
22
21
  };
23
22
  export const CATALOG_RESTART_SLICES = Object.keys(RESTART_SLICE_SET);
24
23
  const r = (name, domain, type, semantics, opts = {}) => ({ name, domain, type, semantics, ...opts });
@@ -31,7 +30,7 @@ const cLimits = (path, over = {}) => ({
31
30
  ...over,
32
31
  });
33
32
  const cGov = (path, over = {}) => ({ domain: "governance", path, centerKey: path, precedence: "center-wins", timing: "hot", ...over });
34
- const cModels = (path) => ({ domain: "models", path, centerKey: "models", precedence: "center-wins", timing: "models-plane" });
33
+ const cModels = (path) => ({ domain: "models", path, centerKey: "models", precedence: "center-wins", timing: "hot" });
35
34
  const cRead = (path, centerKey, over = {}) => ({
36
35
  domain: "readFace",
37
36
  path,
@@ -65,7 +64,7 @@ export const CONFIG_CATALOG = [
65
64
  r("APPROVAL_DENY", "approval", "csv", "始终拒绝的工具名单(装配期拒不可匹配名)", { danger: SEC, configKey: "approvalDeny" }),
66
65
  r("APPROVAL_HMAC_KEYS", "approval", "secret", "直连门(D-G)HMAC 完整性密钥集(空=直连门不激活)", { danger: SEC }),
67
66
  r("APPROVAL_NEVER_AUTO", "approval", "csv", "永不被 AUTO_BUDGET 自动放行的工具集(不可逆动作恒要人)", { danger: SEC, configKey: "approvalNeverAuto" }),
68
- r("APPROVAL_REQUIRE", "approval", "csv", "必须人审的工具名单(F4 门;center 腿走 runtime-gates 重启片)", { danger: SEC, configKey: "approvalRequire", center: cGov("approvalRequire", { timing: "restart", restartSlice: "runtime-gates" }) }),
67
+ r("APPROVAL_REQUIRE", "approval", "csv", "必须人审的工具名单(F4 门;center 腿热:refresh 拍整表换代,无 checkpoint 店的部署发非空名单 ⇒ 审批组整组拒)", { danger: SEC, configKey: "approvalRequire", center: cGov("approvalRequire") }),
69
68
  r("APPROVAL_TERMINAL_BACKSTOP_MS", "approval", "number", "SQL checkpoint 店 pending 行的 crash-safe 绝对 GC 上限(ms;下限 60s,非有限值落默认)", { staticDefault: String(30 * 86_400_000), danger: DATA }),
70
69
  r("APPROVAL_TIMEOUT_SEC", "approval", "number", "durable HITL 等人上限(秒);0=无限期等,时间型 suspended 回收不跑", { staticDefault: "0", configKey: "approvalTimeoutSec" }),
71
70
  r("ASK_QUESTION_ENABLED", "approval", "boolean", "AskUserQuestion 活流 HITL 装配开关", { derivedDefaultNote: "posture 三态:显式词恒赢,缺席 = 单用户(REQUIRE_PRINCIPAL≠true)⇒ on / 多租户 ⇒ off", configKey: "askQuestionEnabled" }),
@@ -74,7 +73,7 @@ export const CONFIG_CATALOG = [
74
73
  r("ATTACHMENT_ORPHAN_GRACE_MS", "limitsHttp", "number", "附件孤儿对象 GC 宽限(ms;0=关本腿,坏值回默认+warn)", { staticDefault: "3600000", danger: DATA, center: cLimits("attachmentOrphanGraceMs") }),
75
74
  r("ATTACHMENT_UNBOUND_TTL_MS", "limitsHttp", "number", "未绑定附件的 TTL(ms)", { staticDefault: String(24 * 3600 * 1000), danger: DATA, configKey: "attachmentUnboundTtlMs", center: cLimits("attachmentUnboundTtlMs") }),
76
75
  r("AUTH_BRIDGE_AUDIENCE", "auth", "string", "registry SSO-JWT 验签期望 aud", { danger: SEC }),
77
- r("AUTH_BRIDGE_ISSUER", "auth", "url", "registry SSO-JWT issuer(auth bridge 的开关之一;S-591 起也是托管形判据的一条证据 —— 只认显式值,不认 center 回退)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 桥的装配处回退到 configCenter.baseUrl(配了 center 即默认开桥);但**托管判据不读那条回退**(fleet 管理轴 ≠ 租户轴)", configKey: "authBridgeIssuer" }),
76
+ r("AUTH_BRIDGE_ISSUER", "auth", "url", "registry SSO-JWT issuer(auth bridge 的开关之一;S-671 / C-R100 ① 起**不是**托管证据 —— 多租户只认 REQUIRE_PRINCIPAL=true)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 桥的装配处回退到 configCenter.baseUrl(配了 center 即默认开桥);显式值与回退值在托管判定上同判(都不算)", configKey: "authBridgeIssuer" }),
78
77
  r("AUTH_BRIDGE_JWKS_URL", "auth", "url", "registry SSO-JWT 的 JWKS 地址(缺省从 issuer 派生)", { danger: SEC }),
79
78
  r("AUTONOMY", "auth", "enum", "运维 autonomy 模式,tighten-only 叠到每个 TaskSpec(read-only/plan ⇒ 只读手,ask ⇒ 恒 shellGate);center 腿热且 non-sticky", { danger: SEC, enumValues: AUTONOMY_MODES, configKey: "autonomy", center: cGov("autonomy") }),
80
79
  r("AUTO_MODE_CLASSIFIER_FALLBACK", "approval", "enum", "auto 分类器席答不出裁决时的梯子(main=主模型代判 / none=只留分类器那一级,整条拒)", { enumValues: ["main", "none"], danger: SEC, derivedDefaultNote: "未设/空串 ⇒ 不铸键,跟随引擎缺省 main(core 7.18.0 起;空串按未设,与其余词表旋钮同判);none = 7.77.0 及以前的行为逐字;词表外的值启动期拒启。权限轴放宽,升级须知见 DEPLOY-PREREQS", configKey: "autoModeClassifierFallback" }),
@@ -102,7 +101,7 @@ export const CONFIG_CATALOG = [
102
101
  r("CORS_ORIGIN", "auth", "csv", "CORS 白名单(单值 v1 兼容回显;多值按请求 Origin 匹配 + Vary;未设=无 CORS 面)", { danger: SEC, configKey: "corsOrigins" }),
103
102
  r("COST_QUOTA_WINDOW_SEC", "limitsHttp", "number", "per-principal 成本配额窗长(秒);SQL 腿窗长进桶主键 ⇒ center 腿显式 deferred(重启生效)", { staticDefault: "86400", danger: BILL, configKey: "costQuotaWindowSec", center: cLimits("costQuotaWindowSec", { timing: "restart", restartReason: "cost-quota-window" }) }),
104
103
  r("CROSS_SESSION_DIALOG_EXPIRY", "approval", "enum", "held 跨会话消息等待人审的期限(CC settings 键名 dialogExpiry;未设=不铸键,引擎自落缺省 5m;词表外拒启)", { danger: SEC, enumValues: [...CROSS_SESSION_DIALOG_EXPIRY_VALUES], configKey: "crossSessionDialogExpiry", derivedDefaultNote: "缺席 ⇒ 不铸键,core 自缺省 5m", resolve: (c) => c.crossSessionDialogExpiry ?? null }),
105
- r("CROSS_SESSION_INBOUND", "approval", "enum", "入站跨会话消息的部署/组织层治理(CC settings 键名 crossSessionInbound 的 managed 层;未设=本层不表态≠accept;词表外拒启)", { danger: SEC, enumValues: [...CROSS_SESSION_INBOUND_SETTINGS], configKey: "crossSessionInbound", derivedDefaultNote: "缺席 ⇒ managed 层不设,由其余层与 mode-parity 决定", resolve: (c) => c.crossSessionInbound ?? null }),
104
+ r("CROSS_SESSION_INBOUND", "approval", "enum", "入站跨会话消息的部署/组织层治理(CC settings 键名 crossSessionInbound 的 managed 层;未设=本层不表态≠ next-turn;词表外拒启)", { danger: SEC, enumValues: [...CROSS_SESSION_INBOUND_SETTINGS], configKey: "crossSessionInbound", derivedDefaultNote: "缺席 ⇒ managed 层不设,由其余层与 mode-parity 决定", resolve: (c) => c.crossSessionInbound ?? null }),
106
105
  r("DB_BACKEND", "store", "enum", "store 后端引擎选择(mysql 兼容/pg/本机文件店/纯内存)", { enumValues: ["mysql", "pg", "local", "memory"], danger: DATA, derivedDefaultNote: "裸 boot ⇒ local(多租户裸 boot ⇒ memory + notice);有 SQL 信号 ⇒ mysql", configKey: "dbBackend", resolve: (c) => c.dbBackend }),
107
106
  r("DB_QUERY_TIMEOUT_MS", "store", "number", "per-query DB 超时(ms;坏值回默认姿态+warn,绝不 NaN 进 driver)", { derivedDefaultNote: "缺席 ⇒ 池级不设/计数族 30s(按引擎)", configKey: "dbQueryTimeoutMs" }),
108
107
  r("DEFAULT_SCENARIO", "integrations", "string", "出厂缺省场景(编码产品出厂态;通用中性 persona 用 default 显式选回)", { staticDefault: "code", configKey: "defaultScenario" }),
@@ -394,7 +393,8 @@ export const CONFIG_CATALOG = [
394
393
  r("SEMA_PIP_INDEX", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 pip index 地址"),
395
394
  r("SEMA_REGISTRY_ADMIN_TOKEN", "integrations", "secret", "写 center 的管理凭证(仅 boot 上报执行公钥用;刻意不入 ServiceConfig —— 不进 configHash/日志面)", { danger: SEC, scope: "bootstrap-only" }),
396
395
  r("SEMA_REGISTRY_DRY_RUN", "integrations", "boolean", "center 腿 dry-run(拉而不应用)", { staticDefault: "false" }),
397
- r("SEMA_REGISTRY_TOKEN", "integrations", "secret", "center 拉取凭据", { danger: SEC }),
396
+ r("SEMA_REGISTRY_TOKEN", "integrations", "secret", "center 拉取凭据(值形;与 SEMA_REGISTRY_TOKEN_FILE 二选一,都设=拒启)", { danger: SEC }),
397
+ r("SEMA_REGISTRY_TOKEN_FILE", "integrations", "path", "center 拉取凭据文件(每条中心请求都重读 ⇒ 轮换 / 换账号不重启;与 SEMA_REGISTRY_TOKEN 二选一,都设=拒启;文件不可读=响亮 + 健康态 unreadable,不当作无凭证)", { danger: SEC }),
398
398
  r("SEMA_REGISTRY_URL", "integrations", "url", "sema-registry 地址(设了=remote 配置源开关)"),
399
399
  r("SEMA_REGISTRY_WORKER", "integrations", "string", "本 worker 在 center 的名字(per-worker effective)"),
400
400
  r("SEMA_UV_INDEX", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 uv index 地址"),
@@ -453,12 +453,12 @@ export const CONFIG_CATALOG = [
453
453
  r("UNREACHED_ASK_TTL_MS", "approval", "number", "零活流铸造的 ask 悬挂窗(ms;[60s,24h] 拒启带界)", { staticDefault: "3600000" }),
454
454
  r("USAGE_WINDOWS", "orchestration", "opaque", "部署级治理窗(JSON 数组;滑动窗内跨任务 token/$ 预算,key=principal)", { danger: BILL }),
455
455
  r("WEB_SEARCH_API_KEY", "orchestration", "secret", "web search 后端 API 凭据(brave/tavily)", { danger: [...SEC, ...BILL] }),
456
- r("WEB_SEARCH_ENDPOINT", "orchestration", "url", "web search 后端 endpoint 覆写(searxng 自建面)"),
457
- r("WEB_SEARCH_MAX_RESULTS", "orchestration", "number", "web search 单次结果数上限", { derivedDefaultNote: "缺席/非正 ⇒ backend 默认" }),
456
+ r("WEB_SEARCH_ENDPOINT", "orchestration", "url", "web search 后端 endpoint 覆写(searxng 实例地址;brave 下可指向 Brave 兼容网关的完整搜索 URL,如 Nólë;空 = 未设;不是 scheme://host 形的绝对 URL ⇒ 拒启)"),
457
+ r("WEB_SEARCH_MAX_RESULTS", "orchestration", "number", "web search 单次结果数上限(非数 / < 1 ⇒ 拒启;> 20 夹到 20)", { derivedDefaultNote: "缺席/空 ⇒ backend 默认 10" }),
458
458
  r("WEB_SEARCH_PROBE_ON_BOOT", "orchestration", "boolean", "boot 期 web search 连通性探测(只认 true/1;含糊值当没开——猜错方向是凭空出网)", { staticDefault: "false" }),
459
- r("WEB_SEARCH_PROVIDER", "orchestration", "enum", "web search 工具后端(设合法词=装配 WebSearch;其它/缺席=不装配)", { enumValues: WEB_SEARCH_PROVIDERS, danger: SEC, resolve: (_c, env) => webSearchConfigFromEnv(env)?.provider ?? null }),
460
- r("WEB_SEARCH_SEARXNG_PARAMS", "orchestration", "string", "searxng 额外查询参数(k=v 对;解析不出任何一对 ⇒ 键不铸)"),
461
- r("WEB_SEARCH_TIMEOUT_MS", "orchestration", "number", "web search 单次超时(ms)", { derivedDefaultNote: "缺席/非正 ⇒ backend 默认" }),
459
+ r("WEB_SEARCH_PROVIDER", "orchestration", "enum", "web search 工具后端(设合法词=装配 WebSearch;其它/缺席=不装配)", { enumValues: WEB_SEARCH_PROVIDERS, danger: SEC, resolve: (_c, env) => webSearchProviderFromEnv(env) ?? null }),
460
+ r("WEB_SEARCH_SEARXNG_PARAMS", "orchestration", "string", "searxng 额外查询参数(name=value;name=value;空 ⇒ 不铸;任一段不成形 / 名不是参数名 / 同名两次 ⇒ 拒启)"),
461
+ r("WEB_SEARCH_TIMEOUT_MS", "orchestration", "number", "web search 单次超时(ms;非数 / < 1 ⇒ 拒启;< 1000 抬到 1000)", { derivedDefaultNote: "缺席/空 ⇒ backend 默认 10000" }),
462
462
  r("WORKFLOW_AGENTS_READONLY", "orchestration", "boolean", "workflow 子 agent 只读钳(TOB 保守旋钮;默认同权 CC parity)", { staticDefault: "false", danger: SEC, configKey: "workflowAgentsReadOnly" }),
463
463
  r("WORKFLOW_JOURNAL_RETENTION_MS", "orchestration", "number", "workflow_journal GC 保留期(ms)", { staticDefault: String(7 * 24 * 60 * 60 * 1000), danger: DATA, configKey: "workflowJournalRetentionMs" }),
464
464
  r("WORKFLOW_ORPHAN_GRACE_MS", "orchestration", "number", "running workflow 判崩溃孤儿的年龄阈(ms)", { staticDefault: String(24 * 60 * 60 * 1000), configKey: "workflowOrphanGraceMs" }),
@@ -536,8 +536,8 @@ export function buildConfigCatalog(inputs) {
536
536
  const seat = spec.center;
537
537
  const domainExists = seat !== undefined && centerDomains.includes(seat.domain);
538
538
  const managedNow = seat !== undefined && (centerManagedKeys?.has(seat.centerKey) ?? false);
539
- const centerTiming = seat === undefined ? undefined : seat.timing === "models-plane" ? "hot" : seat.timing;
540
- const centerRestartSlice = seat === undefined || seat.timing === "models-plane" ? undefined : seat.restartSlice;
539
+ const centerTiming = seat?.timing;
540
+ const centerRestartSlice = seat?.restartSlice;
541
541
  const centerEffective = seat !== undefined && managedNow && centerTiming === "hot" && (seat.precedence === "center-wins" || !envHeld);
542
542
  const effectiveLane = centerEffective ? "center" : envHeld ? "env" : "default";
543
543
  let effectiveValue;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * EffectiveConfig application — mutate the boot `ServiceConfig` OVER with a pulled `EffectiveConfig`
3
- * (models/roles/tiers/projects `applyEffective`, the boot-only approval gate + runtime limit residue
3
+ * (models/roles/tiers/projects `applyEffective`, the approval group + runtime limit residue
4
4
  * `applyRuntimeGates`, the per-request-hot governance baton `applyRuntimeHot`) plus their shared helpers
5
5
  * (`toModel`, `mutateInPlace`, `resolveDefaultModelName`, `logEffectiveDiff`). Split out of `facade.ts`
6
6
  * ([ref] A13, internal-lossless — the facade re-exports every symbol below unchanged).
@@ -10,6 +10,7 @@ import type { ServiceConfig } from "../config-types.js";
10
10
  import { type SealedKeyOpener } from "../sealed-key.js";
11
11
  import type { Logger } from "../observability/logger.js";
12
12
  import type { EffectiveConfig } from "./types.js";
13
+ import { type ConfigApplyReport } from "./apply-ledger.js";
13
14
  /** Apply center config OVER the env-derived config (mutates `config`). Empty domains keep env defaults. */
14
15
  /** Replace a record's contents IN PLACE (clear keys, copy source) so holders of the SAME reference see the
15
16
  * change. This is what makes models/roles/pricing HOT-apply: core's Runner reads
@@ -34,41 +35,38 @@ export declare const UNPARSABLE_AT_MODEL_ALLOWLIST_ENTRY = "\0<unparsable-at-mod
34
35
  * commit 之前,活配置零触碰)。caller 收到 false 必须把**同一世代的旁路消费**(prompts 采用/pricing/
35
36
  * keyResolver/etag 推进/LKG 落盘)一并跳过——否则 applyEffective 拒了主面、旁路却半应用同一个被拒
36
37
  * 世代,混合世代从侧门回来。首次 apply(live 未登记)恒 true。 */
37
- /** [ref]:一次 apply 的**世代账载荷**(post-commit 发,[ref]② —— commit 段仍是零回调零发射)。 */
38
- export interface ApplyReport {
39
- readonly version: number;
40
- /** [ref]:本候选的收货序数(= opts.generation 原样带回;世代账按它记拒收,身份标仍是 version)。 */
41
- readonly ordinal?: number;
42
- readonly limits: {
43
- /** 本世代真正换了值的键。 */
44
- readonly applied: readonly string[];
45
- /** 值合法但**不热**的键(逐键 defer:变更只能重启生效)。 */
46
- readonly deferred?: ReadonlyArray<{
47
- readonly key: string;
48
- readonly reason: string;
49
- }>;
50
- /** 整批被拒时的成因(消毒:键名 + 判据文案,绝不带值)。 */
51
- readonly rejected?: {
52
- readonly keys: string[];
53
- readonly reason: string;
54
- readonly restartReason?: string;
55
- };
56
- };
38
+ /** [ref]:一次 apply 的**世代账载荷**(post-commit 发,[ref]② —— commit 段仍是零回调零发射)。形 = 世代账的
39
+ * `ConfigApplyReport`(**每一组都报**,组闭集 `CONFIG_APPLY_GROUPS` 在 apply-ledger.ts)+ 整代判决。 */
40
+ export interface ApplyReport extends ConfigApplyReport {
57
41
  /** 🔴 codex R2-F1:本候选是否**完整**应用 —— 有任何一组被拒 ⇒ false。调用方据此决定要不要把它发布成
58
42
  * LKG / 推进 etag(一个「其余面提交了但某组被拒」的候选覆盖掉好 LKG,重启后就再也回不去了)。 */
59
43
  readonly fullyApplied: boolean;
60
44
  }
61
- export declare function applyEffective(config: ServiceConfig, eff: EffectiveConfig, logger?: Logger, opts?: {
62
- teamsOnly?: boolean;
63
- sealedKeys?: SealedKeyOpener;
64
- deferModelPlane?: boolean;
65
- generation?: number;
66
- report?: (r: ApplyReport) => void;
67
- swapPlane?: (plane: {
45
+ /**
46
+ * S-668 波 2 —— 一次 apply 在**哪条腿**上跑,以及这条腿知道的**部署事实**。必填(没有缺省腿):安全轴上「忘了说是哪条
47
+ * 腿」不许静默落成某一种语义([ref])。
48
+ * · `boot` —— 装配期(stage-02 的 boot 拉取 / LKG 起服;run-local 的一次性装配)。此刻 checkpoint 店**还没建**,
49
+ * 审批组的「门意图可服务」由装配完之后的 stage-07 对**整份** config 判(拒启)—— 与热腿是同一只
50
+ * `gateIntentUnserviceableReason`,不是两份判据。
51
+ * · `hot` —— 进程已起(refresh 拍 / 迟到 boot 到货)。`checkpointStorePresent` = 本进程建没建出 checkpoint 店
52
+ * (stage-06 的产物,**不在 ServiceConfig 里**),审批组 stage 当场对候选判,不可服务 ⇒ 整组拒。
53
+ */
54
+ export type ApplyLeg = {
55
+ readonly leg: "boot";
56
+ } | {
57
+ readonly leg: "hot";
58
+ readonly checkpointStorePresent: boolean;
59
+ };
60
+ export type ApplyEffectiveOpts = ApplyLeg & {
61
+ readonly sealedKeys?: SealedKeyOpener;
62
+ readonly generation?: number;
63
+ readonly report?: (r: ApplyReport) => void;
64
+ readonly swapPlane?: (plane: {
68
65
  models: Record<string, Model>;
69
66
  tiers: Record<string, string>;
70
67
  }) => void;
71
- }): boolean;
68
+ };
69
+ export declare function applyEffective(config: ServiceConfig, eff: EffectiveConfig, logger: Logger | undefined, opts: ApplyEffectiveOpts): boolean;
72
70
  /**
73
71
  * UNIFORM presence semantics across the center gates (center `f95438a` adopted this from our sentinel flag —
74
72
  * SERVICE-INTEGRATION「Runtime 治理/限额闸」): every gate field is center `.optional()`, so
@@ -87,14 +85,9 @@ export declare function applyEffective(config: ServiceConfig, eff: EffectiveConf
87
85
  /** `runtime` 域的限额五闸(= settings-schema `RuntimeConfig` 的全部键,[ref] 件3 对账门钉集合等式),ONE place:
88
86
  * legacy 位的 presence 判据({@link runtimeGatePresent} → stage-limits 的 `legacyGates`)、config-catalog 的
89
87
  * center 在管键、dry-run 现值快照都从这一张表派生。[ref] 批1 起五键**全热**(属主 stage-limits,boot+refresh
90
- * 双腿),所以本表与 restart-to-apply 再无关系——审批闸不在这里:它住 governance 域({@link BOOT_GATE_KEYS})。 */
88
+ * 双腿),所以本表与 restart-to-apply 再无关系——审批闸不在这里:它住 governance 域({@link stageApprovalGate})。 */
91
89
  export declare const RUNTIME_GATE_KEYS: readonly ["rateLimitPerMin", "maxTaskCostUsd", "maxTaskTokens", "maxPrincipalCostUsd", "costQuotaWindowSec"];
92
90
  export type RuntimeGateKey = (typeof RUNTIME_GATE_KEYS)[number];
93
- /** boot-only(restart-to-apply)的治理闸:今天只有 `governance.approvalRequire` 一键——它被 main.ts 在本函数之后
94
- * 用 `config` 构造进 approval gate,refresh 腿(teamsOnly)不写,变更由 restart-signal 的 `runtime-gates` 片通告。
95
- * ([ref] 批2 若把它转热,这张表清空、那个片随之退役——一处改动。) */
96
- export declare const BOOT_GATE_KEYS: readonly ["approvalRequire"];
97
- export type BootGateKey = (typeof BOOT_GATE_KEYS)[number];
98
91
  /** Uniform presence rule (center f95438a) for the `runtime` limit gates: "managed" when the field is present —
99
92
  * any number (incl. 0). Sole guard: costQuotaWindowSec must be > 0 (a 0 window is nonsensical — core divides by
100
93
  * it; center's `.positive()` already enforces, belt-and-suspenders here). The approval gate's presence rule
@@ -106,7 +99,8 @@ export type BootGateKey = (typeof BOOT_GATE_KEYS)[number];
106
99
  * 判据与各消费腿**同源**,不自铸第二份:
107
100
  * · limits 域 = `hasOwnProperty`(stage-limits 的 published 判据逐字——present 含显式 0);
108
101
  * · 六闸 legacy 位 = {@link runtimeGatePresent}(旧位 presence 规则的唯一属主);
109
- * · governance/runtime 热二件 + approvalRequire = 键在场(applyRuntimeHot 的 non-sticky 判据同门);
102
+ * · governance 三件(autonomy / commandPolicy / approvalRequire)= 键在场(applyRuntimeHot 与 stageApprovalGate
103
+ * 的 non-sticky presence 判据同门:`!== undefined`);
110
104
  * · readFace 域成员 = 键在场(read-face.ts 的逐键 env-wins 合成看的就是成员在场性);
111
105
  * · models 面 = 有启用行(markRosterLanded 同判据);atModelAllowlist 单列(整表重算键)。
112
106
  * 纯投影零副作用;`undefined` eff(还没拉到 / 纯 env 形)⇒ 空集。
@@ -114,11 +108,12 @@ export type BootGateKey = (typeof BOOT_GATE_KEYS)[number];
114
108
  export declare function centerManagedConfigKeys(eff: EffectiveConfig | undefined): ReadonlySet<string>;
115
109
  export declare function runtimeGatePresent(rt: NonNullable<EffectiveConfig["runtime"]>, key: RuntimeGateKey): boolean;
116
110
  /** 独立调用面的兼容壳(测试/外部):stage → 就地赋值 → 通知。applyEffective 不走这里——它把
117
- * assignments 并进自己的 commit 段以保住整世代原子性([ref]②)。入参是 eff 的两个域([ref] 件3:审批闸
111
+ * 赋值并进自己的 commit 段以保住整世代原子性([ref]②)。入参是 eff 的两个域([ref] 件3:审批闸
118
112
  * 住 governance,限额五闸住 runtime——两位各自的属主判据,这里不再合成视图)。
119
113
  * [ref]:限额五闸的属主已移交热腿,所以本壳**同样**要过 stage-limits —— 否则一个仍叫 applyRuntimeGates
120
- * 的函数会静默漏掉五个限额键(比改名更坏的那种沉默)。 */
121
- export declare function applyRuntimeGates(config: ServiceConfig, eff: Pick<EffectiveConfig, "runtime" | "governance"> | undefined, logger?: Logger): void;
114
+ * 的函数会静默漏掉五个限额键(比改名更坏的那种沉默)。S-668 波 2:审批组的判据依赖腿(④ 只在热腿判),
115
+ * 所以本壳与 applyEffective 一样**必须**说是哪条腿。 */
116
+ export declare function applyRuntimeGates(config: ServiceConfig, eff: Pick<EffectiveConfig, "runtime" | "governance"> | undefined, leg: ApplyLeg, logger?: Logger): void;
122
117
  export declare function applyRuntimeHot(config: ServiceConfig, gov: EffectiveConfig["governance"], logger?: Logger): void;
123
118
  /**
124
119
  * [ref]① 显式默认解析——applyEffective 与 dry-run(logEffectiveDiff.wouldDefaultModel)共用的单源。优先级