@boxxy/dispatch 0.1.1 → 0.1.3

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 (190) hide show
  1. package/dist/attempts/attempt-machine.d.ts +804 -0
  2. package/dist/attempts/attempt-moves.d.ts +2 -0
  3. package/dist/attempts/bind-attempt-session.d.ts +14 -0
  4. package/dist/attempts/build-attempt-change.d.ts +73 -0
  5. package/dist/attempts/build-attempt-fact.d.ts +3 -0
  6. package/dist/attempts/end-item-attempts.d.ts +18 -0
  7. package/dist/attempts/is-same-guard.d.ts +5 -0
  8. package/dist/attempts/pick-call-attempt.d.ts +10 -0
  9. package/dist/attempts/stop-stray-session.d.ts +11 -0
  10. package/dist/attempts/types.d.ts +142 -0
  11. package/dist/attempts/write-attempt-move.d.ts +7 -0
  12. package/dist/attempts/write-session-bind.d.ts +3 -0
  13. package/dist/claims/build-snapshot-hash.d.ts +2 -0
  14. package/dist/claims/claim-next.d.ts +6 -0
  15. package/dist/claims/pick-kept-session.d.ts +9 -0
  16. package/dist/claims/request-wake.d.ts +3 -0
  17. package/dist/claims/resume-claim.d.ts +3 -0
  18. package/dist/claims/start-claim.d.ts +3 -0
  19. package/dist/claims/types.d.ts +44 -0
  20. package/dist/closeouts/build-settle-plans.d.ts +17 -0
  21. package/dist/closeouts/close-out-attempt.d.ts +3 -0
  22. package/dist/closeouts/effect-handlers.d.ts +2 -0
  23. package/dist/closeouts/pick-settlement.d.ts +14 -0
  24. package/dist/closeouts/settle-attempt.d.ts +3 -0
  25. package/dist/closeouts/types.d.ts +50 -0
  26. package/dist/index.d.ts +38 -0
  27. package/dist/notices/constants.d.ts +4 -0
  28. package/dist/notices/create-null-notifier.d.ts +2 -0
  29. package/dist/notices/send-notices.d.ts +2 -0
  30. package/dist/notices/types.d.ts +11 -0
  31. package/dist/outbox/build-backoff-ms.d.ts +2 -0
  32. package/dist/outbox/build-initial-sender-state.d.ts +2 -0
  33. package/dist/outbox/build-outbox-status.d.ts +15 -0
  34. package/dist/outbox/constants.d.ts +2 -0
  35. package/dist/outbox/measure-wire-bytes.d.ts +2 -0
  36. package/dist/outbox/pick-sender-move.d.ts +10 -0
  37. package/dist/outbox/run-sender-once.d.ts +2 -0
  38. package/dist/outbox/take-batch.d.ts +3 -0
  39. package/dist/outbox/types.d.ts +61 -0
  40. package/dist/pull-requests/build-link-change.d.ts +11 -0
  41. package/dist/pull-requests/build-state-fact.d.ts +8 -0
  42. package/dist/pull-requests/collect-attempt-trailers.d.ts +2 -0
  43. package/dist/pull-requests/collect-seen-state.d.ts +7 -0
  44. package/dist/pull-requests/constants.d.ts +3 -0
  45. package/dist/pull-requests/link-pull-request.d.ts +2 -0
  46. package/dist/pull-requests/parse-pull-request-url.d.ts +2 -0
  47. package/dist/pull-requests/types.d.ts +37 -0
  48. package/dist/pull-requests/watch-pull-requests.d.ts +2 -0
  49. package/dist/readings/act-on-reading.d.ts +3 -0
  50. package/dist/readings/find-waiting-since.d.ts +9 -0
  51. package/dist/readings/pick-reading-action.d.ts +2 -0
  52. package/dist/readings/types.d.ts +79 -0
  53. package/dist/sessions/build-keep-writes.d.ts +9 -0
  54. package/dist/sessions/build-kept-clear.d.ts +2 -0
  55. package/dist/sessions/forget-sessions.d.ts +2 -0
  56. package/dist/sessions/is-kept-due.d.ts +2 -0
  57. package/dist/sessions/resume-kept-session.d.ts +6 -0
  58. package/dist/sessions/types.d.ts +19 -0
  59. package/dist/test-utils/apply-memory-change.d.ts +3 -0
  60. package/dist/test-utils/build-memory-reader.d.ts +3 -0
  61. package/dist/test-utils/build-mock-claim-change.d.ts +2 -2
  62. package/dist/test-utils/build-mock-new-id.d.ts +2 -0
  63. package/dist/test-utils/build-mock-note-change.d.ts +2 -0
  64. package/dist/test-utils/build-mock-pull-request-change.d.ts +1 -0
  65. package/dist/test-utils/{build-mock-link-change.d.ts → build-mock-relation-change.d.ts} +2 -2
  66. package/dist/test-utils/build-mock-running-reading.d.ts +6 -0
  67. package/dist/test-utils/build-mock-start-change.d.ts +2 -2
  68. package/dist/test-utils/claim-store-close-cases.d.ts +2 -0
  69. package/dist/test-utils/claim-store-copy-cases.d.ts +2 -0
  70. package/dist/test-utils/claim-store-outbox-cases.d.ts +2 -0
  71. package/dist/test-utils/claim-store-race-cases.d.ts +2 -0
  72. package/dist/test-utils/claim-store-seat-cases.d.ts +2 -0
  73. package/dist/test-utils/claim-store-session-cases.d.ts +2 -0
  74. package/dist/test-utils/collect-memory-limit-fills.d.ts +3 -0
  75. package/dist/test-utils/compare-text.d.ts +1 -0
  76. package/dist/test-utils/create-fake-backoff.d.ts +2 -0
  77. package/dist/test-utils/create-fake-clock.d.ts +5 -0
  78. package/dist/test-utils/create-fake-fact-sink.d.ts +8 -0
  79. package/dist/test-utils/create-fake-forge.d.ts +17 -0
  80. package/dist/test-utils/create-fake-worker.d.ts +42 -0
  81. package/dist/test-utils/create-memory-claim-store.d.ts +2 -2
  82. package/dist/test-utils/create-test-dispatch.d.ts +46 -0
  83. package/dist/test-utils/forge-contract.d.ts +15 -0
  84. package/dist/test-utils/is-same-held-seat.d.ts +2 -0
  85. package/dist/test-utils/types.d.ts +12 -2
  86. package/dist/test-utils/worker-contract.d.ts +14 -0
  87. package/dist/timeouts.d.ts +2 -0
  88. package/dist/types.d.ts +13 -0
  89. package/package.json +10 -3
  90. package/src/attempts/attempt-machine.ts +147 -0
  91. package/src/attempts/attempt-moves.ts +218 -0
  92. package/src/attempts/bind-attempt-session.ts +61 -0
  93. package/src/attempts/build-attempt-change.ts +540 -0
  94. package/src/attempts/build-attempt-fact.ts +135 -0
  95. package/src/attempts/end-item-attempts.ts +95 -0
  96. package/src/attempts/is-same-guard.ts +22 -0
  97. package/src/attempts/pick-call-attempt.ts +32 -0
  98. package/src/attempts/stop-stray-session.ts +48 -0
  99. package/src/attempts/types.ts +231 -0
  100. package/src/attempts/write-attempt-move.ts +41 -0
  101. package/src/attempts/write-session-bind.ts +47 -0
  102. package/src/claims/build-snapshot-hash.ts +9 -0
  103. package/src/claims/claim-next.ts +306 -0
  104. package/src/claims/pick-kept-session.ts +41 -0
  105. package/src/claims/request-wake.ts +17 -0
  106. package/src/claims/resume-claim.ts +56 -0
  107. package/src/claims/start-claim.ts +99 -0
  108. package/src/claims/types.ts +67 -0
  109. package/src/closeouts/build-settle-plans.ts +173 -0
  110. package/src/closeouts/close-out-attempt.ts +156 -0
  111. package/src/closeouts/effect-handlers.ts +232 -0
  112. package/src/closeouts/pick-settlement.ts +80 -0
  113. package/src/closeouts/settle-attempt.ts +161 -0
  114. package/src/closeouts/types.ts +87 -0
  115. package/src/index.ts +61 -0
  116. package/src/notices/constants.ts +7 -0
  117. package/src/notices/create-null-notifier.ts +6 -0
  118. package/src/notices/send-notices.ts +41 -0
  119. package/src/notices/types.ts +14 -0
  120. package/src/outbox/build-backoff-ms.ts +14 -0
  121. package/src/outbox/build-initial-sender-state.ts +14 -0
  122. package/src/outbox/build-outbox-status.ts +43 -0
  123. package/src/outbox/constants.ts +15 -0
  124. package/src/outbox/measure-wire-bytes.ts +31 -0
  125. package/src/outbox/pick-sender-move.ts +37 -0
  126. package/src/outbox/run-sender-once.ts +174 -0
  127. package/src/outbox/take-batch.ts +35 -0
  128. package/src/outbox/types.ts +76 -0
  129. package/src/pull-requests/build-link-change.ts +95 -0
  130. package/src/pull-requests/build-state-fact.ts +39 -0
  131. package/src/pull-requests/collect-attempt-trailers.ts +20 -0
  132. package/src/pull-requests/collect-seen-state.ts +19 -0
  133. package/src/pull-requests/constants.ts +5 -0
  134. package/src/pull-requests/link-pull-request.ts +119 -0
  135. package/src/pull-requests/parse-pull-request-url.ts +30 -0
  136. package/src/pull-requests/types.ts +63 -0
  137. package/src/pull-requests/watch-pull-requests.ts +308 -0
  138. package/src/readings/act-on-reading.ts +318 -0
  139. package/src/readings/find-waiting-since.ts +46 -0
  140. package/src/readings/pick-reading-action.ts +315 -0
  141. package/src/readings/types.ts +98 -0
  142. package/src/sessions/build-keep-writes.ts +55 -0
  143. package/src/sessions/build-kept-clear.ts +12 -0
  144. package/src/sessions/forget-sessions.ts +106 -0
  145. package/src/sessions/is-kept-due.ts +27 -0
  146. package/src/sessions/resume-kept-session.ts +25 -0
  147. package/src/sessions/types.ts +39 -0
  148. package/src/test-utils/apply-memory-change.ts +252 -0
  149. package/src/test-utils/build-memory-reader.ts +364 -0
  150. package/src/test-utils/build-mock-bind-change.ts +18 -11
  151. package/src/test-utils/build-mock-claim-change.ts +18 -8
  152. package/src/test-utils/build-mock-file-item-change.ts +3 -1
  153. package/src/test-utils/build-mock-new-id.ts +12 -0
  154. package/src/test-utils/build-mock-note-change.ts +18 -0
  155. package/src/test-utils/build-mock-pause-change.ts +2 -10
  156. package/src/test-utils/build-mock-pull-request-change.ts +19 -9
  157. package/src/test-utils/{build-mock-link-change.ts → build-mock-relation-change.ts} +6 -6
  158. package/src/test-utils/build-mock-running-reading.ts +19 -0
  159. package/src/test-utils/build-mock-settle-change.ts +18 -6
  160. package/src/test-utils/build-mock-start-change.ts +13 -6
  161. package/src/test-utils/claim-store-attempt-reader-cases.ts +163 -133
  162. package/src/test-utils/claim-store-claim-cases.ts +168 -85
  163. package/src/test-utils/claim-store-close-cases.ts +95 -0
  164. package/src/test-utils/claim-store-contract.ts +12 -2
  165. package/src/test-utils/claim-store-copy-cases.ts +100 -0
  166. package/src/test-utils/claim-store-exclusive-cases.ts +65 -19
  167. package/src/test-utils/claim-store-guard-cases.ts +92 -82
  168. package/src/test-utils/claim-store-item-reader-cases.ts +291 -77
  169. package/src/test-utils/claim-store-outbox-cases.ts +281 -0
  170. package/src/test-utils/claim-store-race-cases.ts +150 -0
  171. package/src/test-utils/claim-store-rollback-cases.ts +50 -50
  172. package/src/test-utils/claim-store-seat-cases.ts +270 -0
  173. package/src/test-utils/claim-store-session-cases.ts +289 -0
  174. package/src/test-utils/collect-memory-limit-fills.ts +15 -0
  175. package/src/test-utils/compare-text.ts +8 -0
  176. package/src/test-utils/create-fake-backoff.ts +16 -0
  177. package/src/test-utils/create-fake-clock.ts +20 -0
  178. package/src/test-utils/create-fake-fact-sink.ts +74 -0
  179. package/src/test-utils/create-fake-forge.ts +117 -0
  180. package/src/test-utils/create-fake-worker.ts +303 -0
  181. package/src/test-utils/create-memory-claim-store.ts +73 -484
  182. package/src/test-utils/create-test-dispatch.ts +597 -0
  183. package/src/test-utils/forge-contract.ts +171 -0
  184. package/src/test-utils/is-same-held-seat.ts +11 -0
  185. package/src/test-utils/types.ts +14 -2
  186. package/src/test-utils/worker-contract.ts +192 -0
  187. package/src/timeouts.ts +17 -0
  188. package/src/types.ts +32 -0
  189. package/dist/test-utils/claim-store-pool-cases.d.ts +0 -2
  190. package/src/test-utils/claim-store-pool-cases.ts +0 -178
