@sema-agent/server 7.117.0 → 7.119.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 (159) hide show
  1. package/MIGRATION.md +182 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +58 -28
  5. package/deploy/sema-up/README.md +2 -0
  6. package/deploy/sema-up/chart/templates/minio.yaml +2 -1
  7. package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
  8. package/deploy/sema-up/chart/templates/server.yaml +1 -0
  9. package/deploy/sema-up/chart/values.yaml +7 -1
  10. package/deploy/sema-up/cluster-up.sh +20 -8
  11. package/deploy/sema-up/kube-up.sh +29 -16
  12. package/deploy/sema-up/lib/buckets.sh +21 -0
  13. package/deploy/sema-up/lib/health.sh +33 -0
  14. package/deploy/sema-up/sema-up.sh +14 -9
  15. package/deploy/sema-up/smoke.sh +16 -6
  16. package/deploy/sema-up/stack/compose.yaml +1 -1
  17. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -1
  18. package/dist/approval-ask-audit-store.js +2 -7
  19. package/dist/approval-card.d.ts +40 -10
  20. package/dist/approval-card.js +11 -1
  21. package/dist/approval.d.ts +55 -17
  22. package/dist/approval.js +74 -33
  23. package/dist/audit.d.ts +12 -1
  24. package/dist/audit.js +62 -2
  25. package/dist/auto-mode-face.d.ts +2 -3
  26. package/dist/bake-runner/main.js +1 -6
  27. package/dist/bake-runner/protocol.d.ts +18 -0
  28. package/dist/bake-runner/protocol.js +22 -2
  29. package/dist/bake-runner/runner.d.ts +12 -8
  30. package/dist/bake-runner/runner.js +5 -9
  31. package/dist/boot/config-center.js +1 -1
  32. package/dist/boot/coordinators.js +4 -1
  33. package/dist/boot/device-lane.d.ts +3 -3
  34. package/dist/boot/device-lane.js +1 -2
  35. package/dist/boot/leader.d.ts +4 -4
  36. package/dist/boot/leader.js +1 -2
  37. package/dist/boot/parked-revive-gate.d.ts +4 -6
  38. package/dist/boot/parked-revive-gate.js +2 -6
  39. package/dist/boot/resolve-spec.js +9 -11
  40. package/dist/boot/runner-deps.js +2 -2
  41. package/dist/boot/stage-07-capability-layer.js +7 -3
  42. package/dist/boot/stage-10-http-server.js +3 -2
  43. package/dist/boot/stores.js +12 -7
  44. package/dist/capabilities/center-plugins.d.ts +2 -0
  45. package/dist/capabilities/center-plugins.js +24 -6
  46. package/dist/capabilities/code-review-council.js +8 -11
  47. package/dist/capabilities/hands-lane.d.ts +25 -0
  48. package/dist/capabilities/hands-lane.js +1 -1
  49. package/dist/capabilities/mcp-probe.d.ts +2 -2
  50. package/dist/capabilities/scenarios.d.ts +34 -7
  51. package/dist/capabilities/scenarios.js +33 -6
  52. package/dist/config-catalog.js +3 -3
  53. package/dist/config.d.ts +14 -18
  54. package/dist/config.js +15 -26
  55. package/dist/deployment-governance.d.ts +5 -7
  56. package/dist/deployment-governance.js +4 -5
  57. package/dist/host-decision.d.ts +114 -31
  58. package/dist/host-decision.js +39 -2
  59. package/dist/http/admission.js +12 -6
  60. package/dist/http/approval-sweeps.js +1 -1
  61. package/dist/http/mcp-dial-deadline.d.ts +35 -0
  62. package/dist/http/mcp-dial-deadline.js +21 -0
  63. package/dist/http/memory-compliance-failure-reply.d.ts +11 -5
  64. package/dist/http/memory-compliance-failure-reply.js +17 -8
  65. package/dist/http/memory-store-refusal-reply.d.ts +14 -11
  66. package/dist/http/memory-store-refusal-reply.js +16 -5
  67. package/dist/http/resume-legs.js +8 -3
  68. package/dist/http/routes/approvals-assistant.d.ts +25 -3
  69. package/dist/http/routes/approvals-assistant.js +27 -26
  70. package/dist/http/routes/capabilities.js +6 -3
  71. package/dist/http/routes/mcp-reconnect.d.ts +17 -0
  72. package/dist/http/routes/mcp-reconnect.js +6 -1
  73. package/dist/http/routes/memory-bundle.js +1 -1
  74. package/dist/http/routes/memory-compliance.js +1 -1
  75. package/dist/http/routes/memory-origin.d.ts +1 -1
  76. package/dist/http/routes/memory-origin.js +1 -1
  77. package/dist/http/routes/runs.js +47 -33
  78. package/dist/http/routes/sessions.d.ts +1 -1
  79. package/dist/http/routes/sessions.js +23 -22
  80. package/dist/http/routes/workflows.js +2 -0
  81. package/dist/http/wire-types.d.ts +17 -13
  82. package/dist/images/manifest.d.ts +7 -1
  83. package/dist/images/manifest.js +18 -16
  84. package/dist/memory-store-refusal.d.ts +38 -18
  85. package/dist/memory-store-refusal.js +25 -7
  86. package/dist/model-unreachable-notice.d.ts +1 -1
  87. package/dist/namespaced-rule-name.d.ts +46 -41
  88. package/dist/namespaced-rule-name.js +36 -22
  89. package/dist/observability/corrupt-read-seat.d.ts +4 -0
  90. package/dist/observability/corrupt-read-seat.js +1 -0
  91. package/dist/observability/fail-open.d.ts +19 -3
  92. package/dist/observability/fail-open.js +19 -3
  93. package/dist/operator-ask.d.ts +6 -2
  94. package/dist/orchestration/workflow-agent-session-index.d.ts +82 -59
  95. package/dist/orchestration/workflow-agent-session-index.js +80 -14
  96. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -0
  97. package/dist/orchestration/workflow-completion-inbox.js +39 -17
  98. package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
  99. package/dist/orchestration/workflow-notify-journal.js +7 -2
  100. package/dist/outbound-dispatcher.d.ts +29 -20
  101. package/dist/outbound-dispatcher.js +105 -16
  102. package/dist/parked-decide.d.ts +43 -48
  103. package/dist/parked-decide.js +54 -16
  104. package/dist/permission-rule-vocab.d.ts +4 -3
  105. package/dist/permission-rule-vocab.js +1 -1
  106. package/dist/plugins/approval-ask-store-sql.d.ts +17 -20
  107. package/dist/plugins/approval-ask-store-sql.js +0 -3
  108. package/dist/plugins/approval-exemption-store.d.ts +4 -0
  109. package/dist/plugins/approval-exemption-store.js +1 -0
  110. package/dist/plugins/blob-backend.d.ts +13 -0
  111. package/dist/plugins/blob-backend.js +26 -0
  112. package/dist/plugins/local-session-store.d.ts +78 -47
  113. package/dist/plugins/local-session-store.js +152 -122
  114. package/dist/plugins/one-time-migrations.d.ts +11 -0
  115. package/dist/plugins/one-time-migrations.js +128 -4
  116. package/dist/plugins/pg-pool.js +1 -1
  117. package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
  118. package/dist/plugins/snapshot-bucket-presence.js +40 -0
  119. package/dist/plugins/store-backend.d.ts +17 -6
  120. package/dist/plugins/store-backend.js +46 -16
  121. package/dist/plugins/store-open-wait.d.ts +33 -0
  122. package/dist/plugins/store-open-wait.js +73 -0
  123. package/dist/plugins/tidb-pool.js +1 -1
  124. package/dist/plugins/web-search.d.ts +20 -1
  125. package/dist/plugins/web-search.js +39 -10
  126. package/dist/plugins/workflow-run-store-sql.d.ts +7 -6
  127. package/dist/plugins/workflow-run-store-sql.js +5 -7
  128. package/dist/request-key-closure.js +1 -1
  129. package/dist/request-value-closure.d.ts +1 -1
  130. package/dist/rule-entry-list.d.ts +35 -46
  131. package/dist/rule-entry-list.js +18 -26
  132. package/dist/rules-consent.js +3 -0
  133. package/dist/run-local.js +5 -3
  134. package/dist/runs.d.ts +1 -1
  135. package/dist/security.d.ts +5 -3
  136. package/dist/security.js +4 -0
  137. package/dist/session-policy-wire.d.ts +13 -12
  138. package/dist/session-policy-wire.js +11 -11
  139. package/dist/spec-fields.d.ts +13 -11
  140. package/dist/spec-fields.js +4 -7
  141. package/dist/standing-answer-grant.d.ts +14 -2
  142. package/dist/standing-answer-grant.js +8 -1
  143. package/dist/store-posture.d.ts +14 -6
  144. package/dist/store-posture.js +7 -4
  145. package/dist/task-settings.d.ts +27 -55
  146. package/dist/task-settings.js +13 -21
  147. package/dist/tool-approval.d.ts +133 -138
  148. package/dist/tool-approval.js +175 -66
  149. package/dist/trace/core-keyset-guard.d.ts +5 -5
  150. package/dist/trace/redact.d.ts +6 -0
  151. package/dist/trace/redact.js +9 -9
  152. package/dist/trace/self-minted-wire.d.ts +16 -2
  153. package/dist/trace/self-minted-wire.js +7 -0
  154. package/dist/user-input-ledger.d.ts +30 -14
  155. package/dist/user-input-ledger.js +25 -14
  156. package/package.json +2 -2
  157. package/scripts/drain-check-local.mjs +74 -3
  158. package/scripts/drain-check.mysql.sql +43 -17
  159. package/scripts/drain-check.pg.sql +46 -20
