@sema-agent/server 7.50.0 → 7.52.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 (222) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/USAGE.md +83 -20
  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.d.ts +79 -0
  21. package/dist/boot/device-lane.js +63 -0
  22. package/dist/boot/engine-lease.d.ts +1 -1
  23. package/dist/boot/execution-env.d.ts +48 -2
  24. package/dist/boot/execution-env.js +57 -5
  25. package/dist/boot/governance-seams.d.ts +2 -2
  26. package/dist/boot/leader.d.ts +1 -1
  27. package/dist/boot/memory-boundary.d.ts +2 -2
  28. package/dist/boot/memory-consolidation.d.ts +6 -6
  29. package/dist/boot/org-memory.d.ts +1 -1
  30. package/dist/boot/parked-revive-gate.d.ts +3 -3
  31. package/dist/boot/reapers.d.ts +2 -2
  32. package/dist/boot/reapers.js +17 -4
  33. package/dist/boot/resolve-spec.d.ts +15 -0
  34. package/dist/boot/resolve-spec.js +40 -15
  35. package/dist/boot/retention-lane.d.ts +2 -2
  36. package/dist/boot/runner-deps.d.ts +17 -7
  37. package/dist/boot/runner-deps.js +4 -2
  38. package/dist/boot/runtime-caps.d.ts +23 -0
  39. package/dist/boot/runtime-caps.js +24 -2
  40. package/dist/boot/session-faces.d.ts +6 -1
  41. package/dist/boot/session-faces.js +17 -3
  42. package/dist/boot/shutdown.d.ts +8 -0
  43. package/dist/boot/shutdown.js +3 -1
  44. package/dist/boot/stores.js +40 -1
  45. package/dist/boot/task-list-lane.d.ts +2 -2
  46. package/dist/brain.js +51 -31
  47. package/dist/budget.d.ts +2 -2
  48. package/dist/capabilities/center-plugins.d.ts +1 -1
  49. package/dist/capabilities/collab-workflows.d.ts +1 -1
  50. package/dist/capabilities/hands-lane.d.ts +3 -3
  51. package/dist/capabilities/scenarios.d.ts +3 -3
  52. package/dist/capabilities/skills.d.ts +1 -1
  53. package/dist/config-center/hot-keys-registry.js +5 -0
  54. package/dist/config-center/http-client.d.ts +2 -2
  55. package/dist/config-center/read-face.d.ts +1 -1
  56. package/dist/config-center/restart-signal.d.ts +1 -1
  57. package/dist/config-center/types.d.ts +10 -3
  58. package/dist/config-invariants.d.ts +2 -2
  59. package/dist/config-invariants.js +18 -0
  60. package/dist/config-types.d.ts +90 -18
  61. package/dist/config.js +91 -19
  62. package/dist/deployment-governance.d.ts +1 -1
  63. package/dist/device-enrollment.d.ts +125 -0
  64. package/dist/device-enrollment.js +156 -0
  65. package/dist/device-store.d.ts +385 -0
  66. package/dist/device-store.js +407 -0
  67. package/dist/device-ws-hub.d.ts +182 -0
  68. package/dist/device-ws-hub.js +1012 -0
  69. package/dist/device-ws-protocol.d.ts +429 -0
  70. package/dist/device-ws-protocol.js +464 -0
  71. package/dist/env-facts.d.ts +5 -2
  72. package/dist/env-facts.js +1 -0
  73. package/dist/execution-lane-caps.d.ts +152 -0
  74. package/dist/execution-lane-caps.js +166 -0
  75. package/dist/fleet/fleet-bus.d.ts +1 -1
  76. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  77. package/dist/fleet/fleet-terminal-window.d.ts +1 -1
  78. package/dist/http/active-run-conflict.d.ts +2 -2
  79. package/dist/http/principal-gate.d.ts +1 -1
  80. package/dist/http/route-ctx.d.ts +24 -4
  81. package/dist/http/routes/admin-memory-optout.d.ts +48 -0
  82. package/dist/http/routes/admin-memory-optout.js +75 -0
  83. package/dist/http/routes/agents-roster.d.ts +1 -1
  84. package/dist/http/routes/approvals-assistant.js +21 -21
  85. package/dist/http/routes/capabilities.js +27 -11
  86. package/dist/http/routes/diagnostics.d.ts +1 -1
  87. package/dist/http/routes/leader.d.ts +1 -1
  88. package/dist/http/routes/memory-bundle.d.ts +1 -1
  89. package/dist/http/routes/memory-bundle.js +3 -1
  90. package/dist/http/routes/memory-compliance.d.ts +4 -4
  91. package/dist/http/routes/memory-origin.d.ts +4 -4
  92. package/dist/http/routes/memory-origin.js +3 -1
  93. package/dist/http/routes/memory-policy.js +6 -2
  94. package/dist/http/routes/retention-ops.d.ts +1 -1
  95. package/dist/http/routes/rules.d.ts +5 -2
  96. package/dist/http/routes/rules.js +84 -5
  97. package/dist/http/routes/run-memory-capture.d.ts +34 -0
  98. package/dist/http/routes/run-memory-capture.js +97 -0
  99. package/dist/http/routes/runs.d.ts +1 -0
  100. package/dist/http/routes/runs.js +212 -6
  101. package/dist/http/routes/session-sync.d.ts +4 -6
  102. package/dist/http/routes/session-sync.js +60 -47
  103. package/dist/http/routes/sessions.js +61 -188
  104. package/dist/http/routes/shared-memory.d.ts +1 -1
  105. package/dist/http/routes/tasks.js +1 -1
  106. package/dist/http/routes/workflows.js +19 -0
  107. package/dist/http/server.d.ts +49 -13
  108. package/dist/http/server.js +109 -13
  109. package/dist/http/verify-rounds.d.ts +1 -1
  110. package/dist/http/wire-types.d.ts +41 -8
  111. package/dist/leader/endpoint.d.ts +1 -1
  112. package/dist/leader/leader.d.ts +1 -1
  113. package/dist/leader/repair-oracle.d.ts +2 -2
  114. package/dist/leader/repair-oracle.js +1 -1
  115. package/dist/leader/wire.js +4 -2
  116. package/dist/main.js +23 -9
  117. package/dist/memory-bundle-engine.d.ts +1 -1
  118. package/dist/memory-operator-faces.d.ts +5 -5
  119. package/dist/memory-origin-fence.d.ts +50 -0
  120. package/dist/memory-origin-fence.js +15 -0
  121. package/dist/memory-posture.d.ts +13 -1
  122. package/dist/memory-posture.js +1 -0
  123. package/dist/memory-scope.d.ts +6 -1
  124. package/dist/memory-scope.js +3 -2
  125. package/dist/model-select.d.ts +2 -2
  126. package/dist/observability/fail-open.d.ts +17 -5
  127. package/dist/observability/fail-open.js +17 -5
  128. package/dist/orchestration/hardened-vm-runner.d.ts +9 -2
  129. package/dist/orchestration/hardened-vm-runner.js +11 -1
  130. package/dist/orchestration/hardened-vm-worker-runner.js +2 -2
  131. package/dist/orchestration/workflow-completion-inbox.d.ts +2 -2
  132. package/dist/plugins/background-shell-support.d.ts +2 -2
  133. package/dist/plugins/blob-backend.d.ts +7 -2
  134. package/dist/plugins/blob-backend.js +22 -18
  135. package/dist/plugins/caching-session-store.d.ts +7 -3
  136. package/dist/plugins/checkpoint-store-sql.d.ts +2 -2
  137. package/dist/plugins/device-store-sql.d.ts +130 -0
  138. package/dist/plugins/device-store-sql.js +574 -0
  139. package/dist/plugins/file-history-store-sql.d.ts +170 -0
  140. package/dist/plugins/file-history-store-sql.js +827 -0
  141. package/dist/plugins/file-run-store.d.ts +44 -5
  142. package/dist/plugins/file-run-store.js +85 -13
  143. package/dist/plugins/fork-routing-session-store.d.ts +1 -1
  144. package/dist/plugins/fork-routing-session-store.js +2 -2
  145. package/dist/plugins/leader-run-store-sql.d.ts +1 -1
  146. package/dist/plugins/local-session-store.d.ts +9 -1
  147. package/dist/plugins/memory-embedder-fingerprint.d.ts +1 -1
  148. package/dist/plugins/memory-optout-grant-store-sql.d.ts +139 -0
  149. package/dist/plugins/memory-optout-grant-store-sql.js +157 -0
  150. package/dist/plugins/memory-origin-law.d.ts +2 -2
  151. package/dist/plugins/memory-run-store.d.ts +4 -3
  152. package/dist/plugins/memory-run-store.js +2 -1
  153. package/dist/plugins/permission-rule-store-file.d.ts +16 -6
  154. package/dist/plugins/permission-rule-store-file.js +77 -13
  155. package/dist/plugins/permission-rule-store-sql.d.ts +156 -8
  156. package/dist/plugins/permission-rule-store-sql.js +123 -21
  157. package/dist/plugins/pg-pool.js +59 -0
  158. package/dist/plugins/pg-safe-json.d.ts +1 -1
  159. package/dist/plugins/remote-env-device.d.ts +271 -0
  160. package/dist/plugins/remote-env-device.js +727 -0
  161. package/dist/plugins/remote-env-k8s.d.ts +1 -1
  162. package/dist/plugins/remote-scratchpad.js +1 -1
  163. package/dist/plugins/retention-lane-store-sql.d.ts +3 -3
  164. package/dist/plugins/retention-store-sql.d.ts +7 -7
  165. package/dist/plugins/retention-store-sql.js +6 -0
  166. package/dist/plugins/run-store-sql.d.ts +9 -3
  167. package/dist/plugins/run-store-sql.js +2 -2
  168. package/dist/plugins/send-user-file.d.ts +1 -1
  169. package/dist/plugins/session-placement.d.ts +1 -1
  170. package/dist/plugins/sql-errors.d.ts +1 -1
  171. package/dist/plugins/store-backend.d.ts +68 -29
  172. package/dist/plugins/store-backend.js +78 -15
  173. package/dist/plugins/store-contracts.d.ts +5 -3
  174. package/dist/plugins/task-list-store-sql.d.ts +1 -1
  175. package/dist/plugins/tidb-pool.js +59 -0
  176. package/dist/plugins/web-search.d.ts +1 -1
  177. package/dist/prompts-domain-validate.js +1 -1
  178. package/dist/resource-suspend.d.ts +1 -1
  179. package/dist/resource-window.d.ts +3 -3
  180. package/dist/rules-consent.d.ts +231 -9
  181. package/dist/rules-consent.js +411 -13
  182. package/dist/run-local.js +1 -0
  183. package/dist/runs.d.ts +15 -1
  184. package/dist/runs.js +19 -0
  185. package/dist/runtime-caps-resolver.d.ts +36 -1
  186. package/dist/runtime-caps-resolver.js +25 -0
  187. package/dist/runtime-governance.d.ts +1 -1
  188. package/dist/security.d.ts +5 -5
  189. package/dist/security.js +1 -1
  190. package/dist/session-sync.d.ts +52 -37
  191. package/dist/session-sync.js +19 -16
  192. package/dist/shared-memory-scope-authorizer.d.ts +1 -1
  193. package/dist/sighup-idle.d.ts +1 -1
  194. package/dist/spec-fields.d.ts +22 -1
  195. package/dist/spec-fields.js +11 -0
  196. package/dist/store-live-probe.d.ts +2 -2
  197. package/dist/task-a2a.d.ts +1 -1
  198. package/dist/task-cwd.d.ts +25 -0
  199. package/dist/task-cwd.js +3 -0
  200. package/dist/task-mcp.d.ts +3 -3
  201. package/dist/task-settings.d.ts +3 -3
  202. package/dist/task-settings.js +3 -1
  203. package/dist/task-workflow.d.ts +1 -1
  204. package/dist/text-bidi.d.ts +2 -2
  205. package/dist/tool-approval.d.ts +191 -9
  206. package/dist/tool-approval.js +248 -29
  207. package/dist/trace/core-keyset-guard.d.ts +1 -1
  208. package/dist/trace/engine-notice-wire.d.ts +6 -4
  209. package/dist/trace/engine-notice-wire.js +2 -0
  210. package/dist/trace/ledger-events.d.ts +1 -1
  211. package/dist/trace/ledger-sink.d.ts +2 -2
  212. package/dist/trace/project.d.ts +3 -2
  213. package/dist/trace/project.js +1 -0
  214. package/dist/turn-activity.d.ts +1 -1
  215. package/dist/wall-clock-jump-guard.d.ts +3 -3
  216. package/package.json +2 -2
  217. package/dist/http/tar.d.ts +0 -17
  218. package/dist/http/tar.js +0 -53
  219. package/dist/http/workspace-content.d.ts +0 -11
  220. package/dist/http/workspace-content.js +0 -20
  221. package/dist/plugins/file-snapshot-store-sql.d.ts +0 -170
  222. 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,
