@sema-agent/server 7.116.1 → 7.118.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 (131) hide show
  1. package/MIGRATION.md +195 -0
  2. package/USAGE.md +65 -41
  3. package/dist/approval-card.d.ts +82 -18
  4. package/dist/approval-card.js +46 -4
  5. package/dist/approval.d.ts +4 -12
  6. package/dist/approval.js +4 -26
  7. package/dist/boot/budget-tracing.js +2 -3
  8. package/dist/boot/config-center.d.ts +2 -2
  9. package/dist/boot/config-center.js +5 -7
  10. package/dist/boot/parked-revive-gate.d.ts +1 -7
  11. package/dist/boot/parked-revive-gate.js +2 -12
  12. package/dist/boot/resolve-spec.js +18 -16
  13. package/dist/boot/runner-deps.d.ts +5 -3
  14. package/dist/boot/runner-deps.js +6 -4
  15. package/dist/boot/stage-07-capability-layer.js +2 -2
  16. package/dist/brain.d.ts +17 -8
  17. package/dist/brain.js +10 -7
  18. package/dist/capabilities/hands-lane.d.ts +25 -0
  19. package/dist/capabilities/hands-lane.js +1 -1
  20. package/dist/capabilities/mcp-probe.d.ts +2 -2
  21. package/dist/config-catalog.js +3 -2
  22. package/dist/config-center/apply-effective.d.ts +6 -16
  23. package/dist/config-center/apply-effective.js +0 -6
  24. package/dist/config-center/facade.d.ts +1 -1
  25. package/dist/config-center/facade.js +1 -1
  26. package/dist/config-center/restart-signal.d.ts +1 -1
  27. package/dist/config-types.d.ts +12 -16
  28. package/dist/config.d.ts +32 -29
  29. package/dist/config.js +20 -27
  30. package/dist/deployment-governance.d.ts +13 -19
  31. package/dist/deployment-governance.js +7 -14
  32. package/dist/governance-overrides.d.ts +20 -0
  33. package/dist/governance-overrides.js +8 -0
  34. package/dist/host-decision.d.ts +83 -25
  35. package/dist/host-decision.js +31 -1
  36. package/dist/http/admission.js +13 -7
  37. package/dist/http/approval-sweeps.js +16 -13
  38. package/dist/http/checkpoint-ctx.d.ts +83 -24
  39. package/dist/http/checkpoint-ctx.js +68 -8
  40. package/dist/http/mcp-dial-deadline.d.ts +35 -0
  41. package/dist/http/mcp-dial-deadline.js +21 -0
  42. package/dist/http/memory-compliance-failure-reply.d.ts +11 -5
  43. package/dist/http/memory-compliance-failure-reply.js +17 -8
  44. package/dist/http/memory-store-refusal-reply.d.ts +14 -11
  45. package/dist/http/memory-store-refusal-reply.js +16 -5
  46. package/dist/http/resume-legs.js +18 -18
  47. package/dist/http/retired-skill-manifest.d.ts +1 -1
  48. package/dist/http/route-ctx.d.ts +3 -3
  49. package/dist/http/routes/approvals-assistant.js +21 -24
  50. package/dist/http/routes/capabilities.js +5 -3
  51. package/dist/http/routes/mcp-reconnect.d.ts +17 -0
  52. package/dist/http/routes/mcp-reconnect.js +6 -1
  53. package/dist/http/routes/memory-bundle.js +1 -1
  54. package/dist/http/routes/memory-compliance.js +1 -1
  55. package/dist/http/routes/memory-origin.js +1 -1
  56. package/dist/http/routes/runs.js +53 -33
  57. package/dist/http/routes/sessions.d.ts +1 -1
  58. package/dist/http/routes/sessions.js +3 -6
  59. package/dist/http/routes/workflows.js +2 -0
  60. package/dist/http/wire-types.d.ts +6 -1
  61. package/dist/memory-store-refusal.d.ts +42 -17
  62. package/dist/memory-store-refusal.js +25 -6
  63. package/dist/model-select.d.ts +16 -5
  64. package/dist/model-select.js +7 -3
  65. package/dist/model-unreachable-notice.d.ts +1 -1
  66. package/dist/namespaced-rule-name.d.ts +45 -39
  67. package/dist/namespaced-rule-name.js +36 -22
  68. package/dist/observability/corrupt-read-seat.d.ts +4 -0
  69. package/dist/observability/corrupt-read-seat.js +1 -0
  70. package/dist/observability/fail-open.d.ts +15 -3
  71. package/dist/observability/fail-open.js +15 -3
  72. package/dist/observability/permission-rule-events.d.ts +3 -0
  73. package/dist/observability/permission-rule-events.js +4 -0
  74. package/dist/observability/run-terminal.js +21 -1
  75. package/dist/operator-ask.d.ts +4 -1
  76. package/dist/orchestration/workflow-agent-session-index.d.ts +82 -59
  77. package/dist/orchestration/workflow-agent-session-index.js +80 -14
  78. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -0
  79. package/dist/orchestration/workflow-completion-inbox.js +39 -17
  80. package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
  81. package/dist/orchestration/workflow-notify-journal.js +7 -2
  82. package/dist/outbound-dispatcher.d.ts +47 -20
  83. package/dist/outbound-dispatcher.js +198 -23
  84. package/dist/parked-decide.d.ts +43 -48
  85. package/dist/parked-decide.js +54 -16
  86. package/dist/permission-rule-list.d.ts +20 -31
  87. package/dist/permission-rule-vocab.d.ts +4 -3
  88. package/dist/permission-rule-vocab.js +1 -1
  89. package/dist/plugins/approval-ask-store-sql.d.ts +17 -20
  90. package/dist/plugins/approval-ask-store-sql.js +0 -3
  91. package/dist/plugins/one-time-migrations.d.ts +25 -0
  92. package/dist/plugins/one-time-migrations.js +192 -6
  93. package/dist/plugins/pg-pool.js +1 -1
  94. package/dist/plugins/tidb-pool.js +1 -1
  95. package/dist/plugins/web-search.d.ts +20 -1
  96. package/dist/plugins/web-search.js +39 -10
  97. package/dist/plugins/workflow-run-store-sql.d.ts +7 -6
  98. package/dist/plugins/workflow-run-store-sql.js +5 -7
  99. package/dist/request-key-closure.js +1 -1
  100. package/dist/request-value-closure.d.ts +1 -1
  101. package/dist/router/route-orchestration.d.ts +2 -23
  102. package/dist/rule-entry-list.d.ts +88 -0
  103. package/dist/rule-entry-list.js +102 -0
  104. package/dist/rules-consent.js +3 -0
  105. package/dist/run-local.d.ts +4 -2
  106. package/dist/run-local.js +4 -3
  107. package/dist/runs.d.ts +1 -1
  108. package/dist/runs.js +1 -0
  109. package/dist/runtime-governance.d.ts +5 -21
  110. package/dist/runtime-governance.js +4 -8
  111. package/dist/session-policy-wire.d.ts +13 -12
  112. package/dist/session-policy-wire.js +11 -6
  113. package/dist/spec-fields.d.ts +13 -24
  114. package/dist/spec-fields.js +13 -17
  115. package/dist/standing-answer-grant.d.ts +14 -2
  116. package/dist/standing-answer-grant.js +8 -1
  117. package/dist/task-settings.d.ts +18 -18
  118. package/dist/task-settings.js +63 -32
  119. package/dist/tool-approval.d.ts +77 -145
  120. package/dist/tool-approval.js +80 -62
  121. package/dist/trace/core-keyset-guard.d.ts +6 -6
  122. package/dist/trace/engine-notice-wire.d.ts +4 -6
  123. package/dist/trace/engine-notice-wire.js +14 -1
  124. package/dist/trace/self-minted-wire.d.ts +16 -2
  125. package/dist/trace/self-minted-wire.js +7 -0
  126. package/dist/user-input-ledger.d.ts +24 -12
  127. package/dist/user-input-ledger.js +33 -14
  128. package/package.json +2 -2
  129. package/scripts/drain-check-local.mjs +103 -7
  130. package/scripts/drain-check.mysql.sql +59 -8
  131. package/scripts/drain-check.pg.sql +66 -10
