@sema-agent/core 7.49.4 → 7.50.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/CHANGELOG.md +152 -0
  2. package/dist/agents/observer.js +3 -10
  3. package/dist/agents/peer-directory.d.ts +8 -3
  4. package/dist/agents/peer-directory.js +5 -3
  5. package/dist/agents/subagent-steps.d.ts +6 -14
  6. package/dist/agents/subagent-steps.js +3 -17
  7. package/dist/brain/stream-shared.d.ts +1 -1
  8. package/dist/core/allow-entry.d.ts +1 -1
  9. package/dist/core/allow-entry.js +1 -1
  10. package/dist/core/ask-origin.d.ts +15 -11
  11. package/dist/core/ask-origin.js +6 -14
  12. package/dist/core/auto-mode-prompt.js +6 -2
  13. package/dist/core/checkpoint-store.d.ts +6 -4
  14. package/dist/core/checkpoint-store.js +1 -1
  15. package/dist/core/context-edit.d.ts +2 -2
  16. package/dist/core/decision-receipt.d.ts +2 -2
  17. package/dist/core/decision-receipt.js +30 -11
  18. package/dist/core/delegation-frames.d.ts +66 -18
  19. package/dist/core/delegation-frames.js +22 -0
  20. package/dist/core/engine-notice.d.ts +7 -5
  21. package/dist/core/frame-settlement.d.ts +6 -1
  22. package/dist/core/gate-fold.js +19 -13
  23. package/dist/core/gate-lanes.js +12 -11
  24. package/dist/core/gate-outcome.d.ts +2 -2
  25. package/dist/core/governance-codes.js +2 -0
  26. package/dist/core/hooks.d.ts +28 -20
  27. package/dist/core/human-input-projection.js +2 -0
  28. package/dist/core/lsp-diagnostics.d.ts +3 -0
  29. package/dist/core/lsp-diagnostics.js +6 -0
  30. package/dist/core/lsp-session.d.ts +39 -17
  31. package/dist/core/lsp-session.js +118 -58
  32. package/dist/core/lsp.d.ts +83 -3
  33. package/dist/core/lsp.js +146 -16
  34. package/dist/core/mcp.js +2 -1
  35. package/dist/core/media-byte-cap.d.ts +14 -0
  36. package/dist/core/media-byte-cap.js +15 -3
  37. package/dist/core/memory-engine/consolidation-driver.d.ts +23 -1
  38. package/dist/core/memory-engine/consolidation-driver.js +54 -6
  39. package/dist/core/memory-engine/consolidation-write-gate.d.ts +46 -0
  40. package/dist/core/memory-engine/consolidation-write-gate.js +82 -0
  41. package/dist/core/memory-engine/consolidation.d.ts +17 -0
  42. package/dist/core/memory-engine/consolidation.js +1 -0
  43. package/dist/core/memory-engine/distiller.d.ts +8 -2
  44. package/dist/core/memory-engine/distiller.js +8 -6
  45. package/dist/core/memory-engine/engine.d.ts +37 -4
  46. package/dist/core/memory-engine/engine.js +127 -38
  47. package/dist/core/memory-engine/file-backend.d.ts +8 -3
  48. package/dist/core/memory-engine/file-backend.js +44 -16
  49. package/dist/core/memory-engine/index.d.ts +2 -2
  50. package/dist/core/memory-engine/index.js +1 -1
  51. package/dist/core/memory-engine/landing-admission.d.ts +65 -0
  52. package/dist/core/memory-engine/landing-admission.js +68 -0
  53. package/dist/core/memory-engine/types.d.ts +23 -0
  54. package/dist/core/mode-policy.d.ts +3 -2
  55. package/dist/core/mode-policy.js +2 -0
  56. package/dist/core/permission-rule-model.d.ts +19 -13
  57. package/dist/core/permission-rule-model.js +7 -4
  58. package/dist/core/permission-rule-provider.d.ts +11 -0
  59. package/dist/core/permission-rules.d.ts +1 -1
  60. package/dist/core/permission-rules.js +5 -2
  61. package/dist/core/persisted-rule-arms.d.ts +15 -7
  62. package/dist/core/persisted-rule-arms.js +13 -6
  63. package/dist/core/posix-mode-face.d.ts +10 -0
  64. package/dist/core/posix-mode-face.js +3 -0
  65. package/dist/core/reminder-mint.d.ts +5 -0
  66. package/dist/core/reminder-mint.js +1 -0
  67. package/dist/core/retired-keys.d.ts +8 -0
  68. package/dist/core/retired-keys.js +4 -0
  69. package/dist/core/rule-lane-reads.d.ts +4 -4
  70. package/dist/core/rule-lane-reads.js +10 -1
  71. package/dist/core/runner/construction-doors.d.ts +10 -4
  72. package/dist/core/runner/construction-doors.js +25 -2
  73. package/dist/core/runner/contracts.d.ts +20 -3
  74. package/dist/core/runner/env-capability-census.js +1 -1
  75. package/dist/core/runner/gate-exit.js +2 -1
  76. package/dist/core/runner/memory-consolidation-driver.d.ts +6 -4
  77. package/dist/core/runner/memory-consolidation-driver.js +5 -2
  78. package/dist/core/runner/memory-consolidation-write-judge.d.ts +17 -0
  79. package/dist/core/runner/memory-consolidation-write-judge.js +14 -0
  80. package/dist/core/runner/permission-rule-lanes.d.ts +16 -22
  81. package/dist/core/runner/permission-rule-lanes.js +13 -43
  82. package/dist/core/runner/person-path-rows.d.ts +244 -0
  83. package/dist/core/runner/person-path-rows.js +387 -0
  84. package/dist/core/runner/prepare-acquire-reconcile.js +8 -1
  85. package/dist/core/runner/prepare-artifact.d.ts +5 -3
  86. package/dist/core/runner/prepare-artifact.js +2 -8
  87. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
  88. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  89. package/dist/core/runner/prepare-context-lane.js +2 -2
  90. package/dist/core/runner/prepare-gate-stations.d.ts +3 -3
  91. package/dist/core/runner/prepare-gate-stations.js +1 -1
  92. package/dist/core/runner/prepare-hands-readface.d.ts +19 -10
  93. package/dist/core/runner/prepare-hands-readface.js +57 -43
  94. package/dist/core/runner/prepare-lsp.d.ts +15 -13
  95. package/dist/core/runner/prepare-lsp.js +11 -11
  96. package/dist/core/runner/prepare-policy-chain.d.ts +8 -4
  97. package/dist/core/runner/prepare-policy-chain.js +6 -7
  98. package/dist/core/runner/prepare-run-refs.js +1 -1
  99. package/dist/core/runner/prepare-task.js +24 -16
  100. package/dist/core/runner/prepare-turn-wiring.d.ts +13 -6
  101. package/dist/core/runner/prepare-turn-wiring.js +19 -8
  102. package/dist/core/runner/read-root-grant.d.ts +1 -25
  103. package/dist/core/runner/read-root-grant.js +1 -17
  104. package/dist/core/runner/retired-runner-deps.d.ts +2 -2
  105. package/dist/core/runner/run-brain-sinks.d.ts +1 -5
  106. package/dist/core/runner/run-brain-sinks.js +2 -4
  107. package/dist/core/runner/run-harness-handlers.js +5 -4
  108. package/dist/core/runner/run-identity-wiring.d.ts +4 -4
  109. package/dist/core/runner/run-identity-wiring.js +10 -1
  110. package/dist/core/runner/run-leg.d.ts +1 -1
  111. package/dist/core/runner/run-leg.js +22 -9
  112. package/dist/core/runner/run-settle-and-teardown.d.ts +0 -4
  113. package/dist/core/runner/run-settle-and-teardown.js +7 -4
  114. package/dist/core/runner/run-terminal-adoption.d.ts +1 -1
  115. package/dist/core/runner/run-terminal-adoption.js +1 -1
  116. package/dist/core/runner/run-turn-boundary.js +2 -2
  117. package/dist/core/runner/runtask.js +8 -8
  118. package/dist/core/runner/session-file-state-replay.d.ts +14 -4
  119. package/dist/core/runner/session-file-state-replay.js +8 -0
  120. package/dist/core/runner/stream-steer-verb.js +3 -0
  121. package/dist/core/runner/tool-end-body.d.ts +5 -4
  122. package/dist/core/runner/tool-end-body.js +4 -4
  123. package/dist/core/runner/tool-output-projection.d.ts +6 -4
  124. package/dist/core/runner/tool-output-projection.js +66 -47
  125. package/dist/core/runner-deps.d.ts +6 -3
  126. package/dist/core/shared-memory/types.d.ts +2 -2
  127. package/dist/core/shell-read-silence.d.ts +23 -0
  128. package/dist/core/shell-read-silence.js +18 -0
  129. package/dist/core/synthetic-read-verdict.d.ts +80 -21
  130. package/dist/core/synthetic-read-verdict.js +89 -18
  131. package/dist/core/task-event.d.ts +113 -25
  132. package/dist/core/task-spec.d.ts +44 -0
  133. package/dist/core/task-tool-shape.d.ts +1 -1
  134. package/dist/core/tool-catalog-entries.js +16 -2
  135. package/dist/core/tool-policy.d.ts +25 -15
  136. package/dist/core/tool-policy.js +9 -13
  137. package/dist/core/trace.d.ts +3 -2
  138. package/dist/core/types.d.ts +3 -3
  139. package/dist/core/types.js +1 -1
  140. package/dist/core/untrusted-text.d.ts +2 -2
  141. package/dist/core/user-attachment.d.ts +164 -0
  142. package/dist/core/user-attachment.js +437 -0
  143. package/dist/engine/compaction/compaction.js +10 -3
  144. package/dist/engine/compaction/utils.js +6 -0
  145. package/dist/engine/execution-env/node-execution-env.d.ts +19 -1
  146. package/dist/engine/execution-env/node-execution-env.js +127 -1
  147. package/dist/engine/harness/agent-harness.d.ts +12 -2
  148. package/dist/engine/harness/agent-harness.js +15 -3
  149. package/dist/engine/harness/types.d.ts +21 -0
  150. package/dist/engine/llm/content-block-render.d.ts +15 -0
  151. package/dist/engine/llm/content-block-render.js +10 -0
  152. package/dist/engine/llm/index.d.ts +1 -0
  153. package/dist/engine/llm/index.js +1 -0
  154. package/dist/engine/llm/types.d.ts +80 -4
  155. package/dist/engine/llm/types.js +43 -1
  156. package/dist/engine/session/import-validate.js +2 -0
  157. package/dist/index.d.ts +6 -4
  158. package/dist/index.js +2 -1
  159. package/dist/internal/harness-types.d.ts +1 -1
  160. package/dist/internal/llm.d.ts +2 -2
  161. package/dist/internal/llm.js +1 -1
  162. package/dist/orchestration/workflow.d.ts +4 -5
  163. package/dist/orchestration/workflow.js +2 -2
  164. package/dist/prompts/default.d.ts +1 -1
  165. package/dist/stores/file/permission-rule-store.d.ts +13 -1
  166. package/dist/stores/file/permission-rule-store.js +40 -21
  167. package/dist/tools/artifact/artifact-tool.js +1 -1
  168. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  169. package/dist/tools/fs/dir-entries.d.ts +4 -10
  170. package/dist/tools/fs/dir-entries.js +4 -11
  171. package/dist/tools/fs/file-read-check.js +3 -3
  172. package/dist/tools/fs/fs-pdf.js +2 -4
  173. package/dist/tools/fs/fs-read.d.ts +47 -4
  174. package/dist/tools/fs/fs-read.js +63 -19
  175. package/dist/tools/fs/fs-search-tools.d.ts +3 -3
  176. package/dist/tools/fs/fs-search-tools.js +7 -2
  177. package/dist/tools/fs/fs-write.d.ts +1 -1
  178. package/dist/tools/fs/fs-write.js +7 -6
  179. package/dist/tools/fs/index.d.ts +7 -1
  180. package/dist/tools/fs/index.js +56 -34
  181. package/dist/tools/fs/read-deny-home.d.ts +19 -1
  182. package/dist/tools/fs/read-deny-home.js +32 -22
  183. package/dist/tools/fs/read-deny.d.ts +14 -0
  184. package/dist/tools/fs/read-deny.js +8 -0
  185. package/dist/tools/fs/repo-map.d.ts +3 -2
  186. package/dist/tools/fs/repo-map.js +11 -3
  187. package/dist/tools/fs/safety.d.ts +41 -2
  188. package/dist/tools/fs/safety.js +12 -3
  189. package/dist/tools/fs/shell-read-check.d.ts +12 -3
  190. package/dist/tools/fs/shell-read-check.js +28 -15
  191. package/dist/tools/fs/shell-read-device.d.ts +1 -1
  192. package/dist/tools/fs/shell-read-judge.d.ts +9 -4
  193. package/dist/tools/fs/shell-read-judge.js +1 -1
  194. package/dist/tools/loop-tick.d.ts +2 -2
  195. package/package.json +3 -1
  196. package/test/export-surface.snapshot.json +39 -1
  197. package/wire-changes/core@7.49.5.json +104 -0
  198. package/wire-changes/core@7.50.0.json +1699 -0
  199. package/dist/core/runner/persisted-path-rows.d.ts +0 -88
  200. package/dist/core/runner/persisted-path-rows.js +0 -108
