@sema-agent/server 7.140.0 → 7.141.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 (200) hide show
  1. package/MIGRATION.md +290 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +75 -13
  5. package/dist/approval-batch-census.d.ts +27 -0
  6. package/dist/approval-batch-census.js +27 -0
  7. package/dist/approval-card.d.ts +31 -16
  8. package/dist/approval-card.js +6 -3
  9. package/dist/approval.d.ts +15 -0
  10. package/dist/approval.js +10 -0
  11. package/dist/audit.d.ts +4 -2
  12. package/dist/audit.js +2 -2
  13. package/dist/boot/device-lane.d.ts +11 -0
  14. package/dist/boot/device-lane.js +16 -0
  15. package/dist/boot/engine-lease.d.ts +24 -3
  16. package/dist/boot/engine-lease.js +9 -2
  17. package/dist/boot/execution-env.js +8 -4
  18. package/dist/boot/memory-boundary.d.ts +2 -2
  19. package/dist/boot/memory-consolidation.d.ts +1 -1
  20. package/dist/boot/memory-consolidation.js +1 -1
  21. package/dist/boot/reapers.d.ts +2 -2
  22. package/dist/boot/resolve-spec.js +7 -4
  23. package/dist/boot/session-faces.d.ts +1 -1
  24. package/dist/boot/session-faces.js +4 -3
  25. package/dist/boot/shutdown.js +5 -0
  26. package/dist/boot/stage-05-execution-env.js +3 -1
  27. package/dist/boot/stage-06-runners.d.ts +0 -2
  28. package/dist/boot/stage-06-runners.js +1 -1
  29. package/dist/boot/stage-07-capability-layer.d.ts +0 -1
  30. package/dist/boot/stage-07-capability-layer.js +8 -10
  31. package/dist/boot/stage-08-reapers.d.ts +0 -1
  32. package/dist/boot/stage-09-leader.d.ts +0 -1
  33. package/dist/boot/stage-10-http-server.d.ts +0 -1
  34. package/dist/boot/stage-10-http-server.js +1 -0
  35. package/dist/boot/stores.js +10 -4
  36. package/dist/boot/web-admin.js +1 -0
  37. package/dist/capabilities/scenarios.d.ts +1 -1
  38. package/dist/capability-facts.d.ts +9 -0
  39. package/dist/capability-facts.js +9 -0
  40. package/dist/card-void.d.ts +38 -0
  41. package/dist/card-void.js +23 -0
  42. package/dist/config-catalog.js +11 -8
  43. package/dist/config-provider.d.ts +13 -13
  44. package/dist/config-provider.js +19 -33
  45. package/dist/config-types.d.ts +26 -8
  46. package/dist/config.js +55 -46
  47. package/dist/delegated-session-owner.d.ts +21 -19
  48. package/dist/delegated-session-owner.js +1 -1
  49. package/dist/device-store.d.ts +7 -0
  50. package/dist/device-store.js +3 -0
  51. package/dist/execution-config-root.d.ts +22 -0
  52. package/dist/execution-config-root.js +35 -0
  53. package/dist/execution-lane-caps.d.ts +49 -2
  54. package/dist/execution-lane-caps.js +90 -13
  55. package/dist/execution-placement.d.ts +3 -0
  56. package/dist/execution-placement.js +7 -0
  57. package/dist/fleet/drain-entry.d.ts +3 -0
  58. package/dist/hosted-posture.d.ts +8 -0
  59. package/dist/hosted-posture.js +4 -0
  60. package/dist/http/admission.js +3 -3
  61. package/dist/http/caller-trust.d.ts +3 -2
  62. package/dist/http/caller-trust.js +2 -1
  63. package/dist/http/dispatch.js +4 -0
  64. package/dist/http/live-frame.d.ts +24 -0
  65. package/dist/http/live-frame.js +16 -0
  66. package/dist/http/resume-legs.js +37 -20
  67. package/dist/http/route-table.js +2 -0
  68. package/dist/http/routes/approvals-assistant.js +1 -0
  69. package/dist/http/routes/capabilities.js +5 -4
  70. package/dist/http/routes/diagnostics.d.ts +0 -19
  71. package/dist/http/routes/diagnostics.js +16 -2
  72. package/dist/http/routes/memory-bundle.d.ts +2 -2
  73. package/dist/http/routes/memory-bundle.js +1 -1
  74. package/dist/http/routes/memory-compliance.js +1 -1
  75. package/dist/http/routes/memory-policy.js +2 -2
  76. package/dist/http/routes/run-permission-mode.d.ts +75 -0
  77. package/dist/http/routes/run-permission-mode.js +170 -0
  78. package/dist/http/routes/session-sync.d.ts +3 -6
  79. package/dist/http/routes/session-sync.js +7 -2
  80. package/dist/http/routes/sessions.js +5 -4
  81. package/dist/http/routes/tasks.js +63 -66
  82. package/dist/http/run-read-face.js +3 -3
  83. package/dist/http/send.d.ts +1 -1
  84. package/dist/http/send.js +2 -2
  85. package/dist/http/server.d.ts +9 -2
  86. package/dist/http/sse-log.d.ts +3 -8
  87. package/dist/http/stored-body-replay.d.ts +3 -3
  88. package/dist/http/stored-body-replay.js +3 -1
  89. package/dist/instruction-source-notice.d.ts +7 -0
  90. package/dist/instruction-source-notice.js +6 -6
  91. package/dist/mcp-assertion-header.d.ts +4 -0
  92. package/dist/mcp-assertion-header.js +5 -0
  93. package/dist/memory-posture.d.ts +1 -1
  94. package/dist/memory-scope.d.ts +4 -4
  95. package/dist/memory-scope.js +1 -1
  96. package/dist/memory-sync-client.d.ts +14 -2
  97. package/dist/memory-sync-client.js +80 -18
  98. package/dist/observability/fail-open.d.ts +30 -2
  99. package/dist/observability/fail-open.js +30 -2
  100. package/dist/observability/metrics.js +0 -1
  101. package/dist/observability/run-terminal.d.ts +6 -2
  102. package/dist/observability/run-terminal.js +25 -26
  103. package/dist/observability/secret-env-scrub.d.ts +20 -1
  104. package/dist/observability/secret-env-scrub.js +14 -0
  105. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
  106. package/dist/orchestration/workflow-completion-inbox.js +13 -5
  107. package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
  108. package/dist/orchestration/workflow-notify-journal.js +2 -1
  109. package/dist/permission-mode-admission.d.ts +68 -0
  110. package/dist/permission-mode-admission.js +23 -0
  111. package/dist/permission-rule-list.d.ts +55 -6
  112. package/dist/permission-rule-list.js +52 -5
  113. package/dist/plugins/caching-session-store.d.ts +6 -0
  114. package/dist/plugins/caching-session-store.js +3 -0
  115. package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
  116. package/dist/plugins/checkpoint-store-sql.js +74 -3
  117. package/dist/plugins/device-store-sql.d.ts +5 -2
  118. package/dist/plugins/device-store-sql.js +4 -1
  119. package/dist/plugins/execution-placement-sql.d.ts +11 -1
  120. package/dist/plugins/execution-placement-sql.js +12 -1
  121. package/dist/plugins/file-run-store.d.ts +2 -2
  122. package/dist/plugins/file-run-store.js +1 -1
  123. package/dist/plugins/fork-routing-session-store.d.ts +10 -32
  124. package/dist/plugins/fork-routing-session-store.js +2 -61
  125. package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
  126. package/dist/plugins/k8s-exec-protocol.js +6 -1
  127. package/dist/plugins/local-checkpoint-store.d.ts +10 -1
  128. package/dist/plugins/local-checkpoint-store.js +22 -0
  129. package/dist/plugins/local-session-store.d.ts +227 -35
  130. package/dist/plugins/local-session-store.js +518 -37
  131. package/dist/plugins/local-session-window.d.ts +17 -3
  132. package/dist/plugins/local-session-window.js +7 -13
  133. package/dist/plugins/memory-run-store.d.ts +2 -2
  134. package/dist/plugins/memory-run-store.js +1 -1
  135. package/dist/plugins/one-time-migrations.d.ts +42 -2
  136. package/dist/plugins/one-time-migrations.js +65 -3
  137. package/dist/plugins/pg-pool.js +3 -2
  138. package/dist/plugins/pg-session-storage.d.ts +2 -0
  139. package/dist/plugins/pg-session-storage.js +3 -2
  140. package/dist/plugins/remote-env-e2b.d.ts +24 -3
  141. package/dist/plugins/remote-env-e2b.js +131 -18
  142. package/dist/plugins/remote-env-k8s.d.ts +20 -1
  143. package/dist/plugins/remote-env-k8s.js +148 -25
  144. package/dist/plugins/remote-env-local-docker.d.ts +25 -11
  145. package/dist/plugins/remote-env-local-docker.js +87 -21
  146. package/dist/plugins/run-store-sql.d.ts +3 -3
  147. package/dist/plugins/run-store-sql.js +4 -3
  148. package/dist/plugins/sandbox-read-floor.d.ts +273 -0
  149. package/dist/plugins/sandbox-read-floor.js +608 -0
  150. package/dist/plugins/session-ownership.d.ts +2 -1
  151. package/dist/plugins/session-placement.d.ts +17 -4
  152. package/dist/plugins/session-placement.js +2 -1
  153. package/dist/plugins/session-store.d.ts +18 -20
  154. package/dist/plugins/session-store.js +4 -12
  155. package/dist/plugins/store-backend.d.ts +31 -0
  156. package/dist/plugins/store-backend.js +21 -4
  157. package/dist/plugins/store-contracts.d.ts +9 -2
  158. package/dist/plugins/tidb-pool.js +2 -1
  159. package/dist/plugins/tidb-session-store.d.ts +4 -1
  160. package/dist/plugins/tidb-session-store.js +3 -2
  161. package/dist/run-cancel-context.d.ts +34 -0
  162. package/dist/run-cancel-context.js +7 -0
  163. package/dist/runs.d.ts +39 -12
  164. package/dist/runs.js +68 -53
  165. package/dist/security.d.ts +2 -2
  166. package/dist/server-secret-env.d.ts +2 -1
  167. package/dist/server-secret-env.js +2 -4
  168. package/dist/session-error-wire.d.ts +22 -4
  169. package/dist/session-error-wire.js +16 -0
  170. package/dist/session-incarnation.d.ts +9 -3
  171. package/dist/session-sync-kernel.d.ts +42 -1
  172. package/dist/session-sync-kernel.js +17 -0
  173. package/dist/session-sync.js +16 -4
  174. package/dist/sql-engine-coordinates.d.ts +49 -0
  175. package/dist/sql-engine-coordinates.js +27 -0
  176. package/dist/standing-answer-grant.d.ts +31 -2
  177. package/dist/standing-answer-grant.js +14 -2
  178. package/dist/store-posture.d.ts +1 -1
  179. package/dist/task-a2a.d.ts +1 -1
  180. package/dist/task-mcp.d.ts +1 -1
  181. package/dist/task-settings.d.ts +52 -25
  182. package/dist/task-settings.js +91 -28
  183. package/dist/terminal.d.ts +77 -20
  184. package/dist/terminal.js +66 -20
  185. package/dist/trace/core-keyset-guard.d.ts +79 -2
  186. package/dist/trace/exit-seat.d.ts +7 -0
  187. package/dist/trace/exit-seat.js +1 -0
  188. package/dist/trace/model-segment.d.ts +2 -2
  189. package/dist/trace/redact.js +323 -90
  190. package/dist/trace/self-minted-wire.d.ts +168 -2
  191. package/dist/trace/self-minted-wire.js +63 -0
  192. package/dist/verifier-pause-card.d.ts +8 -0
  193. package/dist/verifier-pause-card.js +19 -0
  194. package/dist/web-admin-client/apply-effective.d.ts +5 -7
  195. package/dist/web-admin-client/apply-effective.js +2 -2
  196. package/dist/web-admin-client/candidate-gate.d.ts +1 -1
  197. package/dist/web-admin-client/read-warnings.d.ts +2 -2
  198. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
  199. package/package.json +2 -2
  200. package/wire-changes/server@7.141.0.json +367 -0
