@sema-agent/server 7.51.0 → 7.52.1

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 (203) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/USAGE.md +103 -24
  4. package/dist/adoption/plan.d.ts +4 -4
  5. package/dist/adoption/plan.js +11 -0
  6. package/dist/approval-card.d.ts +26 -2
  7. package/dist/approval-card.js +9 -0
  8. package/dist/approval-hmac.d.ts +1 -1
  9. package/dist/approval-reconciler.d.ts +2 -2
  10. package/dist/approval-reconciler.js +1 -1
  11. package/dist/bench/l8/artifact.d.ts +1 -1
  12. package/dist/bench/s1/arms.d.ts +1 -1
  13. package/dist/bench/s1/live-deps.d.ts +1 -1
  14. package/dist/bench/s1/oracle.d.ts +1 -1
  15. package/dist/bench/s1/row.d.ts +1 -1
  16. package/dist/boot/budget-tracing.d.ts +1 -1
  17. package/dist/boot/budget-tracing.js +2 -5
  18. package/dist/boot/coordinators.js +3 -1
  19. package/dist/boot/deferred-sandbox-path-env.d.ts +1 -1
  20. package/dist/boot/device-lane.js +2 -0
  21. package/dist/boot/engine-lease.d.ts +1 -1
  22. package/dist/boot/execution-env.d.ts +1 -1
  23. package/dist/boot/governance-seams.d.ts +2 -2
  24. package/dist/boot/leader.d.ts +1 -1
  25. package/dist/boot/memory-boundary.d.ts +2 -2
  26. package/dist/boot/memory-consolidation.d.ts +6 -6
  27. package/dist/boot/org-memory.d.ts +1 -1
  28. package/dist/boot/parked-revive-gate.d.ts +3 -3
  29. package/dist/boot/reapers.d.ts +2 -2
  30. package/dist/boot/reapers.js +17 -4
  31. package/dist/boot/resolve-spec.d.ts +12 -0
  32. package/dist/boot/resolve-spec.js +29 -7
  33. package/dist/boot/retention-lane.d.ts +2 -2
  34. package/dist/boot/runner-deps.d.ts +17 -7
  35. package/dist/boot/runner-deps.js +4 -2
  36. package/dist/boot/runtime-caps.d.ts +23 -0
  37. package/dist/boot/runtime-caps.js +24 -2
  38. package/dist/boot/session-faces.d.ts +1 -1
  39. package/dist/boot/session-faces.js +14 -3
  40. package/dist/boot/stores.js +31 -1
  41. package/dist/boot/task-list-lane.d.ts +2 -2
  42. package/dist/brain.js +51 -31
  43. package/dist/budget.d.ts +2 -2
  44. package/dist/capabilities/center-plugins.d.ts +1 -1
  45. package/dist/capabilities/collab-workflows.d.ts +1 -1
  46. package/dist/capabilities/hands-lane.d.ts +3 -3
  47. package/dist/capabilities/scenarios.d.ts +3 -3
  48. package/dist/capabilities/skills.d.ts +1 -1
  49. package/dist/config-center/hot-keys-registry.js +5 -0
  50. package/dist/config-center/http-client.d.ts +2 -2
  51. package/dist/config-center/read-face.d.ts +1 -1
  52. package/dist/config-center/restart-signal.d.ts +1 -1
  53. package/dist/config-types.d.ts +100 -25
  54. package/dist/config.d.ts +11 -3
  55. package/dist/config.js +91 -15
  56. package/dist/deployment-governance.d.ts +1 -1
  57. package/dist/device-enrollment.d.ts +2 -2
  58. package/dist/device-store.d.ts +3 -3
  59. package/dist/device-ws-hub.d.ts +4 -2
  60. package/dist/device-ws-hub.js +9 -3
  61. package/dist/device-ws-protocol.d.ts +2 -2
  62. package/dist/env-facts.d.ts +1 -1
  63. package/dist/execution-lane-caps.d.ts +1 -1
  64. package/dist/fleet/fleet-bus.d.ts +5 -2
  65. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  66. package/dist/fleet/fleet-terminal-window.d.ts +1 -1
  67. package/dist/hooks/hook-runner.d.ts +8 -1
  68. package/dist/http/active-run-conflict.d.ts +2 -2
  69. package/dist/http/principal-gate.d.ts +1 -1
  70. package/dist/http/route-ctx.d.ts +27 -5
  71. package/dist/http/routes/admin-memory-optout.d.ts +48 -0
  72. package/dist/http/routes/admin-memory-optout.js +75 -0
  73. package/dist/http/routes/agents-roster.d.ts +1 -1
  74. package/dist/http/routes/approvals-assistant.js +21 -21
  75. package/dist/http/routes/capabilities.js +20 -9
  76. package/dist/http/routes/diagnostics.d.ts +1 -1
  77. package/dist/http/routes/leader.d.ts +1 -1
  78. package/dist/http/routes/memory-bundle.d.ts +1 -1
  79. package/dist/http/routes/memory-bundle.js +2 -1
  80. package/dist/http/routes/memory-compliance.d.ts +4 -4
  81. package/dist/http/routes/memory-origin.d.ts +4 -4
  82. package/dist/http/routes/memory-origin.js +3 -1
  83. package/dist/http/routes/memory-policy.js +6 -2
  84. package/dist/http/routes/retention-ops.d.ts +1 -1
  85. package/dist/http/routes/rules.d.ts +5 -2
  86. package/dist/http/routes/rules.js +84 -5
  87. package/dist/http/routes/run-memory-capture.d.ts +34 -0
  88. package/dist/http/routes/run-memory-capture.js +97 -0
  89. package/dist/http/routes/runs.d.ts +1 -0
  90. package/dist/http/routes/runs.js +212 -6
  91. package/dist/http/routes/session-sync.d.ts +4 -6
  92. package/dist/http/routes/session-sync.js +60 -47
  93. package/dist/http/routes/sessions.js +61 -188
  94. package/dist/http/routes/shared-memory.d.ts +1 -1
  95. package/dist/http/routes/tasks.js +1 -1
  96. package/dist/http/routes/workflows.js +19 -0
  97. package/dist/http/server.d.ts +35 -13
  98. package/dist/http/server.js +110 -14
  99. package/dist/http/verify-rounds.d.ts +1 -1
  100. package/dist/http/wire-types.d.ts +41 -8
  101. package/dist/leader/endpoint.d.ts +1 -1
  102. package/dist/leader/leader.d.ts +1 -1
  103. package/dist/leader/repair-oracle.d.ts +2 -2
  104. package/dist/leader/repair-oracle.js +1 -1
  105. package/dist/main.js +14 -6
  106. package/dist/memory-bundle-engine.d.ts +1 -1
  107. package/dist/memory-operator-faces.d.ts +5 -5
  108. package/dist/memory-origin-fence.d.ts +35 -0
  109. package/dist/memory-origin-fence.js +66 -0
  110. package/dist/memory-posture.d.ts +13 -1
  111. package/dist/memory-posture.js +1 -0
  112. package/dist/memory-scope.d.ts +6 -1
  113. package/dist/memory-scope.js +3 -2
  114. package/dist/model-select.d.ts +2 -2
  115. package/dist/observability/fail-open.d.ts +17 -5
  116. package/dist/observability/fail-open.js +17 -5
  117. package/dist/orchestration/hardened-vm-runner.d.ts +2 -2
  118. package/dist/orchestration/workflow-completion-inbox.d.ts +2 -2
  119. package/dist/plugins/background-shell-support.d.ts +2 -2
  120. package/dist/plugins/blob-backend.d.ts +7 -2
  121. package/dist/plugins/blob-backend.js +22 -18
  122. package/dist/plugins/caching-session-store.d.ts +7 -3
  123. package/dist/plugins/checkpoint-store-sql.d.ts +2 -2
  124. package/dist/plugins/file-history-store-sql.d.ts +170 -0
  125. package/dist/plugins/file-history-store-sql.js +827 -0
  126. package/dist/plugins/file-run-store.d.ts +44 -5
  127. package/dist/plugins/file-run-store.js +85 -13
  128. package/dist/plugins/fork-routing-session-store.d.ts +1 -1
  129. package/dist/plugins/fork-routing-session-store.js +2 -2
  130. package/dist/plugins/leader-run-store-sql.d.ts +1 -1
  131. package/dist/plugins/local-session-store.d.ts +9 -1
  132. package/dist/plugins/memory-embedder-fingerprint.d.ts +1 -1
  133. package/dist/plugins/memory-optout-grant-store-sql.d.ts +139 -0
  134. package/dist/plugins/memory-optout-grant-store-sql.js +157 -0
  135. package/dist/plugins/memory-origin-law.d.ts +2 -2
  136. package/dist/plugins/memory-run-store.d.ts +4 -3
  137. package/dist/plugins/memory-run-store.js +2 -1
  138. package/dist/plugins/permission-rule-store-file.d.ts +16 -6
  139. package/dist/plugins/permission-rule-store-file.js +77 -13
  140. package/dist/plugins/permission-rule-store-sql.d.ts +156 -8
  141. package/dist/plugins/permission-rule-store-sql.js +123 -21
  142. package/dist/plugins/pg-pool.js +59 -0
  143. package/dist/plugins/pg-safe-json.d.ts +1 -1
  144. package/dist/plugins/remote-env-device.d.ts +1 -1
  145. package/dist/plugins/remote-env-k8s.d.ts +1 -1
  146. package/dist/plugins/retention-lane-store-sql.d.ts +3 -3
  147. package/dist/plugins/retention-store-sql.d.ts +7 -7
  148. package/dist/plugins/retention-store-sql.js +6 -0
  149. package/dist/plugins/run-store-sql.d.ts +9 -3
  150. package/dist/plugins/run-store-sql.js +2 -2
  151. package/dist/plugins/send-user-file.d.ts +1 -1
  152. package/dist/plugins/session-placement.d.ts +1 -1
  153. package/dist/plugins/sql-errors.d.ts +1 -1
  154. package/dist/plugins/store-backend.d.ts +59 -29
  155. package/dist/plugins/store-backend.js +75 -15
  156. package/dist/plugins/store-contracts.d.ts +5 -3
  157. package/dist/plugins/task-list-store-sql.d.ts +1 -1
  158. package/dist/plugins/tidb-pool.js +59 -0
  159. package/dist/plugins/web-search.d.ts +1 -1
  160. package/dist/prompts-domain-validate.js +1 -1
  161. package/dist/resource-suspend.d.ts +1 -1
  162. package/dist/resource-window.d.ts +3 -3
  163. package/dist/rules-consent.d.ts +231 -9
  164. package/dist/rules-consent.js +411 -13
  165. package/dist/run-local.js +29 -4
  166. package/dist/runs.d.ts +15 -1
  167. package/dist/runs.js +19 -0
  168. package/dist/runtime-caps-resolver.d.ts +36 -1
  169. package/dist/runtime-caps-resolver.js +25 -0
  170. package/dist/runtime-governance.d.ts +1 -1
  171. package/dist/security.d.ts +5 -5
  172. package/dist/security.js +1 -1
  173. package/dist/session-sync.d.ts +52 -37
  174. package/dist/session-sync.js +19 -16
  175. package/dist/shared-memory-scope-authorizer.d.ts +1 -1
  176. package/dist/sighup-idle.d.ts +1 -1
  177. package/dist/spec-fields.d.ts +22 -1
  178. package/dist/spec-fields.js +11 -0
  179. package/dist/store-live-probe.d.ts +2 -2
  180. package/dist/task-a2a.d.ts +1 -1
  181. package/dist/task-cwd.d.ts +1 -1
  182. package/dist/task-mcp.d.ts +3 -3
  183. package/dist/task-settings.d.ts +3 -3
  184. package/dist/task-workflow.d.ts +1 -1
  185. package/dist/text-bidi.d.ts +2 -2
  186. package/dist/tool-approval.d.ts +191 -9
  187. package/dist/tool-approval.js +248 -29
  188. package/dist/trace/core-keyset-guard.d.ts +3 -3
  189. package/dist/trace/engine-notice-wire.d.ts +6 -4
  190. package/dist/trace/engine-notice-wire.js +2 -0
  191. package/dist/trace/ledger-events.d.ts +1 -1
  192. package/dist/trace/ledger-sink.d.ts +2 -2
  193. package/dist/trace/project.d.ts +8 -2
  194. package/dist/trace/project.js +10 -0
  195. package/dist/turn-activity.d.ts +1 -1
  196. package/dist/wall-clock-jump-guard.d.ts +3 -3
  197. package/package.json +3 -3
  198. package/dist/http/tar.d.ts +0 -17
  199. package/dist/http/tar.js +0 -53
  200. package/dist/http/workspace-content.d.ts +0 -11
  201. package/dist/http/workspace-content.js +0 -20
  202. package/dist/plugins/file-snapshot-store-sql.d.ts +0 -170
  203. package/dist/plugins/file-snapshot-store-sql.js +0 -213
