@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
@@ -5,9 +5,11 @@ import { buildModelCompat, readModelCompat } from "../model-compat.js";
5
5
  import { validateCommandRules } from "../runtime-governance.js";
6
6
  import { projectCollabToWorkflows, registerCollabWorkflows } from "../capabilities/collab-workflows.js";
7
7
  import { stageLimits } from "./stage-limits.js";
8
- import { BATCH1_LIMIT_KEYS } from "./hot-keys-registry.js";
8
+ import { APPROVAL_GROUP_KEYS, BATCH1_LIMIT_KEYS } from "./hot-keys-registry.js";
9
9
  import { buildDisplaySafeUrl } from "../trace/redact.js";
10
10
  import { crossInvariant } from "../config-invariants.js";
11
+ import { gateIntentUnserviceableReason } from "../approval.js";
12
+ import { refusedGroups } from "./apply-ledger.js";
11
13
  const charsPerTokenOf = (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 ? v : undefined);
12
14
  function envDerivedModel(config) {
13
15
  const m = config.envModel;
@@ -93,7 +95,7 @@ function stageAtModelAllowlist(raw, models, logger) {
93
95
  }
94
96
  return names;
95
97
  }
96
- export function applyEffective(config, eff, logger, opts = {}) {
98
+ export function applyEffective(config, eff, logger, opts) {
97
99
  const staged = stageEffective(config, eff, logger, opts);
98
100
  const gen = opts.generation ?? staged.version;
99
101
  const live = appliedGeneration.get(config);
@@ -118,25 +120,30 @@ export function applyEffective(config, eff, logger, opts = {}) {
118
120
  commitStaged(config, staged);
119
121
  if (ordered)
120
122
  appliedGeneration.set(config, gen);
121
- opts.report?.({
122
- version: staged.version,
123
- ...(opts.generation !== undefined ? { ordinal: opts.generation } : {}),
124
- limits: {
125
- applied: staged.limits.changed,
126
- ...(staged.limits.deferred.length > 0 ? { deferred: staged.limits.deferred } : {}),
127
- ...(staged.limits.rejected ? { rejected: staged.limits.rejected } : {}),
128
- },
129
- fullyApplied: staged.limits.rejected === undefined,
130
- });
123
+ if (opts.report) {
124
+ const report = {
125
+ version: staged.version,
126
+ ...(opts.generation !== undefined ? { ordinal: opts.generation } : {}),
127
+ groups: {
128
+ limitsHttp: {
129
+ applied: staged.limits.changed,
130
+ ...(staged.limits.deferred.length > 0 ? { deferred: staged.limits.deferred } : {}),
131
+ ...(staged.limits.rejected ? { rejected: staged.limits.rejected } : {}),
132
+ },
133
+ approvalGate: groupReportOf(staged.approvalGate),
134
+ },
135
+ };
136
+ opts.report({ ...report, fullyApplied: refusedGroups(report).length === 0 });
137
+ }
131
138
  if (staged.limits.changed.length > 0)
132
139
  logger?.info("sema_registry_limits", { version: staged.version, keys: staged.limits.changed });
133
140
  if (staged.modelPlane)
134
141
  logger?.info("sema_registry_models", staged.modelPlane.infoLine);
135
- if (staged.gatesInfo)
136
- logger?.info("sema_registry_runtime", staged.gatesInfo);
142
+ if (staged.approvalGate.next !== undefined)
143
+ logger?.info("sema_registry_runtime", { version: staged.version, approvalRequire: staged.approvalGate.next });
137
144
  if (Object.keys(staged.hot.infoLine).length > 0)
138
145
  logger?.info("sema_registry_runtime_hot", staged.hot.infoLine);
139
- for (const n of staged.collabPlane?.notices ?? []) {
146
+ for (const n of staged.collabPlane.notices) {
140
147
  if (n.level === "warn")
141
148
  logger?.warn(n.msg, n.fields);
142
149
  else
@@ -158,10 +165,8 @@ function commitStaged(config, staged) {
158
165
  mutateInPlace(config.tiers, mp.activeTiers);
159
166
  config.atModelAllowlist = mp.atModelAllowlist;
160
167
  }
161
- if (staged.gateAssignments) {
162
- for (const [k, v] of staged.gateAssignments)
163
- config[k] = v;
164
- }
168
+ if (staged.approvalGate.next !== undefined)
169
+ config.approvalRequire = staged.approvalGate.next;
165
170
  for (const [k, v] of staged.limits.assignments)
166
171
  config[k] = v;
167
172
  for (const k of staged.limits.deletions)
@@ -170,12 +175,11 @@ function commitStaged(config, staged) {
170
175
  config.autonomy = staged.hot.autonomyNext;
171
176
  if (staged.hot.setCommandPolicy)
172
177
  config.commandPolicy = staged.hot.commandPolicyNext;
173
- if (staged.collabPlane)
174
- registerCollabWorkflows(staged.collabPlane.workflows);
178
+ registerCollabWorkflows(staged.collabPlane.workflows);
175
179
  mutateInPlace(config.projects, staged.projects);
176
180
  }
177
181
  function stageEffective(config, eff, logger, opts) {
178
- if (!opts.teamsOnly && !eff.version) {
182
+ if (opts.leg === "boot" && !eff.version) {
179
183
  logger?.warn("sema_registry_unpublished", {
180
184
  version: eff.version,
181
185
  note: "empty/unpublished effective config — using env + built-in fallback; publish a config or unset CONFIG_PUBLISH_MODE",
@@ -183,7 +187,7 @@ function stageEffective(config, eff, logger, opts) {
183
187
  }
184
188
  const enabled = (eff.models?.models ?? []).filter((m) => m.enabled !== false);
185
189
  let modelPlane;
186
- if (enabled.length > 0 && opts.deferModelPlane !== true) {
190
+ if (enabled.length > 0) {
187
191
  const models = {};
188
192
  const modelApiKeyEnv = {};
189
193
  const modelApiKeys = {};
@@ -282,21 +286,18 @@ function stageEffective(config, eff, logger, opts) {
282
286
  infoLine: { count: enabled.length, default: models.default.id, defaultSource: picked.source, roles: Object.keys(roles), tiers: Object.keys(activeTiers), atModelAllowlist: atModelAllowlist.length, sealedKeys: Object.values(modelApiKeys).filter((v) => typeof v === "string").length, sealedPoisoned: Object.values(modelApiKeys).filter((v) => typeof v !== "string").length },
283
287
  };
284
288
  }
285
- const gates = opts.teamsOnly ? undefined : stageApprovalGate(eff.governance, logger);
289
+ const approvalGate = stageApprovalGate(config, eff.governance, opts, logger);
286
290
  const limits = stageLimits(config, { version: typeof eff.version === "number" ? eff.version : 0, ...("limits" in eff ? { limits: eff.limits } : {}), legacyGates: legacyGateValues(eff.runtime) }, logger);
287
291
  const hot = stageRuntimeHot(config, eff.governance, logger);
288
- let collabPlane;
289
- if (opts.deferModelPlane !== true) {
290
- const projected = projectCollabToWorkflows(eff.collab?.templates);
291
- const notices = [];
292
- if (Object.keys(projected.workflows).length > 0)
293
- notices.push({ level: "info", msg: "sema_registry_collab_workflows", fields: { workflows: Object.keys(projected.workflows) } });
294
- if (projected.skipped.length > 0)
295
- notices.push({ level: "warn", msg: "sema_registry_collab_skipped", fields: { skipped: projected.skipped } });
296
- if (projected.notes.length > 0)
297
- notices.push({ level: "info", msg: "sema_registry_collab_notes", fields: { notes: projected.notes } });
298
- collabPlane = { workflows: projected.workflows, notices };
299
- }
292
+ const projected = projectCollabToWorkflows(eff.collab?.templates);
293
+ const collabNotices = [];
294
+ if (Object.keys(projected.workflows).length > 0)
295
+ collabNotices.push({ level: "info", msg: "sema_registry_collab_workflows", fields: { workflows: Object.keys(projected.workflows) } });
296
+ if (projected.skipped.length > 0)
297
+ collabNotices.push({ level: "warn", msg: "sema_registry_collab_skipped", fields: { skipped: projected.skipped } });
298
+ if (projected.notes.length > 0)
299
+ collabNotices.push({ level: "info", msg: "sema_registry_collab_notes", fields: { notes: projected.notes } });
300
+ const collabPlane = { workflows: projected.workflows, notices: collabNotices };
300
301
  const projDomain = eff.projects;
301
302
  const rawProjects = (projDomain && typeof projDomain === "object" && !Array.isArray(projDomain) &&
302
303
  "projects" in projDomain && typeof projDomain.projects === "object"
@@ -316,15 +317,14 @@ function stageEffective(config, eff, logger, opts) {
316
317
  return {
317
318
  version: typeof eff.version === "number" && Number.isFinite(eff.version) ? eff.version : 0,
318
319
  ...(modelPlane !== undefined ? { modelPlane } : {}),
319
- ...(gates !== undefined ? { gateAssignments: gates.assignments, ...(gates.info !== undefined ? { gatesInfo: gates.info } : {}) } : {}),
320
+ approvalGate,
320
321
  limits,
321
322
  hot,
322
- ...(collabPlane !== undefined ? { collabPlane } : {}),
323
+ collabPlane,
323
324
  projects,
324
325
  };
325
326
  }
326
327
  export const RUNTIME_GATE_KEYS = ["rateLimitPerMin", "maxTaskCostUsd", "maxTaskTokens", "maxPrincipalCostUsd", "costQuotaWindowSec"];
327
- export const BOOT_GATE_KEYS = ["approvalRequire"];
328
328
  export function centerManagedConfigKeys(eff) {
329
329
  const out = new Set();
330
330
  if (!eff)
@@ -372,40 +372,76 @@ export function runtimeGatePresent(rt, key) {
372
372
  return typeof v === "number" && v > 0;
373
373
  return typeof v === "number";
374
374
  }
375
- function stageApprovalRequire(raw, logger) {
376
- const refuse = (detail, names, kept) => {
377
- logger?.warn("sema_registry_approval_require_invalid", { detail, names: [...names], kept });
378
- return {};
379
- };
380
- const KEPT_WOULD_MATCH_NOTHING = "the prior approvalRequire is unchanged — the published list would have silently matched nothing";
381
- const KEPT_WOULD_REMOVE_GATE = "the prior approvalRequire is unchanged — coercing this malformed value would have silently REMOVED the approval gate";
375
+ const KEPT_WOULD_MATCH_NOTHING = "the prior approvalRequire is unchanged — the published list would have silently matched nothing";
376
+ const KEPT_WOULD_REMOVE_GATE = "the prior approvalRequire is unchanged — coercing this malformed value would have silently REMOVED the approval gate";
377
+ const KEPT_WOULD_NEVER_PARK = "the prior approvalRequire is unchanged — this deployment has nowhere to park a gated call, so applying the list would leave it silently unenforced";
378
+ function stageApprovalRequire(raw) {
382
379
  if (!Array.isArray(raw)) {
383
- return refuse(`published runtime.approvalRequire must be an ARRAY of whole tool names — got ${raw === null ? "null" : typeof raw}. ` +
384
- `Refusing the domain instead of coercing it: an empty list means "no approval gate", so coercing a malformed value would SILENTLY remove the gate.`, [], KEPT_WOULD_REMOVE_GATE);
380
+ const detail = `published governance.approvalRequire must be an ARRAY of whole tool names — got ${raw === null ? "null" : typeof raw}. ` +
381
+ `Refusing the group instead of coercing it: an empty list means "no approval gate", so coercing a malformed value would SILENTLY remove the gate.`;
382
+ return { ok: false, detail, reason: detail, names: [], kept: KEPT_WOULD_REMOVE_GATE };
385
383
  }
386
384
  const nonString = raw
387
385
  .map((n, i) => (typeof n === "string" ? undefined : { i, t: n === null ? "null" : typeof n }))
388
386
  .filter((e) => e !== undefined);
389
387
  if (nonString.length > 0) {
390
- return refuse(`published runtime.approvalRequire contains ${nonString.length} non-string entr${nonString.length === 1 ? "y" : "ies"} ` +
388
+ const detail = `published governance.approvalRequire contains ${nonString.length} non-string entr${nonString.length === 1 ? "y" : "ies"} ` +
391
389
  `(${nonString.map((e) => `[${e.i}]:${e.t}`).join(", ")}) — every entry must be a whole tool name string. ` +
392
- `Refusing the domain instead of dropping them: dropping every entry would SILENTLY remove the approval gate.`, nonString.map((e) => `[${e.i}]`), KEPT_WOULD_REMOVE_GATE);
390
+ `Refusing the group instead of dropping them: dropping every entry would SILENTLY remove the approval gate.`;
391
+ return { ok: false, detail, reason: detail, names: nonString.map((e) => `[${e.i}]`), kept: KEPT_WOULD_REMOVE_GATE };
393
392
  }
394
393
  const names = raw.filter((n) => typeof n === "string").map((n) => n.trim()).filter((n) => n.length > 0);
395
394
  const bad = findUnmatchableToolNames(names, { consumer: "tool-name-table", behavior: "deny" });
396
395
  if (bad.length > 0) {
397
- return refuse(formatUnmatchableToolNames("published runtime.approvalRequire", bad), bad.map((b) => b.name), KEPT_WOULD_MATCH_NOTHING);
396
+ return {
397
+ ok: false,
398
+ detail: formatUnmatchableToolNames("published governance.approvalRequire", bad),
399
+ reason: `published governance.approvalRequire has ${bad.length} entr${bad.length === 1 ? "y" : "ies"} that match no live tool (retired name / incomplete MCP name / CC-form rule) — the offending entries are named in the sema_registry_approval_require_invalid log line`,
400
+ names: bad.map((b) => b.name),
401
+ kept: KEPT_WOULD_MATCH_NOTHING,
402
+ };
398
403
  }
399
- return { value: names };
404
+ return { ok: true, value: names };
400
405
  }
401
- function stageApprovalGate(gov, logger) {
406
+ function unserviceableOnLeg(leg, candidate) {
407
+ switch (leg.leg) {
408
+ case "boot":
409
+ return undefined;
410
+ case "hot":
411
+ return gateIntentUnserviceableReason(candidate, leg.checkpointStorePresent);
412
+ default: {
413
+ const unknownLeg = leg;
414
+ throw new Error(`applyEffective: unknown apply leg ${JSON.stringify(unknownLeg)}`);
415
+ }
416
+ }
417
+ }
418
+ function groupReportOf(g) {
419
+ return { applied: g.next !== undefined ? [...APPROVAL_GROUP_KEYS] : [], ...(g.rejected ? { rejected: g.rejected } : {}) };
420
+ }
421
+ const approvalEnvBaseline = new WeakMap();
422
+ function stageApprovalGate(config, gov, leg, logger) {
423
+ if (!approvalEnvBaseline.has(config))
424
+ approvalEnvBaseline.set(config, Object.freeze([...config.approvalRequire]));
425
+ const refuse = (r) => {
426
+ logger?.warn("sema_registry_approval_require_invalid", { keys: [...APPROVAL_GROUP_KEYS], detail: r.detail, names: [...r.names], kept: r.kept });
427
+ return { rejected: { keys: [...APPROVAL_GROUP_KEYS], reason: r.reason } };
428
+ };
402
429
  const raw = gov?.approvalRequire;
403
- if (!Array.isArray(raw))
404
- return { assignments: [] };
405
- const staged = stageApprovalRequire(raw, logger);
406
- if (staged.value === undefined)
407
- return { assignments: [] };
408
- return { assignments: [["approvalRequire", staged.value]], info: { approvalRequire: staged.value } };
430
+ let next;
431
+ if (raw === undefined) {
432
+ next = [...approvalEnvBaseline.get(config)];
433
+ }
434
+ else {
435
+ const verdict = stageApprovalRequire(raw);
436
+ if (!verdict.ok)
437
+ return refuse(verdict);
438
+ next = verdict.value;
439
+ }
440
+ const unserviceable = unserviceableOnLeg(leg, { ...config, approvalRequire: next });
441
+ if (unserviceable !== undefined)
442
+ return refuse({ detail: unserviceable, reason: unserviceable, names: next, kept: KEPT_WOULD_NEVER_PARK });
443
+ const same = next.length === config.approvalRequire.length && next.every((n, i) => n === config.approvalRequire[i]);
444
+ return same ? {} : { next };
409
445
  }
410
446
  function legacyGateValues(rt) {
411
447
  const out = {};
@@ -416,16 +452,16 @@ function legacyGateValues(rt) {
416
452
  out[k] = rt[k];
417
453
  return out;
418
454
  }
419
- export function applyRuntimeGates(config, eff, logger) {
420
- const s = stageApprovalGate(eff?.governance, logger);
421
- for (const [k, v] of s.assignments)
422
- config[k] = v;
455
+ export function applyRuntimeGates(config, eff, leg, logger) {
456
+ const approval = stageApprovalGate(config, eff?.governance, leg, logger);
457
+ if (approval.next !== undefined)
458
+ config.approvalRequire = approval.next;
423
459
  const limits = stageLimits(config, { version: 0, legacyGates: legacyGateValues(eff?.runtime) }, logger);
424
460
  for (const [k, v] of limits.assignments)
425
461
  config[k] = v;
426
462
  for (const k of limits.deletions)
427
463
  delete config[k];
428
- const info = { ...s.info, ...Object.fromEntries(limits.assignments) };
464
+ const info = { ...(approval.next !== undefined ? { approvalRequire: approval.next } : {}), ...Object.fromEntries(limits.assignments) };
429
465
  if (Object.keys(info).length > 0)
430
466
  logger?.info("sema_registry_runtime", info);
431
467
  }
@@ -20,7 +20,16 @@
20
20
  * 🔴 原子承诺的**域**是单进程(§2):本账记的是「本副本的收敛状态」,不是 fleet 的。fleet 级判据 = 各副本
21
21
  * /health 的 applied == target,拉齐工具归运维面。
22
22
  */
23
- /** 一组的收敛状态(批1 只有 limitsHttp 组;后续批次按组追加,读面形不变)。 */
23
+ /**
24
+ * 世代账按组记账的**组闭集**(读面 `groups` 的键集;加组 = 这里加词,报告形 `Record<组, …>` 随之编译红)。
25
+ * · `limitsHttp` —— [ref] 批1 限额组(属主 `stage-limits.ts`);
26
+ * · `approvalGate` —— S-668 波 2 审批组(`governance.approvalRequire`;属主 `apply-effective.ts` 的
27
+ * `stageApprovalGate`:坏形 / 工具名匹配不到 / ④ 门意图不可服务 ⇒ 整组拒)。
28
+ * 两组同姿势、各用各的判据表(审批组**不**复用限额组的跨键不变量表 —— 那张表的候选视图只叠限额键)。
29
+ */
30
+ export declare const CONFIG_APPLY_GROUPS: readonly ["limitsHttp", "approvalGate"];
31
+ export type ConfigApplyGroup = (typeof CONFIG_APPLY_GROUPS)[number];
32
+ /** 一组的收敛状态(读面形对所有组相同)。 */
24
33
  export interface ConfigApplyGroupState {
25
34
  /** 本组最后一次**真的落地**的源版本(身份标)。 */
26
35
  readonly appliedVersion: number;
@@ -48,27 +57,37 @@ export interface ConfigApplyLedgerView {
48
57
  readonly configAppliedOrdinal: number;
49
58
  /** target≠applied 的**持续**时长(ms);收敛时键缺席(健康面不制造噪音)。 */
50
59
  readonly configApplyStaleMs?: number;
51
- readonly groups: Readonly<Record<string, ConfigApplyGroupState>>;
60
+ readonly groups: Readonly<Partial<Record<ConfigApplyGroup, ConfigApplyGroupState>>>;
61
+ }
62
+ /** 一组在一次 apply 里的报告(所有组同形)。 */
63
+ export interface ConfigApplyGroupReport {
64
+ /** 本世代真正换了值的键。 */
65
+ readonly applied: readonly string[];
66
+ /** 值合法但**不热**的键(逐键 defer:变更只能重启生效)。 */
67
+ readonly deferred?: ReadonlyArray<{
68
+ readonly key: string;
69
+ readonly reason: string;
70
+ }>;
71
+ /** 整组被拒时的成因(消毒:键名 + 判据文案,绝不带值)。 */
72
+ readonly rejected?: {
73
+ readonly keys: readonly string[];
74
+ readonly reason: string;
75
+ readonly restartReason?: string;
76
+ };
52
77
  }
53
- /** applyEffective 的 post-commit 报告(见 apply-effective.ts `ApplyReport`)。 */
78
+ /** applyEffective 的 post-commit 报告(见 apply-effective.ts `ApplyReport`)—— **每一组都报**。 */
54
79
  export interface ConfigApplyReport {
55
80
  readonly version: number;
56
81
  /** [ref]:本候选的收货序数(applyEffective opts.generation 原样带回;缺席=version 兼容臂)。 */
57
82
  readonly ordinal?: number;
58
- readonly limits: {
59
- readonly applied: readonly string[];
60
- readonly deferred?: ReadonlyArray<{
61
- readonly key: string;
62
- readonly reason: string;
63
- }>;
64
- readonly rejected?: {
65
- readonly keys: readonly string[];
66
- readonly reason: string;
67
- readonly restartReason?: string;
68
- };
69
- };
70
- readonly fullyApplied?: boolean;
83
+ readonly groups: Readonly<Record<ConfigApplyGroup, ConfigApplyGroupReport>>;
71
84
  }
85
+ /** 本报告里被整组拒的那些组(按组闭集序)。空 ⇒ 候选完整应用。两条 commit 腿的「部分应用」warn 与世代账共用。 */
86
+ export declare function refusedGroups(report: ConfigApplyReport): Array<{
87
+ readonly group: ConfigApplyGroup;
88
+ readonly keys: readonly string[];
89
+ readonly reason: string;
90
+ }>;
72
91
  export interface ConfigApplyLedger {
73
92
  /** 拉到一个候选(无论后续是否应用)。ordinal=收货序数,version=身份标。 */
74
93
  seenTarget(ordinal: number, version: number): void;
@@ -1,3 +1,10 @@
1
+ export const CONFIG_APPLY_GROUPS = ["limitsHttp", "approvalGate"];
2
+ export function refusedGroups(report) {
3
+ return CONFIG_APPLY_GROUPS.flatMap((group) => {
4
+ const r = report.groups[group].rejected;
5
+ return r ? [{ group, keys: r.keys, reason: r.reason }] : [];
6
+ });
7
+ }
1
8
  export function createConfigApplyLedger(now = () => Date.now()) {
2
9
  let targetOrd = 0;
3
10
  let appliedOrd = 0;
@@ -6,6 +13,21 @@ export function createConfigApplyLedger(now = () => Date.now()) {
6
13
  const rejectedOrds = new Set();
7
14
  let divergedSince;
8
15
  const groups = {};
16
+ const nextGroupState = (prev, r, version) => {
17
+ if (r.rejected) {
18
+ return {
19
+ appliedVersion: prev?.appliedVersion ?? 0,
20
+ ...(prev?.deferredKeys ? { deferredKeys: prev.deferredKeys } : {}),
21
+ lastRejected: { keys: [...r.rejected.keys], reason: r.rejected.reason, version, at: now() },
22
+ ...(r.rejected.restartReason ? { deferredKeys: r.rejected.keys.map((key) => ({ key, reason: r.rejected.restartReason })) } : {}),
23
+ };
24
+ }
25
+ return {
26
+ appliedVersion: version,
27
+ ...(r.deferred && r.deferred.length > 0 ? { deferredKeys: r.deferred.map((d) => ({ key: d.key, reason: d.reason })) } : {}),
28
+ ...(prev?.lastRejected ? { lastRejected: prev.lastRejected } : {}),
29
+ };
30
+ };
9
31
  const reconcile = () => {
10
32
  if (targetOrd === appliedOrd)
11
33
  divergedSince = undefined;
@@ -32,28 +54,13 @@ export function createConfigApplyLedger(now = () => Date.now()) {
32
54
  reconcile();
33
55
  },
34
56
  recordApply(report) {
35
- const prev = groups.limitsHttp;
36
57
  const ord = report.ordinal ?? report.version;
37
- if (report.limits.rejected)
58
+ if (refusedGroups(report).length > 0)
38
59
  rejectedOrds.add(ord);
39
60
  else
40
61
  rejectedOrds.delete(ord);
41
- if (report.limits.rejected) {
42
- groups.limitsHttp = {
43
- appliedVersion: prev?.appliedVersion ?? 0,
44
- ...(prev?.deferredKeys ? { deferredKeys: prev.deferredKeys } : {}),
45
- lastRejected: { keys: [...report.limits.rejected.keys], reason: report.limits.rejected.reason, version: report.version, at: now() },
46
- ...(report.limits.rejected.restartReason
47
- ? { deferredKeys: report.limits.rejected.keys.map((key) => ({ key, reason: report.limits.rejected.restartReason })) }
48
- : {}),
49
- };
50
- return;
51
- }
52
- groups.limitsHttp = {
53
- appliedVersion: report.version,
54
- ...(report.limits.deferred && report.limits.deferred.length > 0 ? { deferredKeys: report.limits.deferred.map((d) => ({ key: d.key, reason: d.reason })) } : {}),
55
- ...(prev?.lastRejected ? { lastRejected: prev.lastRejected } : {}),
56
- };
62
+ for (const g of CONFIG_APPLY_GROUPS)
63
+ groups[g] = nextGroupState(groups[g], report.groups[g], report.version);
57
64
  },
58
65
  view() {
59
66
  const stale = divergedSince !== undefined ? now() - divergedSince : undefined;
@@ -0,0 +1,60 @@
1
+ import { type CenterCredentialSource } from "../center-credential.js";
2
+ export declare const SCHEMA_MAJOR_HEADER = "x-sema-schema-major";
3
+ /** 已装 settings-schema 的 major(机器派生)。版本号不成形 ⇒ 模块装载即抛(装坏了的包不该带着一个猜出来的
4
+ * major 去跟中心协商 wire 形)。 */
5
+ export declare const SETTINGS_SCHEMA_MAJOR: string;
6
+ /** 一个中心端点 = 基址 + 凭证来源(`ServiceConfig.configCenter` 结构上满足)。 */
7
+ export interface CenterEndpoint {
8
+ readonly baseUrl: string;
9
+ readonly credential: CenterCredentialSource;
10
+ }
11
+ /** 中心路由闭集。 */
12
+ export type CenterRoute = {
13
+ readonly kind: "effective";
14
+ readonly worker?: string | undefined;
15
+ readonly principal?: string | undefined;
16
+ } | {
17
+ readonly kind: "skill-content";
18
+ readonly contentHash: string;
19
+ } | {
20
+ readonly kind: "prompt-artifact";
21
+ readonly digest: string;
22
+ } | {
23
+ readonly kind: "prompt-blob";
24
+ readonly digest: string;
25
+ } | {
26
+ readonly kind: "fleet-leases";
27
+ } | {
28
+ readonly kind: "fleet-instances";
29
+ } | {
30
+ readonly kind: "fleet-usage";
31
+ } | {
32
+ readonly kind: "execution-keys";
33
+ };
34
+ /**
35
+ * 拉取视图(`/api/config/effective`)查询串的**唯一拼装点**:`?principal=`(per-principal 视图)、`?worker=`
36
+ * (worker 作用域视图;对 `wpt_<self>` 是授权必需,对全量令牌无害)。
37
+ * [ref] E-15:执行体绑 scope 的参数将来加在这里(契约字段 settings-schema 6.0.0;本批不铸)。
38
+ */
39
+ export declare function pullViewQuery(view: {
40
+ readonly worker?: string | undefined;
41
+ readonly principal?: string | undefined;
42
+ }): string;
43
+ export interface CenterRequestInit {
44
+ readonly method?: "GET" | "POST" | "DELETE";
45
+ /** JSON 请求体(在场 ⇒ `content-type: application/json`)。 */
46
+ readonly json?: unknown;
47
+ /** 条件请求(`if-none-match`)。 */
48
+ readonly etag?: string | undefined;
49
+ readonly timeoutMs: number;
50
+ readonly fetchImpl?: typeof fetch;
51
+ }
52
+ /**
53
+ * 对中心发一条请求。返回响应与**这次请求所用凭证的身份**(调用方据此给拿回来的候选打分箱标记,
54
+ * 见 `boot/config-center.ts` 的 LKG / prompt 状态分箱)。
55
+ */
56
+ export declare function centerFetch(center: CenterEndpoint, route: CenterRoute, init: CenterRequestInit): Promise<{
57
+ res: Response;
58
+ identity: string;
59
+ }>;
60
+ //# sourceMappingURL=center-request.d.ts.map
@@ -0,0 +1,71 @@
1
+ import settingsSchemaPackage from "@sema-agent/settings-schema/package.json" with { type: "json" };
2
+ import { CenterCredentialUnavailableError } from "../center-credential.js";
3
+ export const SCHEMA_MAJOR_HEADER = "x-sema-schema-major";
4
+ export const SETTINGS_SCHEMA_MAJOR = (() => {
5
+ const version = settingsSchemaPackage.version;
6
+ const m = /^(\d+)\.\d+\.\d+/.exec(version);
7
+ if (m === null)
8
+ throw new Error(`cannot derive ${SCHEMA_MAJOR_HEADER}: @sema-agent/settings-schema package.json has version ${JSON.stringify(version)}`);
9
+ return m[1];
10
+ })();
11
+ export function pullViewQuery(view) {
12
+ const parts = [];
13
+ if (view.principal !== undefined)
14
+ parts.push(`principal=${encodeURIComponent(view.principal)}`);
15
+ if (view.worker)
16
+ parts.push(`worker=${encodeURIComponent(view.worker)}`);
17
+ return parts.length === 0 ? "" : `?${parts.join("&")}`;
18
+ }
19
+ function contentRoot(kind) {
20
+ switch (kind) {
21
+ case "user-jwt":
22
+ return "/api/v1/me";
23
+ case "pull-token":
24
+ case "none":
25
+ return "/api/config";
26
+ }
27
+ }
28
+ function routePath(route, kind) {
29
+ switch (route.kind) {
30
+ case "effective":
31
+ return `/api/config/effective${pullViewQuery(route)}`;
32
+ case "skill-content":
33
+ return `${contentRoot(kind)}/skills/content/${encodeURIComponent(route.contentHash)}`;
34
+ case "prompt-artifact":
35
+ return `${contentRoot(kind)}/prompts/epoch-artifacts/${encodeURIComponent(route.digest)}`;
36
+ case "prompt-blob":
37
+ return `${contentRoot(kind)}/prompts/blobs/${encodeURIComponent(route.digest)}`;
38
+ case "fleet-leases":
39
+ return "/api/fleet/leases";
40
+ case "fleet-instances":
41
+ return "/api/fleet/instances";
42
+ case "fleet-usage":
43
+ return "/api/fleet/usage";
44
+ case "execution-keys":
45
+ return "/api/config/execution-keys";
46
+ }
47
+ }
48
+ export async function centerFetch(center, route, init) {
49
+ const scheme = new URL(center.baseUrl).protocol;
50
+ if (scheme !== "http:" && scheme !== "https:")
51
+ throw new Error(`SEMA_REGISTRY_URL must be http(s), got "${scheme}"`);
52
+ const cred = center.credential.read();
53
+ if (!cred.ok)
54
+ throw new CenterCredentialUnavailableError(cred.reason);
55
+ const headers = { [SCHEMA_MAJOR_HEADER]: SETTINGS_SCHEMA_MAJOR };
56
+ if (cred.kind !== "none")
57
+ headers.authorization = `Bearer ${cred.bearer}`;
58
+ if (init.json !== undefined)
59
+ headers["content-type"] = "application/json";
60
+ if (init.etag)
61
+ headers["if-none-match"] = init.etag;
62
+ const url = `${center.baseUrl.replace(/\/+$/, "")}${routePath(route, cred.kind)}`;
63
+ const res = await (init.fetchImpl ?? fetch)(url, {
64
+ method: init.method ?? "GET",
65
+ headers,
66
+ ...(init.json !== undefined ? { body: JSON.stringify(init.json) } : {}),
67
+ signal: AbortSignal.timeout(init.timeoutMs),
68
+ });
69
+ return { res, identity: cred.identity };
70
+ }
71
+ //# sourceMappingURL=center-request.js.map
@@ -25,14 +25,20 @@
25
25
  * reference. Both brains re-resolve `model.baseUrl || config.baseUrl` inside `buildRequest()` on every
26
26
  * call (core 2.1.0 `brain/openai.js` + `brain/anthropic.js`), so a moved gateway takes effect on the
27
27
  * next task with no restart.
28
- * - the same plane under a **tier-frozen** Runner — deferred, not hot: core expands a PRIVATE catalog copy
29
- * at construction, so `main` defers the whole plane and the `models-tiers` restart slice signals.
30
- * - **`degrade-route`** — the one genuinely boot-frozen catalog consumer left: reactive degrade
28
+ * - tiers (and the whole plane under an active tier table) — **HOT** as well: core expands a PRIVATE catalog
29
+ * copy at Runner construction, and the refresh loop swaps every changed plane into all boot Runners before
30
+ * commit (`Runner.swapDeps`, core 7.10.0; production always wires it). The old "tier-frozen ⇒ defer to restart"
31
+ * arm and its `models-tiers` restart slice retired in S-668 波 0 — `/v1/models` and tier-word routing move on
32
+ * the same tick.
33
+ * - **`degrade-route`** — one of the two genuinely boot-frozen catalog consumers left (the other is the
34
+ * memory-consolidation driver seat, last bullet): reactive degrade
31
35
  * (`MODEL_DEGRADE_REACTIVE`) bakes the target Model + its gateway + its key into the brain composition
32
36
  * at boot. It cannot be hot-applied without rebuilding the brain, so it is registered on the existing
33
37
  * restartRequired /health contract instead (see `restart-signal.ts`). Fail-loud beats serving a stale
34
38
  * gateway silently on the rate-limit path.
35
- * - skills/mcp/scenarios/runtime-gates — restart-to-apply by construction (baked into the boot wiring).
39
+ * - skills(+plugins)/mcp/a2a/scenarios — restart-to-apply by construction (baked into the boot
40
+ * wiring); so is the memory-consolidation driver's model seat (`memory-consolidation` slice, S-668 波 0).
41
+ * (`governance.approvalRequire` left this list in S-668 波 2 — its own staged group, hot on both legs.)
36
42
  *
37
43
  * ([ref] A13, internal-lossless) This module is now a FACADE: the implementation lives
38
44
  * alongside it in `src/config-center/` split by responsibility group (HTTP client / EffectiveConfig
@@ -43,7 +49,7 @@
43
49
  */
44
50
  export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
45
51
  export { mutateInPlace, applyEffective, type ApplyReport, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, } from "./apply-effective.js";
46
- export { restartReasons, planeHasActiveTiers, modelPlaneChanged, type RestartSlice, type RestartSliceCtx, type RestartSignal, } from "./restart-signal.js";
52
+ export { restartReasons, modelPlaneChanged, type RestartSlice, type RestartSliceCtx, type RestartSignal, } from "./restart-signal.js";
47
53
  export { applyCenterSkills, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
48
54
  export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS, type CenterReadFaceApply, type CenterReadFaceReport, type ReadFaceBaseline } from "./read-face.js";
49
55
  export type { CenterSkillManifest, CenterMcpServer, CenterA2aPeer, CenterModel, CenterReadFace, EffectiveConfig, ExecutionRuling, SessionMirrorRuling, } from "./types.js";
@@ -1,6 +1,6 @@
1
1
  export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
2
2
  export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, } from "./apply-effective.js";
3
- export { restartReasons, planeHasActiveTiers, modelPlaneChanged, } from "./restart-signal.js";
3
+ export { restartReasons, modelPlaneChanged, } from "./restart-signal.js";
4
4
  export { applyCenterSkills, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
5
5
  export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS } from "./read-face.js";
6
6
  //# sourceMappingURL=facade.js.map
@@ -6,7 +6,7 @@
6
6
  * 键 → 组 → 换代手法 → **参数化用例 ID**(不是文件名——文件名粒度挡不住「文件在、用例没了」)。
7
7
  *
8
8
  * 机器门在 test/config-hot/batch1-hot-keys-registry.test.ts:登记为热却没有三断言用例 = 红;
9
- * 表的键集与 stage 真正消费的键集(`BATCH1_LIMIT_KEYS`)不等 = 红。
9
+ * 表的键集与 stage 真正消费的键集(`BATCH1_LIMIT_KEYS` ∪ `APPROVAL_GROUP_KEYS`)不等 = 红。
10
10
  *
11
11
  * ⚠️ 本表**只登记已落地的批**。批2-4 的键在各自批次里追加(设计稿 §6),不提前写进来 —— 一个「登记为热
12
12
  * 但没接线」的条目正是本表要消灭的那种谎。
@@ -27,8 +27,8 @@ export interface HotKeyEntry {
27
27
  readonly key: keyof ServiceConfigFlat;
28
28
  readonly group: keyof ServiceConfigGroups;
29
29
  readonly form: HotKeyForm;
30
- /** 落地批次(设计稿 §6 的批号)。 */
31
- readonly batch: 1;
30
+ /** 落地批次:1 = [ref] 批1(limitsHttp 组);2 = S-668 波 2(审批组 —— [ref] §6 原排的「批2」)。 */
31
+ readonly batch: 1 | 2;
32
32
  readonly cases: HotKeyCases;
33
33
  }
34
34
  /**
@@ -57,6 +57,13 @@ export declare const DEFERRED_LIMIT_KEYS: ReadonlyArray<{
57
57
  readonly restartReason: string;
58
58
  readonly why: string;
59
59
  }>;
60
- /** 热键表 = 批1 消费键集**减去**显式 deferred 的那些(表只登记真的热的键)。 */
60
+ /**
61
+ * S-668 波 2([ref] §3)—— **审批组**真正接线的键:`apply-effective.ts` 的 `stageApprovalGate` 消费、报告(世代账
62
+ * `groups.approvalGate`)与拒绝(拒因的键名)都取这一张表。换代手法 A′:消费点本就逐请求现读活 config(审批基线 /
63
+ * SendUserFile 子代闸 / 会话与记忆读面 / parked 赎回),leader 腿与单用户基线改成现读之后,热只差 refresh 腿的写口。
64
+ */
65
+ export declare const APPROVAL_GROUP_KEYS: readonly ["approvalRequire"];
66
+ export type ApprovalGroupKey = (typeof APPROVAL_GROUP_KEYS)[number];
67
+ /** 热键表 = 批1 消费键集**减去**显式 deferred 的那些 + 审批组(表只登记真的热的键)。 */
61
68
  export declare const HOT_KEYS_REGISTRY: readonly HotKeyEntry[];
62
69
  //# sourceMappingURL=hot-keys-registry.d.ts.map
@@ -29,11 +29,15 @@ export const DEFERRED_LIMIT_KEYS = [
29
29
  },
30
30
  ];
31
31
  const DEFERRED_KEYS = new Set(DEFERRED_LIMIT_KEYS.map((d) => d.key));
32
- export const HOT_KEYS_REGISTRY = BATCH1_LIMIT_KEYS.filter((key) => !DEFERRED_KEYS.has(key)).map((key) => ({
33
- key,
34
- group: "limitsHttp",
35
- form: (B_SETTER_KEYS.includes(key) ? "B-setter" : "A-prime"),
36
- batch: 1,
37
- cases: cases(key),
38
- }));
32
+ export const APPROVAL_GROUP_KEYS = ["approvalRequire"];
33
+ export const HOT_KEYS_REGISTRY = [
34
+ ...BATCH1_LIMIT_KEYS.filter((key) => !DEFERRED_KEYS.has(key)).map((key) => ({
35
+ key,
36
+ group: "limitsHttp",
37
+ form: (B_SETTER_KEYS.includes(key) ? "B-setter" : "A-prime"),
38
+ batch: 1,
39
+ cases: cases(key),
40
+ })),
41
+ ...APPROVAL_GROUP_KEYS.map((key) => ({ key, group: "approval", form: "A-prime", batch: 2, cases: cases(key) })),
42
+ ];
39
43
  //# sourceMappingURL=hot-keys-registry.js.map