@@ -1,9 +1,10 @@
1
- import { materializeA2aTools, materializeMcpTools, sanitizePathComponent } from "@sema-agent/core";
1
+ import { SessionError, materializeA2aTools, materializeMcpTools, sanitizePathComponent } from "@sema-agent/core";
2
2
  import { a2aForScenario, mcpForScenario } from "../../config-center/facade.js";
3
3
  import { isUuidV7, isUuidShape, legacyUnownedSessionOk } from "../../security.js";
4
- import { windowMessages, truncateMessageBlobs } from "../../audit.js";
4
+ import { windowMessages, truncateMessageBlobs, redactSessionViewForRead } from "../../audit.js";
5
5
  import { redactSecrets } from "../../trace/redact.js";
6
6
  import { recordFailOpen } from "../../observability/fail-open.js";
7
+ import { MCP_DIAL_DEADLINE_MS, raceDeadline } from "../mcp-dial-deadline.js";
7
8
  import { sentMemoryComplianceFailure } from "../memory-compliance-failure-reply.js";
8
9
  import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
9
10
  import { readWritableSessionPermissionRules, describeSessionPermissionRulesProblem } from "../../session-policy-wire.js";
@@ -13,7 +14,6 @@ import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
13
14
  import * as facts from "../../capability-facts.js";
14
15
  import { rosterAgentId } from "./agents-roster.js";
15
16
  const MCP_STATUS_TTL_MS = 5_000;
16
- const MCP_MATERIALIZE_TIMEOUT_MS = 10_000;
17
17
  const A2A_STATUS_TTL_MS = 5_000;
18
18
  const A2A_MATERIALIZE_TIMEOUT_MS = 10_000;
19
19
  const MEMORY_STATUS_TTL_MS = 5_000;
