@sema-agent/core 7.53.0 → 7.54.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/CHANGELOG.md +291 -1
  2. package/LICENSE +3 -1
  3. package/dist/agents/peer-session-drain.d.ts +0 -1
  4. package/dist/agents/peer-session-drain.js +0 -2
  5. package/dist/agents/send-message-tool.js +3 -4
  6. package/dist/agents/subagent.d.ts +3 -5
  7. package/dist/agents/subagent.js +18 -11
  8. package/dist/agents/tool-filter.d.ts +7 -4
  9. package/dist/agents/tool-filter.js +7 -3
  10. package/dist/core/a2a.js +4 -2
  11. package/dist/core/allow-entry.d.ts +1 -0
  12. package/dist/core/allow-entry.js +1 -0
  13. package/dist/core/approval-object.d.ts +87 -0
  14. package/dist/core/approval-object.js +85 -0
  15. package/dist/core/arg-summary.d.ts +3 -2
  16. package/dist/core/arg-summary.js +17 -11
  17. package/dist/core/ask-origin.d.ts +43 -27
  18. package/dist/core/ask-origin.js +20 -11
  19. package/dist/core/ask-question.d.ts +0 -1
  20. package/dist/core/ask-question.js +4 -67
  21. package/dist/core/ask-unresolvable-notice.d.ts +3 -2
  22. package/dist/core/auto-compaction.d.ts +49 -50
  23. package/dist/core/auto-compaction.js +41 -51
  24. package/dist/core/auto-mode-prompt.d.ts +4 -2
  25. package/dist/core/auto-mode-prompt.js +4 -3
  26. package/dist/core/canonical-json.d.ts +69 -0
  27. package/dist/core/canonical-json.js +130 -8
  28. package/dist/core/checkpoint-requirements.d.ts +13 -7
  29. package/dist/core/checkpoint-requirements.js +4 -1
  30. package/dist/core/checkpoint-store.d.ts +33 -18
  31. package/dist/core/checkpoint-store.js +5 -4
  32. package/dist/core/child-clamp-seats.d.ts +45 -6
  33. package/dist/core/child-clamp-seats.js +56 -4
  34. package/dist/core/consolidate-scope.d.ts +1 -1
  35. package/dist/core/cross-boundary-taint.d.ts +213 -0
  36. package/dist/core/cross-boundary-taint.js +196 -0
  37. package/dist/core/decision-receipt.d.ts +44 -1
  38. package/dist/core/decision-receipt.js +16 -0
  39. package/dist/core/effective-path-target.d.ts +3 -0
  40. package/dist/core/effective-path-target.js +4 -0
  41. package/dist/core/engine-notice.d.ts +34 -2
  42. package/dist/core/engine-notice.js +17 -1
  43. package/dist/core/engine-storage-backend.d.ts +73 -0
  44. package/dist/core/engine-storage-backend.js +1 -0
  45. package/dist/core/file-history-store.d.ts +6 -2
  46. package/dist/core/file-history-store.js +1 -0
  47. package/dist/core/gate-fold.js +10 -2
  48. package/dist/core/gate-lanes.js +30 -21
  49. package/dist/core/gate-outcome.d.ts +7 -3
  50. package/dist/core/governance-codes.d.ts +7 -2
  51. package/dist/core/governance-codes.js +6 -0
  52. package/dist/core/hooks.d.ts +14 -14
  53. package/dist/core/hooks.js +1 -2
  54. package/dist/core/locked-config.d.ts +28 -1
  55. package/dist/core/locked-config.js +30 -3
  56. package/dist/core/lsp.d.ts +1 -1
  57. package/dist/core/mcp-server-spec.d.ts +66 -9
  58. package/dist/core/mcp-server-spec.js +52 -19
  59. package/dist/core/mcp.js +5 -4
  60. package/dist/core/memory-engine/engine.js +2 -1
  61. package/dist/core/memory-engine/export-bundle.d.ts +1 -1
  62. package/dist/core/memory-engine/export-bundle.js +1 -1
  63. package/dist/core/memory-engine/file-backend.d.ts +2 -6
  64. package/dist/core/memory-engine/file-backend.js +1 -12
  65. package/dist/core/memory.d.ts +3 -3
  66. package/dist/core/mode-policy.d.ts +13 -8
  67. package/dist/core/mode-policy.js +3 -2
  68. package/dist/core/mods/kernel/receive.d.ts +2 -1
  69. package/dist/core/mods/kernel/receive.js +7 -2
  70. package/dist/core/parent-spec-seat.d.ts +10 -13
  71. package/dist/core/parent-spec-seat.js +14 -6
  72. package/dist/core/pause-registry.d.ts +35 -1
  73. package/dist/core/pause-registry.js +10 -0
  74. package/dist/core/permission-rule-consent.d.ts +18 -5
  75. package/dist/core/permission-rule-consent.js +13 -8
  76. package/dist/core/permission-rule-model.d.ts +29 -6
  77. package/dist/core/permission-rule-model.js +6 -1
  78. package/dist/core/permission-rule-org.d.ts +37 -0
  79. package/dist/core/permission-rule-org.js +34 -42
  80. package/dist/core/permission-rule-session.js +2 -2
  81. package/dist/core/permission-rule-store.d.ts +8 -0
  82. package/dist/core/permission-rule-store.js +1 -0
  83. package/dist/core/permission-rule-sync.js +3 -2
  84. package/dist/core/read-only-shell.d.ts +1 -1
  85. package/dist/core/read-only-shell.js +30 -9
  86. package/dist/core/remote-env.d.ts +41 -1
  87. package/dist/core/remote-env.js +18 -0
  88. package/dist/core/resume-startup-cause.d.ts +18 -0
  89. package/dist/core/resume-startup-cause.js +12 -0
  90. package/dist/core/runner/approval-feedback.d.ts +46 -0
  91. package/dist/core/runner/approval-feedback.js +41 -0
  92. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  93. package/dist/core/runner/compaction-call-options.js +2 -2
  94. package/dist/core/runner/contracts.d.ts +33 -41
  95. package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
  96. package/dist/core/runner/cross-boundary-lane.js +190 -0
  97. package/dist/core/runner/decide-continuation.d.ts +4 -4
  98. package/dist/core/runner/decide-continuation.js +3 -1
  99. package/dist/core/runner/gate-exit.d.ts +32 -12
  100. package/dist/core/runner/gate-exit.js +75 -33
  101. package/dist/core/runner/inherited-ask-grants.js +2 -2
  102. package/dist/core/runner/person-path-rows.d.ts +2 -2
  103. package/dist/core/runner/prepare-acquire-reconcile.d.ts +7 -9
  104. package/dist/core/runner/prepare-acquire-reconcile.js +8 -6
  105. package/dist/core/runner/prepare-ask-lane.js +3 -3
  106. package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
  107. package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
  108. package/dist/core/runner/prepare-config-doors.d.ts +12 -15
  109. package/dist/core/runner/prepare-config-doors.js +8 -3
  110. package/dist/core/runner/prepare-context-lane.js +6 -3
  111. package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
  112. package/dist/core/runner/prepare-delegation-surface.js +2 -3
  113. package/dist/core/runner/prepare-execution-env.d.ts +1 -2
  114. package/dist/core/runner/prepare-execution-env.js +2 -1
  115. package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
  116. package/dist/core/runner/prepare-gate-stations.js +4 -3
  117. package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
  118. package/dist/core/runner/prepare-hands-readface.js +1 -1
  119. package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
  120. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  121. package/dist/core/runner/prepare-park-ask.d.ts +1 -1
  122. package/dist/core/runner/prepare-park-ask.js +9 -3
  123. package/dist/core/runner/prepare-policy-chain.js +2 -7
  124. package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
  125. package/dist/core/runner/prepare-safety-scan.js +34 -27
  126. package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
  127. package/dist/core/runner/prepare-suspend-saga.js +6 -2
  128. package/dist/core/runner/prepare-task.js +13 -10
  129. package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
  130. package/dist/core/runner/prepare-turn-wiring.js +14 -49
  131. package/dist/core/runner/prepare-wiring-manifest.d.ts +2 -2
  132. package/dist/core/runner/prepare-wiring-manifest.js +2 -2
  133. package/dist/core/runner/prepare-workspace-restore.js +2 -2
  134. package/dist/core/runner/resume-admission.js +9 -9
  135. package/dist/core/runner/resume-apply.js +4 -4
  136. package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
  137. package/dist/core/runner/resume-policy-outcome.js +18 -15
  138. package/dist/core/runner/resume-preflight.js +2 -2
  139. package/dist/core/runner/resume-review-outcome.js +3 -3
  140. package/dist/core/runner/run-leg.js +57 -33
  141. package/dist/core/runner/run-settle-and-teardown.js +2 -0
  142. package/dist/core/runner/run-terminal-adoption.js +15 -4
  143. package/dist/core/runner/session-file-state-replay.d.ts +5 -2
  144. package/dist/core/runner/session-file-state-replay.js +3 -2
  145. package/dist/core/runner/stream-steer-verb.js +2 -2
  146. package/dist/core/runner/tool-face-overlay.d.ts +24 -6
  147. package/dist/core/runner/tool-face-overlay.js +24 -15
  148. package/dist/core/runner-deps.d.ts +9 -9
  149. package/dist/core/session-store.d.ts +35 -16
  150. package/dist/core/session-store.js +20 -12
  151. package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
  152. package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
  153. package/dist/core/shell-wrapper-table.d.ts +5 -3
  154. package/dist/core/shell-wrapper-table.js +1 -1
  155. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  156. package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
  157. package/dist/core/store-contracts/session-store-contract.js +683 -0
  158. package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
  159. package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
  160. package/dist/core/task-event.d.ts +6 -4
  161. package/dist/core/task-registry-agent.js +1 -0
  162. package/dist/core/task-registry-shared.d.ts +5 -0
  163. package/dist/core/task-registry.d.ts +7 -0
  164. package/dist/core/task-registry.js +17 -0
  165. package/dist/core/task-result.d.ts +1 -1
  166. package/dist/core/task-spec.d.ts +6 -6
  167. package/dist/core/tighten-task-spec.d.ts +6 -4
  168. package/dist/core/tighten-task-spec.js +2 -2
  169. package/dist/core/tool-catalog-entries.d.ts +1 -1
  170. package/dist/core/tool-catalog-entries.js +52 -52
  171. package/dist/core/tool-face-reader.d.ts +49 -0
  172. package/dist/core/tool-face-reader.js +94 -0
  173. package/dist/core/tool-face.d.ts +17 -8
  174. package/dist/core/tool-face.js +1 -0
  175. package/dist/core/tool-policy.d.ts +64 -23
  176. package/dist/core/tool-policy.js +62 -56
  177. package/dist/core/tool-roster.d.ts +46 -28
  178. package/dist/core/tool-roster.js +91 -14
  179. package/dist/core/tool-spec-members.js +2 -2
  180. package/dist/core/tool-spec.d.ts +20 -4
  181. package/dist/core/tools.js +2 -0
  182. package/dist/core/trace.d.ts +24 -2
  183. package/dist/core/types.d.ts +2 -2
  184. package/dist/core/types.js +1 -1
  185. package/dist/core/untrusted-text.d.ts +7 -0
  186. package/dist/core/untrusted-text.js +3 -8
  187. package/dist/core/user-attachment.d.ts +43 -0
  188. package/dist/core/user-attachment.js +92 -15
  189. package/dist/engine/compaction/compaction.d.ts +2 -2
  190. package/dist/engine/compaction/utils.d.ts +2 -2
  191. package/dist/engine/harness/types.d.ts +7 -4
  192. package/dist/engine/llm/types.d.ts +7 -2
  193. package/dist/engine/llm/types.js +2 -1
  194. package/dist/engine/session/parent-chain-guard.d.ts +10 -0
  195. package/dist/engine/session/parent-chain-guard.js +10 -0
  196. package/dist/engine/session/repo-utils.d.ts +5 -0
  197. package/dist/engine/session/repo-utils.js +9 -0
  198. package/dist/engine/session/storage-base.js +5 -2
  199. package/dist/index.d.ts +23 -9
  200. package/dist/index.js +19 -8
  201. package/dist/internal/harness.d.ts +1 -0
  202. package/dist/internal/harness.js +1 -0
  203. package/dist/internal/llm.d.ts +1 -1
  204. package/dist/internal/llm.js +1 -1
  205. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  206. package/dist/orchestration/run-workflow-tool.js +1 -0
  207. package/dist/orchestration/workflow-governance.d.ts +1 -1
  208. package/dist/orchestration/workflow.d.ts +3 -0
  209. package/dist/orchestration/workflow.js +2 -1
  210. package/dist/prompt-assembly/catalog/schema.d.ts +3 -0
  211. package/dist/prompt-assembly/catalog/schema.js +3 -0
  212. package/dist/prompt-assembly/overlay.js +2 -2
  213. package/dist/prompt-assembly/select.js +2 -2
  214. package/dist/prompts/default.d.ts +1 -1
  215. package/dist/shell/lex.d.ts +87 -8
  216. package/dist/shell/lex.js +283 -49
  217. package/dist/stores/file/index.d.ts +8 -6
  218. package/dist/tools/artifact/artifact-tool.js +10 -0
  219. package/dist/tools/fs/bash-program-position.d.ts +3 -15
  220. package/dist/tools/fs/bash-program-position.js +3 -10
  221. package/dist/tools/fs/bash-readonly-classifier.js +2 -0
  222. package/dist/tools/fs/fs-bash.d.ts +3 -3
  223. package/dist/tools/fs/fs-bash.js +72 -101
  224. package/dist/tools/fs/fs-read.d.ts +12 -0
  225. package/dist/tools/fs/fs-read.js +10 -9
  226. package/dist/tools/fs/fs-shared.d.ts +27 -31
  227. package/dist/tools/fs/fs-shared.js +12 -15
  228. package/dist/tools/fs/fs-write.js +1 -1
  229. package/dist/tools/fs/index.js +2 -2
  230. package/dist/tools/fs/read-deny-home.d.ts +1 -1
  231. package/dist/tools/fs/read-deny-home.js +5 -4
  232. package/dist/tools/fs/read-deny.d.ts +16 -3
  233. package/dist/tools/fs/read-deny.js +12 -9
  234. package/dist/tools/fs/safety.d.ts +7 -6
  235. package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
  236. package/dist/tools/fs/shell-read-bindings.js +15 -16
  237. package/dist/tools/fs/shell-read-device.js +1 -1
  238. package/dist/tools/fs/shell-read-plan.js +36 -47
  239. package/dist/tools/fs/shell-read-word.d.ts +1 -6
  240. package/dist/tools/fs/shell-read-word.js +2 -20
  241. package/dist/tools/fs/shell-reentry-text.js +1 -1
  242. package/dist/tools/web.d.ts +2 -2
  243. package/package.json +4 -1
  244. package/test/export-surface.snapshot.json +5654 -5520
  245. package/wire-changes/core@7.53.0.json +30 -8
  246. package/wire-changes/core@7.54.0.json +2870 -0
  247. package/wire-changes/core@7.54.1.json +37 -0
  248. package/dist/core/shell-scan.d.ts +0 -60
  249. package/dist/core/shell-scan.js +0 -183