@@ -10,7 +10,7 @@ function hostGateCwd(config, localRoot) {
10
10
  return join(config.localDataRoot ?? localRoot, "parked-revive-unrooted");
11
11
  }
12
12
  export function createParkedReviveInheritedGate(deps) {
13
- const { config, question, approvalExemptionStore, logger, localRoot, approverSeat, memoryEngineRoot } = deps;
13
+ const { config, question, logger, localRoot, approverSeat, memoryEngineRoot } = deps;
14
14
  const slots = deps.slots ?? sandboxPathEnvSlots;
15
15
  return async (row, identity) => {
16
16
  const metadata = await chainMetadataOf(deps, row, identity);
@@ -26,17 +26,7 @@ export function createParkedReviveInheritedGate(deps) {
26
26
  ...homeBase,
27
27
  }
28
28
  : { env: createHostAdjudicationEnv(config, localRoot), ...homeBase };
29
- const rootSessionId = row.rootSessionId;
30
- const baseline = createApprovalBaselinePolicy(config, {
31
- kind: "durable",
32
- question,
33
- ...(approvalExemptionStore && rootSessionId
34
- ? {
35
- exempt: (toolName) => approvalExemptionStore.has(rootSessionId, toolName),
36
- onExempted: (toolName, rawToolName) => logger.info("approval_exempted", { sessionId: rootSessionId, toolName, rawToolName, leg: "parked_revive" }),
37
- }
38
- : {}),
39
- });
29
+ const baseline = createApprovalBaselinePolicy(config, { kind: "durable", question });
40
30
  const governed = applyRuntimeGovernance({ objective: "", toolPolicy: baseline }, createDeploymentGovernanceInputs(config, pathAdjudication, { engineDataRoot: memoryEngineRoot }));
41
31
  return {
42
32
  parentConstraints: [
@@ -1,6 +1,6 @@
1
1
  import { realpathSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { NodeExecutionEnv, QUESTION_AWAITS_RESUME, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE, deriveAskEffective, expandTiers, modePolicyOf, resolveAskSeamForm, tightenTaskSpec } from "@sema-agent/core";
3
+ import { NodeExecutionEnv, QUESTION_AWAITS_RESUME, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE, deriveAskEffective, expandTiers, modePolicyOf, resolveAskSeamForm } from "@sema-agent/core";
4
4
  import { cappedCeiling } from "../budget.js";
5
5
  import { centerPromptProvider, centerIdentityAssembled } from "../capabilities/center-prompts.js";
6
6
  import { SessionEnvironmentSelection, selectEnvironmentTool } from "../capabilities/select-environment-tool.js";
@@ -10,7 +10,7 @@ import { stampHandsLane, toolPolicyForHands } from "../capabilities/hands-lane.j
10
10
  import { buildNoticeLaneFacts, shouldDiscloseNoPersistentMemory, withNoPersistentMemoryNotice } from "../capabilities/memory-notice.js";
11
11
  import { applyLongtailDefer } from "../capabilities/tool-defer.js";
12
12
  import { assertGuardPatternsUsable, buildApprovalGrantNotice, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs } from "../deployment-governance.js";
13
- import { singleUserAutoAcceptBaselineApplies } from "../approval.js";
13
+ import { gateIntentUnserviceableReason, singleUserAutoAcceptBaselineApplies } from "../approval.js";
14
14
  import { acceptShellScratchpadDir, buildEnvFacts, egressForRemoteExec, ensureScratchpadDir, resumeFactsForLane, scratchpadCoveringTaskRoot } from "../env-facts.js";
15
15
  import { FleetEventBus } from "../fleet/fleet-bus.js";
16
16
  import { composeHooks, createTaskHooks } from "../hooks/hook-runner.js";
@@ -24,9 +24,10 @@ import { isRemoteScratchpadLane, remoteScratchpadDirFor } from "../plugins/remot
24
24
  import { bindAttachmentsForTask } from "../plugins/task-attachment-store.js";
25
25
  import { resourceSuspendOptIn } from "../resource-suspend.js";
26
26
  import { isIsolatedExecEnv, routeServiceTask, supPostureOverrides } from "../router/route-orchestration.js";
27
+ import { tightenGoverned } from "../governance-overrides.js";
27
28
  import { gateExecutionLane } from "../runtime-caps-resolver.js";
28
29
  import { applyRuntimeGovernance, stripDelegationTools } from "../runtime-governance.js";
29
- import { HttpError, encodeCheckpointScope } from "../security.js";
30
+ import { HttpError, createCodedHttpError, encodeCheckpointScope } from "../security.js";
30
31
  import { memoryProjectKeyFor, memorySpecForRequest } from "../memory-scope.js";
31
32
  import { inheritEnvFieldIssue, singleMemberOffFieldIssue } from "../request-value-closure.js";
32
33
  import { a2aForScenario, mcpForScenario } from "../config-center/facade.js";
@@ -35,6 +36,7 @@ import { cwdHonored, deviceCwdHonored, effectiveHostWorkspace, inProcessSingleUs
35
36
  import { assertRequestA2aUnlocked, resolveRequestA2a } from "../task-a2a.js";
36
37
  import { assertRequestMcpContentOrigin, assertRequestMcpUnlocked, resolveRequestMcp } from "../task-mcp.js";
37
38
  import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, acceptAppendSystemPrompt, applyTaskSettings, effectivePermissionMode, effectiveThinking, hasConstitutionAnchors, parseTaskSettings, taskHooksHonored, providerDropsAppend, settingsPermissionBucketIssue, withPermissionMode } from "../task-settings.js";
39
+ import { skillDenyInertEntryOf } from "../rule-entry-list.js";
38
40
  import { enableForkFromBody, normalizeRetainSubagentSessions, selfOrchestrationFromBody } from "../task-workflow.js";
39
41
  import { redactSecrets } from "../trace/redact.js";
40
42
  import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
@@ -63,6 +65,9 @@ export function createResolveSpec(ctx) {
63
65
  if (approvalGrantNotice)
64
66
  logger.info(approvalGrantNotice.event, approvalGrantNotice.fields);
65
67
  const resolveSpec = async (body, req, auth, opts) => {
68
+ const seatRefusal = gateIntentUnserviceableReason(config, checkpointStore !== undefined);
69
+ if (seatRefusal !== undefined)
70
+ throw createCodedHttpError(501, "capability.checkpoint_store_required", seatRefusal);
66
71
  assertRequestMcpUnlocked(body.mcpServers, lockedKeys);
67
72
  if (opts?.leg === "fresh")
68
73
  assertRequestMcpContentOrigin(body.mcpServers);
@@ -104,6 +109,11 @@ export function createResolveSpec(ctx) {
104
109
  if (bucketIssue)
105
110
  throw new HttpError(400, bucketIssue.message, { code: "request.field_invalid", extra: bucketIssue.fields });
106
111
  }
112
+ {
113
+ const skillDeny = skillDenyInertEntryOf(body);
114
+ if (skillDeny !== undefined)
115
+ throw new HttpError(400, skillDeny.message, { code: "request.field_invalid" });
116
+ }
107
117
  assertRequestA2aUnlocked(body.a2aPeers, lockedKeys);
108
118
  const gated = await gateScenarioAndAppend(body, auth, opts);
109
119
  const effMode = effectivePermissionMode(body, gated.parsedSettings.settings);
@@ -313,7 +323,7 @@ export function createResolveSpec(ctx) {
313
323
  return false;
314
324
  if (headerStr(req?.headers["x-detach-on-disconnect"]) === "true")
315
325
  return false;
316
- const askForm = resolveAskSeamForm({}, approverSeat !== undefined ? { onAsk: approverSeat } : {});
326
+ const askForm = resolveAskSeamForm(body.approvalReach !== undefined ? { approvalReach: body.approvalReach } : {}, approverSeat !== undefined ? { onAsk: approverSeat } : {});
317
327
  return deriveAskEffective(askForm.form, "unresolved") === "human_reachable";
318
328
  };
319
329
  const assembleSpecLiteral = (body, auth, req, opts, parts, effMode) => {
@@ -362,6 +372,7 @@ export function createResolveSpec(ctx) {
362
372
  ...(() => { const v = toolMaterializeStrategyFromBody(body.toolMaterializeStrategy); return v ? { toolMaterializeStrategy: v } : {}; })(),
363
373
  ...(typeof body.interactiveTools === "boolean" ? { interactiveTools: body.interactiveTools } : {}),
364
374
  ...(declaresInteractivePosture(body, req, opts) ? { interactionPosture: "interactive" } : {}),
375
+ ...(body.approvalReach !== undefined ? { approvalReach: body.approvalReach } : {}),
365
376
  ...(typeof body.oneShot === "boolean" ? { oneShot: body.oneShot } : {}),
366
377
  resilience: normalizeResilience(body.resilience, explicitOperatorOk(auth?.principal, config.operatorPrincipals)),
367
378
  finalVerification: cap.finalVerification === true || body.finalVerification === true ? true : undefined,
@@ -449,16 +460,7 @@ export function createResolveSpec(ctx) {
449
460
  a2a: resolveRequestA2a(a2aForScenario(config.a2aPeers, scenarioName), body.a2aPeers, config, logger),
450
461
  promptProvider: centerDecls ? centerPromptProvider(centerDecls, scenarioName) : cap.promptProvider,
451
462
  toolPolicy: toolPolicyForHands(hands, durableEnabled
452
- ? createApprovalBaselinePolicy(config, {
453
- kind: "durable",
454
- question: liveQuestionFace,
455
- ...(approvalExemptionStore && auth?.sessionId
456
- ? {
457
- exempt: (toolName) => approvalExemptionStore.has(auth.sessionId, toolName),
458
- onExempted: (toolName, rawToolName) => logger.info("approval_exempted", { sessionId: auth.sessionId, toolName, rawToolName }),
459
- }
460
- : {}),
461
- })
463
+ ? createApprovalBaselinePolicy(config, { kind: "durable", question: liveQuestionFace })
462
464
  : singleUserAutoAcceptBaselineApplies(config)
463
465
  ? createApprovalBaselinePolicy(config, { kind: "absent" })
464
466
  : undefined),
@@ -590,7 +592,7 @@ export function createResolveSpec(ctx) {
590
592
  ...(e.detail !== undefined ? { extra: { writeExemptDirCoversRoot: e.detail } } : {}),
591
593
  });
592
594
  }
593
- throw new HttpError(422, `settings are tighten-only and cannot loosen the deployment policy: ${e.message}`);
595
+ throw new HttpError(422, `request settings cannot be applied as written: ${e.message}`);
594
596
  }
595
597
  }
596
598
  if (governed.handsReadOnly === true && Array.isArray(governed.tools)) {
@@ -667,7 +669,7 @@ export function createResolveSpec(ctx) {
667
669
  });
668
670
  logger.info("orchestration_routed", { mode: decision.mode, reason: decision.reason, sessionId: auth?.sessionId ?? null });
669
671
  if (decision.mode === "supervisor") {
670
- return tightenTaskSpec(governed, supPostureOverrides());
672
+ return tightenGoverned(governed, supPostureOverrides());
671
673
  }
672
674
  }
673
675
  return governed;
@@ -236,7 +236,7 @@ export interface RunnerDepsCtx {
236
236
  }
237
237
  /** [ref] A10 留档发现②:main runner `RunnerDeps` 与 main.ts subRunner 字面量之间此前手工重复
238
238
  * 的 ~15 个键,类型标注见 {@link createSharedRunnerDeps} 头注。 */
239
- export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "delegationEntryCaps" | "crossSessionInbound" | "crossSessionDialogExpiry" | "writeProtectedPaths" | "models" | "roles" | "tiers" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "hooks" | "toolResultStore" | "checkpointStore" | "hands" | "sessionPolicyStore" | "usageWindows" | "usageWindowStore" | "memoryScopeAdmission" | "deploymentMemoryScopes" | "sharedMemoryStores" | "compliancePostureResolver" | "lockedConfig" | "retentionPolicy" | "onNotice" | "mcpRevocations">;
239
+ export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "delegationEntryCaps" | "crossSessionInbound" | "crossSessionDialogExpiry" | "writeProtectedPaths" | "allowedTools" | "models" | "roles" | "tiers" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "hooks" | "toolResultStore" | "checkpointStore" | "hands" | "sessionPolicyStore" | "usageWindows" | "usageWindowStore" | "memoryScopeAdmission" | "deploymentMemoryScopes" | "sharedMemoryStores" | "compliancePostureResolver" | "lockedConfig" | "retentionPolicy" | "onNotice" | "mcpRevocations">;
240
240
  /**
241
241
  * [ref] A10 留档发现②(review 2026-07-29,[ref]§三族A 同源修补的延续):main runner 的
242
242
  * `RunnerDeps` 字面量(下方 `createRunnerDeps`)与 `main.ts` 里 subRunner 的 `new Runner({...})`
@@ -311,8 +311,10 @@ export declare function createEngineNoticeSeat(logger: {
311
311
  * 「键为 undefined」与「键缺席」对引擎逐字等价)。**不复制引擎缺省**(`roots`/`static-face`/`carry`/`open`/
312
312
  * CC parity 20/200 一个都不抄):上游改缺省之日起本仓不会成为第二份真源。
313
313
  */
314
- export type DeploymentPostureSeats = Pick<RunnerDeps, "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "delegationEntryCaps" | "crossSessionInbound" | "crossSessionDialogExpiry" | "writeProtectedPaths">;
315
- export declare function buildDeploymentPostureSeats(config: Pick<ServiceConfig, keyof DeploymentPostureSeats>): DeploymentPostureSeats;
314
+ export type DeploymentPostureSeats = Pick<RunnerDeps, "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "delegationEntryCaps" | "crossSessionInbound" | "crossSessionDialogExpiry" | "writeProtectedPaths" | "allowedTools">;
315
+ /** 本族读的配置切面:席名与 config 键同名的那几席 + 运维 allow 表(席 `allowedTools` ← config `approvalGrant`,env 名 `APPROVAL_GRANT` 不改)。 */
316
+ export type DeploymentPostureConfig = Pick<ServiceConfig, Exclude<keyof DeploymentPostureSeats, "allowedTools"> | "approvalGrant">;
317
+ export declare function buildDeploymentPostureSeats(config: DeploymentPostureConfig): DeploymentPostureSeats;
316
318
  export declare function createSharedRunnerDeps(ctx: SharedRunnerDepsCtx): SharedRunnerDeps;
317
319
  export declare function createRunnerDeps(ctx: RunnerDepsCtx): RunnerDeps;
318
320
  //# sourceMappingURL=runner-deps.d.ts.map
@@ -9,14 +9,14 @@ import { createHardenedVmRunner } from "../orchestration/hardened-vm-runner.js";
9
9
  import { createWorkerHardenedVmRunner } from "../orchestration/hardened-vm-worker-runner.js";
10
10
  import { WorkflowAgentRegistry } from "../orchestration/workflow-agent-steer.js";
11
11
  import { resolveServedSession } from "../orchestration/workflow-completion-inbox.js";
12
- import { defaultEngineNoticeRouter } from "../trace/engine-notice-wire.js";
12
+ import { defaultEngineNoticeRouter, presentNoticeDetail } from "../trace/engine-notice-wire.js";
13
13
  import { WorkflowNotifyGate } from "../orchestration/workflow-notify-journal.js";
14
14
  import { makeLoadProjectMemory, makeProbeInstructionSources } from "../project-memory.js";
15
15
  import { createInstructionSourceSeat } from "../instruction-source-notice.js";
16
16
  import { workflowModelAllowlistFor } from "../task-workflow.js";
17
17
  import { buildCrossSessionInboundLayers, crossSessionDialogExpiryOf } from "../cross-session-settings.js";
18
18
  import { createPeerDirectory } from "../peer-directory.js";
19
- import { targetOutsideStoreInIncident } from "../memory-store-refusal.js";
19
+ import { storeFormRefusalInIncident } from "../memory-store-refusal.js";
20
20
  import { coreFileHistoryCaptureSeat } from "../file-history-capture.js";
21
21
  import { rewindFailureCode } from "../file-history-disclosure.js";
22
22
  export function createRunnerDepsOnAsk(toolApproval) {
@@ -41,7 +41,8 @@ const PROMPT_CACHE_BREAK_LEVELS = new Map(Object.entries(PROMPT_CACHE_BREAK_CAUS
41
41
  const CEILING_ORIGINS = new Set(["walltime", "turns"]);
42
42
  export function createEngineNoticeForwarder(logger, router = defaultEngineNoticeRouter, metrics) {
43
43
  return (notice) => {
44
- logger.warn("engine_notice", { code: notice.code, message: notice.message, ...(notice.detail !== undefined ? { detail: notice.detail } : {}) });
44
+ const detail = presentNoticeDetail(notice.code, notice.detail);
45
+ logger.warn("engine_notice", { code: notice.code, message: notice.message, ...(detail !== undefined ? { detail } : {}) });
45
46
  if (metrics !== undefined && notice.code === "task.interrupt_unconsumed") {
46
47
  const origin = notice.detail?.origin;
47
48
  if (typeof origin === "string" && CEILING_ORIGINS.has(origin))
@@ -68,6 +69,7 @@ export function buildDeploymentPostureSeats(config) {
68
69
  crossSessionInbound: Object.keys(crossSessionLayers).length > 0 ? crossSessionLayers : undefined,
69
70
  crossSessionDialogExpiry: crossSessionDialogExpiryOf(config),
70
71
  writeProtectedPaths: config.writeProtectedPaths?.entries,
72
+ allowedTools: config.approvalGrant.length > 0 ? config.approvalGrant : undefined,
71
73
  };
72
74
  return seats;
73
75
  }
@@ -195,7 +197,7 @@ export function createRunnerDeps(ctx) {
195
197
  if (report.patches)
196
198
  metrics.inc("memory_harvest_patches_total", { phase: info.phase }, (report.patches.add ?? 0) + (report.patches.update ?? 0));
197
199
  if (report.incident) {
198
- const code = targetOutsideStoreInIncident(report.incident);
200
+ const code = storeFormRefusalInIncident(report.incident);
199
201
  logger.warn("memory_harvest_incident", { kind: report.incident.kind, phase: info.phase, ...(code !== undefined ? { code } : {}) });
200
202
  }
201
203
  const byCode = new Map();
@@ -64,7 +64,7 @@ export async function runStage07(ctx) {
64
64
  newId: uuidv7,
65
65
  });
66
66
  if (converged > 0)
67
- logger.warn("user_input_ledger_crash_converged", { converged, note: "steers accepted with 200 \"applied\" by a previous process that ended before its run settled were converged as cause:crashed; each session's next run announces them (task.user_steer_undrained) and GET /v1/sessions/:id lists them under userInputUndrained" });
67
+ logger.warn("user_input_ledger_crash_converged", { converged, note: "steers accepted with 200 \"applied\" by a previous process that ended before its run settled were converged as cause:crashed; each session's next run announces them (task.user_steer_undrained, or task.user_followup_undrained for steers queued with priority later) and GET /v1/sessions/:id lists them under userInputUndrained" });
68
68
  return ledger;
69
69
  })()
70
70
  : undefined;
@@ -166,7 +166,7 @@ export async function runStage07(ctx) {
166
166
  const parkedReviveSurface = checkpointStore && backgroundAgentStore && builtinDefaultScenario ? createParkedReviveSurface(builtinDefaultScenario) : undefined;
167
167
  const parkedReviveInheritedGate = parkedReviveSurface && config.durableApproval
168
168
  ? createParkedReviveInheritedGate({
169
- config, question, approvalExemptionStore, logger, localRoot,
169
+ config, question, logger, localRoot,
170
170
  memoryEngineRoot: memoryEngine?.root,
171
171
  approverSeat: createRunnerDepsOnAsk(toolApproval),
172
172
  ...(runnerDeps.runtimeCapsResolver ? { resolveRuntimeCaps: runnerDeps.runtimeCapsResolver } : {}),
package/dist/brain.d.ts CHANGED
@@ -11,7 +11,8 @@ import { type RestartSliceCtx } from "./config-center/restart-signal.js";
11
11
  */
12
12
  export declare const FAST_FAIL_MAX_RETRIES = 2;
13
13
  /**
14
- * Build the external "Brain" (LLM) from config.
14
+ * Build the external "Brain" (LLM) from config — and hand out, from the same assembly, the one reading that only the assembly knows: which
15
+ * reactive-degrade target the shell welded ({@link BrainAssembly.degradeRoute}). {@link createBrain} is the projection for callers that need only the brain.
15
16
  *
16
17
  * Composition (A1 + the 1.38 resilience stack, ordered timeout-innermost → breaker → failover-outermost):
17
18
  * - **Tiered timeouts** (1.38; re-cut by core 7.33.0 along the segments of a call) are passed into each
@@ -39,6 +40,16 @@ export declare const FAST_FAIL_MAX_RETRIES = 2;
39
40
  * transport's header / body clocks off too). The Model itself (id / contextWindow / maxTokens / provider) flows
40
41
  * per-request through `Runner({ models })`.
41
42
  */
43
+ export declare function createBrainAssembly(config: ServiceConfig, deps?: {
44
+ fetchImpl?: typeof fetch;
45
+ breakerState?: BreakerState;
46
+ }): BrainAssembly;
47
+ /** {@link createBrainAssembly} 的产物:组好的 brain + 它的降级壳席(只有组装本身知道壳焊的是哪一只)。 */
48
+ export interface BrainAssembly {
49
+ readonly brain: Brain;
50
+ readonly degradeRoute: DegradeRouteWatch;
51
+ }
52
+ /** 只要 brain 的调用方(`run-local` / 旁路 lane / 测试台架)的投影 —— 与 {@link createBrainAssembly} 同一次组装。 */
42
53
  export declare function createBrain(config: ServiceConfig, deps?: {
43
54
  fetchImpl?: typeof fetch;
44
55
  breakerState?: BreakerState;
@@ -84,16 +95,14 @@ export declare function degradeCredentialIdentityOf(config: Pick<ServiceConfig,
84
95
  * 没有焊壳(车道没开 / 没有目标)⇒ `null`。在场 ↔ 缺席的翻转因此自然是一次变化。
85
96
  */
86
97
  export declare function degradeRouteFingerprint(config: DegradeRoutePlane): unknown;
87
- /** `degrade-route` 片的席:boot 焊进去的那一只 vs 活配置此刻的同一只目标(`restartReasons` 的 `degradeRoute` 两侧读数)。 */
98
+ /**
99
+ * `degrade-route` 片的席:boot 焊进去的那一只 vs 活配置此刻的同一只目标(`restartReasons` 的 `degradeRoute` 两侧读数)。`baked` 在 {@link createBrainAssembly}
100
+ * 焊壳的那一处算好、随装配产物交出 —— 不再有「紧随 createBrain 再捕获一次」的第二个出处。与 boot/memory-consolidation.ts `createConsolidationValveWatch` 同形
101
+ * (`{ read(): { baked, current } }`)。
102
+ */
88
103
  export interface DegradeRouteWatch {
89
104
  read(): NonNullable<RestartSliceCtx["degradeRoute"]>;
90
105
  }
91
- /**
92
- * `create*`:返回带捕获状态(组壳那一刻的指纹)的活对象。**须在 {@link createBrain} 的同一时刻、同一份 config 上调**
93
- * (生产:boot/budget-tracing.ts 紧随 createBrain)—— 否则 `baked` 就不是壳真焊进去的那一只。与 boot/memory-consolidation.ts
94
- * `createConsolidationValveWatch` 同形(`{ read(): { baked, current } }`)。
95
- */
96
- export declare function createDegradeRouteWatch(config: DegradeRoutePlane): DegradeRouteWatch;
97
106
  /**
98
107
  * S-753 复审(opus F2 + codex C1)—— **failover 拓扑 × 首字节段没有部署窗**的启动期披露(纯数据,`build*`;告警由 boot 出)。
99
108
  *
package/dist/brain.js CHANGED
@@ -38,7 +38,7 @@ function constructRouteBrain(label, sourceUrls, construct) {
38
38
  }
39
39
  }
40
40
  const DEFAULT_RESILIENCE = buildDefaultResilience();
41
- export function createBrain(config, deps = {}) {
41
+ export function createBrainAssembly(config, deps = {}) {
42
42
  const { fetchImpl, breakerState } = deps;
43
43
  const r = config.resilience ?? DEFAULT_RESILIENCE;
44
44
  const timeouts = {
@@ -95,11 +95,13 @@ export function createBrain(config, deps = {}) {
95
95
  let brain = anthropicBrain
96
96
  ? createRoutingBrain({ anthropic: anthropicBrain }, { fallback: gatewayBrain })
97
97
  : gatewayBrain;
98
+ let degradeBaked = null;
98
99
  if (config.degrade?.reactive) {
99
100
  const degradeTo = config.degrade.to;
100
101
  const fallbackModel = degradeTargetOf(config);
101
102
  if (fallbackModel) {
102
103
  const credential = degradeCredentialIdentityOf(config, fallbackModel);
104
+ degradeBaked = degradeWeldFingerprint(config, fallbackModel, credential);
103
105
  const ownEnvRef = credential.apiKeyEnv !== undefined ? { [fallbackModel.name]: credential.apiKeyEnv } : {};
104
106
  const ownSealed = credential.sealed !== undefined ? { [fallbackModel.name]: credential.sealed } : {};
105
107
  const fbOwnApiKey = () => resolveModelApiKey(fallbackModel.name, ownEnvRef, process.env, ownSealed);
@@ -139,7 +141,10 @@ export function createBrain(config, deps = {}) {
139
141
  });
140
142
  }
141
143
  }
142
- return createModelReachBrain(brain);
144
+ return { brain: createModelReachBrain(brain), degradeRoute: { read: () => ({ baked: degradeBaked, current: degradeRouteFingerprint(config) }) } };
145
+ }
146
+ export function createBrain(config, deps = {}) {
147
+ return createBrainAssembly(config, deps).brain;
143
148
  }
144
149
  export function degradeTargetOf(config) {
145
150
  return config.degrade?.reactive ? config.models?.[config.degrade.to] : undefined;
@@ -159,7 +164,9 @@ export function degradeRouteFingerprint(config) {
159
164
  const target = degradeTargetOf(config);
160
165
  if (target === undefined || config.degrade === undefined)
161
166
  return null;
162
- const credential = degradeCredentialIdentityOf(config, target);
167
+ return degradeWeldFingerprint(config, target, degradeCredentialIdentityOf(config, target));
168
+ }
169
+ function degradeWeldFingerprint(config, target, credential) {
163
170
  return {
164
171
  model: catalogEntryFingerprint(target),
165
172
  endpoint: openaiRouteBaseUrl(target.baseUrl, config.gatewayBaseUrl) ?? null,
@@ -174,10 +181,6 @@ function sealedIdentityFingerprint(sealed) {
174
181
  return { poisoned: modelKeyPoisonIdentity(sealed), reason: sealed.reason };
175
182
  return { digest: createHash("sha256").update(sealed, "utf8").digest("hex").slice(0, 16) };
176
183
  }
177
- export function createDegradeRouteWatch(config) {
178
- const baked = degradeRouteFingerprint(config);
179
- return { read: () => ({ baked, current: degradeRouteFingerprint(config) }) };
180
- }
181
184
  export function buildFailoverFirstByteNotice(config) {
182
185
  const r = config.resilience ?? DEFAULT_RESILIENCE;
183
186
  const fallbacks = config.gatewayFallbackUrls.length;
@@ -33,6 +33,31 @@ export interface HandsRunnerPair {
33
33
  }
34
34
  /** 按场景表态取 Runner。**穷举 switch**:`ScenarioHands` 加词而此处不改 = 编译错(无 default 臂)。 */
35
35
  export declare function pickHandsRunner(hands: ScenarioHands, pair: HandsRunnerPair): Runner;
36
+ /**
37
+ * core 手带 band 的工具名**全集** —— 「挂 executionEnvFactory 才出现、不挂就消失」的那一批。
38
+ *
39
+ * 两段来源,都不是拍脑袋:
40
+ * · `handBandEffects()`(core 7.8.0 B1 起的导出面;此前是手抄表常量 `HAND_TOOL_EFFECTS`)—— band 的
41
+ * effect 表,由**静态工具目录**按 `mountedBy.includes("hands")` 派生。名册单源之后这张表不再是一张
42
+ * 手抄名表,加/退工具由目录一处说了算。
43
+ * · 下方锚定补集 —— `createHandsToolkit` / 手带块还会铸但**不在** effect 表里的 5 名(实测差集,见
44
+ * test/scenario-hands-lane.test.ts 的对表格:它用真 Runner 跑「有手 roster ∖ 无手 roster」,与本常量逐名对账,
45
+ * core 加/改 band 成员时红,防漂移)。
46
+ *
47
+ * ⚠️ 本全集是**观测口径**(用于防漂移对账),**不是** deny 名单 —— deny 用下面的
48
+ * {@link HANDS_EXCLUSIVE_TOOL_NAMES},理由见那里。
49
+ *
50
+ * 🚨 [ref] P0(2026-08-09):派生时必须过滤 core 的 `RETIRED_TOOL_NAMES` —— core 5.21.0 的
51
+ * `HAND_TOOL_EFFECTS` 里残留已退役的 `MultiEdit`,而 core `prepare-task` 的策略名审计见退役名
52
+ * **无条件 throw**(`config.legacy_tool_name`);不过滤,deny 名单一进 `spec.toolPolicy`,
53
+ * `hands: none` 三场景(scan/code-review/team)在**所有部署**上零 turn 全灭。core 5.21.1 已删
54
+ * 那条死条目([ref]),这道 filter 是结构性防御:将来 core 再退役任何 band 名都不会重演
55
+ * (test/scenario-hands-lane.test.ts 格7/格8 双钉:真 Runner 过审计 + 零交集门)。
56
+ * filter 打在**整表**(effect 表 + 锚定补集)之后 —— caret floor 下装置会解析到更新的 core,
57
+ * 补集名哪天被退役,只滤前半就是同一次故障换个名字(codex 复审 finding,2026-08-09)。
58
+ * S-1047(core 7.41.0 #1156):判据换成 core 导出的**那一只** `judgeRuleToolName`(策略名审计、会话规则店、本仓写口与配置表同调)——
59
+ * 修前这里直读 `RETIRED_TOOL_NAMES` 表,是本仓第三份「这是不是旧名」的手写判据。名册 = 空集(这张 deny 名单不挂在任何一跑的名册上判)。
60
+ */
36
61
  export declare const HANDS_BAND_TOOL_NAMES: readonly string[];
37
62
  /**
38
63
  * band 里**只有手才可能出现**的那批(= deny 名单)。
@@ -1,4 +1,5 @@
1
1
  import { handBandEffects, judgeRuleToolName, combinePolicies, createAllowDenyPolicy } from "@sema-agent/core";
2
+ import { NO_DECLARED_TOOL_NAMES } from "../namespaced-rule-name.js";
2
3
  export function withoutExecutionEnv(deps) {
3
4
  const { executionEnvFactory: _handsDropped, ...rest } = deps;
4
5
  return rest;
@@ -11,7 +12,6 @@ export function pickHandsRunner(hands, pair) {
11
12
  return pair.handsless;
12
13
  }
13
14
  }
14
- const NO_DECLARED_TOOL_NAMES = new Set();
15
15
  export const HANDS_BAND_TOOL_NAMES = Object.freeze([
16
16
  ...handBandEffects().keys(),
17
17
  "SendMessage",
@@ -79,7 +79,7 @@ export declare const MCP_PROBE_MAX_INFLIGHT = 32;
79
79
  *
80
80
  * 🔴 **不是新旋钮**:没有 env、不进 config-catalog、部署方改不了它 —— 「不新铸旋钮」这条要求管的是
81
81
  * **旋钮**,不是常量。数值与既有 `GET /v1/sessions/:id/mcp` 面板的物化上界同阶(那边 10s),而**名字刻意
82
- * 独立**:本仓 `sessions.ts` 的四枚同族时限逐字写着理由 ——「两条腿的物化成本不同族,将来任一侧调窗时不该
82
+ * 独立**:本仓 `sessions.ts` 的同族时限(MCP / A2A / memory-status 各一枚)逐字写着理由 ——「两条腿的物化成本不同族,将来任一侧调窗时不该
83
83
  * 被另一侧的名字绑架」。那边界的是**整次物化**,这边界的是**每台的裁决**,连语义都不是一回事。
84
84
  *
85
85
  * ⚠️ core 的硬条款(d.ts 逐字):`timeoutMs` 界**裁决**不界**拨号** —— 被时钟放弃的那次拨号仍在跑,
@@ -99,7 +99,7 @@ export declare const MCP_PROBE_VERDICT_TIMEOUT_MS = 10000;
99
99
  * 与 core 自己成文的走查时长公式**逐字同形**(d.ts:"its own duration is those per-dial times summed over
100
100
  * `ceil(n / concurrency)` rounds"),所以在一台**对端会答或会失败**的部署上这口钟结构上摸不到 —— 摸到它
101
101
  * 就是真有一台对端在卡发送。读数:1 台 ⇒ 14s;32 台 ⇒ 44s。
102
- * 兄弟先例:`GET /v1/sessions/:id/mcp` 面板的 `MCP_MATERIALIZE_TIMEOUT_MS`(那边界的也是整次物化,但它是
102
+ * 兄弟先例:`GET /v1/sessions/:id/mcp` 面板的 `MCP_DIAL_DEADLINE_MS`(`http/mcp-dial-deadline.ts`;那边界的也是整次物化,但它是
103
103
  * 一个定值;本面的申报条数由调用方决定,所以定值在这里就是那个「与所问无关的宽限」)。
104
104
  * **纯数据** ⇒ `build*`。
105
105
  */
@@ -11,6 +11,7 @@ import { MODEL_PROVIDER_WORDS } from "./model-provider.js";
11
11
  import { buildDisplaySafeUrl } from "./trace/redact.js";
12
12
  import { describeAllowlistPostures } from "./env-name-allowlist-knobs.js";
13
13
  import { buildOutboundProxyPosture } from "./outbound-dispatcher.js";
14
+ import { modelSupportsImages } from "./model-select.js";
14
15
  export const CATALOG_VALUE_TYPES = ["boolean", "number", "string", "enum", "csv", "json", "path", "url", "secret", "opaque"];
15
16
  export const CATALOG_TIMINGS = ["hot", "restart", "deploy", "inert"];
16
17
  export const CATALOG_DANGER_AXES = ["security", "billing", "data"];
@@ -58,7 +59,7 @@ export const CONFIG_CATALOG = [
58
59
  r("ANTHROPIC_VERSION", "modelPlane", "string", "Anthropic API version 头(缺省由 SDK 定)"),
59
60
  r("APPROVAL_AUTO_BUDGET", "approval", "number", "每 run-leg 自动放行的普通门控工具调用数上限(0=关;防审批循环的熔断,不是安全 ask 的帽)", { staticDefault: "0", danger: SEC, configKey: "approvalAutoBudget" }),
60
61
  r("APPROVAL_DENY", "approval", "csv", "始终拒绝的工具名单(装配期拒不可匹配名)", { danger: SEC, configKey: "approvalDeny" }),
61
- r("APPROVAL_GRANT", "approval", "csv", "运维的 allow 规则(工具名规则:精确名 / mcp__<server>__* / a2a__<peer>__*;命中 = 带理由放行,不再为这些调用出「没人表态」卡(不放掉任何一层自己的问);HTTP 提交 / durable 续跑 / 停驻后台 agent 赎回 / run-local 四腿同装(leader worker 首跑与修复腿到不了);不越过任何一层的 deny / ask;不是白名单;坏值拒启)", { danger: SEC, configKey: "approvalGrant", derivedDefaultNote: "缺席 = 空表(本层不表态,折叠与未设逐字同)" }),
62
+ r("APPROVAL_GRANT", "approval", "csv", "运维的 allow 表(引擎 allow 层的部署成员 RunnerDeps.allowedTools,每只 Runner 同一席:HTTP 提交 / 续跑 / 停驻赎回 / run-local / leader 一律;规则文法 = 引擎的:工具名 / mcp__<server>__* / a2a__<peer>__* / Bash(<命令>) / Bash(<前缀>:*) / Read(//<dir>/**);命中 = 免问,清普通问与没人表态两类,内置工具也在内;清不掉任何一层的 deny / ask 规则、APPROVAL_NEVER_AUTO、shell 地板、写保护、治理、hook 的问、plan;不是白名单;引擎读成空的条目 / 服务器段铸不出 ⇒ 拒启)", { danger: SEC, configKey: "approvalGrant", derivedDefaultNote: "缺席 = 空表(本层不表态,折叠与未设逐字同)" }),
62
63
  r("APPROVAL_HMAC_KEYS", "approval", "secret", "直连门(D-G)HMAC 完整性密钥集(空=直连门不激活)", { danger: SEC }),
63
64
  r("APPROVAL_NEVER_AUTO", "approval", "csv", "永不被 AUTO_BUDGET 自动放行的工具集(不可逆动作恒要人)", { danger: SEC, configKey: "approvalNeverAuto" }),
64
65
  r("APPROVAL_REQUIRE", "approval", "csv", "必须人审的工具名单(F4 门;center 腿热:refresh 拍整表换代,无 checkpoint 店的部署发非空名单 ⇒ 审批组整组拒)", { danger: SEC, configKey: "approvalRequire", center: cGov("approvalRequire") }),
@@ -318,7 +319,7 @@ export const CONFIG_CATALOG = [
318
319
  },
319
320
  }),
320
321
  r("MODEL_VERIFIER", "modelPlane", "string", "verify 门的异构 verifier(须命中目录;typo 回 default 并 boot warn=自评自批)", { derivedDefaultNote: "缺席/不在目录 ⇒ default(=implementer,去相关性失效警告)" }),
321
- r("MODEL_VISION", "modelPlane", "boolean", "主模型 vision 声明(false 让 model_no_vision 预检真拦)", { staticDefault: "true", resolve: (c) => (Array.isArray(c.model.input) ? c.model.input.includes("image") : true), center: cModels("models[].vision") }),
322
+ r("MODEL_VISION", "modelPlane", "boolean", "主模型 vision 声明(false = 明说不收图:带图请求的图被换成占位句交给模型,提交时计数并告警)", { staticDefault: "true", resolve: (c) => modelSupportsImages(c.model), center: cModels("models[].vision") }),
322
323
  r("MYSQL_DATABASE", "store", "string", "MySQL 协议库名(选中该引擎时必填)", { danger: DATA }),
323
324
  r("MYSQL_HOST", "store", "string", "MySQL 协议主机(在场即算 SQL 信号,参与 DB_BACKEND 默认推导)", { danger: DATA }),
324
325
  r("MYSQL_PASSWORD", "store", "secret", "MySQL 协议口令", { danger: SEC }),
@@ -81,7 +81,7 @@ export declare function applyEffective(config: ServiceConfig, eff: EffectiveConf
81
81
  * 读者一律从这里派生,禁手抄:
82
82
  * · stage:{@link stageModelPlane} 产出组内**每个**域的产物({@link ModelPlaneStages} 按本表映射),组被拒 ⇒ 一个域都没有;
83
83
  * · 世代账:整组拒的 `keys` 与落地的 `applied` 都由 {@link MODEL_PLANE_SURFACES} 按本表派生({@link modelPlaneGroupReport});
84
- * · boot 按组回落 LKG 的合成文档与 restart 基线:{@link withModelPlaneOf}(`boot/config-center.ts`)。
84
+ * · boot 按组回落 LKG 的合成文档:{@link withModelPlaneOf}(`boot/config-center.ts`)。
85
85
  * (与 `config.ts` 的配置组视图 `MODEL_PLANE_GROUP_KEYS` 不是一回事:那是 `ServiceConfig` 的分节视图;这里是「中心热应用的
86
86
  * 模型面这一组包含哪些 effective 域」。)
87
87
  */
@@ -91,29 +91,19 @@ export type ModelPlaneDomain = (typeof MODEL_PLANE_DOMAINS)[number];
91
91
  export type ModelPlaneOf = Pick<EffectiveConfig, ModelPlaneDomain>;
92
92
  /**
93
93
  * `target` 换上 `source` 的**整个模型面**(组内每个域都取 `source` 的;`source` 缺席的域在结果里同样缺席,不留 `target` 的),
94
- * 其余域原样是 `target` 的。boot 按组回落 LKG 的两处(回落 apply 的合成文档、restart 对照基线)都走这一只 —— 禁在调用点
94
+ * 其余域原样是 `target` 的。boot 按组回落 LKG 的合成文档(回落 apply 的入参)走这一只 —— 禁在调用点
95
95
  * 手写 `{ ...x, models, collab }`(漏一个域正是 V1 的病形)。纯数据:不改两个入参。
96
96
  */
97
97
  export declare function withModelPlaneOf<E extends EffectiveConfig>(target: E, source: ModelPlaneOf): E;
98
- /**
99
- * **boot 快照**(restart 片的对照基线,`boot/config-center.ts` 的 `effective`)= 本次 boot 读到的那份(`read`:拉到的候选,或读回的盘上 LKG)
100
- * 换上**真落地**的模型面 —— `appliedPlane` 是模型面组真落地的那一代(`read` 自己 / 按组回落的 LKG),`undefined` = 组被拒且没有可用的上一代
101
- * (进程服务的是 env 表 ⇒「没有中心模型面」)。其余域恒是 `read` 的。
102
- *
103
- * boot 的两条落地路径(候选拉取、整份 LKG 读回)都经这一只,禁在调用点各写一份:S-789 的病形正是这句只在候选路径上成立(2d09c8e6),
104
- * LKG 读回路径把被拒的 LKG 目录记成基线 ⇒ 修好的目录换掉降级目标时零 `degrade-route`,boot 焊死的降级壳永远指着 env 端点。
105
- * S-794 起 `degrade-route` 片不再读快照的目录(改读降级壳 boot 焊的那只目标 vs 活配置,brain.ts `createDegradeRouteWatch`)——今天没有
106
- * 重启片读快照的模型面;本函数保证的是快照本身说真话(进程真在服务的那一代),不让下一个读它的片重演 S-789。
107
- * 纯数据:不改入参;模型面就是 `read` 自己那一代 ⇒ 原样交回(同一引用)。
108
- */
109
- export declare function bootSnapshotOf<E extends EffectiveConfig>(read: E, appliedPlane: ModelPlaneOf | undefined): E;
110
98
  /**
111
99
  * E-1(7.103.1 修复轮,opus 腿):boot 读到的这一代**整套落地**了吗 —— 只有整套落地的那一代才可以成为 LKG(`latestEffective` /
112
100
  * 盘上 LKG 候选)、才让它的 ETag 做条件请求(§9.5-2 + codex R2-F1)。三件同判,缺一不算:读腿没报坏域(`domainErrors`)、
113
101
  * prompts 面过候选门同一只判形、apply 报告没有被整拒的组(`fullyApplied`;报告缺席 = 没 apply,dry-run 只判前两件)。
114
- * boot 的两条落地路径(候选拉取、整份 LKG 读回)都调这一只,禁在调用点各写一份(此前候选路径三件、LKG 路径只抄了一件)。
102
+ * boot 的两条落地路径(候选拉取、整份 LKG 读回)与 refresh / 迟到 boot 两腿的 apply 之后都调这一只,禁在调用点各写一份(此前候选路径三件、LKG 路径只抄了一件,
103
+ * refresh / 迟到 boot 两腿各内联了第三件)。
115
104
  * LKG 读回路径上前两件在上游已有门(`loadLkg` 的载荷复判拒坏 prompts;读回不产 `domainErrors`),这里同判是同一条规则的同一只谓词,
116
- * 不是第二道门。refresh / 迟到 boot 两腿的候选门在 apply **之前**整拒坏域与坏 prompts(拒的是整份候选,不只是 LKG 推进),不走本谓词。
105
+ * 不是第二道门。refresh / 迟到 boot 两腿的候选门在 apply **之前**整拒坏域与坏 prompts(拒的是整份候选,不只是 LKG 推进),所以到 apply 之后那两腿调本谓词时
106
+ * 前两件恒真、实判的是 `fullyApplied` 一件 —— 调它是为了「干净落地」只有这一个读法,不是再加一道门。
117
107
  */
118
108
  export declare function fullyLandedForLkg(read: {
119
109
  readonly effective: EffectiveConfig;
@@ -207,12 +207,6 @@ function takeModelPlaneDomain(out, source, d) {
207
207
  else
208
208
  out[d] = v;
209
209
  }
210
- function buildEnvOnlyModelPlane() {
211
- return { models: { models: [], roles: {} } };
212
- }
213
- export function bootSnapshotOf(read, appliedPlane) {
214
- return appliedPlane === read ? read : withModelPlaneOf(read, appliedPlane ?? buildEnvOnlyModelPlane());
215
- }
216
210
  export function fullyLandedForLkg(read, report) {
217
211
  const prompts = read.effective.prompts;
218
212
  return (read.domainErrors ?? []).length === 0 && (prompts === undefined || validatePromptsDomain(prompts).ok) && report?.fullyApplied !== false;
@@ -48,7 +48,7 @@
48
48
  * importers to update, which was done in the same commit.
49
49
  */
50
50
  export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
51
- export { mutateInPlace, applyEffective, type ApplyReport, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, MODEL_PLANE_DOMAINS, type ModelPlaneDomain, type ModelPlaneOf, withModelPlaneOf, bootSnapshotOf, fullyLandedForLkg, } from "./apply-effective.js";
51
+ export { mutateInPlace, applyEffective, type ApplyReport, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, MODEL_PLANE_DOMAINS, type ModelPlaneDomain, type ModelPlaneOf, withModelPlaneOf, fullyLandedForLkg, } from "./apply-effective.js";
52
52
  export { restartReasons, modelPlaneChanged, type RestartSlice, type RestartSliceCtx, type EngineRestartSignal, } from "./restart-signal.js";
53
53
  export { applyCenterSkills, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
54
54
  export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS, type CenterReadFaceApply, type CenterReadFaceReport, type ReadFaceBaseline } from "./read-face.js";
@@ -1,5 +1,5 @@
1
1
  export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
2
- export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, MODEL_PLANE_DOMAINS, withModelPlaneOf, bootSnapshotOf, fullyLandedForLkg, } from "./apply-effective.js";
2
+ export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, MODEL_PLANE_DOMAINS, withModelPlaneOf, fullyLandedForLkg, } from "./apply-effective.js";
3
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,7 +6,7 @@ export type RestartSlice = "skills" | "mcp" | "a2a" | "scenarios" | "degrade-rou
6
6
  * EffectiveConfig. Absent/empty = that lane is off ⇒ its slice is inert (null on both sides). */
7
7
  export interface RestartSliceCtx {
8
8
  /** S-794:反应式降级壳的两侧读数 —— `baked` = createBrain 组壳那一刻焊进去的目标路由指纹,`current` = 同一只
9
- * 指纹函数(brain.ts `degradeRouteFingerprint`)对本拍活配置再读一次(读法见 brain.ts `createDegradeRouteWatch`)。
9
+ * 指纹函数(brain.ts `degradeRouteFingerprint`)对本拍活配置再读一次(`baked` 的出处见 brain.ts `createBrainAssembly`)。
10
10
  * 缺席 = 调用方没有递席(单元调用)⇒ `degrade-route` 片恒不签;车道没开 / 没焊壳在席内表达为两侧同为 null。
11
11
  * (此前的 `reactiveDegradeTo` 位只剩「阀门开着」一义,已并进本席 —— 一位不留两义。) */
12
12
  degradeRoute?: {
@@ -1325,22 +1325,18 @@ export interface ServiceConfigFlat {
1325
1325
  * 从未进入任何 policy。判定现在单源在 `hasOperatorGateIntent()`(与 inv#2 同文件)。 */
1326
1326
  approvalNeverAuto: string[];
1327
1327
  /**
1328
- * 7.115.0 —— **运维的 allow 规则**(`APPROVAL_GRANT`,逗号分隔的工具名规则:精确名 / `mcp__<server>__*` / `a2a__<peer>__*`)。命中 = 带理由的
1329
- * 放行(core `decisionReason: "rule"`,运维替这些调用表了态 —— 回答没人表态的服务器工具 / A2A 技能调用;内置工具上引擎的写门、shell 检查照问);未命中 = 不表态。与请求腿的 `settings.permissions.allow` 是**同一种东西**:
1330
- * 同一只编译口(`permission-rule-list.ts` `createRuleListPolicy`)、同一只校验(`config.ts` `approvalGrantRefusal` → `unmatchableToolNamesRefusal`,
1331
- * 坏值拒启)。施加点 = 部署治理链的单一构造口(`deployment-governance.ts` → `runtime-governance.ts` `applyRuntimeGovernance` 的一片叶),
1332
- * 所以 HTTP 提交 / durable 续跑(含 leader worker 停驻后经 `/decide` 续跑的那一段)/ 停驻后台 agent 的跨副本赎回 / run-local 四条腿同装(leader worker 首跑与修复腿到不了),每次现取(不 memoize)。**不越过**任何一层的 deny / ask(折叠 deny > ask > allow)。
1333
- *
1334
- * ⚠️ 不是白名单:名字不叫 `…_ALLOW`,与会话规则的 `toolAllow`(「只许这些」)刻意分开 —— 本键只给出表态,不收窄任何东西。
1335
- *
1336
- * 三问:谁需要 = 在没有请求体的腿上接了 MCP 服务器的运维 —— `run-local`,以及 durable 续跑(每一次从持久化体重建,含 leader worker 停驻后经 `/decide`
1337
- * 续跑的那一段;A2A 不在射程:运维声明的对端恒带外发标记,那一问本键不放) —— core 7.40.0 起没人表态的服务器工具调用要问,无席 ⇒ 拒、有 durable 席 ⇒ 每次停驻等人,
1338
- * 这里是运维为自己接的服务器表态的唯一位置。**到不了**:leader worker 的首跑与修复腿(工具策略在 leader 编排里自己折出来 —— `leader/wire.ts` 的
1339
- * `combinePolicies(createDurableQuestionPolicy(), createDurableAskPolicy(…))` —— 不经上面那只构造口);这两条腿今天不挂 MCP / A2A,本键在那里没有对象。谁受伤 = 缺省空表,无人受伤 —— 设了宽规则
1340
- * (`mcp__<server>__*`,含那台服务器将来新增的工具)= 对**每一个租户、每一条腿**的那些调用都不再因「没人表态」而问人,风险随规则的宽度走
1341
- * (射程只到这一问:任何一层自己的问照旧压过它 —— 内置工具的精确名写进来,引擎的写门 / 形状层照问);补偿 = 本键就是
1342
- * core 7.40.0 那条收紧的补偿,deny / ask(`APPROVAL_DENY` / `APPROVAL_REQUIRE` / 请求 settings / 会话规则)照旧压过它。
1343
- * v1 只有 env 一个写入通道(无 config-center 座位,不进 settings-schema)。空表 = 本层不铸叶,折叠逐字与修前同。
1328
+ * **运维的 allow 表**(`APPROVAL_GRANT`,逗号分隔的 allow 规则)—— 7.117.0(core 7.42.0)起是引擎 allow 层的**部署成员** `RunnerDeps.allowedTools`:
1329
+ * 经部署姿态座席族(`boot/runner-deps.ts` `buildDeploymentPostureSeats`)交给本进程造的每一只 Runner(boot / run-local / leader),Runner 构造时读一次。
1330
+ * 命中 = 免问(core `decisionReason: "deployment_rule"`),清「普通问 + 没人表态」两类,内置工具也在内;清不掉任何一层的 deny、ask 规则、运维的强制类
1331
+ * (`APPROVAL_NEVER_AUTO` / `MANUAL_MODE_SHELL_GATE=always` / 写保护 / 治理)、hook 的问、plan 的问。文法 = 引擎的(工具名 / 协议名覆盖形 / `Bash(...)` 命令规则 /
1332
+ * `Read(//dir/**)`);坏值拒启(`config.ts` `approvalGrantRefusal`:不是字符串表 / 协议名对端段铸不出 / 引擎读成空的条目)。
1333
+ *
1334
+ * ⚠️ 不是白名单:名字不叫 `…_ALLOW`,与会话规则的 `toolAllow`(「只许这些」)刻意分开 —— 本键只放,不收窄任何东西。
1335
+ *
1336
+ * 三问(7.117.0 语义变宽):谁需要 = 没有请求体的腿(`run-local` / leader)上接了 MCP 服务器、或想一句话放掉自家某只工具的运维;谁受伤 = 表里写了内置工具名的部署
1337
+ * (7.115.0–7.116.x 本键只回答「没人表态」,`Bash` / `Write` 写进来什么也不放;本版起这两只不再问);补偿 = 起服行 `approval_grant_in_force` 的 note 写着当前效果、
1338
+ * `GET /v1/diagnostics/wiring` 的 `serverGates.approvalGrant`、`MIGRATION.md` 升级前必读一段;要某只工具必须问,用上面那几只本键清不掉的旋钮。
1339
+ * v1 只有 env 一个写入通道(无 config-center 座位,不进 settings-schema)= 进程常量,与 Runner 席位的寿命一致。空表 = 席位缺席(引擎读作「部署没有 allow 规则」)。
1344
1340
  */
1345
1341
  approvalGrant: string[];
1346
1342
  /** [ref] D-G (HMAC = integrity, not identity; ✅ WIRED — the "SCAFFOLD, not yet wired" note here was STALE,