@@ -154,10 +154,7 @@ export async function handleSessions(req, res, match, ctx) {
154
154
  mcpStatusCache.clear();
155
155
  const panel = (async () => {
156
156
  const matP = materializeMcpTools(specs, principal, undefined, undefined, undefined, deps.mcpRevocations);
157
- let timer;
158
- const mat = await Promise.race([matP.catch(() => null), new Promise((r) => { timer = setTimeout(() => r(null), MCP_MATERIALIZE_TIMEOUT_MS); })]);
159
- if (timer)
160
- clearTimeout(timer);
157
+ const mat = await raceDeadline(matP, MCP_DIAL_DEADLINE_MS).catch(() => null);
161
158
  if (!mat) {
162
159
  void matP.then((m) => m?.dispose().catch(() => undefined)).catch(() => undefined);
163
160
  return { servers: [], asOf: new Date().toISOString(), degraded: true };
@@ -530,7 +527,7 @@ export async function handleSessions(req, res, match, ctx) {
530
527
  }
531
528
  catch (err) {
532
529
  const withheld = (coreMessage) => deps.logger?.warn?.("session_memory_erase_refused", { sessionId, err: coreMessage });
533
- if (sentMemoryComplianceFailure(res, err, { kind: "session_owner", withheld }))
530
+ if (sentMemoryComplianceFailure(res, err, { kind: "session_owner", withheld }, deps.logger))
534
531
  return;
535
532
  deps.logger?.warn?.("session_memory_erase_failed", { sessionId, err: String(err) });
536
533
  sendError(res, 500, "internal.error", "session memory erasure failed");
@@ -599,9 +596,13 @@ export async function handleSessions(req, res, match, ctx) {
599
596
  sendError(res, 501, "capability.session_store_required", "the session audit view requires a durable session store (DB_BACKEND=mysql|pg|local)");
600
597
  return;
601
598
  }
602
- const audit = await readOwnedSessionView(auditFact.sessionAudit, match.params.sessionId);
603
- if (audit === undefined)
599
+ const owned = await readOwnedSessionView(auditFact.sessionAudit, match.params.sessionId);
600
+ if (owned === undefined)
604
601
  return;
602
+ if (!Array.isArray(owned.messages)) {
603
+ throw new SessionError("corrupt_session", `session ${match.params.sessionId} is unreadable: its archive projection carries "messages" as ${owned.messages === null ? "null" : typeof owned.messages}, not an array`);
604
+ }
605
+ const ownedMessages = owned.messages;
605
606
  const q = new URLSearchParams(match.search);
606
607
  const qi = (k) => {
607
608
  const v = q.get(k);
@@ -612,12 +613,12 @@ export async function handleSessions(req, res, match, ctx) {
612
613
  };
613
614
  const msgIndex = qi("message");
614
615
  if (msgIndex !== undefined) {
615
- const messages = audit.messages;
616
- if (msgIndex >= messages.length) {
617
- sendError(res, 404, "not_found.message_index", "message index out of range", { total: messages.length });
616
+ if (msgIndex >= ownedMessages.length) {
617
+ sendError(res, 404, "not_found.message_index", "message index out of range", { total: ownedMessages.length });
618
618
  return;
619
619
  }
620
- sendJson(res, 200, { sessionId: audit.sessionId, floorEntryId: audit.floorEntryId, index: msgIndex, message: messages[msgIndex] });
620
+ const one = redactSessionViewForRead({ ...owned, messages: [ownedMessages[msgIndex]] });
621
+ sendJson(res, 200, { sessionId: owned.sessionId, floorEntryId: owned.floorEntryId, index: msgIndex, message: one.messages[0] });
621
622
  return;
622
623
  }
623
624
  const tail = qi("tail");
@@ -634,19 +635,19 @@ export async function handleSessions(req, res, match, ctx) {
634
635
  }
635
636
  const undrainedKey = undrained !== undefined ? { userInputUndrained: undrained } : {};
636
637
  if (tail === undefined && before === undefined && blobCap === undefined) {
637
- sendJson(res, 200, { ...audit, ...undrainedKey });
638
+ sendJson(res, 200, { ...redactSessionViewForRead(owned), ...undrainedKey });
638
639
  return;
639
640
  }
640
- let messages = audit.messages;
641
+ let selected = ownedMessages;
641
642
  let window;
642
643
  if (tail !== undefined || before !== undefined) {
643
- const w = windowMessages(messages, { ...(tail !== undefined ? { tail } : {}), ...(before !== undefined ? { before } : {}), ...(qi("limit") !== undefined ? { limit: qi("limit") } : {}) });
644
- messages = w.messages;
644
+ const w = windowMessages(ownedMessages, { ...(tail !== undefined ? { tail } : {}), ...(before !== undefined ? { before } : {}), ...(qi("limit") !== undefined ? { limit: qi("limit") } : {}) });
645
+ selected = w.messages;
645
646
  window = w.window;
646
647
  }
647
- if (blobCap !== undefined && blobCap > 0)
648
- messages = truncateMessageBlobs(messages, blobCap);
649
- sendJson(res, 200, { ...audit, messages, ...(window ? { window } : {}), ...undrainedKey });
648
+ const view = redactSessionViewForRead({ ...owned, messages: selected });
649
+ const messages = blobCap !== undefined && blobCap > 0 ? truncateMessageBlobs(view.messages, blobCap) : view.messages;
650
+ sendJson(res, 200, { ...view, messages, ...(window ? { window } : {}), ...undrainedKey });
650
651
  return;
651
652
  }
652
653
  }
@@ -759,7 +760,7 @@ export async function handleSessions(req, res, match, ctx) {
759
760
  return;
760
761
  }
761
762
  if (owner === null) {
762
- sendError(res, 409, "conflict.session_ownerless", "session policy requires an owned session (set REQUIRE_PRINCIPAL / attach a principal); this session is ownerless");
763
+ sendError(res, 409, "conflict.session_ownerless", "this session has no recorded owner, so its rules can be neither read nor changed through this route; rules already stored for it keep applying to the runs of the principal they were stored under. Ways out: on the local backend, during the upgrade window, the first task submitted with a principal on a session written by an earlier version claims it for that principal (this route then serves its owner); or delete the session (DELETE /v1/sessions/:id), which removes its rules with it");
763
764
  return;
764
765
  }
765
766
  const operator = explicitOperatorOk(principal, deps.config.operatorPrincipals);
@@ -254,6 +254,8 @@ function projectParkRow(p) {
254
254
  sessionId: p.sessionId,
255
255
  originRunId: p.originRunId,
256
256
  ...(p.originUnconfirmed === true ? { originUnconfirmed: true } : {}),
257
+ ...(p.parkedBy !== undefined ? { parkedBy: p.parkedBy } : {}),
258
+ ...(p.generation !== undefined ? { generation: p.generation } : {}),
257
259
  };
258
260
  }
259
261
  function summarizeWorkflowDetail(run) {
@@ -6,8 +6,7 @@
6
6
  * cut: ONLY TaskRequestBody (its field types are inline or core imports — no other server.ts-local satellites);
7
7
  * sibling request/response shapes can migrate here later on the same pattern.
8
8
  */
9
- import type { A2aServerSpec, AgentDefinition, McpServerSpec, SkillSpec } from "@sema-agent/core";
10
- import type { SettingsPermissionMode } from "../task-settings.js";
9
+ import type { A2aServerSpec, AgentDefinition, ApprovalReach, McpServerSpec, PermissionMode, SkillSpec } from "@sema-agent/core";
11
10
  import type { wiringManifestFaceEventData } from "../trace/project.js";
12
11
  import type { buildSyncSubmitBody } from "./sync-submit-body.js";
13
12
  export interface TaskRequestBody {
@@ -17,7 +16,9 @@ export interface TaskRequestBody {
17
16
  * table — e.g. "default"/"code-review"/"scan"/"discuss"). An INTENT bounded by the deployment's own table AND,
18
17
  * on a governed worker, by the principal's center-resolved allowlist: an unknown name is a typed 400
19
18
  * `scenario_unknown`, a known-but-not-allowed one a typed 400 `scenario_not_allowed`; absent ⇒ the ruling's
20
- * assigned scenario, else `DEFAULT_SCENARIO` (consumed at `boot/resolve-spec.ts` via `gateScenarioRequest`).
19
+ * assigned scenario, else `DEFAULT_SCENARIO` (consumed at `boot/resolve-spec.ts` via `gateScenarioRequest`). A name
20
+ * on that default chain this deployment does not have is NEVER folded to `default`: an assigned one is a typed 503
21
+ * `state.scenario_assignment_unknown`, a dangling `DEFAULT_SCENARIO` refuses to boot.
21
22
  * ⚠️ scenario-SPECIFIC keys (`repo`/`lenses`/`rounds`, read through `ScenarioRequest`'s index signature) are
22
23
  * deliberately NOT declared here — they belong to one scenario each, not to the shared submit surface. */
23
24
  scenario?: string;
@@ -333,18 +334,16 @@ export interface TaskRequestBody {
333
334
  toolMaterializeStrategy?: "static" | "swap";
334
335
  /** R4 (CC parity): the LIGHT top-level per-turn permission-mode intent (CC `permissionMode`). The shell sends
335
336
  * the RAW mode (axis-agnostic, no client-side interpretation); the SERVICE interprets it tighten-only vs the
336
- * deployment baseline. Post-[ref] all FIVE modes are honored as gate-SHAPE choices (`plan` ⇒ read-only hands +
337
- * `present_plan`; `acceptEdits`/`bypassPermissions`/`auto` select how much the mode-derived fs-write ask gate
338
- * asks — they can never subtract from deployment/operator policy, tightenTaskSpec deny-wins; the old "LOOSEN →
339
- * coerced to default" note was pre-[ref] doc-rot). Folds onto the SAME tighten-only governance as
340
- * `settings.permissions.defaultMode` (the heavier bundle), so it's a lighter alias, not a second path.
337
+ * deployment baseline. All SIX core modes are honored (`plan` ⇒ read-only hands + `present_plan`;
338
+ * `acceptEdits` selects how much the mode-derived fs-write ask gate asks; `bypassPermissions`/`auto`/`dontAsk` ride the
339
+ * engine's own mode table verbatim — `dontAsk` refuses every ask by the mode, zero cards, the gate records
340
+ * `deniedBy:"mode"`) — they can never subtract from deployment/operator policy, tightenTaskSpec deny-wins). Folds onto the SAME
341
+ * tighten-only governance as `settings.permissions.defaultMode` (the heavier bundle), so it's a lighter alias, not a second path.
341
342
  * [ref]-②: CLOSED enum on the wire — an unknown word 400s at submit (`request.field_invalid`, sibling of
342
343
  * promptProfile) instead of silently coercing to `default`; resume replay of a stored body stays lenient
343
- * (coercePermissionMode). */
344
- /** 🔴 型**从值表派生**(S-496 U1 / [ref]):闭集的属主是 `task-settings.ts` 的
345
- * `SETTINGS_PERMISSION_MODES`(受理腿 `isSettingsPermissionMode` 与两句 400 文案同读那一份)。
346
- * 改前这里是第 6 份手抄的五词联合 —— 加/退一个模式时 wire 型面与受理腿会各走各的。 */
347
- permissionMode?: SettingsPermissionMode;
344
+ * (coercePermissionMode).
345
+ * 🔴 型与闭集的属主是 core 的 `PermissionMode` / `PERMISSION_MODES`(受理腿 `isPermissionMode` 与三句 400 文案同读那一份)—— 本仓不持词表。 */
346
+ permissionMode?: PermissionMode;
348
347
  /** MF-30 memory PAUSE (shell-host contract, option B per-request — clay 2026-06-27): `false` makes THIS run
349
348
  * read-only over long-term memory (`TaskSpec.memory.writeScope:null` — the [ref] memory ENGINE materializes/
350
349
  * reads but its harvest commits nothing). Absent/`true` ⇒ normal read+write. The shell's `/memory` pause carries
@@ -466,6 +465,11 @@ export interface TaskRequestBody {
466
465
  * typeof read. Declared per [ref] 件1 ([ref] boundary-must-schema): the submit chain reads this key, and an
467
466
  * undeclared read is a bare cast the exported type cannot audit (embedders could not even write it). */
468
467
  interactiveTools?: boolean;
468
+ /** core 7.43.0 `TaskSpec.approvalReach`: the run declares that NOBODY on it can be asked to approve (the only word: `"none"`; closed set and
469
+ * predicate are core's `APPROVAL_REACH` / `isApprovalReach`). Passed to the spec untranslated (boot/resolve-spec.ts) and to the per-leg
470
+ * interactive-posture judgment (an interactive posture beside it is refused by the engine); absent ⇒ key omitted (byte-identical); an
471
+ * unreadable value is a 400 on submit (admission.ts). Not checkpointed by the engine ⇒ resume legs re-supply it from the persisted body. */
472
+ approvalReach?: ApprovalReach;
469
473
  /** The caller's per-request settings stamp (the client-side `SemaSettings` shape). The KEY SET is deliberately
470
474
  * NOT mirrored here — a hand-written second shape on this interface would be the classic mirror-drift form.
471
475
  * Per-key ownership is split, not single: `parseTaskSettings` (src/task-settings.ts) defensively owns the
@@ -6,7 +6,13 @@
6
6
  * one-time seed and the P3 atomic-publish pipeline — kept side-effect-free so it is unit-tested without a DB.
7
7
  */
8
8
  import type { ImageIndexUpsert, ImageNestedBuildMode, ImageStatus } from "../plugins/store-contracts.js";
9
- /** Project the manifest's `nestedBuild` (string posture OR bool) → the enum (center contract). */
9
+ /**
10
+ * Project the manifest's `nestedBuild` (string posture OR bool) → the enum (center contract).
11
+ *
12
+ * 声明型 = 闭集词 | `true`(老写法的裸布尔,含义 = `rootless-buildkit`)| `false` / 缺席(= `none`)。**其余一律抛** —— 修前「未知的非空词」
13
+ * 被上调成 `rootless-buildkit`(一条没人声明过的 nested-build 能力写进 index),运行期的非声明型真值(数字 / 对象 …)走 `v ? … : …` 同样被上调。
14
+ * 抛出的句子沿用该面既有的拒法:`done` 帧的 auto-register 把它收成「built-but-unregistered」终局 FAILED,错误句带这一句(见 `http/routes/images.ts`)。
15
+ */
10
16
  export declare function nestedBuildModeOf(v: string | boolean | undefined): ImageNestedBuildMode;
11
17
  /** The fields of build.sh's `sandbox-manifest.json` this projection reads (a superset is fine; extras ignored). */
12
18
  export interface BuildManifest {
@@ -1,18 +1,19 @@
1
- const NESTED_BUILD_MODES = [
2
- "none",
3
- "docker-cli-only",
4
- "rootless-buildkit",
5
- "privileged-dind",
6
- ];
1
+ const NESTED_BUILD_MODE_WORDS = {
2
+ none: true,
3
+ "docker-cli-only": true,
4
+ "rootless-buildkit": true,
5
+ "privileged-dind": true,
6
+ };
7
+ const isNestedBuildMode = (v) => Object.prototype.hasOwnProperty.call(NESTED_BUILD_MODE_WORDS, v);
7
8
  export function nestedBuildModeOf(v) {
8
- if (typeof v === "string") {
9
- return NESTED_BUILD_MODES.includes(v)
10
- ? v
11
- : v
12
- ? "rootless-buildkit"
13
- : "none";
14
- }
15
- return v ? "rootless-buildkit" : "none";
9
+ if (v === undefined || v === false)
10
+ return "none";
11
+ if (v === true)
12
+ return "rootless-buildkit";
13
+ if (typeof v === "string" && isNestedBuildMode(v))
14
+ return v;
15
+ const got = typeof v === "string" ? `the string ${JSON.stringify(v.slice(0, 64))}` : v === null ? "null" : Array.isArray(v) ? "an array" : `type ${typeof v}`;
16
+ throw new Error(`manifest capabilities.nestedBuild must be one of ${Object.keys(NESTED_BUILD_MODE_WORDS).join(" | ")} (or a bare boolean); got ${got}`);
16
17
  }
17
18
  export function shmToMB(v) {
18
19
  if (!v)
@@ -32,11 +33,12 @@ export function profileFromManifest(m) {
32
33
  export function manifestToIndexEntry(m, opts) {
33
34
  const profile = profileFromManifest(m);
34
35
  const tag = (m.tag ?? "").includes(":") ? (m.tag ?? "").split(":")[1] : (m.tag ?? "");
36
+ const nestedBuildMode = nestedBuildModeOf(m.capabilities?.nestedBuild);
35
37
  const capabilities = {
36
38
  browser: !!m.capabilities?.browser,
37
39
  db: !!m.capabilities?.db,
38
- nestedBuild: !!m.capabilities?.nestedBuild,
39
- nestedBuildMode: nestedBuildModeOf(m.capabilities?.nestedBuild),
40
+ nestedBuild: nestedBuildMode !== "none",
41
+ nestedBuildMode,
40
42
  };
41
43
  const pc = m.capabilities?.podContract;
42
44
  const devShmMB = shmToMB(pc?.devShm);
@@ -1,18 +1,21 @@
1
1
  /**
2
- * core 7.31.1 #1094 —— **`memory.target_outside_store`** 在本仓的唯一读法属主(纯叶:零 http / 零 boot 依赖)。
3
- *
4
- * ## 这个码说的是什么(core `docs/wire-consumption/7.31.1.md` 逐 face 表,tag v7.31.1 亲读)
5
- * 记忆 store 里的一个目录经软链通到了 store **外**(或 store 根在 backend 打开 / 引擎首次挂载之后被改指)⇒ 引擎对落点在外的
6
- * 删 / 写 / 重放 / 自身写一律**不做**,抛普通 `Error` + `.code`(文案以码领头,点名动作、路径、解析到哪)。core 的读法原话:
7
- * operator 要处置的拒(去掉那只目录链),**不是**可重试的 IO 故障,也**不是**对调用方的裁决;链去掉之前重发恒拒。
2
+ * **店形拒**(store-form refusal)在本仓的唯一读法属主(纯叶:零 http / 零 boot 依赖)—— core 一只铸点(`layout.ts` `storeFormRefusal`)的**两码一族**:
3
+ * · `memory.target_outside_store`(core 7.31.1 #1094):记忆 store 里的一个目录经软链通到了 store **外**(或 store 根在 backend 打开 / 引擎首次
4
+ * 挂载之后被改指)⇒ 引擎对落点在外的删 / 写 / 重放 / 自身写一律**不做**;
5
+ * · `memory.scope_home_not_directory`(core 7.44.0 #1368):一只 scope home(`<memoryDir>/<label>`)不是真目录 —— 被换成软链 / 普通文件 / 特殊文件
6
+ * (`detail.form` ∈ `symlink` | `file` | `other`,`lstat` 判、不跟链)。修前 core 对这一形抛不带码的 `ControlPlaneCorruptError`(与真控制面损坏同类同形),
7
+ * 平铺布局(条目直接住在 home 里)于是答 500,而同一换法在嵌套布局下一层答 409 —— 现在两布局同答这一码。
8
+ * 两码的形相同:普通 `Error` + `.code` + `.detail`(`{ path }`;home 那一码多 `form`),**句子以码领头、不含任何宿主路径**(core 7.44.0 起,
9
+ * 路径只在 `detail.path`)。core 的读法:operator 要处置的盘形(去掉那只链 / 放回真目录),**不是**可重试的 IO 故障,也**不是**对调用方的裁决;
10
+ * 盘形修好之前重发恒拒。本仓据此一条规则:两码同族、409、码原样;路径只进运维行(HTTP 映射一处,见 `http/memory-store-refusal-reply.ts`)。
8
11
  *
9
12
  * ## 两条到达车道,两个读者(本模块各给一只,别处不许再写第二份判据)
10
13
  * ① **抛出面**(抹除 / 出处 / 外源列表 / 清标 / bundle 导出导入 —— 凡触 store 树的 backend 面):`.code` 就是码
11
- * ⇒ {@link targetOutsideStoreOf}。HTTP 映射在 `http/memory-store-refusal-reply.ts`(409,operator 面一处字面量)与
14
+ * ⇒ {@link storeFormRefusalOf}。HTTP 映射在 `http/memory-store-refusal-reply.ts`(409,operator 面一处字面量)与
12
15
  * `http/memory-compliance-failure-reply.ts`(合规面两受众的那一只;会话所有者受众换固定句,S-930)。
13
16
  * ② **报告面**(harvest `incident`):core 契约写明 report 车道上码**只在文本里** —— `sidecar_corrupt` 的 detail 恰两种形
14
- * `memory commit refused: memory.target_outside_store: …` / `memory control plane refused: memory.target_outside_store: …`
15
- * (core `docs/wire-consumption/7.31.1.md` 的 harvest 行逐字)⇒ {@link targetOutsideStoreInIncident} 只认**这两个 lead 紧跟码**
17
+ * `memory commit refused: <码>: …` / `memory control plane refused: <码>: …`(core `docs/wire-consumption/7.31.1.md` 的 harvest 行逐字;
18
+ * 7.44.0 安装树 `engine.js` 两处 incident 铸点同形,home 那一码经同一只 `writeFailureText` 到这里)⇒ {@link storeFormRefusalInIncident} 只认**这两个 lead 紧跟码**
16
19
  * 的位置:lead 与码都是 core 铸的字面,路径与引用数据全在码之后。🔴 不做全文子串找(codex 一审 C2:一条真控制面损坏若在
17
20
  * 它引用的坏数据里恰好带着这串字,子串找会把它标成目录链 —— 两者运维动作相反)。不读散文、不猜语义;结构化码位
18
21
  * (`HarvestReport.incident` / `.warnings` 带码)是向 core 索取的欠账。
@@ -20,19 +23,36 @@
20
23
  * 那几行的前缀是**条目路径**(模型可写)—— 本仓今天不读 warnings 车道(既有面,不是本批回归),也不在模型可写的文本上
21
24
  * 找码;这一半候 core 的结构化码位(登记在案,codex 一审 C3)。
22
25
  *
23
- * ## 不是这个码的三形(core 同表「different surface」,本仓**不**自拼码,如实走各自既有臂)
24
- * · journal 重放目标目录成链 ⇒ `ControlPlaneCorruptError`(无 `.code`,KL-1094-b)⇒ 路由 typed 500 `internal.memory_control_plane_corrupt`;
26
+ * ## 不是这一族的三形(core 同表「different surface」,本仓**不**自拼码,如实走各自既有臂)
27
+ * · journal 重放目标目录成链 ⇒ `ControlPlaneCorruptError`(无 `.code`,KL-1094-b)⇒ 路由 typed 500 `internal.memory_control_plane_corrupt`
28
+ * (core 7.44.0 起 scope home 非目录**不再**走这一类 —— 它有了自己的码,见上;`ControlPlaneCorruptError` 只剩真控制面损坏);
25
29
  * · 引擎原地写撞硬链(nlink>1)⇒ 文本 `has N names (a hard link)`、无码(KL-1094-g / #1105)⇒ 走该写点既有的失败车道(harvest
26
30
  * warnings / 挂载失败),抛出面够不着;
27
31
  * · 解析故障(`EACCES` / `ELOOP` / `ENOTDIR`)⇒ 以 errno 本身抛 ⇒ 路由未分类 500 + 运维 warn(`String(err)` 带 core 补救句)。
32
+ *
33
+ * ## 运维行也在这里铸(两受众同一只,{@link logStoreFormRefusal})
34
+ * 路径不上 wire,只进这一条运维 warn —— 它是**日志行**不是 wire 错误体,所以铸点跟读法属主住在一起(不住 `http/`:`http/` 全树门
35
+ * `test/error-code-key-gate.test.ts` 咬的是错误体里的 `code:` 键位,日志字段名不该借 wire 层的目录去过它)。
28
36
  */
29
- /** core 的码(`src/core/memory-engine/layout.ts` `TARGET_OUTSIDE_STORE`;core 不在包根导出它,本常量是它在本仓的唯一写点)。 */
30
- export declare const TARGET_OUTSIDE_STORE = "memory.target_outside_store";
31
- /** 抛出面:`err` 是这一拒 ⇒ 原样交回(已窄化成 `Error`,调用方要它的 `message`);否则 `undefined`。形 = core 的 `Error` + `.code`。 */
32
- export declare function targetOutsideStoreOf(err: unknown): Error | undefined;
33
- /** 报告面:harvest `incident` 是这一拒 ⇒ 码;否则 `undefined`(真控制面损坏的 `sidecar_corrupt` 走这一臂 —— 不被说成链)。 */
34
- export declare function targetOutsideStoreInIncident(incident: {
37
+ import { type ScopeHomeForm, type StoreFormRefusalCode } from "@sema-agent/core";
38
+ import type { Logger } from "./observability/logger.js";
39
+ /** 一次店形拒的读数(纯数据)。`path` / `form` 取自 core 的 `detail`(读不出 ⇒ `undefined`,不猜、不从句子里抠)。 */
40
+ export interface StoreFormRefusalReading {
41
+ readonly code: StoreFormRefusalCode;
42
+ /** core 原句(以码领头;7.44.0 起不含宿主路径)。 */
43
+ readonly message: string;
44
+ /** `detail.path`:落点 / 被改指的根 / 不是目录的 scope home —— **只给运维行**。 */
45
+ readonly path: string | undefined;
46
+ /** `detail.form`(只在 home 那一码上),经 core 闭集读;本仓只把它写进运维行,不按它分支。 */
47
+ readonly form: ScopeHomeForm | undefined;
48
+ }
49
+ /** 抛出面:`err` 是店形拒(core `StoreFormRefusal` 的形:码在族里)⇒ 读数;否则 `undefined`。`detail` 读不全不改判(码定族,`detail` 只供运维行)。 */
50
+ export declare function storeFormRefusalOf(err: unknown): StoreFormRefusalReading | undefined;
51
+ /** 两受众共用的运维行(一处铸点):码 + core `detail` 里的宿主路径与 home 形。`logger` 缺席(只有不接 logger 的测试装配)⇒ 不落。 */
52
+ export declare function logStoreFormRefusal(logger: Pick<Logger, "warn"> | undefined, refusal: StoreFormRefusalReading): void;
53
+ /** 报告面:harvest `incident` 是这一族拒 ⇒ 码;否则 `undefined`(真控制面损坏的 `sidecar_corrupt` 走这一臂 —— 不被说成店形拒)。 */
54
+ export declare function storeFormRefusalInIncident(incident: {
35
55
  readonly kind: string;
36
56
  readonly detail: string;
37
- }): typeof TARGET_OUTSIDE_STORE | undefined;
57
+ }): StoreFormRefusalCode | undefined;
38
58
  //# sourceMappingURL=memory-store-refusal.d.ts.map
@@ -1,14 +1,32 @@
1
- export const TARGET_OUTSIDE_STORE = "memory.target_outside_store";
2
- export function targetOutsideStoreOf(err) {
1
+ import { SCOPE_HOME_FORMS, STORE_FORM_REFUSAL_CODES } from "@sema-agent/core";
2
+ const isStoreFormRefusalCode = (code) => STORE_FORM_REFUSAL_CODES.includes(code);
3
+ const isScopeHomeForm = (form) => SCOPE_HOME_FORMS.includes(form);
4
+ export function storeFormRefusalOf(err) {
3
5
  if (!(err instanceof Error))
4
6
  return undefined;
5
- const { code } = err;
6
- return code === TARGET_OUTSIDE_STORE ? err : undefined;
7
+ const { code, detail } = err;
8
+ if (!isStoreFormRefusalCode(code))
9
+ return undefined;
10
+ const d = typeof detail === "object" && detail !== null ? detail : {};
11
+ return { code, message: err.message, path: typeof d.path === "string" ? d.path : undefined, form: isScopeHomeForm(d.form) ? d.form : undefined };
12
+ }
13
+ export function logStoreFormRefusal(logger, refusal) {
14
+ logger?.warn("memory_store_form_refused", {
15
+ code: refusal.code,
16
+ ...(refusal.path !== undefined ? { path: refusal.path } : {}),
17
+ ...(refusal.form !== undefined ? { form: refusal.form } : {}),
18
+ });
7
19
  }
8
- const INCIDENT_PREFIXES = [`memory commit refused: ${TARGET_OUTSIDE_STORE}: `, `memory control plane refused: ${TARGET_OUTSIDE_STORE}: `];
9
- export function targetOutsideStoreInIncident(incident) {
20
+ const INCIDENT_LEADS = ["memory commit refused: ", "memory control plane refused: "];
21
+ export function storeFormRefusalInIncident(incident) {
10
22
  if (incident.kind !== "sidecar_corrupt")
11
23
  return undefined;
12
- return INCIDENT_PREFIXES.some((p) => incident.detail.startsWith(p)) ? TARGET_OUTSIDE_STORE : undefined;
24
+ for (const lead of INCIDENT_LEADS) {
25
+ if (!incident.detail.startsWith(lead))
26
+ continue;
27
+ const rest = incident.detail.slice(lead.length);
28
+ return STORE_FORM_REFUSAL_CODES.find((code) => rest.startsWith(`${code}: `));
29
+ }
30
+ return undefined;
13
31
  }
14
32
  //# sourceMappingURL=memory-store-refusal.js.map
@@ -23,7 +23,7 @@
23
23
  * 重试 / 子代对同一个网关再失败,不再说一遍)。受众 user;detail = `{ code, targetHost, proxyInUse?, remedy, sessionId }` ——
24
24
  * `proxyInUse` 只在这一跳走了代理时在场,且只是**变量名**(代理地址是基础设施细节,不上租户 wire,与 `OutboundProxyError` 的
25
25
  * F3 裁定同一条)。`targetHost`(模型端点的 `host:port`)对 user 受众是**新披露的部署事实**(F7 裁定:它是部署自己声明的模型端点,
26
- * 不是基础设施秘密,可以上;代理地址仍不上)—— 不要读成「终局句里本就有」:代理形下 core 终局句展开到代理根因、不含模型主机,
26
+ * 不是基础设施秘密,可以上;代理地址仍不上)—— 不要读成「终局句里本就有」:代理形下终局句只带码与 `via outbound proxy (from <变量>)`、不含任何主机地址,
27
27
  * 辅助调用形下 run 可 completed、租户面本无主机句。
28
28
  */
29
29
  import type { Brain } from "@sema-agent/core";
@@ -1,28 +1,28 @@
1
1
  /**
2
- * 协议规则名(`mcp__…` / `a2a__…`)够不够得着一只挂载工具 —— **一只**判官 + **一只**句子。调它的面(点名,不是「每个面」):
3
- * · 运维三张名表(`APPROVAL_REQUIRE` / `DENY` / `NEVER_AUTO`)与中心下发的同名表、请求 `settings.permissions.{ask,deny}`(经 `config.ts` 的
4
- * `unmatchableToolNamesRefusal`);
5
- * · 会话规则写口两腿与 SQL 会话规则店的写口(经 `session-policy-wire.ts`);
6
- * · 引擎 allow 层的三张条目表(core 7.42.0 起):请求的 `settings.permissions.allow`、运维的 `APPROVAL_GRANT`、技能的两只清单 `allowedTools` /
7
- * `disallowedTools`(顶层 `skills[]` 与子代 `agents[].skills[]` 两处受理口)—— 经 `rule-entry-list.ts` 的 `unreachableRuleEntryOf`(同一只函数)。
8
- * **不调它的面**(如实,未接):`/v1/rules` 的持久规则写口与 `cc-import` 导入口(`http/routes/rules.ts`)、子代的工具名表 `agents[].allowTools` /
9
- * `denyTools`(`spec-fields.ts` 只判形与条数)。在这两处写铸不出的服务器段仍被收下、永不命中。
10
- * 规则文本里「工具名那一段」怎么取也只有一种读法:{@link ruleEntryToolName}(配置表与技能清单同读)。
2
+ * 规则里的工具名**够不够得着**一只挂载工具 —— 本仓唯一的一问(判官 + 句子各一只),其余全是引擎判官 `judgeRuleToolName` 的判词。
3
+ *
4
+ * 一个名字永远匹配不到工具的成因,判官是谁:
5
+ * · 退役名 / 无协议前缀的 `__` 短名 / **协议名的对端段引擎铸不出**(`mcp__github.com__*`、`mcp__*`、`mcp____x`、`mcp__`)—— 引擎判官
6
+ * (`retired` / `unprefixed_namespace` / `unmintable_segment`,带 `guidance` 与 1:1 `currentName`)。本仓不再拿 `mintNamespacePrefix` 自比对端段:
7
+ * 那是同一问的第二个读者(core 7.43.0 #1355 (b) 起引擎自己给这个判词)。
8
+ * · **工具段为空的协议名**(`mcp__srv__`)—— 引擎判官答 `current`、规则文法收下、allow 条目读成一条精确名行,而它一只工具都够不着
9
+ * (引擎自己的两种拆法 —— 精确名 `ns.parse`、覆盖形 `isNamespacedCoveringRuleName` —— 都不认它)。这是引擎读不到的一形,本仓在下面点名的那些面上
10
+ * 拒它({@link emptyToolSegmentOf}),句子在这里({@link describeEmptyToolSegment})。翻转条件:引擎判官给这一形一个判词 ⇒ 本文件整只退役。
11
11
  *
12
- * 规则:一条协议规则名够得着挂载工具 ⇔
13
- * ① 分得出对端段 —— 三段名(`ns.parse` 认得:首段为对端、余段非空)取 `parse().peer`;两段覆盖形 `<ns>__<peer>`(core `isNamespacedCoveringRuleName`
14
- * 认、`ns.parse` 不认)取前缀之后的整段;两者都不是(`mcp__` / `mcp__srv__` / `mcp____x`)= **缺段**;
15
- * ② 对端段是引擎**自铸**的那一段:`mintNamespacePrefix(ns, peer) === ns.prefix + peer + "__"`。引擎铸注册名时归一对端段(字符表外换 `_`、超长截断加摘要、
16
- * 折连串下划线、去尾下划线),而规则匹配对对端段逐字比(覆盖形 `namespacedRuleNameCovers`,精确名整串比)—— 铸不出的对端段一只工具都匹配不到。
17
- * 服务器段里带 `*`(`mcp__*`、`mcp__git*__x`)是同一个「够不着」,但出路不同:那是在写通配,而服务器名不支持通配 —— 给一条铸名写法
18
- * (`mcp__<摘要>__*`)没人照做得了 ⇒ 判词单列 `wildcard_peer`,句子说实话(写具体的服务器名)。
19
- * 判据不看规则是覆盖形还是精确名:两种形的对端段命运相同。精确名的**工具段**归一不在这里判(包根没有导出工具段的铸法,不手抄)。
12
+ * **调它的面**(点名,不是「每个面」;判官一问与本仓这一问同调):
13
+ * · 运维三张名表(`APPROVAL_REQUIRE` / `DENY` / `NEVER_AUTO`)与中心下发的同名表、请求 `settings.permissions.{ask,deny}`(经 `config.ts` 的 `unmatchableToolNamesRefusal`);
14
+ * · 会话规则写口(`session-policy-wire.ts`)与 SQL 会话规则店的写口(`assertSessionRuleNamesReachable`);
15
+ * · 引擎 allow 层的条目表:请求的 `settings.permissions.allow`、运维的 `APPROVAL_GRANT`、技能的两只清单 `allowedTools` / `disallowedTools`(顶层 `skills[]` 与子代
16
+ * `agents[].skills[]` 两处受理口)—— 经 `rule-entry-list.ts` 的 `emptyToolSegmentEntryOf`(本仓这一问;退役名 / 铸不出的判词那一半由引擎 `compileAllowList` 给)。
17
+ * **不调它的面**:`/v1/rules` 的持久规则写口与 `cc-import` 导入口(`http/routes/rules.ts`、`rules-consent.ts` 与两只持久规则店零判官调用)—— 如实未接,
18
+ * 在那里写铸不出的服务器段、工具段为空的协议名仍被收下、永不命中;把它接上判官 = 另一件事(不在本文件里加特例;`unmatchableRuleNameOf` 就是现成的那一只)。
19
+ * 子代的工具名表 `agents[].allowTools` / `denyTools` 不是本文件的面:core 7.45.0 #1377 / #1385 起名字归引擎在开跑前一只判(三来源同判,`spec-fields.ts`
20
+ * 受理口只判形与条数),本仓不对它另做对照。
21
+ * **精确名的工具段归一不在这里判**:包根没有导出工具段的铸法,不手抄 —— 精确名的工具段按字面收,本仓每张表上都不判它。
20
22
  *
21
- * 翻转条件:core 的规则名判官(`judgeRuleToolName`)自己给出「对端段铸不出」这一判词 —— core 7.42.x 还没有(包根 `RULE_TOOL_NAME_VERDICTS`
22
- * 只有 current / retired / unprefixed_namespace,亲核),等上游;到货那天本仓这只对照整只退役(连同会话规则写口这一问的码由 `request.field_invalid`
23
- * 归回 `rules.legacy_tool_name`)。
23
+ * 规则文本里「工具名那一段」怎么取也只有一种读法:{@link ruleEntryToolName}(配置表与技能清单同读)。
24
24
  */
25
- import { type ProtocolNamespace } from "@sema-agent/core";
25
+ import { type ProtocolNamespace, type RuleToolNameVerdict } from "@sema-agent/core";
26
26
  /**
27
27
  * 一条**规则文本**里的工具名那一段(判官与旧名判据的主语)= core 包根 `parsePermissionRule` 的 `toolName`,原样 —— 引擎的技能编译器
28
28
  * (`compileSkillGrant`)与规则编译器读条目用的就是这一只:`Bash(npm:*)` ⇒ `Bash`;`mcp__srv(*)` / `mcp__srv()` / 带首尾空格的 `mcp__srv ` ⇒ 光杆名 `mcp__srv`。
@@ -31,26 +31,31 @@ import { type ProtocolNamespace } from "@sema-agent/core";
31
31
  * `unsupported.mcp_paren`),拒由引擎的文法那一问说(配置表的判据链里它排在本判官之前),本判官不抢答。
32
32
  */
33
33
  export declare function ruleEntryToolName(entry: string): string;
34
- /** 一条协议规则名够不着任何挂载工具的判词(闭集)。 */
35
- export type UnreachableNamespacedRuleName =
36
- /** 分不出对端段:光杆前缀、对端段为空、或分隔符之后工具段为空。 */
37
- {
38
- readonly kind: "incomplete";
39
- readonly ns: ProtocolNamespace;
40
- }
41
- /** 对端段不是引擎自铸的那一段;`minted` = 引擎为这样一个对端名铸出的注册名前缀(`mintNamespacePrefix` 的返回值,原样)。 */
42
- | {
43
- readonly kind: "unminted_peer";
44
- readonly ns: ProtocolNamespace;
45
- readonly minted: string;
46
- }
47
- /** 对端段里带 `*`:写的是通配,而服务器 / 对端名不支持通配(规则对这一段逐字比)。 */
48
- | {
49
- readonly kind: "wildcard_peer";
34
+ /**
35
+ * 判官的名册:本仓的配置表 / 请求表 / 会话规则写口都不声明「部署在退役名下挂了自有工具」(本仓没有这样的工具;boot / 请求期也没有哪一跑的名册可看)
36
+ * ⇒ 恒为空集。引擎判官、`compileAllowList` 的第三参、会话规则店同读这一只。
37
+ */
38
+ export declare const NO_DECLARED_TOOL_NAMES: ReadonlySet<string>;
39
+ /** 一个规则名永远匹配不到工具的判词(闭集):引擎判官的三种非现行判词 ∪ 引擎读不到的那一形。 */
40
+ export type UnmatchableRuleName = Exclude<RuleToolNameVerdict, {
41
+ kind: "current";
42
+ }> | {
43
+ readonly kind: "empty_tool_segment";
50
44
  readonly ns: ProtocolNamespace;
51
45
  };
52
- /** 判官:不是协议名、或够得着 ⇒ `undefined`;否则给判词。纯字符串运算,不做 I/O。 */
53
- export declare function unreachableNamespacedRuleNameOf(name: string): UnreachableNamespacedRuleName | undefined;
54
- /** 判词 → 句子(「为什么永远匹配不到 + 怎么写」;不复述条目本身 —— 调用方的句子已点名它)。闭集穷尽:判词加一种 ⇒ 编译红。 */
55
- export declare function describeUnreachableNamespacedRuleName(v: UnreachableNamespacedRuleName): string;
46
+ /** 判:引擎判官先答(非 `current` ⇒ 原样交回),判官放行的再问引擎读不到的那一形;都过 ⇒ `undefined`。 */
47
+ export declare function unmatchableRuleNameOf(name: string): UnmatchableRuleName | undefined;
48
+ /**
49
+ * 判词 → 「为什么永远匹配不到 + 怎么写」(不复述条目本身 —— 调用方的句子已点名它)。引擎判词的 `guidance` 原样带出,是改法指引的**唯一**一句
50
+ * (本仓不在后面追加 1:1 现名:通配名上那个现名是摘要名或更窄的写法,照写不生效;现名过境给机器读的位,如会话规则拒体顶层的 `currentName`);
51
+ * 无前缀短名的出路从 core 的 `PROTOCOL_TABLE` 派生。闭集穷尽:判词加一种 ⇒ 编译红。
52
+ */
53
+ export declare function describeUnmatchableRuleName(v: UnmatchableRuleName, name: string): string;
54
+ /**
55
+ * 工具段为空的协议名(`<ns>__<peer>__`)⇒ 交回它的协议;别的名字 ⇒ `undefined`。判据只读引擎:判官答 `current`(对端段为空 / 铸不出的那些 ——
56
+ * `mcp__`、`mcp____x` —— 是判官自己的判词,不在这里),而引擎自己的两种拆法(精确名 `ns.parse`、覆盖形 `isNamespacedCoveringRuleName`)都不认它。纯字符串运算。
57
+ */
58
+ export declare function emptyToolSegmentOf(name: string): ProtocolNamespace | undefined;
59
+ /** {@link emptyToolSegmentOf} 的句子(「为什么永远匹配不到 + 怎么写」;不复述条目本身 —— 调用方的句子已点名它)。 */
60
+ export declare function describeEmptyToolSegment(ns: ProtocolNamespace): string;
56
61
  //# sourceMappingURL=namespaced-rule-name.d.ts.map
@@ -1,33 +1,47 @@
1
- import { isNamespacedCoveringRuleName, mintNamespacePrefix, parsePermissionRule, protocolOf } from "@sema-agent/core";
1
+ import { PROTOCOL_TABLE, isNamespacedCoveringRuleName, judgeRuleToolName, parsePermissionRule, protocolOf } from "@sema-agent/core";
2
+ import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
2
3
  export function ruleEntryToolName(entry) {
3
4
  return parsePermissionRule(entry).toolName;
4
5
  }
5
- export function unreachableNamespacedRuleNameOf(name) {
6
- const ns = protocolOf(name);
7
- if (ns === undefined)
8
- return undefined;
9
- const peer = ns.parse(name)?.peer ?? (isNamespacedCoveringRuleName(name) ? name.slice(ns.prefix.length) : undefined);
10
- if (peer === undefined)
11
- return { kind: "incomplete", ns };
12
- if (peer.includes("*"))
13
- return { kind: "wildcard_peer", ns };
14
- const minted = mintNamespacePrefix(ns, peer);
15
- return minted === `${ns.prefix}${peer}__` ? undefined : { kind: "unminted_peer", ns, minted };
6
+ export const NO_DECLARED_TOOL_NAMES = new Set();
7
+ export function unmatchableRuleNameOf(name) {
8
+ const verdict = judgeRuleToolName(name, NO_DECLARED_TOOL_NAMES);
9
+ if (verdict.kind !== "current")
10
+ return verdict;
11
+ const ns = emptyToolSegmentOf(name);
12
+ return ns === undefined ? undefined : { kind: "empty_tool_segment", ns };
16
13
  }
17
- export function describeUnreachableNamespacedRuleName(v) {
14
+ function presentName(name) {
15
+ const clean = name.replace(CONTROL_AND_BIDI_STRIP_RE, "");
16
+ return clean.length <= 160 ? clean : `${clean.slice(0, 160)}…(${name.length} chars)`;
17
+ }
18
+ export function describeUnmatchableRuleName(v, name) {
18
19
  switch (v.kind) {
19
- case "incomplete":
20
- return `incomplete ${v.ns.id} name — the live form is ${v.ns.prefix}<server>__<tool>, or ${v.ns.prefix}<server> / ${v.ns.prefix}<server>__* to cover every tool on that server`;
21
- case "wildcard_peer":
22
- return `the ${v.ns.id} server segment does not take a wildcard — a rule compares the server segment literally, so the entry matches nothing; write the concrete server name: ${v.ns.prefix}<server>__* to cover every tool on that server, or ${v.ns.prefix}<server>__<tool>`;
23
- case "unminted_peer":
24
- return (`the engine never mints this server segment as written — a server configured under that name has its tools registered as ${v.minted}<tool>, ` +
25
- `and a rule compares the server segment literally (no wildcard, no normalization), so the entry matches nothing; if that is the configured name, ` +
26
- `write ${v.minted}* to cover every tool on that server, or ${v.minted}<tool>`);
20
+ case "retired":
21
+ return v.guidance;
22
+ case "unmintable_segment":
23
+ return v.guidance;
24
+ case "unprefixed_namespace":
25
+ return `pre-prefix short form — the live name carries a protocol prefix: ${PROTOCOL_TABLE.map((e) => `${e.prefix}${presentName(name)}`).join(" or ")}`;
26
+ case "empty_tool_segment":
27
+ return describeEmptyToolSegment(v.ns);
27
28
  default: {
28
29
  const unhandled = v;
29
- throw new Error(`unhandled namespaced rule name verdict: ${JSON.stringify(unhandled)}`);
30
+ throw new Error(`unhandled rule name verdict: ${JSON.stringify(unhandled)}`);
30
31
  }
31
32
  }
32
33
  }
34
+ export function emptyToolSegmentOf(name) {
35
+ const ns = protocolOf(name);
36
+ if (ns === undefined)
37
+ return undefined;
38
+ if (judgeRuleToolName(name, NO_DECLARED_TOOL_NAMES).kind !== "current")
39
+ return undefined;
40
+ if (ns.parse(name) !== undefined || isNamespacedCoveringRuleName(name))
41
+ return undefined;
42
+ return ns;
43
+ }
44
+ export function describeEmptyToolSegment(ns) {
45
+ return `incomplete ${ns.id} name — the live form is ${ns.prefix}<server>__<tool>, or ${ns.prefix}<server> / ${ns.prefix}<server>__* to cover every tool on that server`;
46
+ }
33
47
  //# sourceMappingURL=namespaced-rule-name.js.map
@@ -148,6 +148,10 @@ export declare const JSONL_REPLAY_SEATS: {
148
148
  readonly direction: "fail-open";
149
149
  readonly tag: "server.workflow-agent-session-index.corrupt-record-skipped";
150
150
  };
151
+ readonly "workflow-completion-inbox": {
152
+ readonly direction: "fail-open";
153
+ readonly tag: "server.workflow-completion-inbox.corrupt-record-skipped";
154
+ };
151
155
  readonly "user-input-ledger": {
152
156
  readonly direction: "fail-open";
153
157
  readonly tag: "server.user-input-ledger.corrupt-record-skipped";
@@ -133,6 +133,7 @@ export const JSONL_REPLAY_SEATS = {
133
133
  "approval-ask-audit": { direction: "fail-open", tag: "server.approval-ask-audit.corrupt-record-skipped" },
134
134
  "workflow-notify-journal": { direction: "fail-open", tag: "server.workflow-notify-journal.corrupt-record-skipped" },
135
135
  "workflow-agent-session-index": { direction: "fail-open", tag: "server.workflow-agent-session-index.corrupt-record-skipped" },
136
+ "workflow-completion-inbox": { direction: "fail-open", tag: "server.workflow-completion-inbox.corrupt-record-skipped" },
136
137
  "user-input-ledger": { direction: "fail-open", tag: "server.user-input-ledger.corrupt-record-skipped" },
137
138
  };
138
139
  function corruptRefusalSentence(seat, path, reason) {