package/dist/config.js CHANGED
@@ -36,7 +36,8 @@ import { laneLivenessOf } from "./exec-bounds.js";
36
36
  import { SANDBOX_IPV6_DEFAULT, SANDBOX_IPV6_WORDS } from "./sandbox-egress-policy.js";
37
37
  import { WORKSPACE_CACHE_DEFAULT, WORKSPACE_CACHE_MOUNT_DEFAULT, WORKSPACE_CACHE_MOUNT_RESERVED, WORKSPACE_CACHE_SIZE_DEFAULT, WORKSPACE_CACHE_WORDS, isK8sQuantity, mountsOverlap, reservedMountCollision } from "./workspace-cache.js";
38
38
  import { DEPLOYMENT_SHAPE_PREDICATES } from "./deployment-shape.js";
39
- import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, laneMissingEnv } from "./execution-lane-caps.js";
39
+ import { sandboxForwardedEnvOf } from "./execution-config-root.js";
40
+ import { SANDBOX_READ_FLOOR_DEFAULT, SANDBOX_READ_FLOOR_WORDS, executionLaneOf, executionLaneRequiredRefusal, laneMissingEnv, readDeclaredLaneTable } from "./execution-lane-caps.js";
40
41
  import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
41
42
  import { AUTONOMY_MODES } from "./runtime-governance.js";
42
43
  import { MAX_NAMED_INERT_ENTRIES, allowLayerEntryCountIssue, describeInertRuleEntries, inertRuleEntriesOf, inertRuleEntryGuidance, ruleEntryListShapeOk } from "./rule-entry-list.js";
@@ -45,7 +46,7 @@ import { createCenterCredentialSource, readCenterCredentialDeclaration } from ".
45
46
  import { parseCenterCredentialRenewLeadMs } from "./center-credential-renewer.js";
46
47
  import { modelProviderRefusal } from "./model-provider.js";
47
48
  import { modelRouteUrlRefusal } from "./model-route-url.js";
48
- import { httpUrlFlaws } from "@sema-agent/settings-schema";
49
+ import { DEVICE_RELAY_NONE, DEVICE_RELAY_RULE, deviceRelayFlaws, httpUrlFlaws } from "@sema-agent/settings-schema";
49
50
  import { buildHttpUrlRefusal, buildRefusalShownUrl } from "./url-refusal.js";
50
51
  import { customPkgSourceFromEnv } from "./sandbox-pkg-source.js";
51
52
  import { anthropicCredentialRefusal, csvOf, declaredCredential, envLaneTable, mintedCheapId, readModelRouteEnv } from "./model-route-gap.js";
@@ -57,6 +58,7 @@ import { allowlistedEnv } from "./server-secret-env.js";
57
58
  import { ENV_NAME_ALLOWLIST_KNOBS, ENV_NAME_ALLOWLIST_KNOB_NAMES, describeAllowlistPostures } from "./env-name-allowlist-knobs.js";
58
59
  import { APPROVAL_ASK_RETENTION_DEFAULT_MS, APPROVAL_ASK_RETENTION_FLOOR_MS } from "./approval-ask-retention.js";
59
60
  import { parseModsDeclaration } from "./mods-declaration.js";
