@sema-agent/server 7.117.0 → 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 (98) hide show
  1. package/MIGRATION.md +97 -0
  2. package/USAGE.md +37 -18
  3. package/dist/approval-card.d.ts +40 -10
  4. package/dist/approval-card.js +11 -1
  5. package/dist/approval.d.ts +4 -12
  6. package/dist/approval.js +4 -26
  7. package/dist/boot/parked-revive-gate.d.ts +1 -7
  8. package/dist/boot/parked-revive-gate.js +2 -12
  9. package/dist/boot/resolve-spec.js +8 -13
  10. package/dist/boot/runner-deps.js +2 -2
  11. package/dist/boot/stage-07-capability-layer.js +2 -2
  12. package/dist/capabilities/hands-lane.d.ts +25 -0
  13. package/dist/capabilities/hands-lane.js +1 -1
  14. package/dist/capabilities/mcp-probe.d.ts +2 -2
  15. package/dist/config.d.ts +14 -18
  16. package/dist/config.js +13 -24
  17. package/dist/deployment-governance.d.ts +2 -7
  18. package/dist/deployment-governance.js +3 -5
  19. package/dist/host-decision.d.ts +83 -25
  20. package/dist/host-decision.js +31 -1
  21. package/dist/http/admission.js +7 -1
  22. package/dist/http/approval-sweeps.js +1 -1
  23. package/dist/http/mcp-dial-deadline.d.ts +35 -0
  24. package/dist/http/mcp-dial-deadline.js +21 -0
  25. package/dist/http/memory-compliance-failure-reply.d.ts +11 -5
  26. package/dist/http/memory-compliance-failure-reply.js +17 -8
  27. package/dist/http/memory-store-refusal-reply.d.ts +14 -11
  28. package/dist/http/memory-store-refusal-reply.js +16 -5
  29. package/dist/http/resume-legs.js +7 -3
  30. package/dist/http/routes/approvals-assistant.js +21 -24
  31. package/dist/http/routes/capabilities.js +2 -0
  32. package/dist/http/routes/mcp-reconnect.d.ts +17 -0
  33. package/dist/http/routes/mcp-reconnect.js +6 -1
  34. package/dist/http/routes/memory-bundle.js +1 -1
  35. package/dist/http/routes/memory-compliance.js +1 -1
  36. package/dist/http/routes/memory-origin.js +1 -1
  37. package/dist/http/routes/runs.js +41 -33
  38. package/dist/http/routes/sessions.d.ts +1 -1
  39. package/dist/http/routes/sessions.js +3 -6
  40. package/dist/http/routes/workflows.js +2 -0
  41. package/dist/http/wire-types.d.ts +6 -1
  42. package/dist/memory-store-refusal.d.ts +42 -17
  43. package/dist/memory-store-refusal.js +25 -6
  44. package/dist/model-unreachable-notice.d.ts +1 -1
  45. package/dist/namespaced-rule-name.d.ts +45 -41
  46. package/dist/namespaced-rule-name.js +36 -22
  47. package/dist/observability/corrupt-read-seat.d.ts +4 -0
  48. package/dist/observability/corrupt-read-seat.js +1 -0
  49. package/dist/observability/fail-open.d.ts +14 -2
  50. package/dist/observability/fail-open.js +14 -2
  51. package/dist/operator-ask.d.ts +4 -1
  52. package/dist/orchestration/workflow-agent-session-index.d.ts +82 -59
  53. package/dist/orchestration/workflow-agent-session-index.js +80 -14
  54. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -0
  55. package/dist/orchestration/workflow-completion-inbox.js +39 -17
  56. package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
  57. package/dist/orchestration/workflow-notify-journal.js +7 -2
  58. package/dist/outbound-dispatcher.d.ts +29 -20
  59. package/dist/outbound-dispatcher.js +105 -16
  60. package/dist/parked-decide.d.ts +43 -48
  61. package/dist/parked-decide.js +54 -16
  62. package/dist/permission-rule-vocab.d.ts +4 -3
  63. package/dist/permission-rule-vocab.js +1 -1
  64. package/dist/plugins/approval-ask-store-sql.d.ts +17 -20
  65. package/dist/plugins/approval-ask-store-sql.js +0 -3
  66. package/dist/plugins/one-time-migrations.d.ts +5 -0
  67. package/dist/plugins/one-time-migrations.js +110 -4
  68. package/dist/plugins/pg-pool.js +1 -1
  69. package/dist/plugins/tidb-pool.js +1 -1
  70. package/dist/plugins/web-search.d.ts +20 -1
  71. package/dist/plugins/web-search.js +39 -10
  72. package/dist/plugins/workflow-run-store-sql.d.ts +7 -6
  73. package/dist/plugins/workflow-run-store-sql.js +5 -7
  74. package/dist/request-key-closure.js +1 -1
  75. package/dist/request-value-closure.d.ts +1 -1
  76. package/dist/rule-entry-list.d.ts +35 -46
  77. package/dist/rule-entry-list.js +17 -25
  78. package/dist/rules-consent.js +3 -0
  79. package/dist/run-local.js +1 -0
  80. package/dist/runs.d.ts +1 -1
  81. package/dist/session-policy-wire.d.ts +13 -12
  82. package/dist/session-policy-wire.js +11 -6
  83. package/dist/spec-fields.d.ts +7 -9
  84. package/dist/spec-fields.js +4 -7
  85. package/dist/standing-answer-grant.d.ts +14 -2
  86. package/dist/standing-answer-grant.js +8 -1
  87. package/dist/task-settings.js +8 -4
  88. package/dist/tool-approval.d.ts +53 -112
  89. package/dist/tool-approval.js +75 -55
  90. package/dist/trace/core-keyset-guard.d.ts +5 -5
  91. package/dist/trace/self-minted-wire.d.ts +16 -2
  92. package/dist/trace/self-minted-wire.js +7 -0
  93. package/dist/user-input-ledger.d.ts +24 -12
  94. package/dist/user-input-ledger.js +33 -14
  95. package/package.json +2 -2
  96. package/scripts/drain-check-local.mjs +74 -3
  97. package/scripts/drain-check.mysql.sql +43 -17
  98. package/scripts/drain-check.pg.sql +46 -20