@@ -10,7 +10,7 @@ import { verifyDirectDoorProof } from "../principal-jwt.js";
10
10
  import {} from "../memory-sync.js";
11
11
  import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_PERMISSION_RULES, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../task-settings.js";
12
12
  import { parseHooksConfig } from "../hooks/hook-runner.js";
13
- import { normalizeApproachNotice, validateTaskAgents } from "../spec-fields.js";
13
+ import { normalizeApproachNotice, unknownTaskAgentModelRef, validateTaskAgents } from "../spec-fields.js";
14
14
  import { isValidCwd, MAX_ADDITIONAL_DIRS } from "../task-cwd.js";
15
15
  import { runInBackground, evictIfConflict, stripCheckpointToken, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort } from "../runs.js";
16
16
  import { readTurnActivityMs, recordTurnActivity } from "../turn-activity.js";
@@ -43,6 +43,7 @@ import { handleAdoption } from "./routes/adoption.js";
43
43
  import { handleRetention } from "./routes/retention-ops.js";
44
44
  import { handleAdminDrain } from "./routes/admin-drain.js";
45
45
  import { handleAdminConfigRefresh } from "./routes/admin-config-refresh.js";
46
+ import { handleMemoryOptOut } from "./routes/admin-memory-optout.js";
46
47
  import { handleMemoryPolicy } from "./routes/memory-policy.js";
47
48
  import { handleMemoryBundle } from "./routes/memory-bundle.js";
48
49
  import { handleMemoryCompliance } from "./routes/memory-compliance.js";
@@ -63,6 +64,7 @@ import { handleNotifyWake, SESSION_WAKE_RE } from "./routes/notify-wake.js";
63
64
  import { handleApprovalsAssistant, streamApprovals, isQuestionAnswer, ASSISTANT_RESUME_RE, ASSISTANT_PLAN_REVIEW_RE } from "./routes/approvals-assistant.js";
64
65
  export { streamApprovals, isQuestionAnswer };
65
66
  import { handleRuns, handleRunVerbs, RUN_SUBAGENT_RESUME_RE } from "./routes/runs.js";
67
+ import { handleRunMemoryCapture } from "./routes/run-memory-capture.js";
66
68
  import { handleSideQuery } from "./routes/side-query.js";
67
69
  import { handleTasks } from "./routes/tasks.js";
68
70
  import { handleLeader } from "./routes/leader.js";