@@ -1,6 +1,7 @@
1
1
  import { setImmediate } from 'node:timers/promises';
2
2
  import { brandSchemas } from '@boxxy/model';
3
- import { buildMockItemRow } from '@boxxy/model/test-utils/build-mock-item-row';
3
+ import { buildMockItemRules } from '@boxxy/model/test-utils/build-mock-item-rules';
4
+ import { buildMockV1ItemRow } from '@boxxy/model/test-utils/build-mock-v1-item-row';
4
5
  import { buildMockFileItemChange } from './build-mock-file-item-change.ts';
5
6
  import type { ClaimStoreCase } from './types.ts';
6
7
 
@@ -38,7 +39,7 @@ export const claimStoreExclusiveCases: readonly ClaimStoreCase[] = [
38
39
  [
39
40
  '#exclusive leaves the state as it was when work throws',
40
41
  async (store) => {
41
- const item = buildMockItemRow();
42
+ const item = buildMockV1ItemRow();
42
43
 
43
44
  const writing = store.exclusive(async (transaction) => {
44
45
  await transaction.applyChange(buildMockFileItemChange(item));
@@ -49,7 +50,7 @@ export const claimStoreExclusiveCases: readonly ClaimStoreCase[] = [
49
50
  await writing.catch(() => null);
50
51
 
51
52
  const snapshot: unknown = await store.read((transaction) =>
52
- transaction.readSnapshot(brandSchemas.instant.parse(5000), new Map()),
53
+ transaction.readSnapshot(brandSchemas.instant.parse(5000), buildMockItemRules()),
53
54
  );
54
55
 
55
56
  return {
@@ -57,9 +58,10 @@ export const claimStoreExclusiveCases: readonly ClaimStoreCase[] = [
57
58
  expected: {
58
59
  storeVersion: 0,
59
60
  items: [],
60
- links: [],
61
+ relations: [],
61
62
  levers: [],
62
- freeSlots: {},
63
+ limits: [],
64
+ wakes: [],
63
65
  },
64
66
  };
65
67
  },
@@ -82,7 +84,7 @@ export const claimStoreExclusiveCases: readonly ClaimStoreCase[] = [
82
84
  [
83
85
  '#exclusive runs a later call after an earlier call throws',
84
86
  async (store) => {
85
- const item = buildMockItemRow();
87
+ const item = buildMockV1ItemRow();
86
88
 
87
89
  const failing = store.exclusive(() => {
88
90
  throw new Error('the work failed');
@@ -95,7 +97,7 @@ export const claimStoreExclusiveCases: readonly ClaimStoreCase[] = [
95
97
  );
96
98
 
97
99
  const read = await store.read((transaction) =>
98
- transaction.readItem(item.id, brandSchemas.instant.parse(5000), new Map()),
100
+ transaction.readItem(item.id, brandSchemas.instant.parse(5000), buildMockItemRules()),
99
101
  );
100
102
 
101
103
  return { received: read?.id, expected: item.id };
@@ -104,8 +106,8 @@ export const claimStoreExclusiveCases: readonly ClaimStoreCase[] = [
104
106
  [
105
107
  '#read during a running exclusive call sees the last committed state',
106
108
  async (store) => {
107
- const committed = buildMockItemRow({ filedAt: brandSchemas.instant.parse(1000) });
108
- const pending = buildMockItemRow({ filedAt: brandSchemas.instant.parse(2000) });
109
+ const committed = buildMockV1ItemRow({ filedAt: brandSchemas.instant.parse(1000) });
110
+ const pending = buildMockV1ItemRow({ filedAt: brandSchemas.instant.parse(2000) });
109
111
  const gate = Promise.withResolvers<void>();
110
112
  const written = Promise.withResolvers<void>();
111
113
 
@@ -124,7 +126,7 @@ export const claimStoreExclusiveCases: readonly ClaimStoreCase[] = [
124
126
  await written.promise;
125
127
 
126
128
  const seen = await store.read((transaction) =>
127
- transaction.listItems({}, brandSchemas.instant.parse(5000), new Map()),
129
+ transaction.listItems({}, brandSchemas.instant.parse(5000), buildMockItemRules()),
128
130
  );
129
131
 
130
132
  gate.resolve();
@@ -137,19 +139,27 @@ export const claimStoreExclusiveCases: readonly ClaimStoreCase[] = [
137
139
  [
138
140
  '#read keeps one snapshot across a commit made during the call',
139
141
  async (store) => {
140
- const committed = buildMockItemRow({ filedAt: brandSchemas.instant.parse(1000) });
141
- const later = buildMockItemRow({ filedAt: brandSchemas.instant.parse(2000) });
142
+ const committed = buildMockV1ItemRow({ filedAt: brandSchemas.instant.parse(1000) });
143
+ const later = buildMockV1ItemRow({ filedAt: brandSchemas.instant.parse(2000) });
142
144
 
143
145
  await store.exclusive((transaction) =>
144
146
  transaction.applyChange(buildMockFileItemChange(committed)),
145
147
  );
146
148
 
147
149
  const seen: unknown = await store.read(async (transaction) => {
148
- const before = await transaction.listItems({}, brandSchemas.instant.parse(5000), new Map());
150
+ const before = await transaction.listItems(
151
+ {},
152
+ brandSchemas.instant.parse(5000),
153
+ buildMockItemRules(),
154
+ );
149
155
 
150
156
  await store.exclusive((writer) => writer.applyChange(buildMockFileItemChange(later)));
151
157
 
152
- const after = await transaction.listItems({}, brandSchemas.instant.parse(5000), new Map());
158
+ const after = await transaction.listItems(
159
+ {},
160
+ brandSchemas.instant.parse(5000),
161
+ buildMockItemRules(),
162
+ );
153
163
 
154
164
  return { before: before.map((item) => item.id), after: after.map((item) => item.id) };
155
165
  });
@@ -157,10 +167,44 @@ export const claimStoreExclusiveCases: readonly ClaimStoreCase[] = [
157
167
  return { received: seen, expected: { before: [committed.id], after: [committed.id] } };
158
168
  },
159
169
  ],
170
+ [
171
+ '#read rejects with the error work throws',
172
+ async (store) => {
173
+ const reading = store.read(() => {
174
+ throw new Error('the read failed');
175
+ });
176
+
177
+ const settled = await reading.then(
178
+ () => 'resolved',
179
+ (error: unknown) => error,
180
+ );
181
+
182
+ return { received: settled, expected: new Error('the read failed') };
183
+ },
184
+ ],
185
+ [
186
+ '#read runs a read started inside another read',
187
+ async (store) => {
188
+ const item = buildMockV1ItemRow();
189
+
190
+ await store.exclusive((transaction) =>
191
+ transaction.applyChange(buildMockFileItemChange(item)),
192
+ );
193
+
194
+ const seen = await store.read(() =>
195
+ store.read((transaction) =>
196
+ transaction.readItem(item.id, brandSchemas.instant.parse(5000), buildMockItemRules()),
197
+ ),
198
+ );
199
+
200
+ return { received: seen?.id, expected: item.id };
201
+ },
202
+ ],
160
203
  [
161
204
  '#applyChange writes the rows and the outbox facts of a change',
162
205
  async (store) => {
163
- const item = buildMockItemRow({ filedAt: brandSchemas.instant.parse(1000) });
206
+ const item = buildMockV1ItemRow({ filedAt: brandSchemas.instant.parse(1000) });
207
+ const { keptSession: _keptSession, ...filed } = item;
164
208
  const change = buildMockFileItemChange(item);
165
209
 
166
210
  await store.exclusive((transaction) => transaction.applyChange(change));
@@ -177,21 +221,23 @@ export const claimStoreExclusiveCases: readonly ClaimStoreCase[] = [
177
221
  entity: `boxxy:item:${item.id}`,
178
222
  actor: 'boxxy:owner:local',
179
223
  at: 1000,
180
- payload: { ...item },
224
+ payload: filed,
181
225
  sentAt: null,
226
+ parkedAt: null,
227
+ parkedReason: null,
182
228
  },
183
229
  ],
184
230
  };
185
231
  },
186
232
  ],
187
233
  [
188
- '#applyChange returns applied with no slot for a change that takes none',
234
+ '#applyChange returns applied for a change that it writes',
189
235
  async (store) => {
190
- const change = buildMockFileItemChange(buildMockItemRow());
236
+ const change = buildMockFileItemChange(buildMockV1ItemRow());
191
237
 
192
238
  const result = await store.exclusive((transaction) => transaction.applyChange(change));
193
239
 
194
- return { received: result, expected: { kind: 'applied', slot: null } };
240
+ return { received: result, expected: { kind: 'applied' } };
195
241
  },
196
242
  ],
197
243
  ];
@@ -1,7 +1,8 @@
1
1
  import { brandSchemas } from '@boxxy/model';
2
- import { buildMockAttemptRow } from '@boxxy/model/test-utils/build-mock-attempt-row';
3
2
  import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
4
- import { buildMockItemRow } from '@boxxy/model/test-utils/build-mock-item-row';
3
+ import { buildMockItemRules } from '@boxxy/model/test-utils/build-mock-item-rules';
4
+ import { buildMockV1AttemptRow } from '@boxxy/model/test-utils/build-mock-v1-attempt-row';
5
+ import { buildMockV1ItemRow } from '@boxxy/model/test-utils/build-mock-v1-item-row';
5
6
  import { buildMockBindChange } from './build-mock-bind-change.ts';
6
7
  import { buildMockClaimChange } from './build-mock-claim-change.ts';
7
8
  import { buildMockFileItemChange } from './build-mock-file-item-change.ts';
@@ -11,81 +12,83 @@ import type { ClaimStoreCase } from './types.ts';
11
12
 
12
13
  export const claimStoreGuardCases: readonly ClaimStoreCase[] = [
13
14
  [
14
- '#applyChange returns stale-generation for a settle under an old slot gen',
15
+ '#applyChange returns stale-generation and keeps the seats for a settle under an old hold gen',
15
16
  async (store) => {
16
- const first = buildMockItemRow();
17
- const second = buildMockItemRow();
18
- const settled = buildMockAttemptRow({ item: first.id, state: 'starting' });
19
- const holder = buildMockAttemptRow({ item: second.id, state: 'starting' });
17
+ const workers = brandSchemas.limitName.parse('workers');
18
+ const item = buildMockV1ItemRow();
19
+ const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting', holdGen: 2 });
20
20
 
21
21
  await store.exclusive(async (transaction) => {
22
- await transaction.syncPoolSlots([{ name: settled.pool, slots: 1 }]);
23
- await transaction.applyChange(buildMockFileItemChange(first));
24
- await transaction.applyChange(buildMockFileItemChange(second));
25
- await transaction.applyChange(buildMockClaimChange(settled));
26
-
27
- await transaction.applyChange(
28
- buildMockSettleChange({
29
- attempt: { ...settled, slot: 1, slotGen: 1 },
30
- state: 'succeeded',
31
- }),
22
+ await transaction.syncLimits(
23
+ [{ name: workers, max: 1, counts: {} }],
24
+ brandSchemas.instant.parse(500),
32
25
  );
33
26
 
34
- await transaction.applyChange(buildMockClaimChange(holder));
27
+ await transaction.applyChange(buildMockFileItemChange(item));
28
+ await transaction.applyChange(buildMockClaimChange(attempt, [workers]));
35
29
  });
36
30
 
37
31
  const result = await store.exclusive((transaction) =>
38
32
  transaction.applyChange(
39
- buildMockSettleChange({ attempt: { ...holder, slot: 1, slotGen: 1 }, state: 'failed' }),
33
+ buildMockSettleChange({ attempt: { ...attempt, holdGen: 1 }, state: 'failed' }),
40
34
  ),
41
35
  );
42
36
 
43
- const slots = await store.read((transaction) => transaction.listPoolSlots());
37
+ const seats = await store.read((transaction) => transaction.listLimitSeats());
44
38
 
45
39
  return {
46
- received: { result, slots },
40
+ received: { result, seats },
47
41
  expected: {
48
42
  result: { kind: 'stale-generation' },
49
- slots: [{ pool: holder.pool, slot: 1, holder: holder.id, gen: 2, retired: false }],
43
+ seats: [
44
+ {
45
+ limit: 'workers',
46
+ attempt: attempt.id,
47
+ holdGen: 2,
48
+ overCap: false,
49
+ takenAt: attempt.claimedAt,
50
+ releasedAt: null,
51
+ },
52
+ ],
50
53
  },
51
54
  };
52
55
  },
53
56
  ],
54
57
  [
55
- '#applyChange releases both leases of a settle under the live generations',
58
+ '#applyChange releases the lease and the seats of a settle under the live generations',
56
59
  async (store) => {
57
- const item = buildMockItemRow();
58
- const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
60
+ const workers = brandSchemas.limitName.parse('workers');
61
+ const item = buildMockV1ItemRow();
62
+ const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting' });
59
63
 
60
64
  await store.exclusive(async (transaction) => {
61
- await transaction.syncPoolSlots([{ name: attempt.pool, slots: 1 }]);
65
+ await transaction.syncLimits(
66
+ [{ name: workers, max: 1, counts: {} }],
67
+ brandSchemas.instant.parse(500),
68
+ );
69
+
62
70
  await transaction.applyChange(buildMockFileItemChange(item));
63
- await transaction.applyChange(buildMockClaimChange(attempt));
71
+ await transaction.applyChange(buildMockClaimChange(attempt, [workers]));
64
72
  });
65
73
 
66
74
  const result = await store.exclusive((transaction) =>
67
- transaction.applyChange(
68
- buildMockSettleChange({
69
- attempt: { ...attempt, slot: 1, slotGen: 1 },
70
- state: 'succeeded',
71
- }),
72
- ),
75
+ transaction.applyChange(buildMockSettleChange({ attempt, state: 'succeeded' })),
73
76
  );
74
77
 
75
78
  const snapshot = await store.read((transaction) =>
76
- transaction.readSnapshot(brandSchemas.instant.parse(5000), new Map()),
79
+ transaction.readSnapshot(brandSchemas.instant.parse(5000), buildMockItemRules()),
77
80
  );
78
81
 
79
82
  return {
80
83
  received: {
81
84
  result,
82
- freeSlots: snapshot.freeSlots,
85
+ limits: snapshot.limits,
83
86
  derived: snapshot.items[0]?.derived,
84
87
  },
85
88
  expected: {
86
- result: { kind: 'applied', slot: null },
87
- freeSlots: { [attempt.pool]: 1 },
88
- derived: { state: 'waiting', reason: 'no PR linked' },
89
+ result: { kind: 'applied' },
90
+ limits: [{ name: 'workers', max: 1, counts: {}, held: 0 }],
91
+ derived: { state: 'waiting', reason: 'no pull request linked' },
89
92
  },
90
93
  };
91
94
  },
@@ -93,11 +96,10 @@ export const claimStoreGuardCases: readonly ClaimStoreCase[] = [
93
96
  [
94
97
  '#applyChange returns stale-generation and writes no fact under a stale lease generation',
95
98
  async (store) => {
96
- const item = buildMockItemRow();
97
- const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
99
+ const item = buildMockV1ItemRow();
100
+ const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting' });
98
101
 
99
102
  await store.exclusive(async (transaction) => {
100
- await transaction.syncPoolSlots([{ name: attempt.pool, slots: 1 }]);
101
103
  await transaction.applyChange(buildMockFileItemChange(item));
102
104
  await transaction.applyChange(buildMockClaimChange(attempt));
103
105
  });
@@ -125,17 +127,13 @@ export const claimStoreGuardCases: readonly ClaimStoreCase[] = [
125
127
  [
126
128
  '#applyChange returns stale-generation under a released lease',
127
129
  async (store) => {
128
- const item = buildMockItemRow();
129
- const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
130
+ const item = buildMockV1ItemRow();
131
+ const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting' });
130
132
 
131
133
  await store.exclusive(async (transaction) => {
132
- await transaction.syncPoolSlots([{ name: attempt.pool, slots: 1 }]);
133
134
  await transaction.applyChange(buildMockFileItemChange(item));
134
135
  await transaction.applyChange(buildMockClaimChange(attempt));
135
-
136
- await transaction.applyChange(
137
- buildMockSettleChange({ attempt: { ...attempt, slot: 1, slotGen: 1 }, state: 'failed' }),
138
- );
136
+ await transaction.applyChange(buildMockSettleChange({ attempt, state: 'failed' }));
139
137
  });
140
138
 
141
139
  const result = await store.exclusive((transaction) =>
@@ -148,11 +146,10 @@ export const claimStoreGuardCases: readonly ClaimStoreCase[] = [
148
146
  [
149
147
  '#applyChange applies a change under the live lease generation',
150
148
  async (store) => {
151
- const item = buildMockItemRow();
152
- const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
149
+ const item = buildMockV1ItemRow();
150
+ const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting' });
153
151
 
154
152
  await store.exclusive(async (transaction) => {
155
- await transaction.syncPoolSlots([{ name: attempt.pool, slots: 1 }]);
156
153
  await transaction.applyChange(buildMockFileItemChange(item));
157
154
  await transaction.applyChange(buildMockClaimChange(attempt));
158
155
  await transaction.applyChange(buildMockStartChange(attempt));
@@ -166,7 +163,7 @@ export const claimStoreGuardCases: readonly ClaimStoreCase[] = [
166
163
  [
167
164
  '#applyChange returns guard-mismatch and writes nothing when an expect fails',
168
165
  async (store) => {
169
- const item = buildMockItemRow();
166
+ const item = buildMockV1ItemRow();
170
167
 
171
168
  await store.exclusive((transaction) =>
172
169
  transaction.applyChange(buildMockFileItemChange(item)),
@@ -197,7 +194,7 @@ export const claimStoreGuardCases: readonly ClaimStoreCase[] = [
197
194
  type: 'boxxy.item.closed',
198
195
  entity: `boxxy:item:${item.id}`,
199
196
  actor: 'boxxy:owner:local',
200
- payload: { reason: 'done by hand' },
197
+ payload: { reason: 'done by hand', byAttempt: null, ended: [] },
201
198
  },
202
199
  ],
203
200
  }),
@@ -207,7 +204,7 @@ export const claimStoreGuardCases: readonly ClaimStoreCase[] = [
207
204
  const read = await transaction.readItem(
208
205
  item.id,
209
206
  brandSchemas.instant.parse(5000),
210
- new Map(),
207
+ buildMockItemRules(),
211
208
  );
212
209
 
213
210
  const facts = await transaction.readUnsentFacts(10);
@@ -219,7 +216,14 @@ export const claimStoreGuardCases: readonly ClaimStoreCase[] = [
219
216
  received: { result, state },
220
217
  expected: {
221
218
  result: { kind: 'guard-mismatch', table: 'items', key: { id: item.id } },
222
- state: { item: { ...item, derived: { state: 'ready' } }, facts: 1 },
219
+ state: {
220
+ item: {
221
+ ...item,
222
+ derived: { state: 'ready' },
223
+ position: { kind: 'phase', phase: 'build', round: 1 },
224
+ },
225
+ facts: 1,
226
+ },
223
227
  },
224
228
  };
225
229
  },
@@ -227,7 +231,7 @@ export const claimStoreGuardCases: readonly ClaimStoreCase[] = [
227
231
  [
228
232
  '#applyChange returns guard-mismatch for an update that finds no row',
229
233
  async (store) => {
230
- const item = buildMockItemRow();
234
+ const item = buildMockV1ItemRow();
231
235
 
232
236
  const result = await store.exclusive((transaction) =>
233
237
  transaction.applyChange({
@@ -247,7 +251,7 @@ export const claimStoreGuardCases: readonly ClaimStoreCase[] = [
247
251
  type: 'boxxy.item.edited',
248
252
  entity: `boxxy:item:${item.id}`,
249
253
  actor: 'boxxy:owner:local',
250
- payload: { title: 'Mend the gate' },
254
+ payload: { title: 'Mend the gate', byAttempt: null },
251
255
  },
252
256
  ],
253
257
  }),
@@ -272,7 +276,7 @@ export const claimStoreGuardCases: readonly ClaimStoreCase[] = [
272
276
  evidence: [],
273
277
  };
274
278
 
275
- const item = buildMockItemRow({ acceptance: [criterion] });
279
+ const item = buildMockV1ItemRow({ acceptance: [criterion] });
276
280
 
277
281
  await store.exclusive((transaction) =>
278
282
  transaction.applyChange(buildMockFileItemChange(item)),
@@ -300,24 +304,23 @@ export const claimStoreGuardCases: readonly ClaimStoreCase[] = [
300
304
  type: 'boxxy.item.edited',
301
305
  entity: `boxxy:item:${item.id}`,
302
306
  actor: 'boxxy:owner:local',
303
- payload: { title: 'Mend the gate' },
307
+ payload: { title: 'Mend the gate', byAttempt: null },
304
308
  },
305
309
  ],
306
310
  }),
307
311
  );
308
312
 
309
- return { received: result, expected: { kind: 'applied', slot: null } };
313
+ return { received: result, expected: { kind: 'applied' } };
310
314
  },
311
315
  ],
312
316
  [
313
317
  '#findAttemptByAgentSession returns the attempt that holds an agent session id',
314
318
  async (store) => {
315
- const item = buildMockItemRow();
316
- const attempt = buildMockAttemptRow({ item: item.id, state: 'starting' });
319
+ const item = buildMockV1ItemRow();
320
+ const attempt = buildMockV1AttemptRow({ item: item.id, state: 'starting' });
317
321
  const agentSession = brandSchemas.agentSessionId.parse('agent-session-1');
318
322
 
319
323
  await store.exclusive(async (transaction) => {
320
- await transaction.syncPoolSlots([{ name: attempt.pool, slots: 1 }]);
321
324
  await transaction.applyChange(buildMockFileItemChange(item));
322
325
  await transaction.applyChange(buildMockClaimChange(attempt));
323
326
  await transaction.applyChange(buildMockBindChange({ attempt, agentSession }));
@@ -341,33 +344,40 @@ export const claimStoreGuardCases: readonly ClaimStoreCase[] = [
341
344
  },
342
345
  ],
343
346
  [
344
- '#applyChange throws on a second attempt row for one agent session id',
347
+ '#findAttemptByAgentSession returns the later of two attempts bound to one agent session',
345
348
  async (store) => {
346
- const first = buildMockItemRow();
347
- const second = buildMockItemRow();
348
- const holder = buildMockAttemptRow({ item: first.id, state: 'starting' });
349
- const other = buildMockAttemptRow({ item: second.id, state: 'starting' });
349
+ const item = buildMockV1ItemRow();
350
350
  const agentSession = brandSchemas.agentSessionId.parse('agent-session-1');
351
351
 
352
- await store.exclusive(async (transaction) => {
353
- await transaction.syncPoolSlots([{ name: holder.pool, slots: 2 }]);
354
- await transaction.applyChange(buildMockFileItemChange(first));
355
- await transaction.applyChange(buildMockFileItemChange(second));
356
- await transaction.applyChange(buildMockClaimChange(holder));
357
- await transaction.applyChange(buildMockClaimChange(other));
358
- await transaction.applyChange(buildMockBindChange({ attempt: holder, agentSession }));
352
+ const earlier = buildMockV1AttemptRow({
353
+ item: item.id,
354
+ state: 'starting',
355
+ claimedAt: brandSchemas.instant.parse(1000),
359
356
  });
360
357
 
361
- const binding = store.exclusive((transaction) =>
362
- transaction.applyChange(buildMockBindChange({ attempt: other, agentSession })),
363
- );
358
+ const later = buildMockV1AttemptRow({
359
+ item: item.id,
360
+ state: 'starting',
361
+ number: 2,
362
+ leaseGen: 2,
363
+ atcSession: earlier.atcSession,
364
+ claimedAt: brandSchemas.instant.parse(4000),
365
+ });
366
+
367
+ await store.exclusive(async (transaction) => {
368
+ await transaction.applyChange(buildMockFileItemChange(item));
369
+ await transaction.applyChange(buildMockClaimChange(earlier));
370
+ await transaction.applyChange(buildMockBindChange({ attempt: earlier, agentSession }));
371
+ await transaction.applyChange(buildMockSettleChange({ attempt: earlier, state: 'failed' }));
372
+ await transaction.applyChange(buildMockClaimChange(later));
373
+ await transaction.applyChange(buildMockBindChange({ attempt: later, agentSession }));
374
+ });
364
375
 
365
- const settled = await binding.then(
366
- () => 'bound',
367
- () => 'thrown',
376
+ const found = await store.read((transaction) =>
377
+ transaction.findAttemptByAgentSession(agentSession),
368
378
  );
369
379
 
370
- return { received: settled, expected: 'thrown' };
380
+ return { received: found, expected: later.id };
371
381
  },
372
382
  ],
373
383
  ];