package/dist/approval.js CHANGED
@@ -4,7 +4,6 @@ import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
4
4
  import { isExplicitPosture } from "./posture-source.js";
5
5
  import { handsOnWorkerFilePlane } from "./execution-lane-caps.js";
6
6
  import { heldRuleName } from "./tool-rule-names.js";
7
- import { answerGrantsBeyondCall } from "./standing-answer-grant.js";
8
7
  export function hasApprovalListIntent(config) {
9
8
  return (config.approvalRequire.length > 0 ||
10
9
  config.approvalDeny.length > 0 ||
@@ -93,33 +92,12 @@ export function createDurableAskPolicy(opts) {
93
92
  if (neverAutoHit !== undefined)
94
93
  return operatorAlwaysHumanAsk(`APPROVAL_NEVER_AUTO(${neverAutoHit})`, `human approval required to run "${req.toolName}"`);
95
94
  const requireHit = heldRuleName(req, require);
96
- if (requireHit !== undefined) {
97
- const remEarly = req.budget?.resourceRemainingMicroUsd;
98
- if (remEarly !== undefined && remEarly <= 0) {
99
- return operatorAlwaysHumanAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
100
- }
101
- if (opts.exempt && heldRuleName(req, isQuestionTool) === undefined && !answerGrantsBeyondCall(req)) {
102
- return (async () => {
103
- let exempted = false;
104
- try {
105
- exempted = (await opts.exempt(req.toolName)) === true;
106
- }
107
- catch {
108
- exempted = false;
109
- }
110
- if (exempted) {
111
- opts.onExempted?.(req.toolName, req.toolName);
112
- return grantedByRule();
113
- }
114
- return decideRequired(req, requireHit);
115
- })();
116
- }
117
- return decideRequired(req, requireHit);
118
- }
95
+ if (requireHit !== undefined)
96
+ return decideRequired(req, operatorAsk(`APPROVAL_REQUIRE(${requireHit})`, `approval required to run "${req.toolName}"`));
119
97
  return { action: "allow" };
120
98
  },
121
99
  };
122
- function decideRequired(req, requireHit) {
100
+ function decideRequired(req, ask) {
123
101
  const rem = req.budget?.resourceRemainingMicroUsd;
124
102
  if (rem !== undefined && rem <= 0) {
125
103
  return operatorAlwaysHumanAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
@@ -128,7 +106,7 @@ export function createDurableAskPolicy(opts) {
128
106
  autoApproved++;
129
107
  return grantedByRule();
130
108
  }
131
- return operatorAsk(`APPROVAL_REQUIRE(${requireHit})`, `approval required to run "${req.toolName}"`);
109
+ return ask;
132
110
  }
133
111
  }
134
112
  //# sourceMappingURL=approval.js.map
@@ -11,10 +11,6 @@ export interface ParkedReviveGateConfigView extends DeploymentGovernanceConfigVi
11
11
  /** host 腿裁决 env 的 cwd 占位根(见下方 `hostGateCwd`)。 */
12
12
  readonly localDataRoot?: ServiceConfig["localDataRoot"];
13
13
  }
14
- /** 会话豁免探针店的所需切面(canonical toolName 键空间;`StoreBackend.approvalExemption()` 结构满足)。 */
15
- export interface ApprovalExemptionProbe {
16
- has(sessionId: string, toolName: string): Promise<boolean>;
17
- }
18
14
  export interface ParkedReviveGateDeps {
19
15
  /** ⚠️ **活引用**:热改字段(autonomy/commandPolicy/守卫集/审批四旋钮)每次赎回现读,见下方「禁 memoize」。 */
20
16
  readonly config: ParkedReviveGateConfigView;
@@ -47,7 +43,6 @@ export interface ParkedReviveGateDeps {
47
43
  * headless auto-deny,比「再问人一次」严且哑。交了之后最坏也是 park,方向 fail-safe。
48
44
  */
49
45
  readonly approverSeat: RunnerDepsOnAsk | undefined;
50
- readonly approvalExemptionStore: ApprovalExemptionProbe | undefined;
51
46
  /**
52
47
  * [ref] —— core `RunnerDeps.runtimeCapsResolver` 的**同一只**(main.ts 的 `runtimeCapsResolver`,
53
48
  * 单一属主同 `boot/runtime-caps.ts`)。重建条目的第三位决议链元数据 `autoModeArmed` 的唯一判据是
@@ -69,7 +64,6 @@ export interface ParkedReviveGateDeps {
69
64
  * 这一座可改读它 —— 接线不在本次提货单内,见 7.106.0 收车档「未闭环」。 */
70
65
  readonly parentAutoModeIntent?: ((row: BackgroundAgentRecord) => boolean) | undefined;
71
66
  readonly logger: {
72
- info(event: string, fields?: Record<string, unknown>): void;
73
67
  warn(event: string, fields?: Record<string, unknown>): void;
74
68
  };
75
69
  /** host 腿 cwd 占位的落地根(main.ts 的 `localRoot`;`config.localDataRoot` 优先)。 */
@@ -96,7 +90,7 @@ export type RebuiltParentConstraint = NonNullable<RebuiltInheritedGate["parentCo
96
90
  * 赎回腿的父约束链重建([ref] 件二)。
97
91
  *
98
92
  * 语义:**同/跨副本一致地重建「部署 ⊇ 操作员」两层完整链**——审批基线(durable question 门 + F4 高危写
99
- * 审批门 + 会话豁免探针)作 base 的 `toolPolicy` 座,经**折叠属主** `applyRuntimeGovernance`
93
+ * 审批门)作 base 的 `toolPolicy` 座,经**折叠属主** `applyRuntimeGovernance`
100
94
  * (→ core `tightenTaskSpec`)叠上部署治理段(autonomy / commandPolicy / MANUAL_MODE_SHELL_GATE /
101
95
  * 守卫集),取其 `toolPolicy` 装进单层 `parentConstraints`。
102
96
  *
@@ -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: [
@@ -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";
@@ -27,7 +27,7 @@ import { isIsolatedExecEnv, routeServiceTask, supPostureOverrides } from "../rou
27
27
  import { tightenGoverned } from "../governance-overrides.js";
28
28
  import { gateExecutionLane } from "../runtime-caps-resolver.js";
29
29
  import { applyRuntimeGovernance, stripDelegationTools } from "../runtime-governance.js";
30
- import { HttpError, encodeCheckpointScope } from "../security.js";
30
+ import { HttpError, createCodedHttpError, encodeCheckpointScope } from "../security.js";
31
31
  import { memoryProjectKeyFor, memorySpecForRequest } from "../memory-scope.js";
32
32
  import { inheritEnvFieldIssue, singleMemberOffFieldIssue } from "../request-value-closure.js";
33
33
  import { a2aForScenario, mcpForScenario } from "../config-center/facade.js";
@@ -65,6 +65,9 @@ export function createResolveSpec(ctx) {
65
65
  if (approvalGrantNotice)
66
66
  logger.info(approvalGrantNotice.event, approvalGrantNotice.fields);
67
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);
68
71
  assertRequestMcpUnlocked(body.mcpServers, lockedKeys);
69
72
  if (opts?.leg === "fresh")
70
73
  assertRequestMcpContentOrigin(body.mcpServers);
@@ -320,7 +323,7 @@ export function createResolveSpec(ctx) {
320
323
  return false;
321
324
  if (headerStr(req?.headers["x-detach-on-disconnect"]) === "true")
322
325
  return false;
323
- const askForm = resolveAskSeamForm({}, approverSeat !== undefined ? { onAsk: approverSeat } : {});
326
+ const askForm = resolveAskSeamForm(body.approvalReach !== undefined ? { approvalReach: body.approvalReach } : {}, approverSeat !== undefined ? { onAsk: approverSeat } : {});
324
327
  return deriveAskEffective(askForm.form, "unresolved") === "human_reachable";
325
328
  };
326
329
  const assembleSpecLiteral = (body, auth, req, opts, parts, effMode) => {
@@ -369,6 +372,7 @@ export function createResolveSpec(ctx) {
369
372
  ...(() => { const v = toolMaterializeStrategyFromBody(body.toolMaterializeStrategy); return v ? { toolMaterializeStrategy: v } : {}; })(),
370
373
  ...(typeof body.interactiveTools === "boolean" ? { interactiveTools: body.interactiveTools } : {}),
371
374
  ...(declaresInteractivePosture(body, req, opts) ? { interactionPosture: "interactive" } : {}),
375
+ ...(body.approvalReach !== undefined ? { approvalReach: body.approvalReach } : {}),
372
376
  ...(typeof body.oneShot === "boolean" ? { oneShot: body.oneShot } : {}),
373
377
  resilience: normalizeResilience(body.resilience, explicitOperatorOk(auth?.principal, config.operatorPrincipals)),
374
378
  finalVerification: cap.finalVerification === true || body.finalVerification === true ? true : undefined,
@@ -456,16 +460,7 @@ export function createResolveSpec(ctx) {
456
460
  a2a: resolveRequestA2a(a2aForScenario(config.a2aPeers, scenarioName), body.a2aPeers, config, logger),
457
461
  promptProvider: centerDecls ? centerPromptProvider(centerDecls, scenarioName) : cap.promptProvider,
458
462
  toolPolicy: toolPolicyForHands(hands, durableEnabled
459
- ? createApprovalBaselinePolicy(config, {
460
- kind: "durable",
461
- question: liveQuestionFace,
462
- ...(approvalExemptionStore && auth?.sessionId
463
- ? {
464
- exempt: (toolName) => approvalExemptionStore.has(auth.sessionId, toolName),
465
- onExempted: (toolName, rawToolName) => logger.info("approval_exempted", { sessionId: auth.sessionId, toolName, rawToolName }),
466
- }
467
- : {}),
468
- })
463
+ ? createApprovalBaselinePolicy(config, { kind: "durable", question: liveQuestionFace })
469
464
  : singleUserAutoAcceptBaselineApplies(config)
470
465
  ? createApprovalBaselinePolicy(config, { kind: "absent" })
471
466
  : undefined),
@@ -16,7 +16,7 @@ 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) {
@@ -197,7 +197,7 @@ export function createRunnerDeps(ctx) {
197
197
  if (report.patches)
198
198
  metrics.inc("memory_harvest_patches_total", { phase: info.phase }, (report.patches.add ?? 0) + (report.patches.update ?? 0));
199
199
  if (report.incident) {
200
- const code = targetOutsideStoreInIncident(report.incident);
200
+ const code = storeFormRefusalInIncident(report.incident);
201
201
  logger.warn("memory_harvest_incident", { kind: report.incident.kind, phase: info.phase, ...(code !== undefined ? { code } : {}) });
202
202
  }
203
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 } : {}),
@@ -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
  */
package/dist/config.d.ts CHANGED
@@ -297,10 +297,10 @@ export declare function isRosterGateArmed(configCenter: ServiceConfig["configCen
297
297
  * - 退役名(`bash`/`Task`/`KillShell`…)与 pre-prefix 短名(`figma__x`):S-1047(core 7.41.0 #1156)起判据是 core 导出的
298
298
  * **那一只** `judgeRuleToolName`(准备阶段、两只会话规则店、本仓会话规则写口同调),本函数零手写臂;指引 = core 表给的现役指引 /
299
299
  * 补全协议前缀。
300
- * - 够不着挂载工具的协议名:缺段(`mcp__`、`mcp__srv__`、`mcp____x`)与**对端段铸不出**(`mcp__github.com`、`mcp__github.com__*`、
301
- * 超长 / 归一后带连串下划线的服务器段;服务器段带通配的 `mcp__*` 单列判词 `wildcard_peer`)—— 前缀对,但引擎铸注册名时归一对端段,而规则对对端段逐字比,永不匹配。
302
- * 判官与句子是 `namespaced-rule-name.ts` 的那**一只**(判据 = core `mintNamespacePrefix` 的自铸对照;会话规则写口两腿与 SQL 店写口同调),
303
- * 本文件零手写臂。修前(7.116.0 之前)这里只有缺段一臂外加一条只排 `*` 的特例(`coversAServer`),对端段带点号 / 超长的覆盖形在每个面都照收。
300
+ * - 够不着挂载工具的协议名:**对端段铸不出**(`mcp__github.com`、`mcp__github.com__*`、`mcp__*`、超长 / 归一后带连串下划线的服务器段、对端段为空的
301
+ * `mcp__` / `mcp____x`)—— 前缀对,但引擎铸注册名时归一对端段,而规则对对端段逐字比,永不匹配。判官是引擎的 `judgeRuleToolName`(core 7.43.0 #1355 (b)
302
+ * 的判词 `unmintable_segment`,句子带它的 guidance 与 1:1 现名);本仓 7.116.0–7.117.x 那只自比 `mintNamespacePrefix` 的对照随之退役。
303
+ * 引擎判官放行、却一只工具都够不着的一形 —— **工具段为空**(`mcp__srv__`)—— 由本仓判(`namespaced-rule-name.ts`,会话规则写口两腿与 SQL 店写口同调)。
304
304
  * ⚠️ 两段覆盖形 `mcp__<server>`(服务器段是自铸形)够得着(S-1047 收掉的分叉,见下)。
305
305
  * - **core 的文法编不出来的串**(名字里带反斜杠、单条 >512 字符、空串):判词取自 core 自己的
306
306
  * `validatePermissionRules`,见 {@link uncompilableRuleGuidance}。[ref]①([ref])补齐 ——
@@ -338,23 +338,19 @@ export interface UnmatchableToolName {
338
338
  * · `"rule-policy"` —— 消费者是 core 的**规则编译器**(`createPermissionRulePolicy`):`settings.permissions.deny` / `.ask`
339
339
  * (S-177 / core 7.9.0 起)。引擎**真的按内容匹配** deny / ask 的内容形(`Bash(rm -r:*)` / `Edit(~/.sema/**)`),所以本仓不许再替它拒 ——
340
340
  * 判据整条交回 core 的 `validatePermissionRules`,按这张表自己的 behavior 问。core 7.42.0 起策略只说 deny / ask(allow 表不再是本消费者)。
341
- * · `"allow-layer"` —— 消费者是引擎的 **allow 层**(`TaskSpec.allowedTools`):`settings.permissions.allow`(7.117.0 起)。本仓**只判名字**
342
- * (退役名 / 无前缀短名 / 协议名够不着挂载工具 —— 与另两张桶同一只判、同一句),**不判内容**:条目的文法、匹配、读成空都归引擎
343
- * (读成空的引擎在开跑时通告)。没有 behavior 一问(引擎的 allow 层不经规则编译器)。
344
- * 翻转条件:引擎的 allow 层对非现行名也出通告(core 7.43.0 起在条目编译拿到工具名册时给「不是工具名 / 近似名」这类原因)—— 提到那一版、
345
- * 并把名册交给条目编译之后,本消费者的名字判退役(allow 侧整只交引擎)。
341
+ * (7.117.0 有过第三种 `"allow-layer"` —— 请求的 `settings.permissions.allow` 只判名字。7.118.0 / core 7.43.0 #1358 起引擎的 allow 层自己按名册读条目名并通告
342
+ * (`permission.allowed_tool_not_granted`,`reason: "tool_name"` / `near_miss`)⇒ 本仓的名字判退役,allow 侧整只交引擎,只剩引擎读不到的那一形在
343
+ * `task-settings.ts` 经 `rule-entry-list.ts` 拒。)
346
344
  */
347
- export type RuleEntryConsumer = "tool-name-table" | "rule-policy" | "allow-layer";
345
+ export type RuleEntryConsumer = "tool-name-table" | "rule-policy";
348
346
  /**
349
347
  * {@link unmatchableToolNamesRefusal} 的判据入参:这张表喂给谁(名表 / 规则编译器 / allow 层);前两种按哪种态度问 core 的文法、路径基 ——
350
348
  * allow 层没有文法一问(见 {@link RuleEntryConsumer}),型上就不带这两位。
351
349
  */
352
350
  export type UnmatchableToolNameJudgment = {
353
- readonly consumer: "tool-name-table" | "rule-policy";
351
+ readonly consumer: RuleEntryConsumer;
354
352
  readonly behavior: PermissionRule["behavior"];
355
353
  readonly pathBases?: PathRuleBases;
356
- } | {
357
- readonly consumer: "allow-layer";
358
354
  };
359
355
  /**
360
356
  * 「这张表里有永远不会生效的条目」的**判据与拒因句,同一只函数**(返回 `undefined` = 通过;拒 ⇒ 句子 + 被点名的条目)。
@@ -372,13 +368,13 @@ export declare function unmatchableToolNamesRefusal(source: string, names: reado
372
368
  } | undefined;
373
369
  /**
374
370
  * 运维 allow 表 `APPROVAL_GRANT` 的**拒启判据与拒因句**(同一只函数;`config-invariants.ts` 的 `…Refusal` 形:返回句子 = 拒,`undefined` = 通过)。
375
- * 7.117.0(core 7.42.0)起这张表整份投 `RunnerDeps.allowedTools`(引擎 allow 层的部署成员,构造时读一次),判据改成**引擎的读法**,与请求的两张
371
+ * 7.117.0(core 7.42.0)起这张表整份投 `RunnerDeps.allowedTools`(引擎 allow 层的部署成员,构造时读一次),判据是**引擎的读法**,与请求的两张
376
372
  * allow 表同一份(`rule-entry-list.ts`):
377
373
  * ① 值不是字符串表 ⇒ 拒(env 的 CSV 恒给字符串表;判在这里是为了「值一旦换了来源,第一道判仍在」);
378
- * ② 协议名对端段铸不出 / 缺段 / 对端段写通配 ⇒ 拒(与技能清单、请求 allow 桶同一只函数);
379
- * ③ 引擎读成空的条目(`compileAllowList(names, "allow").inert` 非空 —— `*`、`mcp__*`、写路径 / 域名形、带空白或畸形的名字)⇒ 拒,
380
- * 句子点名条目、引擎给的原因词、该怎么写。请求的 allow 侧对 ③ 只收通告;运维这一侧拒启:这是运维自己的配置,写错了当场改,
381
- * 而引擎在 Runner 构造期对读成空的部署条目只发一条无会话的通告。
374
+ * ② 引擎读不到的那一形(工具段为空的协议名)⇒ 拒(与技能清单、请求 allow 桶同一只函数);
375
+ * ③ 引擎读成空的条目(`compileAllowList(names, "allow", 名册 = 空集).inert` 非空 —— `*`、`mcp__*`、写路径 / 域名形、带空白或畸形的名字,以及
376
+ * core 7.43.0 起条目名过引擎判官读成 `tool_name` 的退役名 / 无前缀短名 / 对端段铸不出)⇒ 拒,句子点名条目、引擎给的原因词(名字层带判词与现名)、
377
+ * 该怎么写。请求的 allow 侧对 ③ 只收通告;运维这一侧拒启:这是运维自己的配置,写错了当场改,而引擎对读成空的部署条目只在每一跑发一条通告。
382
378
  * 文法随引擎:工具名、协议名覆盖形、`Bash(...)` 命令规则、`Read(//dir/**)` 读目录都收(内容形里带逗号的写不进 CSV —— 这张表的分隔符就是逗号)。
383
379
  */
384
380
  export declare function approvalGrantRefusal(names: unknown): string | undefined;
package/dist/config.js CHANGED
@@ -18,12 +18,12 @@ import { buildReadFacePostureNotice, readFaceForPosture } from "./read-face-post
18
18
  import { THINKING_LEVELS, THINKING_FORMATS, buildModelCompat, isThinkingFormat, isThinkingTier } from "./model-compat.js";
19
19
  import {} from "./posture-source.js";
20
20
  import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
21
- import { describeUnreachableNamespacedRuleName, ruleEntryToolName, unreachableNamespacedRuleNameOf } from "./namespaced-rule-name.js";
21
+ import { describeUnmatchableRuleName, ruleEntryToolName, unmatchableRuleNameOf } from "./namespaced-rule-name.js";
22
22
  import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal } from "./config-invariants.js";
23
23
  import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, isSingleMachineDefaultLane, laneRequiredEnv } from "./execution-lane-caps.js";
24
24
  import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
25
25
  import { AUTONOMY_MODES } from "./runtime-governance.js";
26
- import { allowLayerEntryCountIssue, describeInertRuleEntries, describeRetiredRuleEntry, describeUnreachableRuleEntry, inertRuleEntriesOf, retiredRuleEntryOf, ruleEntryListShapeOk, unreachableRuleEntryOf } from "./rule-entry-list.js";
26
+ import { allowLayerEntryCountIssue, describeEmptyToolSegmentEntry, describeInertRuleEntries, emptyToolSegmentEntryOf, inertRuleEntriesOf, ruleEntryListShapeOk } from "./rule-entry-list.js";
27
27
  import { createCenterCredentialSource, readCenterCredentialDeclaration } from "./center-credential.js";
28
28
  import { parseCenterCredentialRenewLeadMs } from "./center-credential-renewer.js";
29
29
  import { modelProviderRefusal } from "./model-provider.js";
@@ -1243,29 +1243,21 @@ function findUnmatchableToolNames(names, opts) {
1243
1243
  }
1244
1244
  }
1245
1245
  const subject = opts.consumer === "tool-name-table" ? name : ruleEntryToolName(name);
1246
- const verdict = judgeRuleToolName(subject, NO_DECLARED_TOOL_NAMES);
1247
- if (verdict.kind === "retired") {
1248
- out.push({ name, guidance: verdict.guidance });
1246
+ const verdict = unmatchableRuleNameOf(subject);
1247
+ if (verdict?.kind === "retired") {
1248
+ out.push({ name, guidance: describeUnmatchableRuleName(verdict, subject) });
1249
1249
  continue;
1250
1250
  }
1251
- if (opts.consumer !== "allow-layer") {
1252
- const uncompilable = uncompilableRuleGuidance(name, opts.behavior, opts.pathBases);
1253
- if (uncompilable !== undefined) {
1254
- out.push({ name, guidance: uncompilable });
1255
- continue;
1256
- }
1257
- }
1258
- if (verdict.kind === "unprefixed_namespace") {
1259
- out.push({ name, guidance: `pre-prefix short form — the live name carries a protocol prefix: ${PROTOCOL_TABLE.map((e) => `${e.prefix}${presentToolName(name)}`).join(" or ")}` });
1251
+ const uncompilable = uncompilableRuleGuidance(name, opts.behavior, opts.pathBases);
1252
+ if (uncompilable !== undefined) {
1253
+ out.push({ name, guidance: uncompilable });
1260
1254
  continue;
1261
1255
  }
1262
- const unreachable = unreachableNamespacedRuleNameOf(subject);
1263
- if (unreachable !== undefined)
1264
- out.push({ name, guidance: describeUnreachableNamespacedRuleName(unreachable) });
1256
+ if (verdict !== undefined)
1257
+ out.push({ name, guidance: describeUnmatchableRuleName(verdict, subject) });
1265
1258
  }
1266
1259
  return out;
1267
1260
  }
1268
- const NO_DECLARED_TOOL_NAMES = new Set();
1269
1261
  export function unmatchableToolNamesRefusal(source, names, judgment) {
1270
1262
  const bad = findUnmatchableToolNames(names, judgment);
1271
1263
  if (bad.length === 0)
@@ -1282,12 +1274,9 @@ export function approvalGrantRefusal(names) {
1282
1274
  const over = allowLayerEntryCountIssue(APPROVAL_GRANT_ENV, names);
1283
1275
  if (over !== undefined)
1284
1276
  return `${over.message} — refusing to start rather than running with a truncated list`;
1285
- const retired = retiredRuleEntryOf(names);
1286
- if (retired !== undefined)
1287
- return `${describeRetiredRuleEntry(APPROVAL_GRANT_ENV, retired)} — refusing to start rather than running without it`;
1288
- const unreachable = unreachableRuleEntryOf(names);
1289
- if (unreachable !== undefined)
1290
- return `${describeUnreachableRuleEntry(APPROVAL_GRANT_ENV, unreachable)} — refusing to start rather than running without it`;
1277
+ const empty = emptyToolSegmentEntryOf(names);
1278
+ if (empty !== undefined)
1279
+ return `${describeEmptyToolSegmentEntry(APPROVAL_GRANT_ENV, empty)} — refusing to start rather than running without it`;
1291
1280
  const inert = inertRuleEntriesOf(names, "allow");
1292
1281
  if (inert.length > 0)
1293
1282
  return `${describeInertRuleEntries(APPROVAL_GRANT_ENV, inert)} — the engine would grant nothing for ${inert.length === 1 ? "it" : "them"}; refusing to start rather than running with an allow rule that never takes effect`;
@@ -1,5 +1,4 @@
1
1
  import { FileError, StubExecutionEnv, type ExecutionEnv, type Result, type ToolPolicy } from "@sema-agent/core";
2
- import { type DurableAskOptions } from "./approval.js";
3
2
  import type { ServiceConfig } from "./config.js";
4
3
  import type { applyRuntimeGovernance } from "./runtime-governance.js";
5
4
  /** `applyRuntimeGovernance` 的 governance 实参 —— 从折叠属主的签名位**推导**,不复制形状
@@ -92,12 +91,8 @@ export interface DurableApprovalSeat {
92
91
  readonly kind: "durable";
93
92
  /** 活体问答面;`undefined` ⇒ 恒 durable park 的原形门。 */
94
93
  readonly question: LiveQuestionFace | undefined;
95
- /** 每会话「本会话不再询问」探针(canonical toolName 键空间)。 */
96
- readonly exempt?: DurableAskOptions["exempt"];
97
- /** 豁免短路时的审计钩子。 */
98
- readonly onExempted?: DurableAskOptions["onExempted"];
99
94
  }
100
- /** live 席:Runner 的活 onAsk 当场服 ask(无 park、无会话豁免店)。 */
95
+ /** live 席:Runner 的活 onAsk 当场服 ask(无 park)。 */
101
96
  export interface LiveApprovalSeat {
102
97
  readonly kind: "live";
103
98
  }
@@ -268,7 +263,7 @@ export declare function buildApprovalGrantNotice(config: {
268
263
  * `undefined`**:治理层是 tighten-only 的叠加层,没有基线可叠时它自己也产不出「门在场」这件事。
269
264
  *
270
265
  * 三形,按席穷举(S-694,席的定义见 {@link ApprovalSeat}):
271
- * · `durable` ⇒ AskUserQuestion 判决门 + 三张名单的审批门(deny/neverAuto/预算/会话豁免全在
266
+ * · `durable` ⇒ AskUserQuestion 判决门 + 三张名单的审批门(deny/neverAuto/预算全在
272
267
  * `createDurableAskPolicy` 里)。gated `ask` 由 core 变成 durable checkpoint suspend。
273
268
  * · `live` ⇒ **同一只**三张名单审批门,不叠问答门。gated `ask` 由 core 交给 Runner 的活 onAsk 席
274
269
  * (本口**不**做 ask→deny 映射:无人可问时的拒由那只席说,判据与文案都归它)。
@@ -126,9 +126,9 @@ export function buildApprovalGrantNotice(config) {
126
126
  export function createApprovalBaselinePolicy(config, seat) {
127
127
  switch (seat.kind) {
128
128
  case "durable":
129
- return combinePolicies(createDurableQuestionGate(seat.question), createApprovalListPolicy(config, seat));
129
+ return combinePolicies(createDurableQuestionGate(seat.question), createApprovalListPolicy(config));
130
130
  case "live":
131
- return createApprovalListPolicy(config, {});
131
+ return createApprovalListPolicy(config);
132
132
  case "absent":
133
133
  if (hasApprovalListIntent(config)) {
134
134
  throw new Error("approval baseline: this leg has no approval seat (nobody can answer an ask), yet an approval list is in force " +
@@ -142,14 +142,12 @@ export function createApprovalBaselinePolicy(config, seat) {
142
142
  }
143
143
  }
144
144
  }
145
- function createApprovalListPolicy(config, memory) {
145
+ function createApprovalListPolicy(config) {
146
146
  return createDurableAskPolicy({
147
147
  requireApproval: config.approvalRequire,
148
148
  deny: config.approvalDeny,
149
149
  autoBudget: config.approvalAutoBudget,
150
150
  neverAuto: config.approvalNeverAuto,
151
- ...(memory.exempt ? { exempt: memory.exempt } : {}),
152
- ...(memory.onExempted ? { onExempted: memory.onExempted } : {}),
153
151
  });
154
152
  }
155
153
  //# sourceMappingURL=deployment-governance.js.map