@astrosheep/keiyaku 2.9.8 → 2.9.10

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 (230) hide show
  1. package/README.md +50 -182
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/harness/control-types.js +18 -1
  4. package/build/agents/harness/index.js +2 -0
  5. package/build/agents/harness/outcome-error.js +81 -0
  6. package/build/agents/harness/projection.js +10 -7
  7. package/build/agents/harness/router.js +5 -1
  8. package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
  9. package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
  10. package/build/agents/providers/claude-agent-sdk/session.js +19 -5
  11. package/build/agents/providers/codex-app-server/adapter.js +11 -6
  12. package/build/agents/providers/codex-app-server/events.js +67 -6
  13. package/build/agents/providers/codex-app-server/registration.js +11 -1
  14. package/build/agents/providers/opencode-sdk/adapter.js +8 -1
  15. package/build/agents/providers/opencode-sdk/events.js +24 -2
  16. package/build/agents/providers/opencode-sdk/registration.js +11 -1
  17. package/build/agents/providers/pi/checkpoint.js +9 -1
  18. package/build/agents/providers/pi/events.js +2 -0
  19. package/build/agents/providers/pi/registration.js +14 -1
  20. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  21. package/build/cli/commands/akuma/akuma/meta.js +3 -3
  22. package/build/cli/commands/akuma/catalog.js +2 -2
  23. package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
  24. package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
  25. package/build/cli/commands/akuma.js +1 -1
  26. package/build/cli/commands/contract/amend/handler.js +5 -4
  27. package/build/cli/commands/contract/arc/handler.js +5 -4
  28. package/build/cli/commands/contract/audit/handler.js +2 -2
  29. package/build/cli/commands/contract/bind/handler.js +12 -3
  30. package/build/cli/commands/contract/bind/meta.js +45 -15
  31. package/build/cli/commands/contract/forfeit/handler.js +9 -5
  32. package/build/cli/commands/contract/log/handler.js +2 -2
  33. package/build/cli/commands/contract/petition/handler.js +34 -7
  34. package/build/cli/commands/contract/renew/handler.js +7 -7
  35. package/build/cli/commands/contract/renew/meta.js +3 -3
  36. package/build/cli/commands/metadata.js +10 -6
  37. package/build/cli/commands/projection/call/meta.js +1 -1
  38. package/build/cli/commands/projection/catalog.js +2 -0
  39. package/build/cli/commands/projection/history/handler.js +21 -0
  40. package/build/cli/commands/projection/history/meta.js +9 -0
  41. package/build/cli/commands/projection/shared.js +3 -4
  42. package/build/cli/commands/projection/status/handler.js +3 -3
  43. package/build/cli/commands/projection/wait/handler.js +20 -11
  44. package/build/cli/commands/task/add/handler.js +14 -5
  45. package/build/cli/commands/task/add/meta.js +2 -2
  46. package/build/cli/commands/task/catalog.js +4 -0
  47. package/build/cli/commands/task/done/handler.js +1 -4
  48. package/build/cli/commands/task/done/meta.js +1 -1
  49. package/build/cli/commands/task/drop/meta.js +1 -1
  50. package/build/cli/commands/task/hold/handler.js +3 -0
  51. package/build/cli/commands/task/hold/meta.js +8 -0
  52. package/build/cli/commands/task/log/handler.js +1 -1
  53. package/build/cli/commands/task/log/meta.js +2 -2
  54. package/build/cli/commands/task/resume/handler.js +3 -0
  55. package/build/cli/commands/task/resume/meta.js +8 -0
  56. package/build/cli/commands/task/shared.js +39 -14
  57. package/build/cli/commands/task/show/handler.js +1 -1
  58. package/build/cli/commands/task/show/meta.js +1 -1
  59. package/build/cli/commands/task/start/meta.js +1 -1
  60. package/build/cli/commands/task/stop/handler.js +1 -4
  61. package/build/cli/commands/task/stop/meta.js +1 -1
  62. package/build/cli/commands/task/update/handler.js +11 -3
  63. package/build/cli/commands/task/update/meta.js +3 -3
  64. package/build/cli/completion.js +3 -2
  65. package/build/cli/flags.js +51 -14
  66. package/build/cli/help.js +4 -1
  67. package/build/cli/index.js +40 -25
  68. package/build/cli/parse-flags.js +40 -7
  69. package/build/cli/parse-metadata.js +5 -2
  70. package/build/cli/parse-selectors.js +2 -3
  71. package/build/cli/parse.js +40 -6
  72. package/build/cli/projection-address.js +12 -10
  73. package/build/cli/render/amendment-warning.js +8 -0
  74. package/build/cli/render/arc.js +6 -2
  75. package/build/cli/render/audit.js +400 -81
  76. package/build/cli/render/kanshi.js +242 -79
  77. package/build/cli/render/lifecycle.js +56 -0
  78. package/build/cli/render/misc.js +24 -28
  79. package/build/cli/render/petition.js +5 -0
  80. package/build/cli/render/projection-activity.js +65 -29
  81. package/build/cli/render/projection-history.js +64 -0
  82. package/build/cli/render/shared.js +23 -9
  83. package/build/cli/render/status.js +13 -35
  84. package/build/cli/render/task.js +51 -26
  85. package/build/cli/render/terminal-failure.js +3 -1
  86. package/build/cli/render/wait.js +45 -69
  87. package/build/cli/subagent-guard.js +1 -1
  88. package/build/cli/types.js +1 -1
  89. package/build/config/env-keys.js +3 -1
  90. package/build/config/env.js +24 -1
  91. package/build/config/settings/schema.js +3 -1
  92. package/build/core/address-carrier.js +89 -0
  93. package/build/core/addressing.js +255 -196
  94. package/build/core/amend.js +115 -38
  95. package/build/core/amendment-criteria.js +17 -0
  96. package/build/core/amendment-warning.js +10 -0
  97. package/build/core/arc.js +184 -83
  98. package/build/core/audit/candidate.js +110 -9
  99. package/build/core/audit/coordinates.js +120 -65
  100. package/build/core/audit/evidence.js +19 -23
  101. package/build/core/audit/facade.js +34 -11
  102. package/build/core/audit/report.js +5 -5
  103. package/build/core/bind-reconciliation.js +390 -0
  104. package/build/core/bind-workspace.js +12 -20
  105. package/build/core/bind.js +242 -273
  106. package/build/core/call/call.js +1 -0
  107. package/build/core/call/context.js +46 -71
  108. package/build/core/call/prompt.js +6 -3
  109. package/build/core/context.js +49 -35
  110. package/build/core/contract-carrier-runtime.js +408 -0
  111. package/build/core/contract-carrier.js +270 -0
  112. package/build/core/contract-view.js +33 -27
  113. package/build/core/contract.js +5 -1
  114. package/build/core/derived-replay.js +207 -0
  115. package/build/core/draft.js +125 -57
  116. package/build/core/forfeit.js +113 -56
  117. package/build/core/identifier-slug.js +63 -13
  118. package/build/core/ids.js +4 -4
  119. package/build/core/lifecycle-history.js +35 -0
  120. package/build/core/lifecycle-recovery.js +117 -0
  121. package/build/core/lifecycle-runner.js +377 -0
  122. package/build/core/log.js +37 -11
  123. package/build/core/outcome-base.js +181 -0
  124. package/build/core/process-group.js +139 -0
  125. package/build/core/projection/generation/database.js +82 -7
  126. package/build/core/projection/generation/model.js +9 -36
  127. package/build/core/projection/generation/projection-generation-continuation.js +10 -22
  128. package/build/core/projection/generation/projection-generation-execution.js +13 -6
  129. package/build/core/projection/index.js +5 -4
  130. package/build/core/projection/projection-activity.js +29 -8
  131. package/build/core/projection/projection-alias.js +23 -10
  132. package/build/core/projection/projection-core.js +21 -7
  133. package/build/core/projection/projection-execution-observer.js +91 -39
  134. package/build/core/projection/projection-history.js +474 -0
  135. package/build/core/projection/projection-runner-lock.js +12 -0
  136. package/build/core/projection/projection-status-observation.js +365 -0
  137. package/build/core/projection/projection-status.js +35 -337
  138. package/build/core/projection/projection-terminal-failure.js +13 -8
  139. package/build/core/projection/projection-wait.js +7 -2
  140. package/build/core/projection/tell/launch-store.js +244 -0
  141. package/build/core/projection/tell/model.js +3 -3
  142. package/build/core/projection/tell/store.js +70 -118
  143. package/build/core/registry.js +22 -87
  144. package/build/core/renew-build.js +424 -0
  145. package/build/core/renew-plan.js +239 -0
  146. package/build/core/renew-rewrite.js +77 -0
  147. package/build/core/renew.js +295 -520
  148. package/build/core/repository-ledger/accepted-fold-read.js +19 -0
  149. package/build/core/repository-ledger/accepted-tail.js +116 -0
  150. package/build/core/repository-ledger/admission.js +38 -0
  151. package/build/core/repository-ledger/authoritative-transaction.js +1 -0
  152. package/build/core/repository-ledger/claim-evidence.js +147 -0
  153. package/build/core/repository-ledger/codec.js +297 -0
  154. package/build/core/repository-ledger/current-state-store.js +853 -0
  155. package/build/core/repository-ledger/fold-repository.js +375 -0
  156. package/build/core/repository-ledger/fold.js +248 -0
  157. package/build/core/repository-ledger/git-object-store.js +243 -0
  158. package/build/core/repository-ledger/history-read.js +15 -0
  159. package/build/core/repository-ledger/incremental-read.js +153 -0
  160. package/build/core/repository-ledger/index.js +15 -0
  161. package/build/core/repository-ledger/inventory-plan.js +75 -0
  162. package/build/core/repository-ledger/inventory.js +310 -0
  163. package/build/core/repository-ledger/publication-admission.js +125 -0
  164. package/build/core/repository-ledger/publication.js +353 -0
  165. package/build/core/repository-ledger/read-model.js +469 -0
  166. package/build/core/repository-ledger/transaction-membership.js +78 -0
  167. package/build/core/repository-ledger/traversal.js +404 -0
  168. package/build/core/repository-ledger/write-transaction.js +339 -0
  169. package/build/core/seal.js +168 -161
  170. package/build/core/settlement/claim-delivery.js +42 -215
  171. package/build/core/settlement/claim.js +235 -153
  172. package/build/core/settlement/index.js +4 -4
  173. package/build/core/settlement/petition-claim-gates.js +52 -33
  174. package/build/core/settlement/petition-forfeit.js +12 -5
  175. package/build/core/settlement/petition-head-guard.js +8 -5
  176. package/build/core/settlement/petition-preview.js +68 -56
  177. package/build/core/settlement/petition-window.js +50 -0
  178. package/build/core/settlement/petition.js +252 -131
  179. package/build/core/settlement/queue-read-model.js +72 -95
  180. package/build/core/settlement/queue-seat-allocation.js +35 -126
  181. package/build/core/settlement/queue.js +1 -1
  182. package/build/core/settlement/settlement.js +172 -80
  183. package/build/core/settlement/verdict.js +112 -66
  184. package/build/core/settlement/verification-coordination.js +305 -0
  185. package/build/core/settlement/verification-supervisor.js +275 -0
  186. package/build/core/settlement/verification.js +398 -31
  187. package/build/core/status/board.js +125 -127
  188. package/build/core/status/lifecycle.js +7 -80
  189. package/build/core/structured-query.js +23 -0
  190. package/build/core/target-ref.js +1 -1
  191. package/build/core/task/board.js +59 -24
  192. package/build/core/task/commands.js +181 -33
  193. package/build/core/task/coordinate.js +56 -0
  194. package/build/core/task/document.js +25 -9
  195. package/build/core/task/index.js +7 -4
  196. package/build/core/task/query.js +17 -27
  197. package/build/core/task/settlement-git.js +164 -179
  198. package/build/core/task/settlement-policy.js +15 -11
  199. package/build/core/task/source-board.js +31 -13
  200. package/build/core/task/task-bind-preparation.js +193 -0
  201. package/build/core/task/task-contract.js +102 -193
  202. package/build/core/task/task-file-transaction.js +171 -0
  203. package/build/core/task/task-git-runtime.js +76 -14
  204. package/build/core/task/task-git-store.js +68 -169
  205. package/build/core/task/task-store-repository.js +13 -6
  206. package/build/core/task/task-worktree.js +6 -33
  207. package/build/core/task/task.js +1 -1
  208. package/build/core/task/tree.js +3 -3
  209. package/build/core/task/validation.js +4 -3
  210. package/build/core/transcripts.js +93 -1
  211. package/build/core/worktree-bootstrap.js +44 -36
  212. package/build/core/worktree-path.js +210 -77
  213. package/build/flow-error.js +7 -7
  214. package/build/generated/version.js +2 -2
  215. package/build/git/core.js +76 -13
  216. package/build/git/refs.js +168 -46
  217. package/build/git/streaming-batch.js +73 -13
  218. package/package.json +1 -1
  219. package/skills/keiyaku/SKILL.md +1 -1
  220. package/skills/keiyaku-akuma/SKILL.md +3 -3
  221. package/skills/keiyaku-task/SKILL.md +4 -2
  222. package/skills/keiyaku-workflow/SKILL.md +88 -203
  223. package/build/core/entry.js +0 -305
  224. package/build/core/ledger-batch.js +0 -194
  225. package/build/core/ledger.js +0 -84
  226. package/build/core/projection/leash.js +0 -87
  227. package/build/core/ref-log.js +0 -119
  228. package/build/core/renew-session.js +0 -128
  229. package/build/core/status/ledger-batch.js +0 -1
  230. package/build/core/status/reconciliation.js +0 -137