61
+ import { buildDeclaredEngineWithoutCoordinatesRefusal, missingSqlCoordinateKeys } from "./sql-engine-coordinates.js";
60
62
  export { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
61
63
  function csv(name) {
62
64
  return csvOf(process.env[name]);
@@ -378,17 +380,16 @@ function credentialLawRewriteOf(href) {
378
380
  const scanned = redactSecrets(href);
379
381
  return scanned === href ? undefined : scanned;
380
382
  }
381
- function parseDeviceRelayUrlEnv() {
382
- const raw = process.env.DEVICE_RELAY_URL;
383
- if (raw === undefined || raw === "")
384
- return undefined;
385
- const scheme = /^wss?:\/\//i.exec(raw)?.[0];
386
- const asHttp = scheme === undefined ? undefined : `${scheme.length === "wss://".length ? "https" : "http"}://${raw.slice(scheme.length)}`;
387
- const flaws = asHttp === undefined ? ["not-ws-url"] : httpUrlFlaws(asHttp, ["userinfo", "query", "fragment"]).map((f) => (f === "not-http-url" ? "not-ws-url" : f));
383
+ function parseDeviceRelayUrlEnv(raw) {
384
+ if (raw === undefined) {
385
+ throw new Error("internal: the device lane was assembled without DEVICE_RELAY_URL — the lane's required-env gate must refuse this first (the settings-schema lane table lists DEVICE_RELAY_URL as required for device)");
386
+ }
387
+ if (raw === DEVICE_RELAY_NONE)
388
+ return { kind: "none" };
389
+ const flaws = deviceRelayFlaws(raw);
388
390
  if (flaws.length > 0) {
389
- throw new Error(`env DEVICE_RELAY_URL must be an absolute ws:// or wss:// URL with no userinfo, query or fragment (ws(s)://host[:port][/path]) — found: ${flaws.join(", ")}; got ${JSON.stringify(buildRefusalShownUrl(raw))}. ` +
390
- `It is published verbatim on GET /v1/capabilities (deviceExecutor.relayUrl) as the address devices dial, so it has no room for credentials (devices authenticate by signature). ` +
391
- `Leave it unset when devices reach this engine at its own address — they then dial it at deviceExecutor.wsPath.`);
391
+ throw new Error(`env DEVICE_RELAY_URL ${DEVICE_RELAY_RULE} — found: ${flaws.join(", ")}; got ${JSON.stringify(buildRefusalShownUrl(raw))}. ` +
392
+ `A URL has no room for credentials (devices authenticate by signature). Set it to "${DEVICE_RELAY_NONE}" when devices reach this engine at its own address — they then dial it at deviceExecutor.wsPath.`);
392
393
  }
393
394
  const href = new URL(raw).href;
394
395
  const credentialScan = credentialLawRewriteOf(href);
@@ -396,7 +397,26 @@ function parseDeviceRelayUrlEnv() {
396
397
  throw new Error(`env DEVICE_RELAY_URL carries something the credential-redaction law recognizes (${credentialScan}) — it is published verbatim on GET /v1/capabilities (deviceExecutor.relayUrl), ` +
397
398
  `so it must not carry a token-shaped path segment; keep it to ws(s)://host[:port][/path] (devices authenticate by signature, not by the URL).`);
398
399
  }
399
- return raw;
400
+ return { kind: "url", url: raw };
401
+ }
402
+ function declaredExecutionLane(raw) {
403
+ if (raw === undefined || raw === "")
404
+ return undefined;
405
+ const table = readDeclaredLaneTable(raw);
406
+ if ("refusal" in table)
407
+ throw new Error(table.refusal);
408
+ const [lane, ...rest] = table.lanes;
409
+ if (rest.length > 0) {
410
+ throw new Error(`REMOTE_EXEC="${raw}" declares ${table.lanes.length} execution lanes (${table.lanes.join(", ")}; the first is the default) — this engine wires exactly one execution lane per process, ` +
411
+ `so ${rest.join(", ")} would never run anything while the deployment declares ${rest.length === 1 ? "it" : "them"}. ` +
412
+ `Refusing to start rather than serving "${lane}" alone: declare one lane per engine (REMOTE_EXEC=${lane}) and run a separate engine for each further lane.`);
413
+ }
414
+ const missing = laneMissingEnv(lane, process.env);
415
+ if (missing.length > 0) {
416
+ throw new Error(`REMOTE_EXEC=${lane} but required env ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} unset, empty or whitespace-only — it counts as absent: ` +
417
+ `refusing to start rather than silently downgrading to the host/in-process lane (#157; set ${missing.length === 1 ? "it" : "them"}, or unset REMOTE_EXEC if you meant no sandbox lane).`);
418
+ }
419
+ return lane;
400
420
  }
401
421
  function parseDevicePreAuthPerRemoteEnv() {
402
422
  const raw = process.env.DEVICE_MAX_PREAUTH_PER_REMOTE;
@@ -515,17 +535,16 @@ const OWN_CREDENTIAL_HANDOFF = {
515
535
  "sandbox-grant": { where: "a sandbox list puts the named variable, as written, into a sandbox the model can read", whom: "the sandbox" },
516
536
  };
517
537
  function ownCredentialNameProblem(name, channel) {
518
- const engine = inheritEnvPolicyProblem([name]);
519
- if (engine === undefined)
538
+ if (inheritEnvPolicyProblem([name]) === undefined)
520
539
  return undefined;
521
540
  const { where, whom } = OWN_CREDENTIAL_HANDOFF[channel];
522
- return `this deployment's own credential — ${where}; give ${whom} a credential of its own (another name and another value) and list that name instead (the engine refuses it on any allow list: ${engine})`;
541
+ return `this deployment's own credential — ${where}; give ${whom} a credential of its own (another name and another value) and list that name instead (the engine refuses it on any allow list)`;
523
542
  }
524
543
  const NAMES_IN_FORCE = {
525
544
  LSP_ENV_ALLOW: (c) => c.lspEnvAllow,
526
- E2B_SANDBOX_ENV: (c) => (c.remoteExec?.provider === "e2b" ? Object.keys(c.remoteExec.sandboxEnv ?? {}) : []),
527
- K8S_SANDBOX_ENV: (c) => (c.remoteExec?.provider === "k8s" ? Object.keys(c.remoteExec.sandboxEnv ?? {}) : []),
528
- DOCKER_SANDBOX_ENV: (c) => (c.remoteExec?.provider === "local-docker" ? Object.keys(c.remoteExec.env ?? {}) : []),
545
+ E2B_SANDBOX_ENV: (c) => (c.remoteExec?.provider === "e2b" ? Object.keys(sandboxForwardedEnvOf(c)) : []),
546
+ K8S_SANDBOX_ENV: (c) => (c.remoteExec?.provider === "k8s" ? Object.keys(sandboxForwardedEnvOf(c)) : []),
547
+ DOCKER_SANDBOX_ENV: (c) => (c.remoteExec?.provider === "local-docker" ? Object.keys(sandboxForwardedEnvOf(c)) : []),
529
548
  };
530
549
  const SERVER_ENV_NAME_ALLOWLIST_KNOBS = Object.keys(NAMES_IN_FORCE);
531
550
  export function sandboxEnvNamesInForce(seats) {
@@ -889,11 +908,14 @@ function parseStoreDomain(ctx) {
889
908
  },
890
909
  });
891
910
  }
892
- const sqlEngineExplicit = !!process.env.DB_BACKEND && dbBackend !== "local" && dbBackend !== "memory";
893
- const sessionBackendRaw = enumEnv("SESSION_BACKEND", sqlEngineExplicit ? "mysql" : "memory", SESSION_BACKENDS);
911
+ const declaredSqlEngine = dbBackendSet && (dbBackend === "mysql" || dbBackend === "pg") ? dbBackend : undefined;
912
+ const sessionBackendRaw = enumEnv("SESSION_BACKEND", declaredSqlEngine !== undefined ? "mysql" : "memory", SESSION_BACKENDS);
894
913
  const sessionBackend = sessionBackendRaw === "mysql" ? "tidb" : sessionBackendRaw;
895
914
  const localDataRoot = resolveLocalDataRoot(process.env, homedir());
896
915
  const dbHostSet = dbBackend === "pg" ? !!process.env.PG_HOST : !!process.env.MYSQL_HOST;
916
+ if (declaredSqlEngine !== undefined && !dbHostSet) {
917
+ throw new Error(buildDeclaredEngineWithoutCoordinatesRefusal(declaredSqlEngine, missingSqlCoordinateKeys(declaredSqlEngine, process.env)));
918
+ }
897
919
  const needsTidb = sessionBackend === "tidb" || (sessionBackend === "auto" && dbHostSet);
898
920
  const needsDb = needsTidb;
899
921
  const sqlCoordinateKeys = [...(process.env.MYSQL_HOST ? ["MYSQL_HOST"] : []), ...(process.env.PG_HOST ? ["PG_HOST"] : [])];
@@ -1631,7 +1653,7 @@ function parseMemoryEmbedder(ctx) {
1631
1653
  throw new Error(`MEMORY_EMBEDDER_DIM="${dimRaw}" must be a positive integer (the embedding dimension, e.g. 1024). A wrong dimension writes wrong-length vectors into the memory embedding column, which neither the DB nor retrieval reports — the vector plane just silently degrades to the lexical floor (#228).`);
1632
1654
  }
1633
1655
  if (ctx.memoryEngineBackend !== "pg") {
1634
- throw new Error(`MEMORY_EMBEDDER_* is set but MEMORY_ENGINE_BACKEND=${ctx.memoryEngineBackend} — the vector plane exists only on the pg memory backend (the tidb backend is lexical-only in v1 and refuses an embedder; the file engine has no embedder seam). Set MEMORY_ENGINE_BACKEND=pg, or unset MEMORY_EMBEDDER_*.`);
1656
+ throw new Error(`MEMORY_EMBEDDER_* is set but MEMORY_ENGINE_BACKEND=${ctx.memoryEngineBackend} — the vector plane exists only on the pg memory backend (the mysql backend is lexical-only in v1 and refuses an embedder; the file engine has no embedder seam). Set MEMORY_ENGINE_BACKEND=pg, or unset MEMORY_EMBEDDER_*.`);
1635
1657
  }
1636
1658
  if (!ctx.memoryEngineEnabled) {
1637
1659
  throw new Error("MEMORY_EMBEDDER_* is set but MEMORY_ENGINE=off — the memory engine is not wired at all, so the embedder would never be called; unset one of the two (#228)");
@@ -1915,21 +1937,7 @@ function parseOrchestrationDomain(ctx) {
1915
1937
  return boolEnv("LSP_HOST_ENABLED", true);
1916
1938
  })();
1917
1939
  const lspEnvAllow = parseEnvNameAllowlist("LSP_ENV_ALLOW", singleUserTurnkey) ?? [];
1918
- {
1919
- const lane = process.env.REMOTE_EXEC;
1920
- if (lane !== undefined && lane !== "") {
1921
- const word = REMOTE_EXEC_PROVIDER_WORDS.find((w) => w === lane);
1922
- if (word === undefined) {
1923
- throw new Error(`REMOTE_EXEC="${lane}" is not an execution lane — valid: ${REMOTE_EXEC_PROVIDER_WORDS.join(" | ")}. ` +
1924
- `An unrecognized value used to fall through to the host/in-process lane silently; refusing to start instead (#157).`);
1925
- }
1926
- const missing = laneMissingEnv(word, process.env);
1927
- if (missing.length > 0) {
1928
- throw new Error(`REMOTE_EXEC=${lane} but required env ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} unset, empty or whitespace-only — it counts as absent: ` +
1929
- `refusing to start rather than silently downgrading to the host/in-process lane (#157; set ${missing.length === 1 ? "it" : "them"}, or unset REMOTE_EXEC if you meant no sandbox lane).`);
1930
- }
1931
- }
1932
- }
1940
+ const declaredLane = declaredExecutionLane(process.env.REMOTE_EXEC);
1933
1941
  const readFaceFacts = process.env.READ_FACE !== undefined
1934
1942
  ? { readFace: enumEnv("READ_FACE", "roots", ["open", "roots"]), readFaceSource: "env" }
1935
1943
  : { readFaceSource: "engine-default" };
@@ -1945,6 +1953,7 @@ function parseOrchestrationDomain(ctx) {
1945
1953
  return { ...(tiers !== undefined ? { readDenyBuiltinTiers: tiers } : {}), ...(exclude !== undefined ? { readDenyBuiltinExclude: exclude } : {}) };
1946
1954
  })(),
1947
1955
  readDenyFloorRequired: boolEnv("READ_DENY_FLOOR_REQUIRED", false),
1956
+ sandboxReadFloor: enumEnv("SANDBOX_READ_FLOOR", SANDBOX_READ_FLOOR_DEFAULT, SANDBOX_READ_FLOOR_WORDS),
1948
1957
  ...((dir) => (dir !== undefined ? { agentConfigDir: dir } : {}))(declaredSemaConfigDir()),
1949
1958
  ...((p) => (p !== undefined ? { configLkgPath: p } : {}))(lkgPathOverrideOf(process.env.CONFIG_LKG_PATH)),
1950
1959
  configLkgEnabled: boolEnv("CONFIG_LKG_ENABLED", true),
@@ -1960,7 +1969,7 @@ function parseOrchestrationDomain(ctx) {
1960
1969
  }
1961
1970
  return raw;
1962
1971
  })()),