@@ -244,6 +248,7 @@ export function createHttpServer(rawDeps) {
244
248
  res.end();
245
249
  });
246
250
  });
251
+ deps.deviceHub?.attach(server);
247
252
  const routeCtxBase = {
248
253
  deps,
249
254
  registry: { idemCache, inflightRuns, preemptableRuns, cancelledViaVerb, steerableRuns, wakeParkMints, counters },
@@ -440,6 +445,8 @@ export function createHttpServer(rawDeps) {
440
445
  return;
441
446
  if (await handleRunVerbs(req, res, url, ctx))
442
447
  return;
448
+ if (await handleRunMemoryCapture(req, res, url, ctx))
449
+ return;
443
450
  if (await handleLeader(req, res, url, ctx))
444
451
  return;
445
452
  if (await handleImages(req, res, url, ctx))
@@ -458,6 +465,8 @@ export function createHttpServer(rawDeps) {
458
465
  return;
459
466
  if (await handleAdminConfigRefresh(req, res, url, ctx))
460
467
  return;
468
+ if (await handleMemoryOptOut(req, res, url, ctx))
469
+ return;
461
470
  if (await handleMemoryPolicy(req, res, url, ctx))
462
471
  return;
463
472
  if (await handleMemoryBundle(req, res, url, ctx))
@@ -489,7 +498,7 @@ export function createHttpServer(rawDeps) {
489
498
  function runnerFor(spec) {
490
499
  return deps.runnerFor?.(spec) ?? deps.runner;
491
500
  }
492
- async function prepareSpec(req, res, bodyIn, onTypedFailure) {
501
+ async function prepareSpec(req, res, bodyIn, onTypedFailure, legFacts) {
493
502
  const body = bodyIn ?? (await readJson(req));
494
503
  if (!body || typeof body.objective !== "string") {
495
504
  sendError(res, 400, "request.field_invalid", "missing 'objective' string");
@@ -722,10 +731,22 @@ export function createHttpServer(rawDeps) {
722
731
  sendError(res, 400, "request.field_invalid", "rewindFiles must be a boolean");
723
732
  return null;
724
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
+ }
725
742
  if (body.memoryWrite !== undefined && typeof body.memoryWrite !== "boolean") {
726
743
  sendError(res, 400, "request.field_invalid", "memoryWrite must be a boolean (false = pause memory writes for this run)");
727
744
  return null;
728
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
+ }
729
750
  if (body.requireExistingSession !== undefined && typeof body.requireExistingSession !== "boolean") {
730
751
  sendError(res, 400, "request.field_invalid", "requireExistingSession must be a boolean");
731
752
  return null;
@@ -807,6 +828,13 @@ export function createHttpServer(rawDeps) {
807
828
  sendError(res, 400, "request.field_invalid", agentsErr);
808
829
  return null;
809
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
+ }
810
838
  }
811
839
  if (body.interactiveTools !== undefined && typeof body.interactiveTools !== "boolean") {
812
840
  sendError(res, 400, "request.field_invalid", "interactiveTools must be a boolean");
@@ -931,7 +959,7 @@ export function createHttpServer(rawDeps) {
931
959
  }
932
960
  try {
933
961
  const auth = deps.authorize ? await deps.authorize({ req, body }) : undefined;
934
- 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 } : {}) });
935
963
  return { spec, runner: runnerFor(spec), auth, verify, cascade, jobId: body.jobId, body };
936
964
  }
937
965
  catch (err) {
@@ -947,21 +975,52 @@ export function createHttpServer(rawDeps) {
947
975
  }
948
976
  }
949
977
  const resumeFleetScope = (req, auth) => req ? (gatedPrincipal(req, deps.config) ?? "default") : (auth.principal ?? "default");
950
- 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) {
951
979
  const cs = deps.checkpointStore;
952
980
  const token = await cs.findPendingTokenBySession(sessionId);
953
981
  if (binding?.checkpointToken && binding.checkpointToken !== token) {
954
- 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
+ });
955
988
  const attributable = seen !== null && seen.sessionId === sessionId;
956
989
  if (!attributable && seen !== null) {
957
990
  deps.logger?.warn?.("approval_stale_token_foreign", { sessionId, tokenSession: seen.sessionId ?? null });
958
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
+ }
959
1017
  return {
960
1018
  status: 409,
961
1019
  body: {
962
1020
  error: "the approval you are deciding is no longer current (already resolved or superseded since you viewed it)",
963
1021
  errorCode: "approval_stale",
964
1022
  ...(attributable ? { terminal: "resolved" } : {}),
1023
+ ...(currentPending ? { currentPending } : {}),
965
1024
  },
966
1025
  };
967
1026
  }
@@ -981,6 +1040,13 @@ export function createHttpServer(rawDeps) {
981
1040
  }
982
1041
  if (!cp)
983
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
+ }
984
1050
  const decideGateKind = cp.gate?.kind;
985
1051
  if (!isApprovalGateKind(decideGateKind)) {
986
1052
  const activeForPath = decideGateKind
@@ -1008,6 +1074,13 @@ export function createHttpServer(rawDeps) {
1008
1074
  },
1009
1075
  };
1010
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;
1011
1084
  if (req !== undefined && deps.backgroundAgentStore !== undefined && deps.parkedReviveTool !== undefined) {
1012
1085
  const parked = await decideParkedAgent({
1013
1086
  agentStore: deps.backgroundAgentStore,
@@ -1021,7 +1094,7 @@ export function createHttpServer(rawDeps) {
1021
1094
  token, scope: cp.scope, sessionId: cp.sessionId, pendingAction: cp.pendingAction,
1022
1095
  decision, settledBy, ...(reason !== undefined ? { reason } : {}),
1023
1096
  ...(binding !== undefined ? { binding: { ...(binding.boundCallId !== undefined ? { boundCallId: binding.boundCallId } : {}), ...(binding.boundInputHash !== undefined ? { boundInputHash: binding.boundInputHash } : {}), ...(binding.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}) } } : {}),
1024
- ...(onResumeCommitted ? { grantRemember: (rootSessionId) => onResumeCommitted(rootSessionId) } : {}),
1097
+ ...(boundGrant ? { grantRemember: (rootSessionId) => boundGrant(rootSessionId) } : {}),
1025
1098
  ...(answer !== undefined ? { answer } : {}),
1026
1099
  });
1027
1100
  if (parked !== undefined) {
@@ -1090,7 +1163,7 @@ export function createHttpServer(rawDeps) {
1090
1163
  settledBy,
1091
1164
  };
1092
1165
  const verifyRounds = verifyRoundsFromBody(ctx.body);
1093
- 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 } : {}) });
1094
1167
  }