@@ -0,0 +1,244 @@
1
+ import { ProjectionStateError } from "../../atomic-publish.js";
2
+ import { withRead, withWrite } from "./database.js";
3
+ import { parseFence, requireExecutionId, submittedFromRow } from "./model.js";
4
+ function selectTell(database, tellId) {
5
+ return database.prepare(`
6
+ SELECT tell_id, text, created_at, effort, state, execution_id, fence_json, replayed_from_json
7
+ FROM projection_tells WHERE tell_id = ?
8
+ `).get(tellId);
9
+ }
10
+ export function claimFencedTells(projectionDirectory, fence, executionId) {
11
+ const owner = requireExecutionId(executionId);
12
+ return withWrite(projectionDirectory, (database) => {
13
+ const claimed = [];
14
+ const seen = new Set();
15
+ for (const tellId of fence) {
16
+ if (seen.has(tellId))
17
+ throw new ProjectionStateError(`duplicate tell in generation fence: ${tellId}`);
18
+ seen.add(tellId);
19
+ const row = selectTell(database, tellId);
20
+ if (!row)
21
+ throw new ProjectionStateError(`generation tell fence member is missing: ${tellId}`);
22
+ if (row.state === "inbox") {
23
+ database.prepare(`
24
+ UPDATE projection_tells
25
+ SET state = 'inflight'
26
+ WHERE tell_id = ? AND state = 'inbox'
27
+ `).run(tellId);
28
+ claimed.push(tellId);
29
+ continue;
30
+ }
31
+ if (row.state === "inflight") {
32
+ claimed.push(tellId);
33
+ continue;
34
+ }
35
+ if (row.state === "submitted" || row.state === "seen") {
36
+ const submitted = submittedFromRow(row);
37
+ if (submitted.executionId === owner) {
38
+ throw new ProjectionStateError(`submitted tell belongs to current execution: ${tellId}`);
39
+ }
40
+ const replayedFrom = [
41
+ ...(submitted.replayedFrom ?? []),
42
+ { executionId: submitted.executionId, fence: submitted.fence },
43
+ ];
44
+ const result = database.prepare(`
45
+ UPDATE projection_tells
46
+ SET
47
+ state = 'inflight',
48
+ execution_id = NULL,
49
+ fence_json = NULL,
50
+ replayed_from_json = ?
51
+ WHERE tell_id = ? AND state = ?
52
+ `).run(JSON.stringify(replayedFrom), tellId, row.state);
53
+ if (Number(result.changes) !== 1) {
54
+ throw new ProjectionStateError(`generation tell fence member changed while claiming: ${tellId}`);
55
+ }
56
+ claimed.push(tellId);
57
+ continue;
58
+ }
59
+ if (row.state === "consumed" && row.execution_id === owner)
60
+ continue;
61
+ throw new ProjectionStateError(`generation tell fence member is not claimable: ${tellId}`);
62
+ }
63
+ return claimed;
64
+ });
65
+ }
66
+ /**
67
+ * The generation launch reducer: one heart transaction validates the frozen
68
+ * fence, claims exactly its non-consumed members, and establishes the matching
69
+ * immutable reservation before any provider can start.
70
+ */
71
+ export function claimAndReserveFencedTells(projectionDirectory, fence, executionId) {
72
+ const owner = requireExecutionId(executionId);
73
+ return withWrite(projectionDirectory, (database) => {
74
+ const seen = new Set();
75
+ for (const tellId of fence) {
76
+ if (seen.has(tellId))
77
+ throw new ProjectionStateError(`duplicate tell in generation fence: ${tellId}`);
78
+ seen.add(tellId);
79
+ }
80
+ const existing = database.prepare(`
81
+ SELECT tell_id FROM projection_tell_launch_batches
82
+ WHERE execution_id = ? ORDER BY tell_id
83
+ `).all(owner);
84
+ const reservationExists = database.prepare(`
85
+ SELECT 1 FROM projection_tell_launch_admissions WHERE execution_id = ?
86
+ `).get(owner) !== undefined;
87
+ if (reservationExists) {
88
+ const requested = [...seen].sort();
89
+ const reserved = existing.map((row) => row.tell_id);
90
+ if (reserved.length !== requested.length || reserved.some((tellId, index) => tellId !== requested[index])) {
91
+ throw new ProjectionStateError(`launch batch conflict for execution '${owner}'`);
92
+ }
93
+ // A retry is an insert-or-verify of immutable membership. Receipt stages
94
+ // may already have advanced, so it must not re-run claim validation.
95
+ return reserved;
96
+ }
97
+ const claimed = [];
98
+ for (const tellId of fence) {
99
+ const row = selectTell(database, tellId);
100
+ if (!row)
101
+ throw new ProjectionStateError(`generation tell fence member is missing: ${tellId}`);
102
+ if (row.state === "inbox") {
103
+ database.prepare(`UPDATE projection_tells SET state = 'inflight' WHERE tell_id = ? AND state = 'inbox'`).run(tellId);
104
+ claimed.push(tellId);
105
+ continue;
106
+ }
107
+ if (row.state === "inflight") {
108
+ claimed.push(tellId);
109
+ continue;
110
+ }
111
+ if (row.state === "submitted" || row.state === "seen") {
112
+ const submitted = submittedFromRow(row);
113
+ if (submitted.executionId === owner)
114
+ throw new ProjectionStateError(`submitted tell belongs to current execution: ${tellId}`);
115
+ const replayedFrom = [...(submitted.replayedFrom ?? []), { executionId: submitted.executionId, fence: submitted.fence }];
116
+ const result = database.prepare(`
117
+ UPDATE projection_tells
118
+ SET state = 'inflight', execution_id = NULL, fence_json = NULL, replayed_from_json = ?
119
+ WHERE tell_id = ? AND state = ?
120
+ `).run(JSON.stringify(replayedFrom), tellId, row.state);
121
+ if (Number(result.changes) !== 1)
122
+ throw new ProjectionStateError(`generation tell fence member changed while claiming: ${tellId}`);
123
+ claimed.push(tellId);
124
+ continue;
125
+ }
126
+ if (row.state !== "consumed" || row.execution_id !== owner) {
127
+ throw new ProjectionStateError(`generation tell fence member is not claimable: ${tellId}`);
128
+ }
129
+ }
130
+ database.prepare(`INSERT INTO projection_tell_launch_admissions (execution_id) VALUES (?)`).run(owner);
131
+ for (const tellId of claimed) {
132
+ database.prepare(`INSERT INTO projection_tell_launch_batches (execution_id, tell_id) VALUES (?, ?)`).run(owner, tellId);
133
+ }
134
+ return claimed;
135
+ });
136
+ }
137
+ /**
138
+ * Atomically bind an adapter-owned native coordinate to a pre-existing launch
139
+ * reservation. Heart never derives a coordinate from later activity.
140
+ */
141
+ export function bindTellLaunchReservation(projectionDirectory, admission) {
142
+ const executionId = requireExecutionId(admission.executionId);
143
+ const fence = parseFence(admission.fence);
144
+ withWrite(projectionDirectory, (database) => {
145
+ const unique = new Set();
146
+ for (const tellId of admission.tellIds) {
147
+ if (unique.has(tellId))
148
+ throw new ProjectionStateError(`duplicate tell in launch batch: ${tellId}`);
149
+ unique.add(tellId);
150
+ }
151
+ const established = database.prepare(`
152
+ SELECT 1 FROM projection_tell_launch_admissions WHERE execution_id = ?
153
+ `).get(executionId) !== undefined;
154
+ const existing = database.prepare(`
155
+ SELECT tell_id FROM projection_tell_launch_batches
156
+ WHERE execution_id = ? ORDER BY tell_id
157
+ `).all(executionId);
158
+ const requested = [...unique].sort();
159
+ if (!established) {
160
+ throw new ProjectionStateError(`launch admission has no reservation for execution '${executionId}'`);
161
+ }
162
+ const admitted = existing.map((row) => row.tell_id);
163
+ if (admitted.length !== requested.length || admitted.some((tellId, index) => tellId !== requested[index])) {
164
+ throw new ProjectionStateError(`launch batch conflict for execution '${executionId}'`);
165
+ }
166
+ database.prepare(`
167
+ INSERT INTO projection_tell_launch_coordinates (execution_id, fence_json)
168
+ VALUES (?, ?)
169
+ ON CONFLICT(execution_id, fence_json) DO NOTHING
170
+ `).run(executionId, JSON.stringify(fence));
171
+ for (const tellId of unique) {
172
+ const row = selectTell(database, tellId);
173
+ if (!row)
174
+ throw new ProjectionStateError(`tell '${tellId}' does not exist`);
175
+ if (row.state === "inflight") {
176
+ database.prepare(`
177
+ UPDATE projection_tells SET state = 'submitted', execution_id = ?, fence_json = ?
178
+ WHERE tell_id = ? AND state = 'inflight'
179
+ `).run(executionId, JSON.stringify(fence), tellId);
180
+ }
181
+ else if (row.execution_id !== executionId || !["submitted", "seen", "consumed"].includes(row.state)) {
182
+ throw new ProjectionStateError(`launch batch tell changed while admitting: ${tellId}`);
183
+ }
184
+ }
185
+ });
186
+ }
187
+ export function listTellLaunchBatch(projectionDirectory, executionId) {
188
+ const owner = requireExecutionId(executionId);
189
+ return withRead(projectionDirectory, (database) => database.prepare(`
190
+ SELECT tell_id FROM projection_tell_launch_batches
191
+ WHERE execution_id = ?
192
+ ORDER BY tell_id
193
+ `).all(owner).map((row) => row.tell_id));
194
+ }
195
+ export function listTellLaunchAdmissions(projectionDirectory, executionId) {
196
+ const owner = requireExecutionId(executionId);
197
+ return withRead(projectionDirectory, (database) => {
198
+ const tells = database.prepare(`
199
+ SELECT tell_id FROM projection_tell_launch_batches
200
+ WHERE execution_id = ? ORDER BY tell_id
201
+ `).all(owner);
202
+ return database.prepare(`
203
+ SELECT fence_json FROM projection_tell_launch_coordinates
204
+ WHERE execution_id = ? ORDER BY fence_json
205
+ `).all(owner).map((row) => ({
206
+ fence: parseFence(JSON.parse(row.fence_json)),
207
+ tellIds: tells.map((tell) => tell.tell_id),
208
+ }));
209
+ });
210
+ }
211
+ export function isTellLaunchAdmissionMember(projectionDirectory, executionId, tellId) {
212
+ const owner = requireExecutionId(executionId);
213
+ return withRead(projectionDirectory, (database) => database.prepare(`
214
+ SELECT 1 FROM projection_tell_launch_batches
215
+ WHERE execution_id = ? AND tell_id = ?
216
+ `).get(owner, tellId) !== undefined);
217
+ }
218
+ export function markTellSubmitted(projectionDirectory, tellId, fields) {
219
+ const executionId = requireExecutionId(fields.executionId);
220
+ const fence = parseFence(fields.fence);
221
+ const replayedFrom = (fields.replayedFrom ?? []).map((entry) => ({
222
+ executionId: requireExecutionId(entry.executionId),
223
+ fence: parseFence(entry.fence),
224
+ }));
225
+ withWrite(projectionDirectory, (database) => {
226
+ const row = selectTell(database, tellId);
227
+ if (!row)
228
+ throw new ProjectionStateError(`tell '${tellId}' does not exist`);
229
+ if (row.state === "submitted")
230
+ return;
231
+ if (row.state !== "inflight") {
232
+ throw new ProjectionStateError(`tell '${tellId}' is not inflight`);
233
+ }
234
+ database.prepare(`
235
+ UPDATE projection_tells
236
+ SET
237
+ state = 'submitted',
238
+ execution_id = ?,
239
+ fence_json = ?,
240
+ replayed_from_json = COALESCE(?, replayed_from_json)
241
+ WHERE tell_id = ? AND state = 'inflight'
242
+ `).run(executionId, JSON.stringify(fence), replayedFrom.length > 0 ? JSON.stringify(replayedFrom) : null, tellId);
243
+ });
244
+ }
@@ -3,10 +3,10 @@ export const PROJECTION_TELL_STATES = [
3
3
  "inbox",
4
4
  "inflight",
5
5
  "submitted",
6
+ "seen",
6
7
  "consumed",
7
- "delivered",
8
8
  ];