@@ -0,0 +1,244 @@
1
+ /**
2
+ * The person's persisted rules as ONE run reads them.
3
+ *
4
+ * ## One read per adjudicated call
5
+ *
6
+ * A call is judged against ONE read of the store, pinned to its `toolCallId` ({@link PersonPathRows.pin}): whichever of the
7
+ * call's consumers asks first — the read station's judgment, the org layer, the personal rule lane — takes the read, the
8
+ * others are handed the same answer, and the tables the call's tool judges with when it executes are compiled from it
9
+ * ({@link PathTablesFacade.forCall}). A call is an attempt the leg's occurrence table holds; the pin is released when the
10
+ * table no longer holds that attempt. A judgment that is no call of the leg (a visibility probe's fixed id, a gate with no
11
+ * leg) keeps nothing: its rule lanes take a read of their own, its read station the run's latest tables.
12
+ *
13
+ * ## The run's unit, one entry per partition
14
+ *
15
+ * Every read's outcome lands in the run's unit, per personal partition (the durable one — its user and project rows — and
16
+ * the session one), and only where it is newer, by the order the reads STARTED: a read that answers late never replaces
17
+ * a later one. The prepare read is the unit's first write. A reader that is not a call (the compaction summary's working-file
18
+ * reader, the user-attachment seat's reader, the Artifact tool's local read, a read root's grant eligibility, the classifier's table, a language server's session read)
19
+ * reads the unit's newest tables: while the rows the unit last took are compiling it waits for them (within
20
+ * {@link PATH_TABLES_READY_BOUND_MS}), and a read of tables still compiling, or that could not be compiled, refuses —
21
+ * never the tables of rows the unit has since replaced.
22
+ *
23
+ * ## One failure law, every consumer
24
+ *
25
+ * A partition a read could not read (it failed, or the whole read failed or did not answer in time) contributes the deny
26
+ * and ask rows of the run's last successful read of that partition, and none of its allow rows (a `Read(//dir/**)` read
27
+ * directory among them): what could not be read widens nothing, and what was read before still protects. A partition the
28
+ * read did read contributes its rows as read, both directions. The cost: a deny row the person just removed keeps
29
+ * refusing while its partition cannot be read — the run's one notice says so.
30
+ *
31
+ * ## One table facade
32
+ *
33
+ * The read deny table, the write deny table and the read directories are compiled from the rows a read stands on, once
34
+ * per distinct content (row text × scope, which fixes the anchors; the directories), and held behind one facade whose
35
+ * every reader takes the call's tables or the unit's latest.
36
+ */
37
+ import type { EffectivePermissionRules, PermissionRuleStoreProvider, PersonRowsRead } from "../permission-rule-provider.js";
38
+ import { type PathRuleBases } from "../permission-rule-model.js";
39
+ import { type BoundPathRule, type HeadLeads } from "../path-rule-binding.js";
40
+ import type { RunnerDeps } from "../runner-deps.js";
41
+ import { type PathRuleDenyRow, type ReadDenyBuiltinConfig, type ReadDenyEntry, type ReadDenySeat, type WriteDenyMatcher } from "../../tools/fs/read-deny.js";
42
+ import { type HomeViewSource, type PlacedReadDenyMatcher } from "../../tools/fs/read-deny-home.js";
43
+ import { type ShellReadCheck } from "../../tools/fs/shell-read-check.js";
44
+ import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
45
+ /**
46
+ * How long a read waits for the store before its call is judged by the failure law: one second inside the rule lanes' own
47
+ * wait (`ORG_ADJUDICATION_TIMEOUT_MS`, the gate's bound on each lane), so the law's answer — the deny and ask rows last
48
+ * read — reaches a lane before the lane's wait ends and reads its absence as no rule at all.
49
+ */
50
+ export declare const PERSON_READ_BOUND_MS: number;
51
+ /** The one bound on path tables becoming ready: a compile settles or fails within it, and a reader waits for its tables at
52
+ * most this long (a call's own, or the unit's newest) before it reads them — and is refused if they are not ready. */
53
+ export declare const PATH_TABLES_READY_BOUND_MS = 30000;
54
+ /** The person's rows a run's path tables take: their face-keyed path deny rows, bound at their anchors, and the
55
+ * directories of their `Read(//dir/**)` allow rows (lexical, absolute). */
56
+ export interface PersonTableRows {
57
+ readonly denyRows: readonly BoundPathRule[];
58
+ readonly readDirectories: readonly string[];
59
+ }
60
+ /** A run's path tables, compiled over one set of the person's rows beside the leg's own. */
61
+ export interface PathTables {
62
+ /** The read deny table, placed at its homes. */
63
+ readonly readDeny: PlacedReadDenyMatcher;
64
+ /** The write deny table, with the read table's home views. */
65
+ readonly writeDeny: WriteDenyMatcher & {
66
+ readonly homeViews: HomeViewSource;
67
+ };
68
+ /** The person's read directories, canonical. */
69
+ readonly readDirectories: readonly string[];
70
+ /** The rule rows both tables were compiled over (the leg's own and the person's, as bound — before their twins where
71
+ * their heads lead): what the tables say they guard. */
72
+ readonly ruleRows: readonly PathRuleDenyRow[];
73
+ }
74
+ /** A read-root set read per judgment, and the one a call judges with (`forCall`: its pinned read's, or the latest); `ready`
75
+ * settles once the set a reader that is no call takes is compiled ({@link PersonPathRows.tablesReady}). */
76
+ export type ReadRootsSeat = (() => readonly string[]) & {
77
+ forCall(toolCallId: string | undefined): () => readonly string[];
78
+ ready(signal?: AbortSignal): Promise<void>;
79
+ };
80
+ /** The read-root seat of a leg with no read face: no roots, ready. */
81
+ export declare const EMPTY_READ_ROOTS: ReadRootsSeat;
82
+ /** The tables, a call's or the unit's newest: `forCall(undefined)` and a call holding no pinned read take the newest — a
83
+ * read of them while they compile, or after they could not be compiled, throws (the check refuses). */
84
+ export interface PathTablesFacade {
85
+ /** The tables ONE call judges with: its pinned read's, or the unit's newest (an unknown or released call). */
86
+ forCall(toolCallId: string | undefined): PathTables;
87
+ /** The read deny table: each judgment reads the unit's latest; `forCall` hands a call its own. */
88
+ readonly readDeny: PlacedReadDenyMatcher & {
89
+ forCall(toolCallId: string | undefined): PlacedReadDenyMatcher;
90
+ };
91
+ /** The write deny table, the same way. */
92
+ readonly writeDeny: WriteDenyMatcher & {
93
+ readonly homeViews: HomeViewSource;
94
+ forCall(toolCallId: string | undefined): WriteDenyMatcher & {
95
+ readonly homeViews: HomeViewSource;
96
+ };
97
+ };
98
+ /** The person's read directories (canonical), a call's or the unit's latest. */
99
+ readDirectories(toolCallId?: string): readonly string[];
100
+ /** The rule rows of the unit's latest tables ({@link PathTables.ruleRows}). */
101
+ ruleRows(): readonly PathRuleDenyRow[];
102
+ }
103
+ /** The one reader of a run's person's store. */
104
+ export interface PersonPathRows {
105
+ /** Whether the run has a person whose rows a read could carry (a durable partition, and an owner). */
106
+ readonly holdsPersonRows: boolean;
107
+ /** The prepare read — the unit's first write. `undefined`, synchronously, when there is no person's rows to read. */
108
+ prime(): Promise<void> | undefined;
109
+ /** The rows of a call's one read — a call the leg's occurrence table holds: taken by its first consumer, handed to every
110
+ * other until the table no longer holds that attempt; any other judgment (no leg, or an id the table does not hold — a
111
+ * visibility probe) takes a read of its own, kept by nobody. Bounded ({@link PERSON_READ_BOUND_MS}); never rejects. */
112
+ pin(toolCallId: string): Promise<PersonRowsRead>;
113
+ /** Settles once the tables a reader takes are compiled: a pinned call's own; any other reader's (no id, or a call holding
114
+ * no pin), the unit's newest — through every change that lands meanwhile, within {@link PATH_TABLES_READY_BOUND_MS} —
115
+ * or at `signal` or the run's cut ({@link PersonPathRowsConfig.signal}). Never rejects: a reader whose wait was cut
116
+ * checks its own signal and refuses; tables still compiling, or that could not be compiled, refuse at the read
117
+ * ({@link PathTablesFacade.forCall}). */
118
+ tablesReady(toolCallId?: string, signal?: AbortSignal): Promise<void>;
119
+ /** Whether `toolCallId` is a call of the leg (an attempt its occurrence table holds): the judgments that read a call's
120
+ * one read. A read station's judgment that is no call reads the run's latest tables and takes no read. */
121
+ holds(toolCallId: string): boolean;
122
+ /** Whether a partition of the rows a judgment stands on could not be read — a pinned call's read's, or the run's latest
123
+ * read of each partition: there "no row forbids this" cannot be told from "a row it cannot see does" (`false` for a run
124
+ * with no person's rows). */
125
+ unreadFor(toolCallId?: string): boolean;
126
+ /** The tables over the unit's latest and every call's read, compiled by `compile` (the leg's own rows beside the
127
+ * person's): compiled now from the unit's latest. */
128
+ attachTables(compile: (person: PersonTableRows) => Promise<PathTables>): Promise<PathTablesFacade>;
129
+ }
130
+ export interface PersonPathRowsConfig {
131
+ provider: PermissionRuleStoreProvider | undefined;
132
+ localOwnerDeclared: boolean;
133
+ principal: string | undefined;
134
+ sessionId: string;
135
+ /** The leg's own root: which project rows apply (either spelling of it — {@link projectScopeIdentity}). */
136
+ root: string;
137
+ rootCanonical: string | undefined;
138
+ /** The leg's resolution of locations (a project row's scope root, where it leads). */
139
+ leads: HeadLeads;
140
+ /** The anchors of the global / session rows and of `~/`: the chain's top leg's root and home. */
141
+ chainTop: PathRuleBases;
142
+ /** The leg's calls: a call is pinned to its attempt here, and released when the table no longer holds that attempt. */
143
+ occurrences?: {
144
+ get(toolCallId: string): unknown;
145
+ };
146
+ /** An engine defect the run must hear of beyond its trace (a table that could not be compiled). */
147
+ onDefect?: (err: Error) => void;
148
+ /** The operator's line for each read the store could not answer, each disclosure line a read carries, each row a table
149
+ * could not anchor. */
150
+ onDisclosure: (message: string) => void;
151
+ /** The run's one notice for a store that could not be read ({@link ruleStoreUnreadableNotice}). */
152
+ onUnread: (at: "prepare" | "call", sources?: EffectivePermissionRules["unreadableSources"]) => void;
153
+ /** The run's one notice for rows left out ({@link ruleRowsDroppedNotice}). */
154
+ onRowsDropped: (rows: EffectivePermissionRules["droppedRows"]) => void;
155
+ /** The run's cut: a read it interrupts is abandoned quietly — a cancelled wait is no fact about the store — and every wait
156
+ * on the tables ends at it. */
157
+ signal?: AbortSignal;
158
+ }
159
+ export declare function createPersonPathRows(cfg: PersonPathRowsConfig): PersonPathRows;
160
+ /** The compile of a run's path tables over the leg's own rows beside one set of the person's: every row also judged where
161
+ * its head leads in this environment; the home-anchored and rooted rows placed at their homes and where they lead (the
162
+ * host's declared configuration root among them); the write face over the same rows with the read table's home views;
163
+ * the person's read directories canonicalized by `canonicalize` (the leg's admission of a declared read root). */
164
+ export declare function pathTablesCompile(seats: {
165
+ readonly env: ExecutionEnv;
166
+ readonly additions: readonly ReadDenyEntry[];
167
+ readonly builtin: ReadDenyBuiltinConfig;
168
+ readonly legRows: readonly PathRuleDenyRow[];
169
+ readonly leads: HeadLeads;
170
+ readonly configRoot: string | undefined;
171
+ readonly canonicalize: (dirs: readonly string[]) => Promise<readonly string[]>;
172
+ }): (person: PersonTableRows) => Promise<PathTables>;
173
+ /** The read face's root set over the leg's own roots (`own`, read per judgment) and the person's read directories — a
174
+ * call's own (`forCall`) or the run's latest. A person's directory that admits any part of the local artifact store
175
+ * (`store`, once known) is no read root: the store rule an in-flight grant meets; `onHeld` hears each such directory once. */
176
+ export declare function readRootsOver(seats: {
177
+ readonly own: () => readonly string[];
178
+ readonly tables: PathTablesFacade;
179
+ readonly ready: PersonPathRows["tablesReady"];
180
+ readonly store: () => string | undefined;
181
+ readonly onHeld: (dir: string, store: string) => void;
182
+ }): ReadRootsSeat;
183
+ export declare function denySeatsOf(t: PathTables): {
184
+ readonly read: ReadDenySeat;
185
+ readonly write: {
186
+ readonly writeDenyMatch?: (p: string) => string | null;
187
+ };
188
+ };
189
+ /**
190
+ * The leg's read stations as ONE gate input, each judgment on its call's pinned read of the person's rules: a station is
191
+ * a call's first consumer of the person's rows (it sits in the fold, ahead of the rule lanes), so it takes the call's
192
+ * read before it judges — the read every later consumer of the call is handed — and judges with that read's tables. A
193
+ * judgment that is no call of the leg (no id, or one the leg does not hold) takes no read and judges with the run's
194
+ * newest tables once they are ready, as every reader that is no call does; a judged call the station finds nothing to
195
+ * read in is vouched for, as it would have been. (A partition of those rows that could not be read reaches the station on
196
+ * the boundary it judges with — {@link PersonPathRows.unreadFor} — where it vouches for nothing.)
197
+ */
198
+ export declare function onPinnedRead(rows: PersonPathRows, stations: ReadonlyArray<{
199
+ readonly check: ShellReadCheck;
200
+ readonly tools: ReadonlySet<string>;
201
+ }>): ShellReadCheck | undefined;
202
+ /** A tool that holds the run's tables, run once the tables it reads are compiled (or the call is cancelled) — the tool
203
+ * reads its call's own (`seatForCall`) at the start of its execution; a call no gate consumer pinned reads the run's
204
+ * newest. The wait is bounded ({@link PATH_TABLES_READY_BOUND_MS}). */
205
+ export declare function afterCallTables(rows: PersonPathRows, tool: AgentTool): AgentTool;
206
+ /** A reader for one task's prepare: the deployment's store and owner, its notices on the run's one notice seats (`notices`,
207
+ * minted once per run: every reader of the run tells through them), its lines on the run's trace, cut by `signals` (the
208
+ * run's own; none for the judgment asked after the run has ended), released with the leg's calls. */
209
+ export declare function personPathRowsOfTask(t: {
210
+ readonly deps: Pick<RunnerDeps, "permissionRuleStore" | "localOwnerRules" | "tracer" | "onError">;
211
+ readonly notices: Pick<PersonPathRowsConfig, "onUnread" | "onRowsDropped">;
212
+ readonly principal: string | undefined;
213
+ readonly ids: {
214
+ readonly sessionId: string;
215
+ readonly runId: string;
216
+ readonly taskId: string;
217
+ };
218
+ readonly root: string;
219
+ readonly rootCanonical: string | undefined;
220
+ readonly leads: HeadLeads;
221
+ readonly chainTop: PathRuleBases;
222
+ readonly occurrences: {
223
+ get(toolCallId: string): unknown;
224
+ };
225
+ readonly signals: readonly AbortSignal[];
226
+ }): PersonPathRows;
227
+ /**
228
+ * The run's ONE `permission.rule_store_unavailable` notice (audience user): the person's rules — the whole store, or the
229
+ * sources of it whose partition could not be read — could not be read; minted by whichever read found it first (the
230
+ * prepare read, or a call's), once per run however many reads fail after it. The trace keeps every read's line.
231
+ */
232
+ export declare function ruleStoreUnreadableNotice(onNotice: RunnerDeps["onNotice"], ids: {
233
+ readonly sessionId: string;
234
+ readonly runId: string;
235
+ }): (at: "prepare" | "call", sources?: EffectivePermissionRules["unreadableSources"]) => void;
236
+ /**
237
+ * The run's ONE `permission.rule_row_dropped` notice (audience user): the person's store served project-scoped rows whose
238
+ * scope root is not a complete absolute path, left out of the checks; minted by whichever read met them first, once per
239
+ * run. The sentence names no path; the rows (bounded) ride the detail. The trace keeps every read's line.
240
+ */
241
+ export declare function ruleRowsDroppedNotice(onNotice: RunnerDeps["onNotice"], ids: {
242
+ readonly sessionId: string;
243
+ readonly runId: string;
244
+ }): (rows: EffectivePermissionRules["droppedRows"]) => void;
@@ -0,0 +1,387 @@
1
+ import { ownerStoreOf } from "../permission-rule-provider.js";
2
+ import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
3
+ import { PATH_RULE_FACES, READ_RULE_TOOL, eligiblePersisted } from "../permission-rule-model.js";
4
+ import { bindPathRules, persistedRowAnchors, projectScopeIdentity } from "../path-rule-binding.js";
5
+ import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
6
+ import { thrownText } from "../thrown-value.js";
7
+ import { inlineUntrusted } from "../untrusted-text.js";
8
+ import { deliverEngineNotice } from "../engine-notice.js";
9
+ import { compileReadDeny, compileWriteDeny, readDenySeat, rowsWhereTheyLead } from "../../tools/fs/read-deny.js";
10
+ import { carryHomeViews, placeReadDenyAtHome, readDenyOver } from "../../tools/fs/read-deny-home.js";
11
+ import { composeReadChecks } from "../../tools/fs/file-read-check.js";
12
+ import { readStationMemberOf } from "../../tools/fs/shell-read-check.js";
13
+ import { rootAdmittingArtifactStore } from "../../tools/fs/safety.js";
14
+ import { emitTrace } from "../trace.js";
15
+ const PARTITIONS = ["session", "durable"];
16
+ const partitionOf = (source) => (source === "session" ? "session" : "durable");
17
+ const tightening = (rule) => rule.behavior === "deny" || rule.behavior === "ask";
18
+ export const PERSON_READ_BOUND_MS = ORG_ADJUDICATION_TIMEOUT_MS - 1_000;
19
+ export const PATH_TABLES_READY_BOUND_MS = DEFAULT_GATE_AWAIT_TIMEOUT_MS;
20
+ const COMPILED_TABLES_KEPT = 8;
21
+ export const EMPTY_READ_ROOTS = Object.assign(() => [], { forCall: () => () => [], ready: () => Promise.resolve() });
22
+ export function createPersonPathRows(cfg) {
23
+ const provider = cfg.provider;
24
+ const anonymous = cfg.principal === undefined || cfg.principal === "";
25
+ const holdsPersonRows = provider !== undefined && provider.partitions.durable && (!anonymous || cfg.localOwnerDeclared);
26
+ const partitions = PARTITIONS.filter((p) => (p === "durable" ? provider?.partitions.durable === true : provider?.partitions.session === true));
27
+ const unit = { durable: { failedSeq: 0 }, session: { failedSeq: 0 } };
28
+ let seqs = 0;
29
+ const pins = new Map();
30
+ let compile;
31
+ const compiled = new Map();
32
+ let unitTables;
33
+ const unanchoredSaid = new Set();
34
+ const land = (o) => {
35
+ if (o.cut === true)
36
+ return;
37
+ let changed = false;
38
+ for (const p of partitions) {
39
+ const cell = unit[p];
40
+ const okSeq = cell.ok?.seq ?? 0;
41
+ if (o.failed.has(p) || o.view === undefined) {
42
+ if (o.seq > okSeq && o.seq > cell.failedSeq) {
43
+ cell.failedSeq = o.seq;
44
+ changed = true;
45
+ }
46
+ }
47
+ else if (o.seq >= cell.failedSeq && o.seq > okSeq) {
48
+ cell.ok = { seq: o.seq, rows: o.view.rules.filter((r) => partitionOf(r.source) === p) };
49
+ changed = true;
50
+ }
51
+ }
52
+ if (changed && compile !== undefined)
53
+ refreshUnitTables();
54
+ };
55
+ const lawed = (p, read) => read !== undefined ? read : (unit[p].ok?.rows ?? []).filter(tightening);
56
+ const latestUnread = () => holdsPersonRows && partitions.some((p) => unit[p].failedSeq > (unit[p].ok?.seq ?? 0));
57
+ const unitLatestRules = () => partitions.flatMap((p) => {
58
+ const cell = unit[p];
59
+ return lawed(p, cell.ok !== undefined && cell.failedSeq <= cell.ok.seq ? cell.ok.rows : undefined);
60
+ });
61
+ const readOnce = (at) => {
62
+ const seq = ++seqs;
63
+ if (provider === undefined)
64
+ return Promise.resolve({ rules: [] });
65
+ let raw;
66
+ try {
67
+ raw = ownerStoreOf(provider, cfg.principal, cfg.localOwnerDeclared).effective({ sessionId: cfg.sessionId });
68
+ }
69
+ catch (err) {
70
+ raw = Promise.reject(err);
71
+ }
72
+ const outcomeOf = (view) => ({ seq, view: holdsPersonRows ? view : { ...view, rules: [] }, failed: holdsPersonRows ? failedOf(view) : new Set() });
73
+ void raw.then((view) => {
74
+ for (const line of view.disclosures)
75
+ cfg.onDisclosure(line);
76
+ cfg.onRowsDropped(view.droppedRows);
77
+ if (holdsPersonRows && view.unreadable !== undefined)
78
+ announce(at, view.unreadable, view.unreadableSources);
79
+ land(outcomeOf(view));
80
+ }, () => undefined);
81
+ return withGateTimeout(raw, PERSON_READ_BOUND_MS, cfg.signal, "personRules").then((view) => readOf(outcomeOf(view)), (err) => {
82
+ const cut = gateWaitFailureOf(err) === "aborted";
83
+ const outcome = { seq, failed: new Set(partitions), why: thrownText(err), ...(cut ? { cut: true } : {}) };
84
+ land(outcome);
85
+ if (holdsPersonRows && !cut)
86
+ announce(at, outcome.why);
87
+ return readOf(outcome);
88
+ });
89
+ };
90
+ const failedOf = (view) => view.unreadable !== undefined && (view.unreadableSources ?? []).length === 0 ? new Set(partitions) : new Set((view.unreadableSources ?? []).map(partitionOf).filter((p) => partitions.includes(p)));
91
+ const defect = (message) => {
92
+ cfg.onDisclosure(message);
93
+ cfg.onDefect?.(new Error(message));
94
+ };
95
+ const readOf = (o) => {
96
+ const rules = partitions.flatMap((p) => lawed(p, o.view !== undefined && !o.failed.has(p) ? o.view.rules.filter((r) => partitionOf(r.source) === p) : undefined));
97
+ const unreadable = o.view !== undefined ? o.view.unreadable : (o.why ?? "the store did not answer");
98
+ return { rules, ...(o.view !== undefined ? { org: o.view.org } : {}), ...(unreadable !== undefined ? { unreadable } : {}) };
99
+ };
100
+ const announce = (at, why, sources) => {
101
+ cfg.onDisclosure(`the person's persisted rules could not be read (${at === "prepare" ? "when the run started" : "for a call"}${sources !== undefined && sources.length > 0 ? `; ${sourcesWord(sources)} rows` : ""}): their allow rows take no part until a read succeeds, the deny and ask rows of the run's last successful read still apply — ${why}`);
102
+ cfg.onUnread(at, sources);
103
+ };
104
+ const tableRowsOf = async (rules) => {
105
+ if (rules.length === 0)
106
+ return { denyRows: [], readDirectories: [] };
107
+ const scopeIdentity = await projectScopeIdentity(rules, cfg.rootCanonical, cfg.leads);
108
+ const call = { tool: READ_RULE_TOOL, cwd: cfg.root, sessionId: cfg.sessionId, ...(scopeIdentity !== undefined ? { scopeIdentity } : {}) };
109
+ const readDirectories = [];
110
+ const denySpecs = [];
111
+ for (const rule of rules) {
112
+ if (rule.status !== "live")
113
+ continue;
114
+ if (rule.behavior === "allow" && rule.tool === READ_RULE_TOOL && rule.match === "subpath" && eligiblePersisted(rule, call, "allow")) {
115
+ if (!readDirectories.includes(rule.command))
116
+ readDirectories.push(rule.command);
117
+ continue;
118
+ }
119
+ const faces = PATH_RULE_FACES[rule.tool]?.deny;
120
+ if (rule.behavior !== "deny" || faces === undefined || (rule.match !== "path" && rule.match !== "subpath") || !eligiblePersisted(rule, { ...call, tool: rule.tool }, "deny"))
121
+ continue;
122
+ denySpecs.push({ parsed: rule, ruleText: rule.rule, faces, rule });
123
+ }
124
+ const denyRows = [];
125
+ for (const row of bindPathRules(denySpecs, (spec) => persistedRowAnchors(spec.rule.scope, cfg.chainTop))) {
126
+ if (row.unanchored !== undefined) {
127
+ if (!unanchoredSaid.has(row.rule)) {
128
+ unanchoredSaid.add(row.rule);
129
+ cfg.onDisclosure(`the persisted deny rule ${inlineUntrusted(row.rule, 200)} needs a ${row.unanchored.join(" / ")} base this task does not declare — the shell's file commands and the search tools do not judge it on this leg (the gate asks)`);
130
+ }
131
+ continue;
132
+ }
133
+ denyRows.push(row);
134
+ }
135
+ return { denyRows, readDirectories };
136
+ };
137
+ const tablesOf = (rules) => {
138
+ const make = compile;
139
+ if (make === undefined)
140
+ return Promise.resolve(undefined);
141
+ const compiling = (async () => {
142
+ const person = await tableRowsOf(rules);
143
+ const key = JSON.stringify([person.denyRows.map((r) => [r.rule, scopeKey(r.spec.rule)]), person.readDirectories]);
144
+ let tables = compiled.get(key);
145
+ if (tables === undefined) {
146
+ tables = withGateTimeout(make(person), PATH_TABLES_READY_BOUND_MS, undefined, "pathTablesCompile");
147
+ compiled.set(key, tables);
148
+ if (compiled.size > COMPILED_TABLES_KEPT)
149
+ compiled.delete(compiled.keys().next().value);
150
+ tables.catch(() => compiled.delete(key));
151
+ }
152
+ return await tables;
153
+ })();
154
+ return withGateTimeout(compiling, PATH_TABLES_READY_BOUND_MS, undefined, "pathTablesCompile");
155
+ };
156
+ const contentOf = (rules) => JSON.stringify(rules.map(({ adds: _adds, ...row }) => row));
157
+ const refreshUnitTables = () => {
158
+ const rules = unitLatestRules();
159
+ const key = contentOf(rules);
160
+ if (unitTables !== undefined && unitTables.key === key && unitTables.state !== "failed")
161
+ return;
162
+ const cell = { key, state: "compiling", settled: Promise.resolve() };
163
+ cell.settled = tablesOf(rules).then((tables) => {
164
+ cell.tables = tables;
165
+ cell.state = "ready";
166
+ }, (err) => {
167
+ cell.failed = thrownText(err);
168
+ cell.state = "failed";
169
+ defect(`the path tables over the person's newest rows could not be compiled; the checks that are no call's refuse until a later read compiles: ${cell.failed}`);
170
+ });
171
+ unitTables = cell;
172
+ };
173
+ const unitReady = async (signal) => {
174
+ const deadline = Date.now() + PATH_TABLES_READY_BOUND_MS;
175
+ for (let cell = unitTables; cell?.state === "compiling" && signal?.aborted !== true; cell = unitTables) {
176
+ const left = deadline - Date.now();
177
+ if (left <= 0)
178
+ return;
179
+ await withGateTimeout(cell.settled, left, signal, "pathTablesReady").catch(() => undefined);
180
+ }
181
+ };
182
+ const pinOf = (toolCallId) => {
183
+ const p = toolCallId === undefined ? undefined : pins.get(toolCallId);
184
+ if (p === undefined || cfg.occurrences?.get(toolCallId) === p.attempt)
185
+ return p;
186
+ pins.delete(toolCallId);
187
+ return undefined;
188
+ };
189
+ const enter = (toolCallId, attempt) => {
190
+ const rows = readOnce("call").then((read) => (entry.read = read));
191
+ const entry = { attempt, rows, tables: holdsPersonRows ? rows.then((read) => settleTables(entry, read)) : Promise.resolve(), ...(holdsPersonRows ? {} : { settled: {} }) };
192
+ pins.set(toolCallId, entry);
193
+ return entry;
194
+ };
195
+ const pin = (toolCallId) => {
196
+ for (const id of [...pins.keys()])
197
+ pinOf(id);
198
+ const known = pinOf(toolCallId);
199
+ if (known !== undefined)
200
+ return known.rows;
201
+ const attempt = cfg.occurrences?.get(toolCallId);
202
+ return attempt === undefined ? readOnce("call") : enter(toolCallId, attempt).rows;
203
+ };
204
+ const settleTables = async (entry, read) => {
205
+ try {
206
+ const tables = await tablesOf(read.rules);
207
+ entry.settled = tables !== undefined ? { tables } : {};
208
+ }
209
+ catch (err) {
210
+ entry.settled = { failed: thrownText(err) };
211
+ defect(`the path tables of a call's read of the person's rows could not be compiled — the call's file and shell checks refuse it rather than judge it with other tables: ${entry.settled.failed}`);
212
+ }
213
+ };
214
+ const tablesFor = (toolCallId) => {
215
+ const p = pinOf(toolCallId);
216
+ if (p?.settled?.failed !== undefined)
217
+ throw new Error(`this call's path tables could not be compiled, so the call is refused: ${p.settled.failed}`);
218
+ if (p !== undefined && p.settled === undefined)
219
+ throw new Error("a call's path tables were read before they were compiled (an engine defect: a check ran ahead of its call's read)");
220
+ if (p?.settled?.tables !== undefined)
221
+ return p.settled.tables;
222
+ const u = unitTables;
223
+ if (u === undefined)
224
+ throw new Error("the run's path tables were read before they were compiled (an engine defect)");
225
+ if (u.state === "ready" && u.tables !== undefined)
226
+ return u.tables;
227
+ throw new Error(u.state === "failed"
228
+ ? `the path tables over the person's newest rows could not be compiled, so this check refuses rather than judge with the tables of the rows they replace: ${u.failed}`
229
+ : "the path tables over the person's newest rows are still being compiled, so this check refuses rather than judge with the tables of the rows they replace");
230
+ };
231
+ return {
232
+ holdsPersonRows,
233
+ prime: () => holdsPersonRows
234
+ ? readOnce("prepare").then(() => undefined)
235
+ : undefined,
236
+ pin,
237
+ tablesReady: (toolCallId, signal) => {
238
+ const p = pinOf(toolCallId);
239
+ const cut = signal === undefined || cfg.signal === undefined ? (signal ?? cfg.signal) : AbortSignal.any([signal, cfg.signal]);
240
+ return p !== undefined ? withGateTimeout(p.tables, undefined, cut, "callTables").catch(() => undefined) : unitReady(cut);
241
+ },
242
+ holds: (toolCallId) => cfg.occurrences?.get(toolCallId) !== undefined,
243
+ unreadFor: (toolCallId) => {
244
+ const read = pinOf(toolCallId)?.read;
245
+ return read !== undefined ? holdsPersonRows && read.unreadable !== undefined : latestUnread();
246
+ },
247
+ attachTables: async (make) => {
248
+ compile = make;
249
+ const rules = unitLatestRules();
250
+ const tables = await tablesOf(rules);
251
+ unitTables ??= { key: contentOf(rules), state: "ready", tables: tables, settled: Promise.resolve() };
252
+ const judging = () => {
253
+ if (latestUnread())
254
+ throw new Error("a partition of the person's persisted rules could not be read when the run last read it, so this check refuses rather than judge past rows it cannot see");
255
+ return tablesFor(undefined);
256
+ };
257
+ const readDeny = Object.assign(readDenyOver(() => tablesFor(undefined).readDeny, tables.readDeny.homeViews), {
258
+ matchPath: (path, opts) => judging().readDeny.matchPath(path, opts),
259
+ matchTarget: (canonicalKey, lexicalView, opts) => judging().readDeny.matchTarget(canonicalKey, lexicalView, opts),
260
+ unexcludableUnder: (scope) => judging().readDeny.unexcludableUnder?.(scope) ?? false,
261
+ rowAtOrBelow: (path) => judging().readDeny.rowAtOrBelow?.(path) ?? null,
262
+ forCall: (id) => tablesFor(id).readDeny,
263
+ });
264
+ const writeDeny = {
265
+ face: "write",
266
+ get ruleRows() {
267
+ return tablesFor(undefined).writeDeny.ruleRows;
268
+ },
269
+ matchPath: (path) => judging().writeDeny.matchPath(path),
270
+ matchTarget: (canonicalKey, lexicalView, opts) => judging().writeDeny.matchTarget(canonicalKey, lexicalView, opts),
271
+ homeViews: readDeny.homeViews,
272
+ forCall: (id) => tablesFor(id).writeDeny,
273
+ };
274
+ return { forCall: tablesFor, readDeny, writeDeny, readDirectories: (id) => tablesFor(id).readDirectories, ruleRows: () => tablesFor(undefined).ruleRows };
275
+ },
276
+ };
277
+ }
278
+ export function pathTablesCompile(seats) {
279
+ return async (person) => {
280
+ const rows = [...seats.legRows, ...person.denyRows];
281
+ const led = rowsWhereTheyLead(rows, await seats.leads.resolve(rows));
282
+ const placed = await placeReadDenyAtHome(seats.env, (homes, linked) => compileReadDeny(seats.additions, "readDenyPatterns", seats.builtin, led, homes, linked), undefined, undefined, seats.configRoot);
283
+ return { readDeny: placed.matcher, writeDeny: carryHomeViews(compileWriteDeny(led), placed.matcher.homeViews), readDirectories: [...new Set(await seats.canonicalize(person.readDirectories))], ruleRows: rows };
284
+ };
285
+ }
286
+ export function readRootsOver(seats) {
287
+ const said = new Set();
288
+ const rootsOf = (personDirs) => {
289
+ const own = seats.own();
290
+ const store = seats.store();
291
+ const held = store === undefined ? [] : personDirs.filter((k) => !own.includes(k) && rootAdmittingArtifactStore(store, [k]) !== undefined);
292
+ for (const dir of held)
293
+ if (!said.has(dir))
294
+ (said.add(dir), seats.onHeld(dir, store));
295
+ return [...new Set([...own, ...personDirs.filter((k) => !held.includes(k))])];
296
+ };
297
+ return Object.assign(() => rootsOf(seats.tables.readDirectories()), { forCall: (toolCallId) => () => rootsOf(seats.tables.readDirectories(toolCallId)), ready: (signal) => seats.ready(undefined, signal) });
298
+ }
299
+ const SEATS = new WeakMap();
300
+ export function denySeatsOf(t) {
301
+ let seats = SEATS.get(t);
302
+ if (seats === undefined) {
303
+ seats = { read: readDenySeat(t.readDeny), write: t.writeDeny.ruleRows.length > 0 ? { writeDenyMatch: (p) => t.writeDeny.matchPath(p)?.pattern ?? null } : {} };
304
+ SEATS.set(t, seats);
305
+ }
306
+ return seats;
307
+ }
308
+ const VOUCHED = { disposition: "vouch", finding: "none" };
309
+ export function onPinnedRead(rows, stations) {
310
+ const check = composeReadChecks(stations);
311
+ if (check === undefined || !rows.holdsPersonRows)
312
+ return check;
313
+ const members = new Set(stations.flatMap((s) => [...s.tools]));
314
+ return {
315
+ judge(call, args, cwd, signal, callOpts) {
316
+ const id = callOpts.toolCallId;
317
+ if (readStationMemberOf(call, members) === undefined)
318
+ return check.judge(call, args, cwd, signal, callOpts);
319
+ const ready = id !== undefined && rows.holds(id) ? rows.pin(id).then(() => rows.tablesReady(id)) : rows.tablesReady(undefined, signal);
320
+ return ready.then(() => check.judge(call, args, cwd, signal, callOpts) ?? VOUCHED);
321
+ },
322
+ unanswered: (call, cause) => check.unanswered(call, cause),
323
+ };
324
+ }
325
+ export function afterCallTables(rows, tool) {
326
+ return {
327
+ ...tool,
328
+ execute: async (toolCallId, params, signal, onUpdate) => {
329
+ await rows.tablesReady(toolCallId, signal);
330
+ return await tool.execute(toolCallId, params, signal, onUpdate);
331
+ },
332
+ };
333
+ }
334
+ export function personPathRowsOfTask(t) {
335
+ const { sessionId, taskId } = t.ids;
336
+ return createPersonPathRows({
337
+ provider: t.deps.permissionRuleStore,
338
+ localOwnerDeclared: t.deps.localOwnerRules === true,
339
+ principal: t.principal,
340
+ sessionId,
341
+ root: t.root,
342
+ rootCanonical: t.rootCanonical,
343
+ leads: t.leads,
344
+ chainTop: t.chainTop,
345
+ occurrences: t.occurrences,
346
+ onDisclosure: (message) => emitTrace(t.deps.tracer, () => ({ kind: "permission.rule_store_unreadable", version: 1, taskId, message, ts: Date.now() })),
347
+ onUnread: t.notices.onUnread,
348
+ onRowsDropped: t.notices.onRowsDropped,
349
+ onDefect: (err) => t.deps.onError?.(err, { phase: "config", sessionId }),
350
+ signal: AbortSignal.any([...t.signals]),
351
+ });
352
+ }
353
+ function scopeKey(rule) {
354
+ const scope = rule?.scope;
355
+ return scope === undefined ? "" : scope.kind === "project" ? `project:${scope.root}` : scope.kind;
356
+ }
357
+ function sourcesWord(sources) {
358
+ return sources.length <= 1 ? sources.join("") : `${sources.slice(0, -1).join(", ")} and ${sources[sources.length - 1]}`;
359
+ }
360
+ export function ruleStoreUnreadableNotice(onNotice, ids) {
361
+ let sent = false;
362
+ return (at, sources) => {
363
+ if (sent)
364
+ return;
365
+ sent = true;
366
+ const which = sources !== undefined && sources.length > 0 ? `your persisted permission rules from the ${sourcesWord(sources)} sources` : "your persisted permission rules";
367
+ deliverEngineNotice(onNotice, {
368
+ code: "permission.rule_store_unavailable",
369
+ message: `${which} could not be read ${at === "prepare" ? "when this run started" : "for a call on this run"}. Until a read of them succeeds, none of their allow rows (Read directory rows included) applies, while the deny and ask rows most recently read from them this run still apply — a deny row you removed since that read keeps refusing until then. While they cannot be read, every call your rules judge (a file read, a write, a command) is asked about rather than allowed, until a read of them succeeds. Every later check reads them again.`,
370
+ detail: { at, sessionId: ids.sessionId, runId: ids.runId },
371
+ });
372
+ };
373
+ }
374
+ export function ruleRowsDroppedNotice(onNotice, ids) {
375
+ let sent = false;
376
+ return (rows) => {
377
+ if (sent || rows === undefined || rows.length === 0)
378
+ return;
379
+ sent = true;
380
+ const one = rows.length === 1;
381
+ deliverEngineNotice(onNotice, {
382
+ code: "permission.rule_row_dropped",
383
+ message: `${rows.length} of your persisted permission rule rows ${one ? "is" : "are"} scoped to a project directory that is not written as a complete absolute path, so ${one ? "it takes" : "they take"} no part in this run's checks — a deny or ask row among them protects nothing. Rewrite ${one ? "it" : "them"} with the project's full path, or remove ${one ? "it" : "them"} (this notice's detail names ${one ? "it" : "them"}).`,
384
+ detail: { rows: rows.slice(0, 8).map((r) => ({ rule: inlineUntrusted(r.rule, 200), scopeRoot: inlineUntrusted(r.scopeRoot, 200) })), count: rows.length, sessionId: ids.sessionId, runId: ids.runId },
385
+ });
386
+ };
387
+ }
@@ -1,4 +1,6 @@
1
1
  import { StoredSession, isSessionConflict } from "../session.js";