1095
1168
  async function driveResumeIntoRunLog(args) {
1096
1169
  if (args.acceptEarly !== true)
@@ -1157,6 +1230,17 @@ export function createHttpServer(rawDeps) {
1157
1230
  ? fleetRunPublisher(deps.fleetBus, { runId: taskId, scope: fleetScope, rootTaskId: taskId, ...fleetRunLabels(resumeObjective) })
1158
1231
  : undefined;
1159
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
+ }
1160
1244
  if (taskId && resumeTaskConfig.resourceSuspend)
1161
1245
  recordResumeResourceWindow(taskId, resumeTaskConfig.resourceSuspend);
1162
1246
  let fleetSettled = false;
@@ -1188,7 +1272,7 @@ export function createHttpServer(rawDeps) {
1188
1272
  const cancelCtrl = new AbortController();
1189
1273
  const preemptCtrl = new AbortController();
1190
1274
  if (taskId && deps.runStore) {
1191
- claimedRow = freshRunClaimed ? true : await deps.runStore.markResuming(taskId);
1275
+ claimedRow = freshRunClaimed ? true : await deps.runStore.markResuming(taskId, deps.instanceId ?? "default");
1192
1276
  if (!claimedRow)
1193
1277
  return { status: 409, body: { error: "run is not in a resumable (suspended) state (already resumed, decided, or expired)", errorCode: "conflict.not_resumable" } };
1194
1278
  markChildrenStoppedByUserOnAbort(cancelCtrl.signal, taskId, principal);
@@ -1604,6 +1688,8 @@ export function createHttpServer(rawDeps) {
1604
1688
  e.code === "resume.constraint_unprojectable" ||
1605
1689
  e.code === "resume.usage_window_exhausted" ||
1606
1690
  e.code === "resume.principal_mismatch" ||
1691
+ e.code === "resume.placement_mismatch" ||
1692
+ e.code === "resume.preflight_rejected" ||
1607
1693
  e.code.startsWith("wake.") ||
1608
1694
  (e.code === "checkpoint.unsupported_version" && checkpointRowRedeemableElsewhere(e.detail?.reason));
1609
1695
  if (claimedRow && taskId && deps.runStore) {
@@ -1633,7 +1719,7 @@ export function createHttpServer(rawDeps) {
1633
1719
  ? (outcome.gate === "policy_ask" ? "approval_binding_mismatch" : "resume_outcome_invalid")
1634
1720
  : e.code,
1635
1721
  ...(e.detail?.field ? { field: e.detail.field } : {}),
1636
- ...(e.code === "resume.usage_window_exhausted" && typeof e.detail?.retryAfterMs === "number" && Number.isFinite(e.detail.retryAfterMs) && e.detail.retryAfterMs > 0
1722
+ ...((e.code === "resume.usage_window_exhausted" || e.code === "resume.preflight_rejected") && typeof e.detail?.retryAfterMs === "number" && Number.isFinite(e.detail.retryAfterMs) && e.detail.retryAfterMs > 0
1637
1723
  ? { retryAfterSec: Math.max(1, Math.ceil(e.detail.retryAfterMs / 1000)) }
1638
1724
  : {}),
1639
1725
  ...(retriable ? { retriable: true } : {}),
@@ -2115,7 +2201,7 @@ export function createHttpServer(rawDeps) {
2115
2201
  ...(req.gateBoundInputHash !== null ? { boundInputHash: req.gateBoundInputHash } : {}),
2116
2202
  ...(req.updatedInput !== undefined ? { updatedInput: req.updatedInput } : {}),
2117
2203
  };
2118
- const out = await resumeCheckpoint(cp.sessionId, req.decision, req.note, "human", req.httpReq, undefined, binding, undefined, true);
2204
+ const out = await resumeCheckpoint(cp.sessionId, req.decision, req.note, "human", req.httpReq, undefined, binding, undefined, true, { principal: req.principal, explicitOperator: false });
2119
2205
  return { status: out.status, body: out.body };
2120
2206
  }
2121
2207
  const isRunLiveOnThisReplica = (taskId) => inflightRuns.has(taskId) || steerableRuns.has(taskId);
@@ -2159,22 +2245,28 @@ export function isCredentialGatedRewrite(method, url) {
2159
2245
  if (m === "POST") {
2160
2246
  return (url === "/v1/adoption" ||
2161
2247
  url.startsWith("/v1/rules/cc-import/") ||
2248
+ url.startsWith("/v1/rules/local-import/") ||
2162
2249
  url === "/v1/memory/import" ||
2163
2250
  url === "/v1/memory/export" ||
2164
2251
  url === "/v1/memory/erase" ||
2165
2252
  url.startsWith("/v1/memory/origin/entries/"));
2166
2253
  }
2254
+ if (m === "PUT")
2255
+ return url === "/v1/admin/memory-optout" || url.startsWith("/v1/admin/memory-optout/");
2167
2256
  if (m === "DELETE")
2168
- return url === "/v1/rules";
2257
+ return url === "/v1/rules" || url.startsWith("/v1/admin/memory-optout/");
2169
2258
  if (m === "GET") {
2170
2259
  return (/^\/v1\/memory\/entries\/[^/]+\/provenance$/.test(url) ||
2171
2260
  url === "/v1/memory/origin/external" ||
2172
- url === "/v1/memory/origin/clearances");
2261
+ url === "/v1/memory/origin/clearances" ||
2262
+ url === "/v1/admin/memory-optout");
2173
2263
  }
2174
2264
  return false;
2175
2265
  }
2176
2266
  const ROUTE_LABEL_PATTERNS = [
2177
2267
  [/^\/v1\/runs\/[^/]+\/steer$/, "/v1/runs/:id/steer"],
2268
+ [/^\/v1\/runs\/[^/]+\/memory\/capture-optout$/, "/v1/runs/:id/memory/capture-optout"],
2269
+ [/^\/v1\/runs\/[^/]+\/interrupt$/, "/v1/runs/:id/interrupt"],
2178
2270
  [/^\/v1\/runs\/[^/]+\/detach$/, "/v1/runs/:id/detach"],
2179
2271
  [/^\/v1\/runs\/[^/]+\/cancel$/, "/v1/runs/:id/cancel"],
2180
2272
  [/^\/v1\/runs\/[^/]+\/compact$/, "/v1/runs/:id/compact"],
@@ -2192,10 +2284,12 @@ const ROUTE_LABEL_PATTERNS = [
2192
2284
  [/^\/v1\/approvals\/[^/]+\/decide$/, "/v1/approvals/:id/decide"],
2193
2285
  [/^\/v1\/approvals\/[^/]+\/exemptions(\/[^/]+)?$/, "/v1/approvals/:id/exemptions"],
2194
2286
  [/^\/v1\/sessions\/[^/]+\/(wake|fork|init|notify|events|head|mcp|a2a|policy|settings)$/, "/v1/sessions/:id/$verb"],
2287
+ [/^\/v1\/admin\/memory-optout\/[^/]+$/, "/v1/admin/memory-optout/:principal"],
2288
+ [/^\/v1\/sessions\/[^/]+\/memory\/erase$/, "/v1/sessions/:id/memory/erase"],
2195
2289
  [/^\/v1\/sessions\/[^/]+\/sync\/(manifest|entries|plan)$/, "/v1/sessions/:id/sync/$verb"],
2196
2290
  [/^\/v1\/sessions\/[^/]+\/sync\/import(\/[^/]+\/entries)?$/, "/v1/sessions/:id/sync/import"],
2197
2291
  [/^\/v1\/sessions\/[^/]+\/sync\/blobs(\/[^/]+)?$/, "/v1/sessions/:id/sync/blobs"],
2198
- [/^\/v1\/sessions\/[^/]+\/sync\/snapshots\/[^/]+\/blobs\/[^/]+$/, "/v1/sessions/:id/sync/snapshots/:key/blobs/:hash"],
2292
+ [/^\/v1\/sessions\/[^/]+\/sync\/history\/blobs\/[^/]+$/, "/v1/sessions/:id/sync/history/blobs/:hash"],
2199
2293
  [/^\/v1\/sessions\/[^/]+\/workspace(\/[^/]+(\/(tree|file|archive))?)?$/, "/v1/sessions/:id/workspace"],
2200
2294
  [/^\/v1\/sessions\/[^/]+$/, "/v1/sessions/:id"],
2201
2295
  [/^\/v1\/assistant\/tasks\/[^/]+\/preempt$/, "/v1/assistant/tasks/:id/preempt"],
@@ -2237,8 +2331,10 @@ const ROUTE_LABEL_LITERALS = new Set([
2237
2331
  "/v1/adoption",
2238
2332
  "/v1/admin/drain",
2239
2333
  "/v1/admin/config/refresh",
2334
+ "/v1/admin/memory-optout",
2240
2335
  "/v1/admin/memory/consolidation/run", "/v1/admin/memory/consolidation",
2241
2336
  "/v1/rules/cc-import/prepare", "/v1/rules/cc-import/redeem",
2337
+ "/v1/rules/local-import/prepare", "/v1/rules/local-import/redeem",
2242
2338
  "/v1/rules",
2243
2339
  "/.well-known/agent-card.json", "/v1/a2a",
2244
2340
  "/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 });
@@ -366,7 +366,8 @@ export function createLeaderRunner(cfg) {
366
366
  };
367
367
  const provisionIntegrationSandbox = async () => {
368
368
  if (cfg.envFactory) {
369
- const env = cfg.envFactory({ sessionId: `leader-integ-${Date.now()}` });
369
+ const integSessionId = `leader-integ-${Date.now()}`;
370
+ const env = cfg.envFactory({ sessionId: integSessionId, placementRootSessionId: integSessionId });
370
371
  try {
371
372
  await sh(env)(body.seedCmd);
372
373
  await injectOracles(env);
@@ -412,7 +413,8 @@ export function createLeaderRunner(cfg) {
412
413
  const resolveRepair = async (solo, _sub) => {
413
414
  let env;
414
415
  if (cfg.envFactory) {
415
- env = cfg.envFactory({ sessionId: `leader-grader-${Date.now()}` });
416
+ const graderSessionId = `leader-grader-${Date.now()}`;
417
+ env = cfg.envFactory({ sessionId: graderSessionId, placementRootSessionId: graderSessionId });
416
418
  }
417
419
  else {
418
420
  if (!cfg.e2bApiKey)