@@ -136,6 +138,7 @@ const ROUTE_DOMAINS = [
136
138
  handleA2a,
137
139
  handleWorkflowAgentSteer,
138
140
  handleRunVerbs,
141
+ handleRunMemoryCapture,
139
142
  handleLeader,
140
143
  handleImages,
141
144
  handleApprovalsAssistant,
@@ -145,6 +148,7 @@ const ROUTE_DOMAINS = [
145
148
  handleRetention,
146
149
  handleAdminDrain,
147
150
  handleAdminConfigRefresh,
151
+ handleMemoryOptOut,
148
152
  handleMemoryPolicy,
149
153
  handleMemoryBundle,
150
154
  handleMemoryCompliance,
@@ -441,6 +445,8 @@ export function createHttpServer(rawDeps) {
441
445
  return;
442
446
  if (await handleRunVerbs(req, res, url, ctx))
443
447
  return;
448
+ if (await handleRunMemoryCapture(req, res, url, ctx))
449
+ return;
444
450
  if (await handleLeader(req, res, url, ctx))
445
451
  return;
446
452
  if (await handleImages(req, res, url, ctx))
@@ -459,6 +465,8 @@ export function createHttpServer(rawDeps) {
459
465
  return;
460
466
  if (await handleAdminConfigRefresh(req, res, url, ctx))
461
467
  return;
468
+ if (await handleMemoryOptOut(req, res, url, ctx))
469
+ return;
462
470
  if (await handleMemoryPolicy(req, res, url, ctx))
463
471
  return;
464
472
  if (await handleMemoryBundle(req, res, url, ctx))
@@ -490,7 +498,7 @@ export function createHttpServer(rawDeps) {
490
498
  function runnerFor(spec) {
491
499
  return deps.runnerFor?.(spec) ?? deps.runner;
492
500
  }
493
- async function prepareSpec(req, res, bodyIn, onTypedFailure) {
501
+ async function prepareSpec(req, res, bodyIn, onTypedFailure, legFacts) {
494
502
  const body = bodyIn ?? (await readJson(req));
495
503
  if (!body || typeof body.objective !== "string") {
496
504
  sendError(res, 400, "request.field_invalid", "missing 'objective' string");
@@ -723,10 +731,22 @@ export function createHttpServer(rawDeps) {
723
731
  sendError(res, 400, "request.field_invalid", "rewindFiles must be a boolean");
724
732
  return null;
725
733
  }
734
+ if (body.restoreFiles !== undefined && typeof body.restoreFiles !== "boolean") {
735
+ sendError(res, 400, "request.field_invalid", "restoreFiles must be a boolean");
736
+ return null;
737
+ }
738
+ if (body.acceptPartialRestore !== undefined && typeof body.acceptPartialRestore !== "boolean") {
739
+ sendError(res, 400, "request.field_invalid", "acceptPartialRestore must be a boolean");
740
+ return null;
741
+ }
726
742
  if (body.memoryWrite !== undefined && typeof body.memoryWrite !== "boolean") {
727
743
  sendError(res, 400, "request.field_invalid", "memoryWrite must be a boolean (false = pause memory writes for this run)");
728
744
  return null;
729
745
  }
746
+ if (body.memoryCapture !== undefined && body.memoryCapture !== "off") {
747
+ sendError(res, 400, "request.field_invalid", "memoryCapture must be the string 'off' when present (the session-level memory-capture opt-out declaration; absent = capture as usual — there is no 'on' spelling)");
748
+ return null;
749
+ }
730
750
  if (body.requireExistingSession !== undefined && typeof body.requireExistingSession !== "boolean") {
731
751
  sendError(res, 400, "request.field_invalid", "requireExistingSession must be a boolean");
732
752
  return null;
@@ -808,6 +828,13 @@ export function createHttpServer(rawDeps) {
808
828
  sendError(res, 400, "request.field_invalid", agentsErr);
809
829
  return null;
810
830
  }
831
+ const agentsBare = deps.config.models ?? {};
832
+ const agentsCatalog = expandTiers(agentsBare, deps.config.tiers ?? {}) ?? agentsBare;
833
+ const um = unknownTaskAgentModelRef(body.agents, agentsCatalog);
834
+ if (um !== undefined) {
835
+ sendError(res, 400, "request.unknown_reference", `unknown agents[${um.index}].model "${um.ref.slice(0, 120)}" — not in the configured catalog (a catalog name or tier word)`, { available: mentionableNames(deps.config, agentsCatalog) });
836
+ return null;
837
+ }
811
838
  }
812
839
  if (body.interactiveTools !== undefined && typeof body.interactiveTools !== "boolean") {
813
840
  sendError(res, 400, "request.field_invalid", "interactiveTools must be a boolean");
@@ -932,7 +959,7 @@ export function createHttpServer(rawDeps) {
932
959
  }
933
960
  try {
934
961
  const auth = deps.authorize ? await deps.authorize({ req, body }) : undefined;
935
- const spec = await deps.resolveSpec(body, req, auth, { leg: "fresh" });
962
+ const spec = await deps.resolveSpec(body, req, auth, { leg: "fresh", ...(legFacts?.liveStream === true ? { liveStream: true } : {}) });
936
963
  return { spec, runner: runnerFor(spec), auth, verify, cascade, jobId: body.jobId, body };
937
964
  }
938
965
  catch (err) {
@@ -948,21 +975,52 @@ export function createHttpServer(rawDeps) {
948
975
  }
949
976
  }
950
977
  const resumeFleetScope = (req, auth) => req ? (gatedPrincipal(req, deps.config) ?? "default") : (auth.principal ?? "default");
951
- async function resumeCheckpoint(sessionId, decision, reason, settledBy, req, answer, binding, onResumeCommitted, acceptEarly) {
978
+ async function resumeCheckpoint(sessionId, decision, reason, settledBy, req, answer, binding, onResumeCommitted, acceptEarly, decider) {
952
979
  const cs = deps.checkpointStore;
953
980
  const token = await cs.findPendingTokenBySession(sessionId);
954
981
  if (binding?.checkpointToken && binding.checkpointToken !== token) {
955
- const seen = await cs.get(binding.checkpointToken).catch(() => null);
982
+ let seenUnreadable = false;
983
+ const seen = await cs.get(binding.checkpointToken).catch((e) => {
984
+ seenUnreadable = true;
985
+ recordFailOpen("server.approvals.stale-echoed-token-unreadable", `session=${sessionId} err=${e instanceof Error ? e.message : String(e)}`);
986
+ return null;
987
+ });
956
988
  const attributable = seen !== null && seen.sessionId === sessionId;
957
989
  if (!attributable && seen !== null) {
958
990
  deps.logger?.warn?.("approval_stale_token_foreign", { sessionId, tokenSession: seen.sessionId ?? null });
959
991
  }
992
+ if (!token && !attributable && !seenUnreadable) {
993
+ deps.logger?.info?.("approval_decide_no_pending", { sessionId, staleTokenEchoed: true });
994
+ return { status: 404, body: { error: "approval not found", errorCode: "not_found.approval" } };
995
+ }
996
+ let currentPending;
997
+ if (token && decider !== undefined) {
998
+ const cur = await cs.get(token).catch((e) => {
999
+ recordFailOpen("server.approvals.stale-current-pending-unreadable", `session=${sessionId} err=${e instanceof Error ? e.message : String(e)}`);
1000
+ return null;
1001
+ });
1002
+ const curPa = cur?.pendingAction;
1003
+ const curOwner = cur ? decodeCheckpointScope(cur.scope) : undefined;
1004
+ const scopeOk = cur !== null && (decider.explicitOperator || curOwner === undefined || curOwner === decider.principal);
1005
+ if (cur && cur.sessionId === sessionId && !scopeOk) {
1006
+ deps.logger?.info?.("approval_decide_no_pending", { sessionId, staleTokenEchoed: true, foreignCurrentSuppressed: true });
1007
+ return { status: 404, body: { error: "approval not found", errorCode: "not_found.approval" } };
1008
+ }
1009
+ if (cur && cur.status === "pending" && cur.sessionId === sessionId && scopeOk && curPa?.kind === "tool_approval" && curPa.toolCallId) {
1010
+ currentPending = {
1011
+ toolName: curPa.toolName,
1012
+ boundCallId: curPa.toolCallId,
1013
+ ...(curPa.boundInputHash ? { boundInputHash: curPa.boundInputHash } : {}),
1014
+ };
1015
+ }
1016
+ }
960
1017
  return {
961
1018
  status: 409,
962
1019
  body: {
963
1020
  error: "the approval you are deciding is no longer current (already resolved or superseded since you viewed it)",
964
1021
  errorCode: "approval_stale",
965
1022
  ...(attributable ? { terminal: "resolved" } : {}),
1023
+ ...(currentPending ? { currentPending } : {}),
966
1024
  },
967
1025
  };
968
1026
  }
@@ -982,6 +1040,13 @@ export function createHttpServer(rawDeps) {
982
1040
  }
983
1041
  if (!cp)
984
1042
  return { status: 404, body: { error: "checkpoint not found", errorCode: "not_found.checkpoint" } };
1043
+ if (decider !== undefined && !decider.explicitOperator) {
1044
+ const cpOwner = decodeCheckpointScope(cp.scope);
1045
+ if (cpOwner !== undefined && cpOwner !== decider.principal) {
1046
+ deps.logger?.warn?.("approval_decide_row_scope_mismatch", { sessionId, note: "loaded pending row is foreign to the verified caller — TOCTOU fold" });
1047
+ return { status: 404, body: { error: "approval not found", errorCode: "not_found.approval" } };
1048
+ }
1049
+ }
985
1050
  const decideGateKind = cp.gate?.kind;
986
1051
  if (!isApprovalGateKind(decideGateKind)) {
987
1052
  const activeForPath = decideGateKind
@@ -1009,6 +1074,13 @@ export function createHttpServer(rawDeps) {
1009
1074
  },
1010
1075
  };
1011
1076
  }
1077
+ const paForGrant = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction : undefined;
1078
+ const loadedGrantTool = paForGrant !== undefined && paForGrant.toolName && paForGrant.toolName !== "AskUserQuestion"
1079
+ ? paForGrant.toolName
1080
+ : null;
1081
+ const boundGrant = onResumeCommitted
1082
+ ? (overrideSessionId) => onResumeCommitted({ ...(overrideSessionId !== undefined ? { overrideSessionId } : {}), validatedToolName: loadedGrantTool })
1083
+ : undefined;
1012
1084
  if (req !== undefined && deps.backgroundAgentStore !== undefined && deps.parkedReviveTool !== undefined) {
1013
1085
  const parked = await decideParkedAgent({
1014
1086
  agentStore: deps.backgroundAgentStore,
@@ -1022,7 +1094,7 @@ export function createHttpServer(rawDeps) {
1022
1094
  token, scope: cp.scope, sessionId: cp.sessionId, pendingAction: cp.pendingAction,
1023
1095
  decision, settledBy, ...(reason !== undefined ? { reason } : {}),
1024
1096
  ...(binding !== undefined ? { binding: { ...(binding.boundCallId !== undefined ? { boundCallId: binding.boundCallId } : {}), ...(binding.boundInputHash !== undefined ? { boundInputHash: binding.boundInputHash } : {}), ...(binding.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}) } } : {}),
1025
- ...(onResumeCommitted ? { grantRemember: (rootSessionId) => onResumeCommitted(rootSessionId) } : {}),
1097
+ ...(boundGrant ? { grantRemember: (rootSessionId) => boundGrant(rootSessionId) } : {}),
1026
1098
  ...(answer !== undefined ? { answer } : {}),
1027
1099
  });
1028
1100
  if (parked !== undefined) {
@@ -1091,7 +1163,7 @@ export function createHttpServer(rawDeps) {
1091
1163
  settledBy,
1092
1164
  };
1093
1165
  const verifyRounds = verifyRoundsFromBody(ctx.body);
1094
- return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, verifyRounds, ...(onResumeCommitted ? { onResumeCommitted } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}) });
1166
+ return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, verifyRounds, ...(boundGrant ? { onResumeCommitted: boundGrant } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}) });
1095
1167
  }
1096
1168
  async function driveResumeIntoRunLog(args) {
1097
1169
  if (args.acceptEarly !== true)
@@ -1158,6 +1230,17 @@ export function createHttpServer(rawDeps) {
1158
1230
  ? fleetRunPublisher(deps.fleetBus, { runId: taskId, scope: fleetScope, rootTaskId: taskId, ...fleetRunLabels(resumeObjective) })
1159
1231
  : undefined;
1160
1232
  const resumeTaskConfig = taskId ? { ...taskConfig, taskId } : taskConfig;
1233
+ if (deps.toolApproval && taskId) {
1234
+ const toolApproval = deps.toolApproval;
1235
+ resumeTaskConfig.onAsk = approvalLeg.windowZero
1236
+ ? () => Promise.resolve(toolApproval.unattendedAskOutcome())
1237
+ : toolApproval.boundAsk({
1238
+ owner: principal ?? null,
1239
+ taskId,
1240
+ sessionId,
1241
+ ...(approvalLeg.active ? { legKey, ...(legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic } : {}) } : {}),
1242
+ });
1243
+ }
1161
1244
  if (taskId && resumeTaskConfig.resourceSuspend)
1162
1245
  recordResumeResourceWindow(taskId, resumeTaskConfig.resourceSuspend);
1163
1246
  let fleetSettled = false;
@@ -1189,7 +1272,7 @@ export function createHttpServer(rawDeps) {
1189
1272
  const cancelCtrl = new AbortController();
1190
1273
  const preemptCtrl = new AbortController();
1191
1274
  if (taskId && deps.runStore) {
1192
- claimedRow = freshRunClaimed ? true : await deps.runStore.markResuming(taskId);
1275
+ claimedRow = freshRunClaimed ? true : await deps.runStore.markResuming(taskId, deps.instanceId ?? "default");
1193
1276
  if (!claimedRow)
1194
1277
  return { status: 409, body: { error: "run is not in a resumable (suspended) state (already resumed, decided, or expired)", errorCode: "conflict.not_resumable" } };
1195
1278
  markChildrenStoppedByUserOnAbort(cancelCtrl.signal, taskId, principal);
@@ -1944,7 +2027,7 @@ export function createHttpServer(rawDeps) {
1944
2027
  const gateOwner = fleetWide ? null : (gatedPrincipal(req, deps.config) || null);
1945
2028
  return { fleetWide, gateOwner };
1946
2029
  }
1947
- function runOwnerOk(req, res, owner) {
2030
+ function runOwnerOk(req, res, owner, notFoundForm = "run") {
1948
2031
  const principal = gatedPrincipal(req, deps.config);
1949
2032
  if (deps.config.requirePrincipal && !principal) {
1950
2033
  sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
@@ -1952,7 +2035,10 @@ export function createHttpServer(rawDeps) {
1952
2035
  }
1953
2036
  const legacyShellOwner = !deps.config.requirePrincipal && owner === "anon:shell-live" && (principal === undefined || principal === null);
1954
2037
  if (!legacyShellOwner && owner !== null && principal !== owner) {
1955
- sendError(res, 404, "not_found.run", RUN_NOT_FOUND_MESSAGE);
2038
+ if (notFoundForm === "session")
2039
+ sendError(res, 404, "not_found.session", "session not found");
2040
+ else
2041
+ sendError(res, 404, "not_found.run", RUN_NOT_FOUND_MESSAGE);
1956
2042
  return false;
1957
2043
  }
1958
2044
  return true;
@@ -2118,7 +2204,7 @@ export function createHttpServer(rawDeps) {
2118
2204
  ...(req.gateBoundInputHash !== null ? { boundInputHash: req.gateBoundInputHash } : {}),
2119
2205
  ...(req.updatedInput !== undefined ? { updatedInput: req.updatedInput } : {}),
2120
2206
  };
2121
- const out = await resumeCheckpoint(cp.sessionId, req.decision, req.note, "human", req.httpReq, undefined, binding, undefined, true);
2207
+ const out = await resumeCheckpoint(cp.sessionId, req.decision, req.note, "human", req.httpReq, undefined, binding, undefined, true, { principal: req.principal, explicitOperator: false });
2122
2208
  return { status: out.status, body: out.body };
2123
2209
  }
2124
2210
  const isRunLiveOnThisReplica = (taskId) => inflightRuns.has(taskId) || steerableRuns.has(taskId);
@@ -2162,22 +2248,28 @@ export function isCredentialGatedRewrite(method, url) {
2162
2248
  if (m === "POST") {
2163
2249
  return (url === "/v1/adoption" ||
2164
2250
  url.startsWith("/v1/rules/cc-import/") ||
2251
+ url.startsWith("/v1/rules/local-import/") ||
2165
2252
  url === "/v1/memory/import" ||
2166
2253
  url === "/v1/memory/export" ||
2167
2254
  url === "/v1/memory/erase" ||
2168
2255
  url.startsWith("/v1/memory/origin/entries/"));
2169
2256
  }
2257
+ if (m === "PUT")
2258
+ return url === "/v1/admin/memory-optout" || url.startsWith("/v1/admin/memory-optout/");
2170
2259
  if (m === "DELETE")
2171
- return url === "/v1/rules";
2260
+ return url === "/v1/rules" || url.startsWith("/v1/admin/memory-optout/");
2172
2261
  if (m === "GET") {
2173
2262
  return (/^\/v1\/memory\/entries\/[^/]+\/provenance$/.test(url) ||
2174
2263
  url === "/v1/memory/origin/external" ||
2175
- url === "/v1/memory/origin/clearances");
2264
+ url === "/v1/memory/origin/clearances" ||
2265
+ url === "/v1/admin/memory-optout");
2176
2266
  }
2177
2267
  return false;
2178
2268
  }
2179
2269
  const ROUTE_LABEL_PATTERNS = [
2180
2270
  [/^\/v1\/runs\/[^/]+\/steer$/, "/v1/runs/:id/steer"],
2271
+ [/^\/v1\/runs\/[^/]+\/memory\/capture-optout$/, "/v1/runs/:id/memory/capture-optout"],
2272
+ [/^\/v1\/runs\/[^/]+\/interrupt$/, "/v1/runs/:id/interrupt"],
2181
2273
  [/^\/v1\/runs\/[^/]+\/detach$/, "/v1/runs/:id/detach"],
2182
2274
  [/^\/v1\/runs\/[^/]+\/cancel$/, "/v1/runs/:id/cancel"],
2183
2275
  [/^\/v1\/runs\/[^/]+\/compact$/, "/v1/runs/:id/compact"],
@@ -2195,10 +2287,12 @@ const ROUTE_LABEL_PATTERNS = [
2195
2287
  [/^\/v1\/approvals\/[^/]+\/decide$/, "/v1/approvals/:id/decide"],
2196
2288
  [/^\/v1\/approvals\/[^/]+\/exemptions(\/[^/]+)?$/, "/v1/approvals/:id/exemptions"],
2197
2289
  [/^\/v1\/sessions\/[^/]+\/(wake|fork|init|notify|events|head|mcp|a2a|policy|settings)$/, "/v1/sessions/:id/$verb"],
2290
+ [/^\/v1\/admin\/memory-optout\/[^/]+$/, "/v1/admin/memory-optout/:principal"],
2291
+ [/^\/v1\/sessions\/[^/]+\/memory\/erase$/, "/v1/sessions/:id/memory/erase"],
2198
2292
  [/^\/v1\/sessions\/[^/]+\/sync\/(manifest|entries|plan)$/, "/v1/sessions/:id/sync/$verb"],
2199
2293
  [/^\/v1\/sessions\/[^/]+\/sync\/import(\/[^/]+\/entries)?$/, "/v1/sessions/:id/sync/import"],
2200
2294
  [/^\/v1\/sessions\/[^/]+\/sync\/blobs(\/[^/]+)?$/, "/v1/sessions/:id/sync/blobs"],
2201
- [/^\/v1\/sessions\/[^/]+\/sync\/snapshots\/[^/]+\/blobs\/[^/]+$/, "/v1/sessions/:id/sync/snapshots/:key/blobs/:hash"],
2295
+ [/^\/v1\/sessions\/[^/]+\/sync\/history\/blobs\/[^/]+$/, "/v1/sessions/:id/sync/history/blobs/:hash"],
2202
2296
  [/^\/v1\/sessions\/[^/]+\/workspace(\/[^/]+(\/(tree|file|archive))?)?$/, "/v1/sessions/:id/workspace"],
2203
2297
  [/^\/v1\/sessions\/[^/]+$/, "/v1/sessions/:id"],
2204
2298
  [/^\/v1\/assistant\/tasks\/[^/]+\/preempt$/, "/v1/assistant/tasks/:id/preempt"],
@@ -2240,8 +2334,10 @@ const ROUTE_LABEL_LITERALS = new Set([
2240
2334
  "/v1/adoption",
2241
2335
  "/v1/admin/drain",
2242
2336
  "/v1/admin/config/refresh",
2337
+ "/v1/admin/memory-optout",
2243
2338
  "/v1/admin/memory/consolidation/run", "/v1/admin/memory/consolidation",
2244
2339
  "/v1/rules/cc-import/prepare", "/v1/rules/cc-import/redeem",
2340
+ "/v1/rules/local-import/prepare", "/v1/rules/local-import/redeem",
2245
2341
  "/v1/rules",
2246
2342
  "/.well-known/agent-card.json", "/v1/a2a",
2247
2343
  "/v1/agents/roster",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * design/97 R9 — the verify-rounds shape (used by the developer-mode adversarial-verification gate, 1.44) pulled
2
+ * design/97 R9 — the verify-rounds shape (used by the developer-mode independent-verification gate, 1.44) pulled
3
3
  * out to a NEUTRAL LEAF: it was three copies of the same anonymous `{ maxRounds: number; costCeilingMicroUsd?:
4
4
  * number }` object type (route-ctx.ts ×2, runs.ts `runInBackground`'s public signature) plus four call sites in
5
5
  * http/server.ts that each re-derived the same "verify:true → clamp rounds + fold cost ceiling" expression from a
@@ -75,7 +75,7 @@ export interface TaskRequestBody {
75
75
  * (MAX_TASK_COST_USD / MAX_TASK_TOKENS) — a caller may ask for less, never more. */
76
76
  maxCostUsd?: number;
77
77
  maxTokens?: number;
78
- /** Developer mode (1.44): run the task behind the adversarial verification gate (impl → independent
78
+ /** Developer mode (1.44): run the task behind the independent verification gate (impl → independent
79
79
  * read-only verify → fix loop). Default off. `verifyRounds` is clamped to [1,5] (default 2). Not
80
80
  * supported on /v1/tasks/stream (the gate is multi-round, not a single stream). */
81
81
  verify?: boolean;
@@ -100,7 +100,7 @@ export interface TaskRequestBody {
100
100
  * `TaskSpec.skills`. Merged AFTER the scenario's skills with SCENARIO-WINS on a name clash (the security
101
101
  * baseline can never be shadowed by user content — see mergeUserSkills). Caps: ≤10 skills, content ≤32KB
102
102
  * each. User skills are user-authored content in the user's OWN principal-isolated task (self-harm
103
- * surface, not privilege escalation); scenario/center skills remain the only trusted layer. */
103
+ * surface, not privilege widening); scenario/center skills remain the only trusted layer. */
104
104
  skills?: Array<{
105
105
  name: string;
106
106
  description: string;
@@ -163,14 +163,23 @@ export interface TaskRequestBody {
163
163
  count?: number;
164
164
  role?: string;
165
165
  };
166
- /** Rewind-files (shell-host contract E19): opt-in snapshot the working tree each completed turn + RESTORE it when a
167
- * `resumeAt` branch is taken ("rewind to this message" also rewinds the files). Requires a FileSnapshotStore; works for
168
- * ANY execution env since core's gate-split (1.134.0). OFF/absent no snapshot/restore work. */
166
+ /** S-15 file restore (design/381): with `resumeAt`, ALSO converge the working tree's **agent-edited files**
167
+ * (the tracked set nothing else is visible to the history, so a user's parallel work is structurally
168
+ * untouchable) to that entry's history boundary. Capture needs no request anymore: first-touch tracking is
169
+ * always on when the deployment wires a history store. Requires a FileHistoryStore. */
170
+ restoreFiles?: boolean;
171
+ /** DV-15: opt-in tolerance for a PARTIAL file restore (default = loud terminal failure carrying the per-file
172
+ * ledger; `true` = keep going and disclose the same ledger on the result's rewindNotes). */
173
+ acceptPartialRestore?: boolean;
174
+ /** RETIRED spelling (whole-tree epoch). Still accepted on the wire: its capture sense (true, no resumeAt) is
175
+ * a tolerated no-op with a deprecation disclosure; its restore sense (true + resumeAt) is a typed rejection
176
+ * naming `restoreFiles` — the restore semantics genuinely changed (whole tree ⇒ tracked set), so that
177
+ * migration is explicit by design (core adjudicates; the server never remaps it silently). */
169
178
  rewindFiles?: boolean;
170
- /** R8 code-only rewind (CC "Restore code" mode, core 1.166 `rewindFilesTo`): restore the working tree to the snapshot
171
- * at a prior USER message WITHOUT forking the conversation (the session leaf is untouched — only the files move). The
179
+ /** R8 code-only rewind (CC "Restore code" mode): converge the TRACKED set to the history boundary at a prior
180
+ * USER message WITHOUT forking the conversation (the session leaf is untouched — only the files move). The
172
181
  * value is the same handle as `resumeAt` (the prompt's taskId); the service resolves it → the entry id. Honored ONLY
173
- * when `resumeAt` is ABSENT (the two modes are mutually exclusive). Requires the anchor map + a FileSnapshotStore. */
182
+ * when `resumeAt` is ABSENT (the two modes are mutually exclusive). Requires the anchor map + a FileHistoryStore. */
174
183
  rewindFilesTo?: string;
175
184
  /** design/131 (core 1.246): per-task resilience INTENT flags. allowDegrade/allowFailover are caller-facing;
176
185
  * bypassBreaker is operator-only (resolveSpec drops it for non-operators — a permission downgrade, not a 400). */
@@ -294,6 +303,17 @@ export interface TaskRequestBody {
294
303
  * adopt-on-read semantics: a paused run still sees the user's hand-edits and git-pull drops, it just commits
295
304
  * nothing. Restriction is the session's DECLARATION, never the plane's structure. */
296
305
  memoryWrite?: boolean;
306
+ /** design/383 §2.1(core 7.0.0 片2/3;server S-49):**session 记忆采集 opt-out 声明** —— `"off"` = 「本会话
307
+ * 不进长期记忆」:单成员闭集,**没有** `"on"`(缺席就是照常采集的唯一形)。透传为 `TaskSpec.memory.capture`,
308
+ * 由 core 按部署姿态(`MEMORY_CAPTURE_POLICY`)+ 该 principal 的 `allowMemoryOptOut` verdict 裁:拒 = 403
309
+ * `memory.capture_optout_denied`。**一次性**:声明落一条单向控制面记录,后续 resume 恒不采集(record wins),
310
+ * 同会话重复声明幂等;要回到采集只能开新会话。与 `memoryWrite:false`(本 run 只读平面,可逐 run 切换)
311
+ * **不是一个轴**:那是「这次别写」,这是「这个会话永远别记」。
312
+ * ⚠️ 拼写门**两腿同判、响亮拒**:fresh 400 `request.field_invalid`;resume 重放同判(不做 resume 降级 ——
313
+ * 把一条隐私请求按打字错误静默丢掉恰是 core 禁的方向;core 的 `config.memory_capture_spelling` 是第二道墙)。
314
+ * 记忆面暗的部署(引擎未接线 / 多租户)上本键随 `memory` 整体缺席而无处落 —— 那里本就零采集。
315
+ * Rides the persisted body onto resume legs(与 memoryWrite 同姿势)。 */
316
+ memoryCapture?: "off";
297
317
  /** 142-S4 projectId 线程化(design/142 §2):这个 run 归属的项目(center 登记簿键,generic lowercase
298
318
  * UUID — core S1 `resolveProjectId` marker 或 S3 无仓 mint 的产物;客户端只透传,零铸造权威)。是「哪个
299
319
  * 项目」的选择器,不是身份/capability — scope 的 tenant 段永远来自 verified principal。SHAPE 门在
@@ -387,4 +407,17 @@ export type DecideBinding = {
387
407
  /** design/37 edit: operator-corrected tool args, applied by core AFTER the binding check, on approve only. */
388
408
  updatedInput?: unknown;
389
409
  };
410
+ /** S-02([5522]④→[5525] 坐标轮换判定):409 `approval_stale` 拒体上的 additive 机读指路 ——
411
+ * **本会话当前 pending** 的 D-1 回显坐标,让持旧坐标的壳一跳重定位(免重拉列表)。
412
+ * 键名/键形与 GET /v1/approvals 的 pending 行逐字同名(壳复用同一解析)。
413
+ * 🔴 恒**不含** checkpointToken(runs.ts §12-C:resume 凭证从不外发);只在当前 pending **在场**、
414
+ * 属 tool_approval、且已过路由级 owner 门的域内铸 —— 无 pending / 非工具门 / 读失败 ⇒ 键缺席(不造键)。 */
415
+ export type ApprovalStaleCurrentPending = {
416
+ /** 当前 pending 被门住的工具名(与 pending 行的 `toolName` 同源同值)。 */
417
+ toolName: string;
418
+ /** 当前 pending 的 `pendingAction.toolCallId` —— 壳重定位后 decide 时回显的 D-1 锚。 */
419
+ boundCallId: string;
420
+ /** 当前 pending 的 server 铸 `boundInputHash`(行上有才带;逐字回显,壳不重算)。 */
421
+ boundInputHash?: string;
422
+ };
390
423
  //# sourceMappingURL=wire-types.d.ts.map
@@ -44,7 +44,7 @@ export interface LeaderEndpoint {
44
44
  status: number;
45
45
  body: object;
46
46
  } | null>;
47
- /** 对抗复查 B-1(CRITICAL):the number of leader runs still RUNNING in the background — the drain gate
47
+ /** 交叉复查 B-1(CRITICAL):the number of leader runs still RUNNING in the background — the drain gate
48
48
  * counts them (a SIGTERM used to see 0 and hard-shut mid-leader-run).
49
49
  *
50
50
  * 🔴 刻意**只数本副本**(即使 store 在场):drain 门问的是「这个进程还在驱动几条 run」,不是「全集群
@@ -94,7 +94,7 @@ export interface LeaderDeps {
94
94
  maxRepairs?: number;
95
95
  };
96
96
  /** core 1.72 native L3a (design/54 §4): when set, each worker runs via `runWithVerification` (run the module +
97
- * an ADVERSARIAL DECORRELATED verifier tries to break it reading the diff/workspace + a bounded fix-loop)
97
+ * an INDEPENDENT DECORRELATED verifier tries to break it reading the diff/workspace + a bounded fix-loop)
98
98
  * instead of plain fan-out. The worker runner's `models` catalog MUST carry the heterogeneous `verifierModel`
99
99
  * (decorrelation = deployment contract). Mutually exclusive with `moduleGate` (this IS the native version). */
100
100
  verifyWorker?: VerifyConfig;
@@ -6,7 +6,7 @@
6
6
  * fixes the `OracleResult` shape + the read-only/identity contract; the composition lives HERE so the core type
7
7
  * never grows a discriminated union.
8
8
  *
9
- * Hardened by the adversarial round (§10):
9
+ * Hardened by the cross-review round (§10):
10
10
  * - §10.1 HERMETIC grade: the grader tree is reset to a clean base (`git reset --hard <integBase> && git clean
11
11
  * -fdx`) BEFORE applying the worker's LATEST full `base..HEAD` diff each call — so a 2nd/3rd attempt never
12
12
  * applies cumulatively onto an already-applied tree (the BROKEN finding). The diff is recomputed fresh every
@@ -63,7 +63,7 @@ export interface RepairOracleCfg {
63
63
  };
64
64
  /** fork1.2 — paths the candidate patch must NOT touch (the seeded hidden-oracle / test paths).
65
65
  * The §10.2 factory lane OMITs the engine's path-level probe, so a patch that EDITS the grader's oracle could
66
- * tamper with the grade and get rubber-stamped as `candidate_only`. A patch touching one of these is REFUSED
66
+ * rewrite the grade and get rubber-stamped as `candidate_only`. A patch touching one of these is REFUSED
67
67
  * as `needs_human_oracle` (surfaced for a human, never a silent candidate). Matched by exact path or subtree. */
68
68
  oraclePaths?: string[];
69
69
  /** 🔴 council 2026-06-17 — the seeded hidden-oracle files (path + content). The hermetic `git clean -fdx`
@@ -36,7 +36,7 @@ export function mkRepairOracle(cfg) {
36
36
  if (tampered) {
37
37
  cfg.log?.("repair_oracle_tampering_guard", { path: tampered });
38
38
  cfg.onGraded?.({ diff: diff.patch, hash: diffHash, passed: false });
39
- return { tier: "none", passed: false, flaky: false, retries: 0, trace: `candidate patch touches oracle/test path '${tampered}' — refusing to grade (fork1.2 test-tampering guard)` };
39
+ return { tier: "none", passed: false, flaky: false, retries: 0, trace: `candidate patch touches oracle/test path '${tampered}' — refusing to grade (fork1.2 test-alteration guard)` };
40
40
  }
41
41
  if (lastGradedHash !== undefined && diffHash === lastGradedHash) {
42
42
  cfg.log?.("repair_oracle_stale_diff", { hash: diffHash });
package/dist/main.js CHANGED
@@ -30,6 +30,7 @@ import { setRedactionObserver, redactSecrets } from "./trace/redact.js";
30
30
  import { RateLimiter } from "./observability/rate-limit.js";
31
31
  import { createHttpServer, explicitOperatorOk, hasServiceAuth } from "./http/server.js";
32
32
  import { exportMemoryScope } from "./memory-export.js";
33
+ import { workflowModelAllowlistFor } from "./task-workflow.js";
33
34
  import { createMemoryBundleFaces } from "./memory-bundle-engine.js";
34
35
  import { backendOwnsControlPlane, createMemoryComplianceFaces, createMemoryOriginFaces } from "./memory-operator-faces.js";
35
36
  import { performMemorySync } from "./memory-sync.js";
@@ -134,7 +135,7 @@ async function main() {
134
135
  if (adoptionBoot.scanned > 0 || adoptionBoot.error !== undefined) {
135
136
  logger.info("adoption_boot_scan", { ...adoptionBoot });
136
137
  }
137
- const { brain, pricing, counterDegradeHook, costQuota, modelUsageTracker, promptManifestTracker, fleetUsage, fleetLease, tracer, sideQueryAccounting, toolResultStore, sessionPolicyStore, fileSnapshotStore, } = createBudgetAndTracing({ config, logger, metrics, backend, breakerState });
138
+ const { brain, pricing, counterDegradeHook, costQuota, modelUsageTracker, promptManifestTracker, fleetUsage, fleetLease, tracer, sideQueryAccounting, toolResultStore, sessionPolicyStore, fileHistoryStore, } = createBudgetAndTracing({ config, logger, metrics, backend, breakerState });
138
139
  const permissionRuleStores = config.permissionRulesEnabled && backend ? backend.permissionRule() : undefined;
139
140
  const ruleConsent = permissionRuleStores ? createRuleConsentLane(permissionRuleStores) : undefined;
140
141
  if (config.permissionRulesEnabled && permissionRuleStores === undefined) {
@@ -163,7 +164,8 @@ async function main() {
163
164
  getParkedRedeem: () => parkedAskRedeem,
164
165
  ruleScopeRootFor: (sessionId) => cardRuleScopeRoot(sessionId !== undefined ? perSessionCwd.get(sessionId) : undefined, config),
165
166
  });
166
- const { principalCaps, centerRuntimeCapsResolver, runtimeCapsResolver } = createRuntimeCaps({ config, logger });
167
+ const memoryOptOutGrantStore = backend?.memoryOptOutGrant();
168
+ const { principalCaps, centerRuntimeCapsResolver, runtimeCapsResolver } = createRuntimeCaps({ config, logger, memoryOptOutGrant: memoryOptOutGrantStore });
167
169
  const orgMemoryAdmission = createOrgMemoryAdmissionWiring({ config, logger, metrics });
168
170
  const sharedMemoryStore = orgMemoryAdmission.directory !== undefined
169
171
  ? backend?.sharedMemoryStore?.(createSharedMemoryScopeAuthorizer({
@@ -238,12 +240,14 @@ async function main() {
238
240
  retentionExecutorWired: config.retentionSweep.intervalSec > 0 && retentionExecutor !== undefined && retentionLaneStore !== undefined,
239
241
  });
240
242
  const mcpRevocations = configCenter.takeMcpRevocations();
243
+ const workflowModelAllowlistSnapshot = workflowModelAllowlistFor(config) ?? [];
241
244
  const runnerDeps = createRunnerDeps({
242
245
  hands: commitHands,
243
246
  mcpRevocations,
244
247
  config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, usageWindowStore, brain,
245
248
  pricing, tracer, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine,
246
- memorySyncRunner, toolResultStore: runnerOffloadStore, sessionPolicyStore, runtimeCapsResolver, fileSnapshotStore,
249
+ memorySyncRunner, toolResultStore: runnerOffloadStore, sessionPolicyStore, runtimeCapsResolver, fileHistoryStore,
250
+ legacyRewindBoundaryProbe: backend?.legacyRewindBoundaryProbe ? backend.legacyRewindBoundaryProbe.bind(backend) : undefined,
247
251
  permissionRuleStore: permissionRuleStores?.provider,
248
252
  executionEnvFactory, lspManager, fleetBus, deploymentHooks, workflowRunStore, workflowJournalStore,
249
253
  workflowAgentRegistry, workflowNotifyGate, workflowCompletionInbox, deliverWorkflowCompletion, orgMemoryAdmission,
@@ -452,7 +456,7 @@ async function main() {
452
456
  let runDenySweep;
453
457
  const reaper = startReapers({
454
458
  config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore,
455
- rateLimiter, costQuota, toolResultStore, fileSnapshotStore, taskAttachmentStore, imageBakes, worktreeReap,
459
+ rateLimiter, costQuota, toolResultStore, fileHistoryStore, taskAttachmentStore, imageBakes, worktreeReap,
456
460
  workflowNotifyGate, workflowRecoverOpts, workflowJournalStore, sqlWorkflowRunStore, workflowNotifyJournal, rosterStore,
457
461
  backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores,
458
462
  getRunDenySweep: () => runDenySweep,
@@ -521,7 +525,7 @@ async function main() {
521
525
  deviceStore: deviceLane?.store,
522
526
  dropSessionScoped,
523
527
  config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore,
524
- resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileSnapshotStore,
528
+ resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileHistoryStore,
525
529
  workflowCompletionInbox, taskListLane,
526
530
  });
527
531
  const leaderEndpoint = createLeaderFace({ config, logger, brain, pricing, executionEnvFactory, toolResultStore, sessionStore, checkpointStore, governanceSeams, backend });
@@ -551,6 +555,7 @@ async function main() {
551
555
  posture: staticWiring.interaction.posture,
552
556
  streamApproval: streamApprovalGate.active ? "active" : streamApprovalGate.reason,
553
557
  subagentTranscripts: staticWiring.fleet.subagentTranscripts,
558
+ subagentTranscriptRetentionMs: config.backgroundAgentRetentionMs,
554
559
  });
555
560
  const stores = {
556
561
  runStore,
@@ -559,7 +564,7 @@ async function main() {
559
564
  checkpointStore,
560
565
  sessionPolicyStore: sessionPolicyStore ? sessionPolicyStore : undefined,
561
566
  ruleConsent,
562
- fileSnapshotStore: fileSnapshotStore ? fileSnapshotStore : undefined,
567
+ fileHistoryStore: fileHistoryStore ? fileHistoryStore : undefined,
563
568
  toolResultStore,
564
569
  taskAttachmentStore: taskAttachmentStore ? taskAttachmentStore : undefined,
565
570
  backgroundAgentStore: backgroundAgentStore ? backgroundAgentStore : undefined,
@@ -606,6 +611,7 @@ async function main() {
606
611
  memoryCompliance: memoryComplianceFaces,
607
612
  memoryOriginFace: memoryOriginFaces,
608
613
  memoryConsolidation: memoryConsolidationFaces,
614
+ memoryOptOutGrant: memoryOptOutGrantStore,
609
615
  orgMemoryDirectory: orgMemoryAdmission.directory,
610
616
  sharedMemoryStore: sharedMemoryStore ? sharedMemoryStore : undefined,
611
617
  sessionMirrorRuling: principalCaps
@@ -639,6 +645,7 @@ async function main() {
639
645
  modelReady: () => configCenter.modelReady(),
640
646
  scenarioDetails,
641
647
  workflowsCapable,
648
+ workflowModelAllowlist: workflowModelAllowlistSnapshot,
642
649
  staticWiring,
643
650
  memoryPosture,
644
651
  instanceId,
@@ -678,6 +685,7 @@ async function main() {
678
685
  sessionEnvSelection,
679
686
  lockedKeys: governanceSeams.lockedKeys,
680
687
  liveQuestionFace: question,
688
+ approverSeat: createRunnerDepsOnAsk(toolApproval),
681
689
  });
682
690
  const runnerFor = (spec) => pickHandsRunner(handsLanes.laneOf(spec), { full: runner, handsless: handslessRunner });
683
691
  const sideQuery = createSideQueryLane({ brain, config, getKeyResolver: () => configCenter.getKeyResolver() });
@@ -29,7 +29,7 @@
29
29
  * HTTP 面诚实 501。
30
30
  *
31
31
  * 🔴 首版把这件事当成**运行期**事实(照常挂载,让 core 每次抛 `memory.export_incomplete` / 422),
32
- * 被 codex 对抗复审驳倒(#277 提货批 [medium],验真后修):`typeof backend.exportSnapshotOf === "function"`
32
+ * 被 codex 交叉复审驳倒(#277 提货批 [medium],验真后修):`typeof backend.exportSnapshotOf === "function"`
33
33
  * 是一次**属性检查**,boot 期就判得出;而且它是**永久**缺席(换后端才会变),不是瞬时状态。照旧挂着
34
34
  * 就成了「能力位说 yes、每一次调用都确定性失败」——本仓「says yes ⟺ route works」是结构性承诺,
35
35
  * 一个恒假的 yes 会让壳给用户开出一条走不通的迁移流程。
@@ -23,7 +23,7 @@
23
23
  * Runner 每任务现构的引擎会看到**两个不同的控制面**。
24
24
  *
25
25
  * ⇒ 裁:`controlPlaneRoot` 缺席 ⇒ 本工厂返回 **undefined**,两口整个不挂载,HTTP 面诚实 501。今天满足这一
26
- * 条的只有 core 自带的 `FileMemoryEngineBackend`(它把控制面钉死在 config-root 一侧);本仓两只 SQL 记忆
26
+ * 条的只有 core 自带的 `FileMemoryEngineBackend`(它把控制面写死在 config-root 一侧);本仓两只 SQL 记忆
27
27
  * 孪生(`plugins/memory-engine-{pg,tidb}.ts`)没有这个面 ⇒ 在那些部署上两口不挂。SQL 控制面是 core 的候升件
28
28
  * (design/316 §0b-4 咨询单),**绝不**在本层为它们现造一个「够用的」控制面 —— 那是把一个 core 拒绝生产的
29
29
  * 半吊子治理面自己造出来,与 bundle 那条「a governance-less export is the laundering shape」同族。
@@ -36,7 +36,7 @@
36
36
  *
37
37
  * 判据是一次**属性检查**、boot 期就判得出、而且是**永久**缺席(换后端才会变)——不是瞬时状态。照旧挂着
38
38
  * 就成了「能力位说 yes、每一次调用都确定性失败」,而本仓「says yes ⟺ route works」是结构性承诺。
39
- * (这条纪律是 #264 提货批被 codex 对抗复审驳倒后立的,见 memory-bundle-engine.ts 头注。)
39
+ * (这条纪律是 #264 提货批被 codex 交叉复审驳倒后立的,见 memory-bundle-engine.ts 头注。)
40
40
  *
41
41
  * ⚠️ **刻意不把审计/托管/证据三个可选面编进构造判定**:`committedSnapshotOf` / `custodyOf` 缺席时 core
42
42
  * 明写要诚实答 `binding:"unknown"` / `contentState:"capability-absent"` / `custody:"capability-absent"`
@@ -70,7 +70,7 @@ export interface MemoryComplianceFaces {
70
70
  }) => Promise<MemoryErasureAttestation>;
71
71
  }
72
72
  /**
73
- * **选择子替身**的拦截谓词(codex 对抗复审 [critical],亲验复现后修 —— 复现器与结论逐条在
73
+ * **选择子替身**的拦截谓词(codex 交叉复审 [critical],亲验复现后修 —— 复现器与结论逐条在
74
74
  * `test/memory-compliance-http.test.ts` 的同名 describe 与 `test/memory-operator-faces.test.ts` 里)。
75
75
  *
76
76
  * ## 病(在 core 5.57.0 上实测,不是纸面推演)
@@ -82,7 +82,7 @@ export interface MemoryComplianceFaces {
82
82
  * `__proto__` 那一下改的是克隆体的**原型**,于是这个键从 `Object.keys` 里消失;
83
83
  * · core 的 `erasureRequestInvalid` 数的是 `Object.keys` ⇒ 只看见一个 `scope` ⇒ **判合法**;
84
84
  * · 而两条执行腿的分派都写作 `"ids" in select`(engine.js:477 降级腿、file-backend.js:2614 证据腿),
85
- * `in` **查原型链** ⇒ 继承来的 `ids` 赢,删的是攻击者点名的那批 id;
85
+ * `in` **查原型链** ⇒ 继承来的 `ids` 赢,删的是不当调用方点名的那批 id;
86
86
  * · 收执里的 `select` / `selectHash` 记的却是那个自有的 `scope`。
87
87
  * ⇒ **删掉的东西与凭据上写的不是同一批**,而这一口的全部意义就是那份可归档的凭据;删除不可回滚。
88
88
  *
@@ -94,7 +94,7 @@ export interface MemoryComplianceFaces {
94
94
  * 本仓禁止的形,而且悄悄改写调用方的请求会让它以为自己删的是别的东西)。
95
95
  *
96
96
  * 两个执法点共用**这一只**谓词:路由拿它出 400(wire 可见、有钉),seam 拿它 fail-closed 兜底(seam 是
97
- * 可导出模块,不能靠「今天只有一个调用方」这句话来安全;走到那一臂 = 路由的门被绕过了 = 装配 bug,
97
+ * 可导出模块,不能靠「今天只有一个调用方」这句话来安全;走到那一臂 = 路由的门被跳过了 = 装配 bug,
98
98
  * 500 fail-loud 是对的)。刻意不做「只在 seam 拦」:那样 wire 上就没有一句能测的诚实拒。
99
99
  *
100
100
  * 走法是**迭代**而非递归:体的深度只受体积上限约束,一个深嵌套的 JSON 能把递归版打爆栈(把一个