2
+ import { userAttachmentOf } from "../../internal/llm.js";
3
+ import { lastAttachmentFrameOf } from "../user-attachment.js";
2
4
  import { reconcileInterruptedSession } from "../session-reconcile.js";
3
5
  import { detectConflicts, settleBranchWriteBack } from "./branch-writeback.js";
4
6
  import { settleTeardownLeg } from "./teardown-bounded.js";
@@ -57,6 +59,11 @@ export async function prepareAcquireReconcile(input) {
57
59
  e.code = "resume_at.not_a_message";
58
60
  throw e;
59
61
  }
62
+ if (userAttachmentOf(msg) !== undefined) {
63
+ const e = new Error(`resumeAt entry "${spec.resumeAt}" is an attachment frame that trails a user message; resume at that user message instead`);
64
+ e.code = "resume_at.not_a_message";
65
+ throw e;
66
+ }
60
67
  }
61
68
  if (spec.resumeAtMode === "before") {
62
69
  const role = entry.type === "message" ? entry.message.role : undefined;
@@ -84,7 +91,7 @@ export async function prepareAcquireReconcile(input) {
84
91
  throw e;
85
92
  }
86
93
  }
87
- const storage = session.getStorage(), leafBefore = await storage.getLeafId(), leafSet = spec.resumeAtMode === "before" ? entry.parentId : spec.resumeAt;
94
+ const storage = session.getStorage(), leafBefore = await storage.getLeafId(), leafSet = spec.resumeAtMode === "before" ? entry.parentId : entry.type === "message" && entry.message.role === "user" ? lastAttachmentFrameOf(await session.getEntries(), spec.resumeAt) : spec.resumeAt;
88
95
  await storage.setLeafId(leafSet);
89
96
  own.ids = new Set();
90
97
  branch = { storage, leafBefore, leafSet, own };