9
- export const PENDING_PROJECTION_TELL_STATES = ["inbox", "inflight", "submitted"];
9
+ export const PENDING_PROJECTION_TELL_STATES = ["inbox", "inflight", "submitted", "seen"];
10
10
  export function requireExecutionId(value) {
11
11
  if (typeof value !== "string" || value.trim() === "") {
12
12
  throw new ProjectionStateError("submitted tell executionId must be a non-empty string");
@@ -63,7 +63,7 @@ export function originalFromRow(row) {
63
63
  };
64
64
  }
65
65
  export function submittedFromRow(row) {
66
- if (row.state !== "submitted" && row.state !== "consumed" && row.state !== "delivered") {
66
+ if (row.state !== "submitted" && row.state !== "seen" && row.state !== "consumed") {
67
67
  throw new ProjectionStateError(`tell '${row.tell_id}' is not submitted`);
68
68
  }
69
69
  if (row.execution_id === null || row.fence_json === null) {
@@ -1,6 +1,7 @@
1
1
  import { ProjectionStateError, randomHex8 } from "../../atomic-publish.js";
2
2
  import { withRead, withReadSnapshot, withWrite } from "./database.js";
3
3
  import { assertTellIdRandomHex, originalFromRow, PENDING_PROJECTION_TELL_STATES, parseFence, requireExecutionId, submittedFromRow, } from "./model.js";
4
+ export { bindTellLaunchReservation, claimAndReserveFencedTells, claimFencedTells, isTellLaunchAdmissionMember, listTellLaunchAdmissions, listTellLaunchBatch, markTellSubmitted, } from "./launch-store.js";
4
5
  function selectTell(database, tellId) {
5
6
  return database.prepare(`
6
7
  SELECT
@@ -11,8 +12,7 @@ function selectTell(database, tellId) {
11
12
  state,
12
13
  execution_id,
13
14
  fence_json,
14
- replayed_from_json,
15
- delivered_at
15
+ replayed_from_json
16
16
  FROM projection_tells
17
17
  WHERE tell_id = ?
18
18
  `).get(tellId);
@@ -62,40 +62,13 @@ export function readPendingTellWindowFromDatabase(database, limit) {
62
62
  LIMIT ?
63
63
  `).all(...PENDING_PROJECTION_TELL_STATES, limit);
64
64
  return {
65
- totals: { undelivered: Number(total.count) },
65
+ totals: { pending: Number(total.count) },
66
66
  window: rows.map((row) => ({ tellId: row.tell_id, text: row.text, createdAt: row.created_at, state: row.state })),
67
67
  };
68
68
  }
69
69
  export function readPendingTellWindow(projectionDirectory, limit) {
70
70
  return withReadSnapshot(projectionDirectory, (database) => readPendingTellWindowFromDatabase(database, limit));
71
71
  }
72
- /** Whether a failed successor fence still retains delivered mail for replay. */
73
- export function hasRetainedDeliveredTellFromDatabase(database, retainedFence) {
74
- if (retainedFence.length === 0)
75
- return false;
76
- const statement = database.prepare(`
77
- SELECT 1
78
- FROM projection_tells
79
- WHERE tell_id = ? AND state = 'delivered'
80
- LIMIT 1
81
- `);
82
- return retainedFence.some((tellId) => statement.get(tellId) !== undefined);
83
- }
84
- export function hasRetainedDeliveredTell(projectionDirectory, retainedFence) {
85
- return withRead(projectionDirectory, (database) => hasRetainedDeliveredTellFromDatabase(database, retainedFence));
86
- }
87
- /** Whether interrupted replay has any delivered tell left to return to inbox. */
88
- export function hasDeliveredTellFromDatabase(database, executionId) {
89
- return database.prepare(`
90
- SELECT 1
91
- FROM projection_tells
92
- WHERE state = 'delivered' AND execution_id = ?
93
- LIMIT 1
94
- `).get(executionId) !== undefined;
95
- }
96
- export function hasDeliveredTell(projectionDirectory, executionId) {
97
- return withRead(projectionDirectory, (database) => hasDeliveredTellFromDatabase(database, executionId));
98
- }
99
72
  export function readTellOriginal(projectionDirectory, state, tellId) {
100
73
  return withRead(projectionDirectory, (database) => {
101
74
  const row = selectTell(database, tellId);
@@ -160,100 +133,79 @@ export function claimAllInbox(projectionDirectory) {
160
133
  return rows.map((row) => row.tell_id);
161
134
  });
162
135
  }
163
- export function claimFencedTells(projectionDirectory, fence, executionId) {
164
- const owner = requireExecutionId(executionId);
165
- return withWrite(projectionDirectory, (database) => {
166
- const claimed = [];
167
- const seen = new Set();
168
- for (const tellId of fence) {
169
- if (seen.has(tellId))
170
- throw new ProjectionStateError(`duplicate tell in generation fence: ${tellId}`);
171
- seen.add(tellId);
172
- const row = selectTell(database, tellId);
173
- if (!row)
174
- throw new ProjectionStateError(`generation tell fence member is missing: ${tellId}`);
175
- if (row.state === "inbox") {
176
- database.prepare(`
177
- UPDATE projection_tells
178
- SET state = 'inflight'
179
- WHERE tell_id = ? AND state = 'inbox'
180
- `).run(tellId);
181
- claimed.push(tellId);
182
- continue;
183
- }
184
- if (row.state === "inflight") {
185
- claimed.push(tellId);
186
- continue;
187
- }
188
- if (row.state === "submitted" || row.state === "delivered") {
189
- const submitted = submittedFromRow(row);
190
- if (submitted.executionId === owner) {
191
- throw new ProjectionStateError(`submitted tell belongs to current execution: ${tellId}`);
192
- }
193
- const replayedFrom = [
194
- ...(submitted.replayedFrom ?? []),
195
- { executionId: submitted.executionId, fence: submitted.fence },
196
- ];
197
- const result = database.prepare(`
198
- UPDATE projection_tells
199
- SET
200
- state = 'inflight',
201
- execution_id = NULL,
202
- fence_json = NULL,
203
- replayed_from_json = ?,
204
- delivered_at = NULL
205
- WHERE tell_id = ? AND state = ?
206
- `).run(JSON.stringify(replayedFrom), tellId, row.state);
207
- if (Number(result.changes) !== 1) {
208
- throw new ProjectionStateError(`generation tell fence member changed while claiming: ${tellId}`);
209
- }
210
- claimed.push(tellId);
211
- continue;
212
- }
213
- if (row.state === "consumed" && row.execution_id === owner)
214
- continue;
215
- throw new ProjectionStateError(`generation tell fence member is not claimable: ${tellId}`);
216
- }
217
- return claimed;
218
- });
136
+ function receiptTimestamp() {
137
+ return new Date().toISOString();
219
138
  }
220
- export function markTellSubmitted(projectionDirectory, tellId, fields) {
221
- const executionId = requireExecutionId(fields.executionId);
222
- const fence = parseFence(fields.fence);
223
- const replayedFrom = (fields.replayedFrom ?? []).map((entry) => ({
224
- executionId: requireExecutionId(entry.executionId),
225
- fence: parseFence(entry.fence),
226
- }));
139
+ /** Reduce persisted native evidence through the one monotonic heart-stage algebra. */
140
+ function recordTellReceiptFromDatabase(database, receipt) {
141
+ const executionId = requireExecutionId(receipt.executionId);
142
+ const fence = receipt.fence ? parseFence(receipt.fence) : undefined;
143
+ const receivedAt = receipt.receivedAt ?? receiptTimestamp();
144
+ const row = selectTell(database, receipt.tellId);
145
+ if (!row || row.execution_id !== executionId)
146
+ return false;
147
+ const inserted = database.prepare(`
148
+ INSERT INTO projection_tell_receipts (
149
+ tell_id, execution_id, stage, correlation_kind, fence_json, received_at
150
+ ) VALUES (?, ?, ?, ?, ?, ?)
151
+ ON CONFLICT(tell_id, execution_id, stage) DO NOTHING
152
+ `).run(receipt.tellId, executionId, receipt.stage, receipt.correlation, fence ? JSON.stringify(fence) : null, receivedAt);
153
+ const receipts = database.prepare(`
154
+ SELECT stage FROM projection_tell_receipts
155
+ WHERE tell_id = ? AND execution_id = ?
156
+ `).all(receipt.tellId, executionId);
157
+ const stages = new Set(receipts.map((entry) => entry.stage));
158
+ let state = row.state;
159
+ let advanced = false;
160
+ if (state === "submitted" && stages.has("seen")) {
161
+ database.prepare(`UPDATE projection_tells SET state = 'seen' WHERE tell_id = ? AND state = 'submitted'`)
162
+ .run(receipt.tellId);
163
+ state = "seen";
164
+ advanced = true;
165
+ }
166
+ if (state === "seen" && stages.has("consumed")) {
167
+ database.prepare(`UPDATE projection_tells SET state = 'consumed' WHERE tell_id = ? AND state = 'seen'`)
168
+ .run(receipt.tellId);
169
+ advanced = true;
170
+ }
171
+ return Number(inserted.changes) === 1 || advanced;
172
+ }
173
+ /**
174
+ * Persist a provider-native receipt and advance the mailbox only when the
175
+ * receipt proves the next monotonic heart stage. Event-ledger persistence is
176
+ * deliberately not part of this transaction.
177
+ */
178
+ export function recordTellReceipt(projectionDirectory, receipt) {
179
+ return withWrite(projectionDirectory, (database) => recordTellReceiptFromDatabase(database, receipt));
180
+ }
181
+ /** Persist the execution's native receipt strength independently of AgentEvent storage. */
182
+ export function recordTellReceiptCapabilities(projectionDirectory, executionId, capabilities) {
183
+ const owner = requireExecutionId(executionId);
227
184
  withWrite(projectionDirectory, (database) => {
228
- const row = selectTell(database, tellId);
229
- if (!row)
230
- throw new ProjectionStateError(`tell '${tellId}' does not exist`);
231
- if (row.state === "submitted")
185
+ const existing = database.prepare(`
186
+ SELECT seen, consumed FROM projection_tell_receipt_capabilities
187
+ WHERE execution_id = ?
188
+ `).get(owner);
189
+ if (existing) {
190
+ if (existing.seen !== capabilities.seen || existing.consumed !== capabilities.consumed) {
191
+ throw new ProjectionStateError(`tell receipt capabilities conflict for execution '${owner}'`);
192
+ }
232
193
  return;
233
- if (row.state !== "inflight") {
234
- throw new ProjectionStateError(`tell '${tellId}' is not inflight`);
235
194
  }
236
195
  database.prepare(`
237
- UPDATE projection_tells
238
- SET
239
- state = 'submitted',
240
- execution_id = ?,
241
- fence_json = ?,
242
- replayed_from_json = COALESCE(?, replayed_from_json)
243
- WHERE tell_id = ? AND state = 'inflight'
244
- `).run(executionId, JSON.stringify(fence), replayedFrom.length > 0 ? JSON.stringify(replayedFrom) : null, tellId);
196
+ INSERT INTO projection_tell_receipt_capabilities (execution_id, seen, consumed)
197
+ VALUES (?, ?, ?)
198
+ `).run(owner, capabilities.seen, capabilities.consumed);
245
199
  });
246
200
  }
247
- export function markTellConsumed(projectionDirectory, tellId) {
248
- withWrite(projectionDirectory, (database) => {
249
- const result = database.prepare(`
250
- UPDATE projection_tells
251
- SET state = 'consumed', delivered_at = NULL
252
- WHERE tell_id = ? AND state IN ('submitted', 'delivered')
253
- `).run(tellId);
254
- if (Number(result.changes) !== 1) {
255
- throw new ProjectionStateError(`tell '${tellId}' is not submitted or delivered`);
256
- }
201
+ export function readTellReceiptCapabilities(projectionDirectory, executionId) {
202
+ const owner = requireExecutionId(executionId);
203
+ return withRead(projectionDirectory, (database) => {
204
+ const row = database.prepare(`
205
+ SELECT seen, consumed FROM projection_tell_receipt_capabilities
206
+ WHERE execution_id = ?
207
+ `).get(owner);
208
+ return row && { seen: row.seen, consumed: row.consumed };
257
209
  });
258
210
  }
259
211
  export function readProjectionTellCounts(projectionDirectory) {
@@ -262,8 +214,8 @@ export function readProjectionTellCounts(projectionDirectory) {
262
214
  inbox: 0,
263
215
  inflight: 0,
264
216
  submitted: 0,
217
+ seen: 0,
265
218
  consumed: 0,
266
- delivered: 0,
267
219
  };
268
220
  const rows = database.prepare(`
269
221
  SELECT state, COUNT(*) AS count
@@ -1,71 +1,30 @@
1
1
  import { FlowError } from "../flow-error.js";
2
2
  import { createGit, wrapGitError } from "../git/core.js";
3
- import { readLedger } from "./ledger.js";
4
- import { appendRefLogWithRetry, readRefLog } from "./ref-log.js";
5
3
  import { patternsOverlap, scopePathEvidence } from "./scope.js";
6
- import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
7
- export const REGISTRY_REF = "refs/keiyaku/registry";
4
+ import { readRepositoryLifecycle } from "./repository-ledger/read-model.js";
8
5
  export const SCOPE_OVERLAP_PATH_PREVIEW_LIMIT = 10;
9
- function parseRegistryEntry(message) {
10
- const body = message.slice(message.indexOf("\n\n") + 2).trim();
11
- const parsed = JSON.parse(body);
12
- if (parsed.v !== 1 || !parsed.contract || !["register", "release", "repair"].includes(parsed.kind)) {
13
- throw new FlowError("INTERNAL_STATE", `invalid registry entry for ${REGISTRY_REF}`);
14
- }
15
- return parsed;
16
- }
17
- function registrySubject(entry) {
18
- return `keiyaku registry: ${entry.kind} ${entry.contract}`;
19
- }
20
- async function isActiveNonTerminal(cwd, contractId) {
21
- const ledger = await readLedger(cwd, contractId);
22
- if (!ledger)
23
- return false;
24
- const state = deriveContractState(ledger.entries);
25
- return state.state !== "corrupt" && !isTerminalState(state);
26
- }
27
- async function activeRegistrations(cwd, entries) {
28
- const released = new Set(entries.filter((entry) => entry.kind === "release").map((entry) => entry.contract));
29
- const registrations = new Map();
30
- for (const entry of entries) {
31
- if (entry.kind === "register" && !released.has(entry.contract)) {
32
- registrations.set(entry.contract, { scope: entry.scope, tree: entry.tree });
33
- }
34
- }
35
- const active = [];
36
- for (const [contract, registration] of registrations) {
37
- if (await isActiveNonTerminal(cwd, contract))
38
- active.push({ contract, scope: registration.scope, tree: registration.tree });
39
- }
40
- return active;
6
+ function activeRegistrations(read) {
7
+ return read.contracts
8
+ .filter((model) => !["claimed", "forfeited"].includes(model.phase))
9
+ .map((model) => ({ contract: model.contract, scope: [...model.effectiveScope] }));
41
10
  }
42
- async function trackedRepositoryPaths(cwd) {
11
+ async function trackedRepositoryPaths(cwd, tree) {
43
12
  try {
44
- return (await createGit(cwd).raw(["ls-files", "-z"]))
13
+ return (await createGit(cwd).raw(["ls-tree", "-r", "-z", "--name-only", tree]))
45
14
  .split("\0")
46
15
  .filter(Boolean)
47
16
  .sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
48
17
  }
49
18
  catch (error) {
50
- throw wrapGitError("ls-files -z", error, cwd);
19
+ throw wrapGitError(`ls-tree ${tree}`, error, cwd);
51
20
  }
52
21
  }
53
- async function scopeRegistrationAdmission(cwd, input, entries, trackedPaths) {
22
+ async function scopeRegistrationAdmission(cwd, input, read, trackedPaths) {
54
23
  const nextOverlaps = [];
55
- const active = await activeRegistrations(cwd, entries);
56
- if (input.exclusive && active.length > 0) {
57
- const blocking = active.find((existing) => existing.contract !== input.contractId) ?? active[0];
58
- throw new FlowError("ACTIVE_KEIYAKU_EXISTS", `exclusive bind rejected: active contract ${blocking.contract} already exists`);
59
- }
24
+ const active = activeRegistrations(read);
60
25
  for (const existing of active) {
61
26
  if (existing.contract === input.contractId)
62
27
  continue;
63
- if (existing.tree === input.tree) {
64
- throw new FlowError("ACTIVE_KEIYAKU_EXISTS", [
65
- `tree is already occupied by active contract ${existing.contract}`,
66
- `Run \`keiyaku forfeit @${existing.contract}\` to free this tree.`,
67
- ].join(" "));
68
- }
69
28
  const [nextEvidence, existingEvidence] = await Promise.all([
70
29
  scopePathEvidence(cwd, input.scope, trackedPaths),
71
30
  scopePathEvidence(cwd, existing.scope, trackedPaths),
@@ -121,44 +80,20 @@ async function scopeRegistrationAdmission(cwd, input, entries, trackedPaths) {
121
80
  }
122
81
  return nextOverlaps;
123
82
  }
124
- /** Read-only bind admission: validate exclusivity and classify scope overlap. */
125
- export async function previewScopeRegistration(cwd, input) {
126
- const [entries, trackedPaths] = await Promise.all([
127
- readRefLog(cwd, REGISTRY_REF, parseRegistryEntry),
128
- trackedRepositoryPaths(cwd),
129
- ]);
130
- return await scopeRegistrationAdmission(cwd, input, entries, trackedPaths);
83
+ function refuseExclusiveOverlap(contractId, overlaps) {
84
+ const occupants = [...new Set(overlaps.map((overlap) => overlap.contracts[1]))].sort();
85
+ throw new FlowError("INVALID_TARGET", `exclusive bind ${contractId} overlaps active contract${occupants.length === 1 ? "" : "s"}: ${occupants.join(", ")}`);
131
86
  }
132
- export async function registerScope(cwd, input) {
133
- let overlaps = [];
134
- const trackedPaths = await trackedRepositoryPaths(cwd);
135
- const result = await appendRefLogWithRetry(cwd, REGISTRY_REF, registrySubject, parseRegistryEntry, async (entries) => {
136
- overlaps = await scopeRegistrationAdmission(cwd, input, entries, trackedPaths);
137
- return {
138
- v: 1,
139
- kind: "register",
140
- contract: input.contractId,
141
- at: input.at,
142
- actor: input.actor,
143
- scope: input.scope,
144
- tree: input.tree,
145
- };
146
- });
147
- if (!result.ok)
148
- throw new FlowError("INTERNAL_STATE", `failed to register scope for ${input.contractId}: ${result.reason}`);
149
- return { head: result.head, overlaps };
87
+ /** Bind admission against one immutable repository fold and candidate Git tree. */
88
+ export async function admitScopeRegistration(cwd, input, read) {
89
+ const overlaps = await scopeRegistrationAdmission(cwd, input, read, await trackedRepositoryPaths(cwd, input.tree));
90
+ if (input.exclusive && overlaps.length > 0)
91
+ refuseExclusiveOverlap(input.contractId, overlaps);
92
+ return overlaps;
150
93
  }
151
- export async function releaseScope(cwd, input) {
152
- const result = await appendRefLogWithRetry(cwd, REGISTRY_REF, registrySubject, parseRegistryEntry, (entries) => {
153
- const hasActiveRegistration = entries.some((entry) => entry.kind === "register" && entry.contract === input.contractId);
154
- const alreadyReleased = entries.some((entry) => entry.kind === "release" && entry.contract === input.contractId);
155
- if (!hasActiveRegistration || alreadyReleased)
156
- return null;
157
- return { v: 1, kind: "release", contract: input.contractId, at: input.at, actor: input.actor };
158
- });
159
- if (!result.ok && result.reason !== "aborted") {
160
- throw new FlowError("INTERNAL_STATE", `failed to release scope for ${input.contractId}: ${result.reason}`);
161
- }
94
+ /** Read-only preview observes the repository once and shares mutation admission. */
95
+ export async function previewScopeRegistration(cwd, input) {
96
+ return await admitScopeRegistration(cwd, input, await readRepositoryLifecycle(cwd));
162
97
  }
163
98
  export function bindScope(entry) {
164
99
  return entry.data.scope;