@@ -14,7 +14,7 @@ import type { OnElicit } from "./mcp-server-spec.js";
14
14
  import type { Brain, ModelRef, ModelRoles } from "./model-seat.js";
15
15
  import type { RuntimeCaps, WorkflowGovernanceBaseline } from "./runtime-caps.js";
16
16
  /**
17
- * parity-204 — structured `RunnerDeps.loadProjectMemory` return (backward-compatible: a bare
17
+ * parity-204 — structured `RunnerDeps.loadProjectMemory` return (backward-compatible, RL-1336-project-memory-bare: a bare
18
18
  * `string | null` keeps its exact historical meaning). Adds the CC 2.1.204 `seededFromContext`
19
19
  * seam (cc204-bundle @17917159: CC preloads readFileState for CLAUDE.md/nested-memory files it
20
20
  * placed in context, and a DEFAULT whole-file Read of an unchanged seeded file answers with the
@@ -73,8 +73,8 @@ export interface ResumePreflightInfo {
73
73
  * the resume entry's identity-continuity rung already refused a contradicting supplied principal
74
74
  * before this hook runs, so a present value IS the resumed leg's identity. */
75
75
  principal?: string;
76
- /** provider / deviceId / mountPath — the placement facts. */
77
- workspaceHandle?: import("./remote-env.js").WorkspaceHandle;
76
+ /** provider / deviceId / mountPath — the placement facts: the row handle's one frozen copy (as `resumeWorkspace`, `priorHandle`). */
77
+ workspaceHandle?: Readonly<import("./remote-env.js").WorkspaceHandle>;
78
78
  gateKind: import("./checkpoint-store.js").CheckpointGate["kind"];