1963
- remoteExec: process.env.REMOTE_EXEC === "e2b" && process.env.E2B_API_KEY
1972
+ remoteExec: declaredLane === "e2b" && process.env.E2B_API_KEY
1964
1973
  ? {
1965
1974
  provider: "e2b",
1966
1975
  apiKey: process.env.E2B_API_KEY,
@@ -1973,7 +1982,7 @@ function parseOrchestrationDomain(ctx) {
1973
1982
  orphanReclaim: boolEnv("E2B_ORPHAN_RECLAIM", true),
1974
1983
  }
1975
1984
  :
1976
- process.env.REMOTE_EXEC === "k8s" && process.env.K8S_SANDBOX_IMAGE && process.env.K8S_NAMESPACE
1985
+ declaredLane === "k8s" && process.env.K8S_SANDBOX_IMAGE && process.env.K8S_NAMESPACE
1977
1986
  ? {
1978
1987
  provider: "k8s",
1979
1988
  image: process.env.K8S_SANDBOX_IMAGE,
@@ -1995,7 +2004,7 @@ function parseOrchestrationDomain(ctx) {
1995
2004
  ...((c) => (c !== undefined ? { workspaceCache: c } : {}))(k8sWorkspaceCache()),
1996
2005
  }
1997
2006
  :
1998
- process.env.REMOTE_EXEC === "ssh" && process.env.SSH_HOST && process.env.SSH_USER && process.env.SSH_PRIVATE_KEY
2007
+ declaredLane === "ssh" && process.env.SSH_HOST && process.env.SSH_USER && process.env.SSH_PRIVATE_KEY
1999
2008
  ? {
2000
2009
  provider: "ssh",
2001
2010
  host: process.env.SSH_HOST,
@@ -2006,7 +2015,7 @@ function parseOrchestrationDomain(ctx) {
2006
2015
  ...((hk) => (hk !== undefined ? { hostKey: hk } : {}))(parseSshHostKeyEnv()),
2007
2016
  }
2008
2017
  :
2009
- process.env.REMOTE_EXEC === "adb" && process.env.ADB_SERIAL
2018
+ declaredLane === "adb" && process.env.ADB_SERIAL
2010
2019
  ? {
2011
2020
  provider: "adb",
2012
2021
  serial: process.env.ADB_SERIAL,
@@ -2014,14 +2023,14 @@ function parseOrchestrationDomain(ctx) {
2014
2023
  ...(process.env.ADB_MOUNT_PATH ? { mountPath: process.env.ADB_MOUNT_PATH } : {}),
2015
2024
  }
2016
2025
  :
2017
- process.env.REMOTE_EXEC === "host"
2026
+ declaredLane === "host"
2018
2027
  ? {
2019
2028
  provider: "host",
2020
2029
  ...(process.env.HOST_WORKSPACE_BASE ? { workspaceBase: process.env.HOST_WORKSPACE_BASE } : {}),
2021
2030
  ...((n) => (n !== undefined ? { commandTimeoutMs: n } : {}))(optNumEnvStrict("HOST_COMMAND_TIMEOUT_MS")),
2022
2031
  }
2023
2032
  :
2024
- process.env.REMOTE_EXEC === "local-docker" && process.env.DOCKER_IMAGE
2033
+ declaredLane === "local-docker" && process.env.DOCKER_IMAGE
2025
2034
  ? {
2026
2035
  provider: "local-docker",
2027
2036
  image: process.env.DOCKER_IMAGE,
@@ -2039,7 +2048,7 @@ function parseOrchestrationDomain(ctx) {
2039
2048
  ...((names) => (names !== undefined ? { env: allowlistedEnv(names, process.env) } : {}))(parseEnvNameAllowlist("DOCKER_SANDBOX_ENV", singleUserTurnkey)),
2040
2049
  }
2041
2050
  :
2042
- process.env.REMOTE_EXEC === "device"
2051
+ declaredLane === "device"
2043
2052
  ? {
2044
2053
  provider: "device",
2045
2054
  ...((n) => (n !== undefined ? { deliveryTimeoutMs: n } : {}))(parseDeviceBoundedIntEnv("DEVICE_DELIVERY_TIMEOUT_MS", process.env.DEVICE_DELIVERY_TIMEOUT_MS, DEVICE_DELIVERY_TIMEOUT_BOUNDS)),
@@ -2048,7 +2057,7 @@ function parseOrchestrationDomain(ctx) {
2048
2057
  ...((n) => (n !== undefined ? { maxInflightPerDevice: n } : {}))(parseDeviceBoundedIntEnv("DEVICE_MAX_INFLIGHT_PER_DEVICE", process.env.DEVICE_MAX_INFLIGHT_PER_DEVICE, DEVICE_HELLO_ACK_BOUNDS.maxInflight)),
2049
2058
  ...((n) => (n !== undefined ? { maxPreAuthPerRemote: n } : {}))(parseDevicePreAuthPerRemoteEnv()),
2050
2059
  ...((n) => (n !== undefined ? { enrollTokenTtlSec: n } : {}))(parseDeviceEnrollTokenTtlEnv()),
2051
- ...((n) => (n !== undefined ? { relayUrl: n } : {}))(parseDeviceRelayUrlEnv()),
2060
+ relay: parseDeviceRelayUrlEnv(process.env.DEVICE_RELAY_URL),
2052
2061
  ...(process.env.DEVICE_SUPERSEDE ? { supersede: enumEnv("DEVICE_SUPERSEDE", "stale_only", ["stale_only", "deny"]) } : {}),
2053
2062
  }
2054
2063
  :
@@ -2472,7 +2481,7 @@ const ORCHESTRATION_GROUP_KEYS = [
2472
2481
  "workflowSizeGuideline", "backgroundAgentRetentionMs", "backgroundAgentStaleRunningMs",
2473
2482
  "backgroundAgentParkClaimStaleMs", "delegationEntryCaps", "rosterRetentionMs", "leaderRunStaleMs", "scratchpadSweepTtlMs", "sandboxPkgSource", "sandboxPkgSourceCustom",
2474
2483
  "sessionAutoTitle", "sessionAutoTitleSource", "selectEnvironmentTool", "envFactsEnabled", "toolDeferLongtail", "lspEnabled", "lspHostEnabled", "lspEnvAllow",
2475
- "imageBakes", "readFace", "readFaceSource", "taskWriteFaceEscalation", "readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude", "readDenyFloorRequired",
2484
+ "imageBakes", "readFace", "readFaceSource", "taskWriteFaceEscalation", "readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude", "readDenyFloorRequired", "sandboxReadFloor",
2476
2485
  "agentConfigDir", "configLkgPath", "configLkgEnabled",
2477
2486
  ];
2478
2487
  const LIMITS_HTTP_GROUP_KEYS = [
@@ -2,7 +2,7 @@
2
2
  * 🔴 **委派子代会话的属主 —— 一条律;三处写点,各自的来源集;属主 = 判断时见证到的,写只 CAS 子行自己**([ref])。
3
3
  *
4
4
  * ## 律
5
- * 一只委派子代会话(引擎为委派铸的 placed 会话,或停驻时从私店拷进宿主店的子代会话)的属主**不是它自己的事实**:它是发起它的那棵委派树的
5
+ * 一只委派子代会话(引擎为委派铸的 placed 会话;升级前停驻时晋升进宿主店的普通行在停驻挂点也按这条律比对)的属主**不是它自己的事实**:它是发起它的那棵委派树的
6
6
  * **根会话的属主**。树根是**铸行那一刻存在的那只根行**(id + 住客化身,`session-incarnation.ts`);此后同 id 另起的会话不是这棵树的根。所以写下的属主 =
7
7
  * **判断时见证到的**(读法顺序按写点交来的来源集):
8
8
  * 1. 根会话在本店有行 ⇒ 它的属主(根无主 ⇒ 子代也无主 —— 与根同读数,不是另一种读数);
@@ -29,26 +29,28 @@
29
29
  * 会话面 / fork / 删除 / 审批豁免 / notify 同读;dev-open 照旧 fleet-wide),具名主体与没有 service 凭据的无身份调用方一律与别人的行同体 404 ——
30
30
  * 所以还没定下的行只是暂时只有 fleet 面读得到,不是谁都能读。它们由一次性迁移 M23(窗内每次起服)或这一行的下一次停驻(停驻挂点的认领)
31
31
  * 定下;两处都不承诺「一次收敛」。
32
- * **只有记录委派树的店才认领**:SQL 两店的 placed 行记着委派树(placement 元组);local 店不实现 placement、不记委派树 ⇒ 没有可按的树 ⇒ 不认领
33
- * (它的无主子代行在停驻挂点照「属主不是律给的那一只 ⇒ 拒」处置;收口是 local 店的 placement 协议)。不按更松的判据认领。
32
+ * **只有记录委派树的行才认领**:三只持久会话店的 placed 行都记着委派树(placement 元组:SQL 两店在 `session_meta`,local 店在会话存档行头);
33
+ * 不记委派树的行(普通行,含升级前停驻时晋升进来的行)⇒ 没有可按的树 ⇒ 不认领(在停驻挂点照「属主不是律给的那一只 ⇒ 拒」处置)。不按更松的判据认领。
34
34
  *
35
35
  * ## 三处写点 —— 律一只,**来源集按写点不同**(都调这里,不各写一份判据)
36
- * · **SQL 两店按 placement 建行**(`plugins/tidb-session-store.ts` / `plugins/pg-session-storage.ts` 的 acquire,{@link createPlacedSessionRow}):来源 = placement 上的
37
- * 根、父两只 id({@link ownerSourcesOf});读来源 → 判 → 一条 INSERT(属主与 placement 同一笔落库,首写律)。读来源之后来源才变有主(滚动升级窗里别的副本的
36
+ * · **三只持久会话店按 placement 建行**({@link createPlacedSessionRow}:`plugins/tidb-session-store.ts` / `plugins/pg-session-storage.ts` 的 acquire 一条 INSERT,属主与 placement
37
+ * 同一笔落库;`plugins/local-session-store.ts` 的 acquire 先建存档(行头带元组)、再写会话元数据那一格(属主 + 化身))。来源 = placement 上的根、父两只 id({@link ownerSourcesOf});
38
+ * 读来源 → 判 → 写(首写律)。读来源之后来源才变有主(滚动升级窗里别的副本的
38
39
  * M23 回填了无主的父)⇒ 写下的仍是判断时的读数 —— 按律如此;那一行若因此无主,照上一节处置。
39
- * · **停驻挂点**(`plugins/session-store.ts` 的 `ensureChildSessionDurableWithPromotion`;快路 + 晋升):来源 = **调用方交出的委派树根,只有这一只**,连同
40
+ * · **停驻挂点**(`plugins/session-store.ts` 的 `attestChildSessionDurable`;只验、不搬):来源 = **调用方交出的委派树根,只有这一只**,连同
40
41
  * 调用方判断那一刻读到的根行住客({@link DelegationRootWitness};见证值,判断用,不进 CAS):请求腿 = 本请求经鉴权解析的会话,住客 = 受理时那一读
41
42
  * (`boot/resolve-spec.ts`);`/decide` 的赎回腿 = 行上的 `rootSessionId`,住客 = 赎回腿写口放行的那一位(认领之前解析器那一读;`parked-revive-surface.ts`,每一行都交)。
42
- * 挂点先读一次根行:它在而住客不是见证的那一位 ⇒ 根被删、同 id 换了住客 ⇒ **拒**(不按新住客晋升 / 认领,{@link assertRootStillWitnessed});它不在 ⇒ 律给哨兵
43
+ * 挂点先读一次根行:它在而住客不是见证的那一位 ⇒ 根被删、同 id 换了住客 ⇒ **拒**(不按新住客认领,{@link assertRootStillWitnessed});它不在 ⇒ 律给哨兵
43
44
  * (根真没了)。不回落父:core 的挂点 `ensureChildSessionDurable(sessionId)` 只交子代 id,父 id 在这一写点拿不到;两条候选读法都读不到东西 ——
44
- * ① 读 `session_meta(子代).placement_parent_session_id`:晋升的拷贝分支恰恰只在子代**没有** placed 行时才走(有 placed 行 ⇒ 快路),这一列按构造为空;
45
+ * ① 读子代行记下的父:记着委派树的行(placed 行)在下面的定夺里本就按**它记录的**根、父再判一次;不记委派树的行(升级前晋升进来的普通行)没有这一格;
45
46
  * ② 登记簿(后台子代行 / core 注册簿):core 接口没有「按子代 sessionId 查行」的读口,SQL 表 `background_agent.session_id` 无索引,加一条只在一处用的私有读腿
46
47
  * = 多一只读法;`scope` 列还把匿名折成 `"default"`(与真名 `default` 的租户不可分)。行上没有根(没有会话的取用形)⇒ 来源集空 ⇒ 哨兵。
47
48
  * 宿主行的属主经 {@link settleDelegatedOwner} 定下:宿主行记着委派树而不是这一棵 ⇒ 拒(两边属主都是 null 也拒);记着这一棵 ⇒ 按**它记录的** placement(根、父)
48
49
  * 再判一次(与 M23 对同一行同读数 —— 见证只答「是不是这一棵、根住客换没换」,来源集取行上记的),无主 ⇒ 认领(CAS 子行),认领试了没落下 ⇒ 拒;然后比对 ——
49
- * 不是律给的那一只 ⇒ 停驻否决(晋升建行前那一道与停驻快路同一只函数、同一句拒因)。
50
+ * 不是律给的那一只 ⇒ 停驻否决。
50
51
  * · **一次性迁移 M23**(`plugins/one-time-migrations.ts`):存量 placed 行,来源 = 根、父(扫描时同一条语句连读两行);回填 = 认领(同一句,绑扫描读到的子行化身)。
51
- * local 店不实现 placement(子代住私店、只经停驻挂点进宿主店),没有第一处与第三处,也不认领(上一节)。
52
+ * local 店没有第三处:它的 placed 行自本律起由第一处写下(属主在建行那一笔);存档在而那一格缺(建行两笔之间崩了,或同一数据根上 run-local 腿写下的 placed 存档)
53
+ * 读作从没被记下的属主 = 无主,照「无主的子代行」一节处置(停驻挂点的认领)。
52
54
  */
53
55
  import type { SessionPlacement } from "@sema-agent/core";
54
56
  import { type HeadRead, type SessionFace } from "./session-incarnation.js";
@@ -103,8 +105,8 @@ export declare function readDelegatedOwner(childId: string, placement: Placement
103
105
  export interface OrphanOwnerLog {
104
106
  warn(msg: string, fields?: Record<string, unknown>): void;
105
107
  }
106
- /** 写点的名字(warn 行的 `site` 字段:运维据它知道是哪一笔写下的哨兵)。`promotion` = 停驻挂点(晋升建行,或对宿主无主行的认领)。 */
107
- export type DelegatedOwnerSite = "placed_create" | "promotion";
108
+ /** 写点的名字(warn 行的 `site` 字段:运维据它知道是哪一笔写下的哨兵)。`placed_create` = 按 placement 建行;`park` = 停驻挂点对宿主无主 placed 行的认领。 */
109
+ export type DelegatedOwnerSite = "placed_create" | "park";
108
110
  /** 一笔写**已落下**之后调:写下的是孤行哨兵 ⇒ 一行 warn(写失败的那一笔走不到这里,不留下「已写」的话)。继承来的属主(含继承自孤行祖先的哨兵)不告。 */
109
111
  export declare function reportWrittenOwner(log: OrphanOwnerLog, site: DelegatedOwnerSite, childId: string, judged: JudgedOwner): void;
110
112
  /**
@@ -116,7 +118,7 @@ export declare function reportWrittenOwner(log: OrphanOwnerLog, site: DelegatedO
116
118
  export declare const CLAIM_OWNERLESS_PLACED_SQL: Readonly<Record<SqlDialect, string>>;
117
119
  /** 认领句的绑参(两方言同序;店与 M23 都经它,不各排一份):`incarnation` = 判断时读到的子行化身。 */
118
120
  export declare function claimOwnerlessParams(sessionId: string, incarnation: string, owner: string): readonly [string, string, string];
119
- /** 店的认领口(SQL 两店 + 缓存包装层同一个签名):这只会话的那一位住客(`incarnation`)仍是无主的 placed 行 ⇒ 写 `owner`,返回是否写成。 */
121
+ /** 店的认领口(三只持久会话店 + 缓存包装层同一个签名):这只会话的那一位住客(`incarnation`)仍是无主的 placed 行 ⇒ 写 `owner`,返回是否写成。 */
120
122
  export type OwnerlessClaim = (sessionId: string, incarnation: string, owner: string) => Promise<boolean>;
121
123
  /** 一只 SQL 店建 placed 行要交的 I/O(两店按各自方言交,都是店的既有读写口;读法与顺序只住在 {@link createPlacedSessionRow})。 */
122
124
  export interface PlacedRowWriter {
@@ -146,7 +148,7 @@ export interface DelegationRootWitness {
146
148
  * 根行此刻不在 ⇒ 不抛(根真没了 ⇒ 律给哨兵,不是「换了住客」)。这是**判断**的一部分;判断之后、写之前根的变化不改变写进去的值(律:属主 = 判断时见证到的)。
147
149
  */
148
150
  export declare function assertRootStillWitnessed(sessionId: string, witness: DelegationRootWitness | undefined, judged: JudgedOwner): void;
149
- /** 记录委派树的宿主店多交的两只口(SQL 两店与缓存包装层;local 店两只都没有 ⇒ 没有可按的树 ⇒ 不认领)。 */
151
+ /** 记录委派树的宿主店多交的两只口(三只持久会话店与缓存包装层;无 repo 的内存店两只都没有 ⇒ 没有可按的树 ⇒ 不认领)。 */
150
152
  export interface DelegationTreeStore {
151
153
  /** 本行落库的 placement 元组(`undefined` = 本行不是 placed 行 ⇒ 不记委派树)。 */
152
154
  readonly placementOf: (sessionId: string) => Promise<PlacementSources | undefined>;
@@ -159,7 +161,7 @@ export interface DelegatedOwnerHost {
159
161
  readonly trees: DelegationTreeStore | undefined;
160
162
  }
161
163
  /**
162
- * 「这只宿主店能不能承载委派子代的属主」—— 只判这一处(停驻快路与晋升建行读同一只):店有按 id 的头读(`headOf`,「有没有」沿用 `headReadOf` 的同一处判法)⇒
164
+ * 「这只宿主店能不能承载委派子代的属主」—— 只判这一处(停驻挂点读它):店有按 id 的头读(`headOf`,「有没有」沿用 `headReadOf` 的同一处判法)⇒
163
165
  * 绑好的 {@link DelegatedOwnerHost};没有 ⇒ `undefined`(调用方响亮拒:读不回属主的宿主店上,停驻出来的就是一只谁都能读的转录)。记录委派树的两只口
164
166
  * (`placementOf` + `claimOwnerless`)**同在或同缺**:同在 ⇒ `trees`;同缺 ⇒ 不记委派树;只有一只 ⇒ `undefined`(记着树却认领不了,或认领却读不回树 —— 半只店不猜)。
165
167
  */
@@ -174,13 +176,13 @@ export interface SettledOwner {
174
176
  readonly judged: JudgedOwner | undefined;
175
177
  }
176
178
  /**
177
- * 宿主店里这只会话的行属主**按律定下来**(停驻快路与晋升建行前**同一只函数、同一句拒因**;拒句不带任何一方的属主值 —— 它会进停驻否决的错误面)。
178
- * `owner` / `tree` = 调用方按见证的根判出的属主与委派树(晋升建行写下的就是它)。
179
- * 1. 宿主行**记着委派树**(SQL 店的 placed 行):记下的根 ≠ `tree.root` ⇒ 抛(不以属主是否 null 为前提:那是另一棵树的行);是这一棵 ⇒ **按它记录的 placement(根、父)
179
+ * 宿主店里这只会话的行属主**按律定下来**(停驻挂点的那一道;拒句不带任何一方的属主值 —— 它会进停驻否决的错误面)。
180
+ * `owner` / `tree` = 调用方按见证的根判出的属主与委派树。
181
+ * 1. 宿主行**记着委派树**(placed 行):记下的根 ≠ `tree.root` ⇒ 抛(不以属主是否 null 为前提:那是另一棵树的行);是这一棵 ⇒ **按它记录的 placement(根、父)
180
182
  * 再判一次**(唯一判据 {@link judgeDelegatedOwner} —— 与 M23 / 建行复核对这一行同读数:根不在取父),调用方那一读之后根住客变了 ⇒ 抛({@link assertRootStillWitnessed},
181
183
  * 调用方的读数当见证);行无主而律给出属主 ⇒ 认领(CAS 刚读到的那一位住客;写进去的是这一次判的读数)—— **试了没落下 ⇒ 抛**(这一行在判断之后被写过 /
182
184
  * 删过 / 换了住客,不由事后的属主比对放行);
183
- * 2. 不记委派树的行(local 店的行、SQL 店的普通行)不认领、不再判;
185
+ * 2. 不记委派树的行(普通行,含升级前晋升进来的行)不认领、不再判;
184
186
  * 3. 比对 —— 此刻的属主不是律给的那一只(记着委派树 ⇒ 按记录的树判的那一只;否则 = `owner`)⇒ 抛。
185
187
  */
186
188
  export declare function settleDelegatedOwner(host: DelegatedOwnerHost, sessionId: string, owner: string | null, tree: DelegationTree): Promise<SettledOwner>;
@@ -80,7 +80,7 @@ export function delegatedOwnerHostOf(store) {
80
80
  }
81
81
  export async function settleDelegatedOwner(host, sessionId, owner, tree) {
82
82
  const refuse = () => new Error(`session "${sessionId}" cannot be parked as this delegated session: the host session store holds a session with this id that is not this delegation tree's ` +
83
- "(a different owner, or a different recorded delegation root), and the transcript is neither copied into it nor attested by it");
83
+ "(a different owner, or a different recorded delegation root), so it does not attest this delegated transcript");
84
84
  const face = sessionFaceOfHead(sessionId, await host.headOf(sessionId));
85
85
  const trees = host.trees;
86
86
  const recorded = face === undefined || trees === undefined ? undefined : await trees.placementOf(sessionId);
@@ -395,6 +395,12 @@ export interface DeviceStore {
395
395
  placeDeviceExecution(input: PlaceDeviceExecutionInput): Promise<PlaceDeviceExecutionResult>;
396
396
  /** O4 显式换绑(v2 稿 §2 步 2/3/5):目标设备门 + 写事务内锁根行读认领 + 前态 CAS + `session_rebound` 审计行同一事务。 */
397
397
  rebindDeviceExecution(input: RebindDeviceExecutionInput): Promise<RebindDeviceExecutionResult>;
398
+ /**
399
+ * 绑定数:根会话行上 writer(device, D) 的格数 —— 唯一读法 `boundDeviceOf` 的聚合(SQL 侧只缩小读的行,每行过唯一解码:`execution-placement-sql.ts`
400
+ * `countDeviceBindingsOn`)。数的是格,不 join 设备行(吊销设备上的格照数:格还在,会话仍放在那台设备上)。只交一个数。读故障与解码器拒的行原样抛(不折成 0)。
401
+ * 读者:起服门「设备入口 × 已绑设备」(`boot/device-lane.ts`)。
402
+ */
403
+ countDeviceBindings(): Promise<number>;
398
404
  appendAudit(input: AppendAuditInput): Promise<void>;
399
405
  listAudit(filter?: ListAuditFilter): Promise<DeviceAuditRow[]>;
400
406
  }
@@ -432,6 +438,7 @@ export declare class InMemoryDeviceStore implements DeviceStore {
432
438
  releaseConnection(deviceId: string, generation: number): Promise<boolean>;
433
439
  placeDeviceExecution(input: PlaceDeviceExecutionInput): Promise<PlaceDeviceExecutionResult>;
434
440
  rebindDeviceExecution(input: RebindDeviceExecutionInput): Promise<RebindDeviceExecutionResult>;
441
+ countDeviceBindings(): Promise<number>;
435
442
  appendAudit(input: AppendAuditInput): Promise<void>;
436
443
  listAudit(filter?: ListAuditFilter): Promise<DeviceAuditRow[]>;
437
444
  private isOnline;
@@ -386,6 +386,9 @@ export class InMemoryDeviceStore {
386
386
  this.pushAudit({ event: "session_rebound", deviceId: input.targetDeviceId, owner: input.owner, detail: { from: out.from, to: input.targetDeviceId, rev: out.device.rev } });
387
387
  return out;
388
388
  }
389
+ async countDeviceBindings() {
390
+ return this.seat.countDeviceBindings();
391
+ }
389
392
  async appendAudit(input) {
390
393
  this.pushAudit(input);
391
394
  }
@@ -0,0 +1,22 @@
1
+ import { type LaneHomeDeployment, type RemoteExecProvider } from "./execution-lane-caps.js";
2
+ /** 选中车道转进沙箱的 env 那一座(`ServiceConfig.remoteExec` 的判别联合每一臂结构上都满足:e2b / k8s 的 `sandboxEnv`、local-docker 的 `env`,别的臂两键都没有)。 */
3
+ export interface SandboxEnvSeat {
4
+ readonly remoteExec?: {
5
+ readonly provider: RemoteExecProvider;
6
+ readonly sandboxEnv?: Readonly<Record<string, string>> | undefined;
7
+ readonly env?: Readonly<Record<string, string>> | undefined;
8
+ } | undefined;
9
+ }
10
+ /** 求配置根要读的部署事实(`ServiceConfig` 结构上满足;存体预判的问点传活配置整份)。 */
11
+ export interface ExecutionConfigRootDeployment extends LaneHomeDeployment, SandboxEnvSeat {
12
+ readonly agentConfigDir?: string;
13
+ readonly remoteExec?: SandboxEnvSeat["remoteExec"];
14
+ }
15
+ /**
16
+ * 被选中的沙箱车道上运维点名转进沙箱的 env(e2b / k8s 的 `sandboxEnv`、local-docker 的 `env` —— 名单型旋钮经 `allowlistedEnv` 取的原值);
17
+ * 车道没有这一座 ⇒ 空。名单此刻交出去的名(`config.ts` 的 `sandboxEnvNamesInForce`)读的也是这一只。
18
+ */
19
+ export declare function sandboxForwardedEnvOf(deployment: SandboxEnvSeat): Readonly<Record<string, string>>;
20
+ /** 执行环境的配置根(规则见文件头);读不出 ⇒ `undefined`。 */
21
+ export declare function executionConfigRootOf(deployment: ExecutionConfigRootDeployment): string | undefined;
22
+ //# sourceMappingURL=execution-config-root.d.ts.map
@@ -0,0 +1,35 @@
1
+ import { posix } from "node:path";
2
+ import { DEPLOYMENT_SHAPE_PREDICATES } from "./deployment-shape.js";
3
+ import { executionLaneHomeDir, executionLaneOf } from "./execution-lane-caps.js";
4
+ import { configRootOf } from "./sema-config-dir.js";
5
+ const CONFIG_DIR_ENV = "SEMA_CONFIG_DIR";
6
+ export function sandboxForwardedEnvOf(deployment) {
7
+ const lane = executionLaneOf(deployment.remoteExec?.provider);
8
+ switch (lane) {
9
+ case "e2b":
10
+ case "k8s":
11
+ return deployment.remoteExec?.sandboxEnv ?? {};
12
+ case "local-docker":
13
+ return deployment.remoteExec?.env ?? {};
14
+ case "in-process":
15
+ case "host":
16
+ case "ssh":
17
+ case "adb":
18
+ case "device":
19
+ return {};
20
+ default: {
21
+ const unhandled = lane;
22
+ return unhandled;
23
+ }
24
+ }
25
+ }
26
+ export function executionConfigRootOf(deployment) {
27
+ if (!DEPLOYMENT_SHAPE_PREDICATES.sandboxPathAdjudication.holds(deployment))
28
+ return configRootOf(deployment);
29
+ const forwarded = sandboxForwardedEnvOf(deployment)[CONFIG_DIR_ENV];
30
+ if (forwarded !== undefined)
31
+ return forwarded;
32
+ const home = executionLaneHomeDir(deployment);
33
+ return home === undefined ? undefined : posix.join(home, ".sema");
34
+ }
35
+ //# sourceMappingURL=execution-config-root.js.map
@@ -65,12 +65,34 @@ export interface ExecutionLaneCaps {
65
65
  * (不是遗漏:设备侧长驻进程、LSP sidecar、直传腿都是 follow-on,声明了就是谎)。
66
66
  */
67
67
  export declare function executionLaneCaps(lane: ExecutionLane): ExecutionLaneCaps;
68
- /** {@link capabilitiesFor} 读的部署配置:已装配的 `remoteExec` 里决定块的两键(`ServiceConfig` 的判别联合每一臂结构上都满足)。 */
68
+ /** 读地板旋钮的 env 名(拒起句与起服行点名它)。 */
69
+ export declare const SANDBOX_READ_FLOOR_ENV = "SANDBOX_READ_FLOOR";
70
+ /** `SANDBOX_READ_FLOOR` 闭集:`keep` = 地板模块服务这条车道时守地板;`off` = 不守(车道块 `keepsReadFloor:false`,起动字节与没有地板模块时同)。 */
71
+ export declare const SANDBOX_READ_FLOOR_WORDS: readonly ["keep", "off"];
72
+ export type SandboxReadFloorKnob = (typeof SANDBOX_READ_FLOOR_WORDS)[number];
73
+ /** 缺省 `keep`:服务集翻进一条车道的那一版,这条车道就守地板(运维不必再开一次)。 */
74
+ export declare const SANDBOX_READ_FLOOR_DEFAULT: SandboxReadFloorKnob;
75
+ /** 地板模块能服务的车道(沙箱三车道;主机 / ssh / adb / device 约束不了进程,恒不守)。 */
76
+ export type SandboxFloorLane = Extract<ExecutionLane, "e2b" | "k8s" | "local-docker">;
77
+ /** 一条沙箱车道此刻的地板状态:`kept` = 守 · `off` = 部署关了 · `unserved` = 地板模块还不服务这条车道。起服行原样报这个词。 */
78
+ export type SandboxReadFloorState = "kept" | "off" | "unserved";
79
+ /** 派生的纯形(旋钮 × 服务与否 → 状态);`off` 先于服务集(部署关掉的车道不论服务与否都报 `off`)。 */
80
+ export declare function readFloorStateOf(knob: SandboxReadFloorKnob, served: boolean): SandboxReadFloorState;
81
+ /**
82
+ * 一条沙箱车道的地板状态 —— 沙箱块 `keepsReadFloor`(`=== "kept"`)、起服行与适配器手里地板模块的**唯一**判据。旋钮缺席 ⇒ {@link SANDBOX_READ_FLOOR_DEFAULT}。
83
+ *
84
+ * **服务集**住在这里,每条车道一臂(穷举 switch、无 default 臂):第二个实参 = 地板模块在这条车道上的起动接缝已经过真沙箱验收(性能闸 + 黑盒复跑)、
85
+ * 可以如实声明守地板。今天三臂皆 `false` —— 翻某一臂的前提是 core 让每一次起动都带地板、契约写明路径语义,以及那条车道的真跑读数。
86
+ */
87
+ export declare function sandboxReadFloorState(lane: SandboxFloorLane, knob: SandboxReadFloorKnob | undefined): SandboxReadFloorState;
88
+ /** {@link capabilitiesFor} 读的部署配置:已装配的 `remoteExec` 里决定块的两键(`ServiceConfig` 的判别联合每一臂结构上都满足)+ 部署级读地板旋钮
89
+ * (`SANDBOX_READ_FLOOR`;缺席 ⇒ 缺省 `keep`,与 loader 同一只常量)。 */
69
90
  export interface CapabilitiesDeployment {
70
91
  readonly remoteExec?: {
71
92
  readonly provider?: string | undefined;
72
93
  readonly s3Snapshot?: unknown;
73
94
  } | undefined;
95
+ readonly sandboxReadFloor?: SandboxReadFloorKnob | undefined;
74
96
  }
75
97
  /**
76
98
  * 「这份部署上 run 所在的执行环境能做什么」—— 引擎能力块(八键)的**唯一作者**。
@@ -87,8 +109,15 @@ export declare function capabilitiesFor(deployment: {
87
109
  readonly provider: RemoteExecProvider;
88
110
  readonly s3Snapshot?: unknown;
89
111
  } | undefined;
112
+ readonly sandboxReadFloor?: SandboxReadFloorKnob | undefined;
90
113
  }): ExecutionEnvCapabilities;
91
114
  export declare function capabilitiesFor(deployment: CapabilitiesDeployment): ExecutionEnvCapabilities | undefined;
115
+ /**
116
+ * 本部署车道的读地板状态(`kept` / `off` / `unserved`;纯数据)—— 只沙箱三车道有这一格,别的车道(与认不出的词)⇒ `undefined`。
117
+ * 判据与块的 `keepsReadFloor` 同一只({@link sandboxBlock} 读的 `sandboxReadFloorState`):`keepsReadFloor === (本值 === "kept")` 是构造保证。
118
+ * 读者:起服行 `remote_exec_enabled.sandboxReadFloor`(说清不守的原因:部署关了,还是模块还没服务这条车道)。穷举 switch、无 default 臂。
119
+ */
120
+ export declare function laneSandboxReadFloorState(deployment: CapabilitiesDeployment): SandboxReadFloorState | undefined;
92
121
  /**
93
122
  * 车道普查全集 = 本仓内部状态名 `in-process` + 词表正本的可声明车道(次序 = 正本声明序;对账测试按它逐 lane 跑真判别式)。
94
123
  * 由正本派生、不手维护(手维护的「全表」已被代码反证漏过行)。
@@ -109,7 +138,7 @@ export declare function executionLaneRow(lane: RemoteExecProvider): ExecutionLan
109
138
  * 一条车道**必需**的 env 键名 = 正本表行的 `requiredEnv ∪ requiredSecretEnv`(次序:非密坐标在前、密钥在后 —— 与 7.113.0 本仓手写表逐字同)。
110
139
  * 读者:`config.ts` 的 [ref] 拒启门(点了名却缺件 ⇒ 点名缺哪几键)、部署面门(`test/deploy-lanes-contract.test.ts`:`lanes.json` 每行登记
111
140
  * 本车道必需键)、`scripts/gen-deploy-lanes.ts`(从同一张正本生成 `lanes.json` 的必需键列,以及根 / review compose 与 sema-up 栈 compose 里的必需键透传)。
112
- * device 没有必需 env(设备是运行期连进来的;它的 boot 前提是跨键的,在跨域不变量区拒启)。
141
+ * device 在正本表(15.2.0)上没有必需 env;它的必填键 `DEVICE_RELAY_URL` 的缺席暂由 `config.ts` `parseDeviceRelayUrlEnv` 拒(过渡:表行收进它即删那一臂)。
113
142
  */
114
143
  export declare function laneRequiredEnv(lane: RemoteExecProvider): readonly string[];
115
144
  /**
@@ -126,6 +155,24 @@ export declare function isBlankRequiredEnvValue(value: string | undefined): bool
126
155
  * 读者:`config.ts` 的 [ref] 拒启门(非空 ⇒ 拒启并点名缺哪几键);判的是调用方交来的 env 快照,本函数不读 `process.env`。
127
156
  */
128
157
  export declare function laneMissingEnv(lane: RemoteExecProvider, env: Readonly<Record<string, string | undefined>>): readonly string[];
158
+ /** {@link readDeclaredLaneTable} 的答案:合文法 ⇒ 有序车道表(非空;首项 = 缺省);不合 ⇒ 拒句(点名那一段)。 */
159
+ export type DeclaredLaneTableReading = {
160
+ readonly lanes: readonly [RemoteExecProvider, ...RemoteExecProvider[]];
161
+ } | {
162
+ readonly refusal: string;
163
+ };
164
+ /**
165
+ * `REMOTE_EXEC` 的**唯一读法** —— 部署声明的有序执行车道表。它是契约 `runtime.execution.lanes`(settings-schema 16.0.0 起是车道集)渲成 env 的形:
166
+ * `lanes.join(",")`,首项 = 缺省;单词 = 一元表,与此前的单值读法逐字同。
167
+ *
168
+ * 文法(契约写闸对 `lanes` 的同一组规则,在 env 串上的读法):逗号分段;每段**逐字**是一个可声明车道词({@link REMOTE_EXEC_PROVIDER_WORDS} = 契约词表,
169
+ * 不 trim、不折大小写 —— 渲染器不写空白,` k8s` 不是车道词);段不空;一条车道至多出现一次。`in-process` 是「没点名」这个状态的名字,不是可写词。
170
+ * 未设 / 空串**不是表**(没有声明 —— 调用方先判,本函数只收非空串)。
171
+ * 不合文法 ⇒ `refusal`,拒句点名那一段;只有一段时与单值时代的拒句逐字同。契约的退役车道值(墓碑 `RETIRED_LANE_WORDS`)拒句取契约的
172
+ * `retiredLaneWordRefusal`(出路在源头给,一句 —— 车道文件 `provider` 读到同一个旧词时契约给的是同一句)。本函数只判文法;本引擎版本能服务几条车道、
173
+ * 每条车道缺不缺必需键,是调用方的门。
174
+ */
175
+ export declare function readDeclaredLaneTable(raw: string): DeclaredLaneTableReading;
129
176
  /**
130
177
  * [ref] 件③ / [ref] —— **「`REQUIRE_EXECUTION_LANE=true` ∧ 没有任何车道被点名 ⇒ 拒启」的判据与拒因句的唯一属主**(`modelProviderRefusal` /
131
178
  * `modelRouteEndpointRefusal` 同形:属主在叶模块,执法点各自调它,登记在 `config-invariants.ts`)。