79
79
  }
80
80
  /** design/380 O2 — a {@link RunnerDeps.resumePreflight} answer. */
@@ -141,10 +141,8 @@ export interface RunnerDeps {
141
141
  * probe must not brick every MCP call) with a once-per-MATERIALIZATION `mcp.revocation_probe_failed`
142
142
  * notice (a resume re-materializes and may announce again — the standing condition is re-news at
143
143
  * each fresh mount, never per-call). The notice is a deployment wiring fact: `detail: { message }`
144
- * only, no session attribution, `"operator"` audience by its explicit {@link NOTICE_AUDIENCE}
145
- * row (#433 made the registry total over the catalog — no engine-minted code is
146
- * audience-defaulted any more) — a
147
- * wire projector forwards it operator-tier and needs no per-session de-duplication of its own.
144
+ * only, no session attribution, `"operator"` audience by its explicit {@link NOTICE_AUDIENCE} row — a wire projector
145
+ * forwards it operator-tier and needs no per-session de-duplication of its own.
148
146
  * The probe's second argument, `{ fingerprint }`, is passed exactly when the server's declaration carries one.
149
147
  */
150
148
  mcpRevocations?: import("./mcp-server-spec.js").McpRevocationLedger;
@@ -160,8 +158,7 @@ export interface RunnerDeps {
160
158
  rosterStore?: import("../agents/roster-store.js").RosterStore;
161
159
  /**
162
160
  * The center prompt-artifact source (artifact v3 only — a v2 body is refused `schema`, INTEGRATION-CORE §12.11).
163
- * `current()` supplies the verified
164
- * candidate a top-level session adopts at session start and at a compaction; `get(digest)` resolves a PINNED center
161
+ * `current()` supplies the verified candidate a top-level session adopts at session start and at a compaction; `get(digest)` resolves a PINNED center
165
162
  * layer on resume (a miss is fail-loud `prompt.snapshot_unavailable` — core never recomposes a hybrid from newer
166
163
  * content; a copy the engine refuses is told apart from a missing one, in the failure's sentence and on `onError`);
167
164
  * `sourceState()` is the tri-state every adoption decision keys on. What a source hands a run is re-checked
@@ -343,6 +340,9 @@ export interface RunnerDeps {
343
340
  * to CONTINUE instead — zero capture and zero out-of-policy running, availability paid).
344
341
  */
345
342
  memoryCapturePolicy?: "open" | "governed" | "capture-required";
343
+ /** The deployment's cross-boundary posture, one of {@link import("./cross-boundary-taint.js").CROSS_BOUNDARY_POSTURES} (who is hurt when a tree that
344
+ * executes on a target device reaches past it). Absent reads `"single_user"`; any other value refuses the prepare, and a resume before its claim (`config.cross_boundary_posture`). */
345
+ crossBoundaryPosture?: "single_user" | "multi_tenant";
346
346
  /**
347
347
  * design/383 §2.1b — an optional host-supplied carrier for the session capture opt-out record
348
348
  * (per memory plane, keyed by that plane's control dir). Absent = the control-plane file trio
@@ -1,4 +1,4 @@
1
- import { type SessionRepo } from "../internal/harness.js";
1
+ import { type SessionPlacementRecord, type SessionRepo } from "../internal/harness.js";
2
2
  import { type AcquiredSession, type PlacedSessionRow, type SessionPlacement, type SessionStore, type SessionStoreSummary } from "./session.js";
3
3
  import type { StoreDurability } from "./checkpoint-store.js";
4
4
  import { SESSION_DEFAULT_TTL_DAYS } from "../config/defaults.js";
@@ -52,6 +52,37 @@ export interface TtlSessionStoreOptions {
52
52
  };
53
53
  };
54
54
  }
55
+ /**
56
+ * The one placement admission of a session store's `acquire` — every backend that declares the placement partition
57
+ * judges each resolved session through it (this module's {@link TtlSessionStore} on its four resolution paths: cache
58
+ * hit, pending-promise inherit, open, idempotent re-create), so no backend writes the two directions a second time.
59
+ *
60
+ * Placement obligation 6 (claim closure): an EXISTING placed session refuses the claim-form
61
+ * acquire — no `requireExisting` (the read/revive form, obligation 5) and no creation placement
62
+ * (the trusted-internals form; the creating call and its dedup/retry re-acquires stay legal, and
63
+ * placement immutability means the argument can never REWRITE anything — the persisted tuple wins).
64
+ *
65
+ * …and the MIRROR direction (the pre-claim SQUAT race): the child session id is
66
+ * published on the spawn frame BEFORE the child's prepare creates the session, so a concurrent
67
+ * claim-form acquire could win first creation and hand the trusted placement caller an ORDINARY
68
+ * session — host-listable, never really-deleted, the declared full tier falsified in silence. On
69
+ * a store that DECLARES the partition (`capability.subagentPlacement`; an ignoring store legally drops
70
+ * placements), an acquire carrying a placement must therefore come back with a PERSISTED tuple whose
71
+ * join identity matches the declaration — a missing or foreign tuple refuses loudly.
72
+ *
73
+ * @param persisted the tuple the backend holds for `sessionId` as resolved (`undefined`: an unplaced session).
74
+ * @param request the `acquire` options the caller passed.
75
+ * @param capability whether the store declares the subagent placement partition. `subagentPlacement` is read once,
76
+ * and only when the mirror direction is judged (a placement request that is not the read form, after `request`'s
77
+ * members are read), so a store may pass a getter over its live declaration.
78
+ * @throws SessionError `placement_refused` on either direction; returns nothing when admitted.
79
+ */
80
+ export declare function admitSessionPlacement(sessionId: string, persisted: SessionPlacementRecord | undefined, request: {
81
+ requireExisting?: boolean;
82
+ placement?: SessionPlacement;
83
+ }, capability: {
84
+ subagentPlacement: boolean;
85
+ }): void;
55
86
  /**
56
87
  * TTL-cached {@link SessionStore} over a pluggable {@link SessionRepo}.
57
88
  *
@@ -120,21 +151,9 @@ export declare class TtlSessionStore implements SessionStore {
120
151
  requireExisting?: boolean;
121
152
  placement?: SessionPlacement;
122
153
  }): Promise<AcquiredSession>;
123
- /**
124
- * Placement obligation 6 (claim closure): an EXISTING placed session refuses the claim-form
125
- * acquire — no `requireExisting` (the read/revive form, obligation 5) and no creation placement
126
- * (the trusted-internals form; the creating call and its dedup/retry re-acquires stay legal, and
127
- * placement immutability means the argument can never REWRITE anything — the persisted tuple wins).
128
- *
129
- * …and the MIRROR direction (the pre-claim SQUAT race): the child session id is
130
- * published on the spawn frame BEFORE the child's prepare creates the session, so a concurrent
131
- * claim-form acquire could win first creation and hand the trusted placement caller an ORDINARY
132
- * session — host-listable, never really-deleted, the declared full tier falsified in silence. On
133
- * a store that DECLARES the partition (an ignoring store legally drops placements), an acquire
134
- * carrying a placement must therefore come back with a PERSISTED tuple whose join identity
135
- * matches the declaration — a missing or foreign tuple refuses loudly, on every resolution path
136
- * (cache hit, pending-promise inherit, open, idempotent re-create).
137
- */
154
+ /** The placement admission ({@link admitSessionPlacement}) for this store, judged on every resolution path. The
155
+ * partition declaration is a getter over this store's live field, so it is read where the admission judges the
156
+ * mirror direction (after the request's own members, and only then) — never captured at construction. */
138
157
  private assertPlacementAdmission;
139
158
  /** Resume an existing session from the repo, or create it if the backend has no such id (unless
140
159
  * `requireExisting`, in which case a genuinely-missing id fails loud instead — design/114 Phase3). */
@@ -4,6 +4,20 @@ import { readMember } from "./thrown-value.js";
4
4
  const DAY_MS = 24 * 60 * 60 * 1000;
5
5
  import { SESSION_DEFAULT_TTL_DAYS } from "../config/defaults.js";
6
6
  export { SESSION_DEFAULT_TTL_DAYS };
7
+ export function admitSessionPlacement(sessionId, persisted, request, capability) {
8
+ if (persisted !== undefined && request.requireExisting !== true && request.placement === undefined) {
9
+ throw new SessionError("placement_refused", `session ${sessionId} belongs to the ${persisted.kind} placement partition — a claim-form acquire cannot attach to it (read it with requireExisting, or address the agent through its a* handle)`);
10
+ }
11
+ if (request.placement !== undefined && request.requireExisting !== true && capability.subagentPlacement) {
12
+ const want = request.placement;
13
+ if (persisted === undefined) {
14
+ throw new SessionError("placement_refused", `session ${sessionId} exists OUTSIDE the ${want.kind} placement partition it was declared into — refusing to run a subagent transcript on it (a concurrent claim-form create won this id, or the repo dropped the tuple)`);
15
+ }
16
+ if (persisted.kind !== want.kind || persisted.scope !== want.scope || persisted.handle !== want.handle) {
17
+ throw new SessionError("placement_refused", `session ${sessionId}'s persisted placement does not match the declared one (persisted ${persisted.kind}/${persisted.scope ?? "-"}/${persisted.handle ?? "-"} vs declared ${want.kind}/${want.scope ?? "-"}/${want.handle ?? "-"}) — refusing a cross-tuple attach`);
18
+ }
19
+ }
20
+ }
7
21
  export class TtlSessionStore {
8
22
  retention = "none";
9
23
  durability;
@@ -74,18 +88,12 @@ export class TtlSessionStore {
74
88
  return { session, sessionId: meta.id };
75
89
  }
76
90
  assertPlacementAdmission(sessionId, placement, opts) {
77
- if (placement !== undefined && opts?.requireExisting !== true && opts?.placement === undefined) {
78
- throw new SessionError("placement_refused", `session ${sessionId} belongs to the ${placement.kind} placement partition — a claim-form acquire cannot attach to it (read it with requireExisting, or address the agent through its a* handle)`);
79
- }
80
- if (opts?.placement !== undefined && opts.requireExisting !== true && this.placements?.subagent !== undefined) {
81
- const want = opts.placement;
82
- if (placement === undefined) {
83
- throw new SessionError("placement_refused", `session ${sessionId} exists OUTSIDE the ${want.kind} placement partition it was declared into — refusing to run a subagent transcript on it (a concurrent claim-form create won this id, or the repo dropped the tuple)`);
84
- }
85
- if (placement.kind !== want.kind || placement.scope !== want.scope || placement.handle !== want.handle) {
86
- throw new SessionError("placement_refused", `session ${sessionId}'s persisted placement does not match the declared one (persisted ${placement.kind}/${placement.scope ?? "-"}/${placement.handle ?? "-"} vs declared ${want.kind}/${want.scope ?? "-"}/${want.handle ?? "-"}) — refusing a cross-tuple attach`);
87
- }
88
- }
91
+ const store = this;
92
+ admitSessionPlacement(sessionId, placement, opts ?? {}, {
93
+ get subagentPlacement() {
94
+ return store.placements?.subagent !== undefined;
95
+ },
96
+ });
89
97
  }
90
98
  async openOrCreate(id, requireExisting = false, placement) {
91
99
  try {
@@ -1,23 +1,47 @@
1
- /** One argv word as the lexer read it. */
1
+ /**
2
+ * The TIGHTENING reader's view of a command line — "which programs could this command line run, and can the text
3
+ * even be read?" — for the deny/ask direction of the permission-rule lane (`permission-rule-model.ts`
4
+ * `programRunReachOf`) and the read-only allow member (`read-only-shell.ts`). It reads the ONE shell lexer's result
5
+ * (`shell/lex.ts`) and adds only this reader's own domain: which pieces of a line are run candidates, the leading
6
+ * keyword of a control structure, and the additive peel. A segment that carries a command/process substitution
7
+ * anywhere (an argument, a redirection operand, an unquoted heredoc body) is one whose program runs are not fully
8
+ * known — the reader answers `unreadable` for every rule such a segment does not otherwise reach, never silence; the
9
+ * leading assignments and the wrapper programs of {@link SHELL_WRAPPER_TABLE} are peeled ADDITIVELY — a segment keeps
10
+ * its spelled argv as one run candidate and gains one per peeled layer, so `sudo env X=1 nohup rm -r x` is a run of
11
+ * `sudo …`, of `env …`, of `nohup …` and of `rm -r x`, and a deny on any of them reaches.
12
+ *
13
+ * What it answers is deliberately THREE-valued per segment: the argv words it could read, each marked literal or
14
+ * expansion-bearing, or an `unreadable` reason where the program runs cannot be known from the text (a command the
15
+ * lexer cannot delimit, a syntax error, a control structure whose commands need a syntax tree, a wrapper handed its
16
+ * command as one string or an option of unknown arity). The reader above turns "unreadable" into a fail-closed ask
17
+ * rather than into silence — reading it as "no match" is how a deny went blind on `rm -r x; echo "$HOME"`. Where the
18
+ * lexer cannot delimit the text, the segments it read before it stopped stay candidates (bash runs every complete line
19
+ * before the one it cannot parse) and the segment it stopped in is the unreadable one.
20
+ *
21
+ * The ALLOW direction's rule lane never reads this module: a standing yes covers the exact spelling a person read
22
+ * (`ruleAdmitsCommand`), and its whole-string refusal of every shell construct stays where it is.
23
+ */
24
+ import { type LexedWord } from "../shell/lex.js";
25
+ /** One argv word as the reader projects it. */
2
26
  export interface ShellWord {
3
27
  /** The word's value with quotes and escapes removed — meaningful only when `expands` is `false`. */
4
28
  readonly text: string;
5
29
  /** The unquoted source spelling (assignment and wrapper-flag tests read this, never `text`). */
6
30
  readonly raw: string;
7
31
  /** `false`: a literal word. `"one"`: carries an expansion but yields exactly ONE word (it was double-quoted).
8
- * `"many"`: an unquoted expansion, glob or brace pattern, or a quoted `"$@"` / array expansion — may
9
- * yield zero, one or several words. */
32
+ * `"many"`: an unquoted expansion, glob or brace pattern, an ANSI-C quote, or a quoted `"$@"` / array expansion —
33
+ * may yield zero, one or several words. */
10
34
  readonly expands: false | "one" | "many";
11
35
  }
12
- /** One redirection of a segment, as read: the operator and its operand word (a heredoc's operand is its
13
- * delimiter; the IO number or `{name}` before the operator is folded into the operator's meaning and not
14
- * kept). The tightening reader never reads these — a rule speaks about program runs; the read-only
15
- * reader does (an output redirection is a write). */
36
+ /** One redirection of a segment, as read: the operator and its operand word (a heredoc's operand is its delimiter;
37
+ * a descriptor duplication's is the descriptor; the IO number or `{name}` before the operator is folded into the
38
+ * operator's meaning and not kept). The tightening reader never reads these — a rule speaks about program runs; the
39
+ * read-only reader does (an output redirection is a write). */
16
40
  export interface ShellRedirection {
17
41
  readonly op: string;
18
42
  readonly target: ShellWord;
19
43
  }
20
- /** One program run the lexer found, after keyword stripping. */
44
+ /** One program run the reader found, after keyword stripping. */
21
45
  export interface ShellSegment {
22
46
  /** The run as SPELLED (a leading keyword removed): the first candidate a rule is compared against. */
23
47
  readonly argv: readonly ShellWord[];
@@ -32,7 +56,7 @@ export interface ShellSegment {
32
56
  /** Present when the segment's program runs are NOT fully known although its candidates could be read:
33
57
  * a peel that stopped before the wrapped program could be named (its options carry an expansion, a
34
58
  * command string, an option of unknown arity), or a command/process substitution anywhere in the
35
- * segment (it runs a program the lexer does not read). The candidates stand — a rule they reach is
59
+ * segment (it runs a program the reader does not read). The candidates stand — a rule they reach is
36
60
  * reached — and every other rule reads `unreadable`. */
37
61
  readonly peelUnreadable?: string;
38
62
  }
@@ -54,10 +78,10 @@ export interface ShellCommandShape {
54
78
  readonly strayRedirection: boolean;
55
79
  }
56
80
  export { SHELL_WRAPPER_TABLE, type ShellWrapperName } from "./shell-wrapper-table.js";
57
- /** Longer than this and the command is not read at all (one unreadable segment): every pass is linear,
58
- * but a substitution nested inside quoting re-enters the quote reader once per nesting level, and the
59
- * cap is what bounds that on model-supplied text. Upstream's own reader stops at the same size. */
81
+ /** Longer than this and the command is not read at all (one unreadable segment). Upstream's own reader stops at the
82
+ * same size. */
60
83
  export declare const MAX_SHELL_READ_CHARS = 10000;
84
+ export declare function lexedWordOf(w: ShellWord): LexedWord | undefined;
61
85
  /** Read `source` as the tightening reader must: every program run the text could perform, or why it cannot be read. */
62
86
  export declare function readShellCommand(source: string): ShellCommandShape;
63
87
  /** Every program run of `shape` can be NAMED from the text: no segment is unreadable, no peel stopped,