@astrosheep/keiyaku 4.5.18 → 4.5.19
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.
- package/build/src/akuma/akuma-handle.js +10 -10
- package/build/src/akuma/akuma-observe.d.ts +2 -0
- package/build/src/akuma/akuma-observe.js +34 -25
- package/build/src/akuma/akuma.d.ts +1 -2
- package/build/src/akuma/akuma.js +1 -5
- package/build/src/akuma/body.js +6 -13
- package/build/src/akuma/fleet-execution.js +35 -11
- package/build/src/akuma/heart/index.d.ts +11 -17
- package/build/src/akuma/heart/index.js +91 -81
- package/build/src/akuma/heart/request-authority.js +19 -19
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +19 -6
- package/build/src/akuma/heart/storage.d.ts +2 -4
- package/build/src/akuma/heart/storage.js +63 -63
- package/build/src/akuma/heart/tells.d.ts +9 -19
- package/build/src/akuma/heart/tells.js +76 -76
- package/build/src/akuma/heart/timeline.d.ts +7 -1
- package/build/src/akuma/heart/timeline.js +62 -53
- package/build/src/git/integration.js +4 -4
- package/build/src/git/process.d.ts +2 -0
- package/build/src/git/process.js +106 -0
- package/build/src/git/read-observation.js +79 -56
- package/build/src/git/target-placement.d.ts +13 -2
- package/build/src/git/target-placement.js +119 -68
- package/build/src/library/akuma-creation.js +4 -2
- package/build/src/plugin/runtime.js +15 -1
- package/build/src/protocol/placement.js +47 -29
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/package.json +7 -5
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { parsePublicHistoryId } from "../identity.js";
|
|
2
2
|
import { answeredTurnFact, endBodyFact, finishBodyFact, insertActivityFact, insertTurnEndFact, insertTurnStartFact, insertPauseControl, insertSessionFact, insertStopControl, killFactForBody, latestBodyFact, latestKillFact, latestSessionFact, lastAnsweredTurnFact, pauseFact, sessionFactForCoordinate, stopFact, turnFact, } from "./rows.js";
|
|
3
|
-
import { insertTellDeliveryFact, dispositionSnapshotProven, insertTellBindingFact, insertTellDispositionSnapshot, insertTellFact, insertTellReceiptFact, insertUndeliveredTellReceipts, latestOpenTellDisposition, openBoundTurns, openTellDispositionIds, pendingTellFacts, resolveTellDispositionSnapshot,
|
|
4
|
-
import { activityFactSlice, lastActivityAt as readLastActivityAt, pruneActivityFacts, } from "./timeline.js";
|
|
5
|
-
import { isHeartAbsent, readSealFromLeash,
|
|
3
|
+
import { insertTellDeliveryFact, dispositionSnapshotProven, insertTellBindingFact, insertTellDispositionSnapshot, insertTellFact, insertTellReceiptFact, insertUndeliveredTellReceipts, latestOpenTellDisposition, openBoundTurns, openTellDispositionIds, pendingTellFacts, hasPendingTell, resolveTellDispositionSnapshot, tellDispositionResolved, tellFact, tellIdsForFence, } from "./tells.js";
|
|
4
|
+
import { activityFactSlice, statusFacts, lastActivityAt as readLastActivityAt, pruneActivityFacts, } from "./timeline.js";
|
|
5
|
+
import { isHeartAbsent, readSealFromLeash, withHeartTransaction, withReadOnlyHeart } from "./storage.js";
|
|
6
6
|
import { soulFact } from "./soul.js";
|
|
7
|
-
import { AkumaBusyError } from "./facts.js";
|
|
7
|
+
import { AkumaBusyError, HeartAuthorityCorruptionError } from "./facts.js";
|
|
8
8
|
export { HeartAbsentError, HeldAkumaLeash, classifyHeartSchema, initializeHeart, isHeartAbsent, probeLeash, } from "./storage.js";
|
|
9
9
|
export { admitRequest, beginRequest, isRequestInputConflict, readNonterminalRequests, readRequest, refuseRequest, reserveRequest, serveRequest, serveUpstreamRequest, unproveRequest, voidRequest, } from "./request-authority.js";
|
|
10
10
|
export { AkumaBusyError, life, lifeAt, projectTell } from "./facts.js";
|
|
@@ -12,7 +12,7 @@ export { drainPendingTells } from "./tells.js";
|
|
|
12
12
|
const ACTIVITY_LIMIT = 5_000;
|
|
13
13
|
export async function readSoul(paths) {
|
|
14
14
|
try {
|
|
15
|
-
return await
|
|
15
|
+
return await withReadOnlyHeart(paths, soulFact);
|
|
16
16
|
}
|
|
17
17
|
catch (error) {
|
|
18
18
|
if (isHeartAbsent(error))
|
|
@@ -22,7 +22,7 @@ export async function readSoul(paths) {
|
|
|
22
22
|
}
|
|
23
23
|
export async function heartExists(paths) {
|
|
24
24
|
try {
|
|
25
|
-
return await
|
|
25
|
+
return await withReadOnlyHeart(paths, () => true);
|
|
26
26
|
}
|
|
27
27
|
catch (error) {
|
|
28
28
|
if (isHeartAbsent(error))
|
|
@@ -41,23 +41,43 @@ export async function readSeal(paths) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
export async function recordSession(paths, input) {
|
|
44
|
-
return await
|
|
44
|
+
return await withHeartTransaction(paths, (heart) => ({ sequence: insertSessionFact(heart, input), ...input }));
|
|
45
45
|
}
|
|
46
46
|
export async function appendActivity(paths, input) {
|
|
47
|
-
return await
|
|
47
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
48
48
|
const sequence = insertActivityFact(heart, input);
|
|
49
49
|
pruneActivityFacts(heart, ACTIVITY_LIMIT);
|
|
50
50
|
return sequence;
|
|
51
|
-
})
|
|
51
|
+
});
|
|
52
52
|
}
|
|
53
53
|
export async function activitySlice(paths) {
|
|
54
|
-
return await
|
|
54
|
+
return await withReadOnlyHeart(paths, (heart) => activityFactSlice(heart));
|
|
55
|
+
}
|
|
56
|
+
export async function readStatusFacts(paths, input) {
|
|
57
|
+
return await withReadOnlyHeart(paths, (heart) => statusFacts(heart, input));
|
|
58
|
+
}
|
|
59
|
+
/** Lifecycle probes do not load narration, Tell bodies, or delivery histories. */
|
|
60
|
+
export async function readLifeSnapshot(paths) {
|
|
61
|
+
try {
|
|
62
|
+
return await withReadOnlyHeart(paths, (heart) => ({
|
|
63
|
+
soul: soulFact(heart),
|
|
64
|
+
latestBody: latestBodyFact(heart),
|
|
65
|
+
latestSession: latestSessionFact(heart),
|
|
66
|
+
latestKill: latestKillFact(heart),
|
|
67
|
+
hasPendingTell: hasPendingTell(heart),
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (isHeartAbsent(error))
|
|
72
|
+
return { soul: null, latestBody: null, latestSession: null, latestKill: null, hasPendingTell: false };
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
55
75
|
}
|
|
56
76
|
function sameTellInput(existing, tell) {
|
|
57
77
|
return (existing.body === tell.body && existing.recordedAt === tell.recordedAt && existing.schemaJson === tell.schemaJson);
|
|
58
78
|
}
|
|
59
79
|
export async function recordTell(paths, tell, options = {}) {
|
|
60
|
-
return await
|
|
80
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
61
81
|
if (soulFact(heart) === null)
|
|
62
82
|
return { kind: "not-born" };
|
|
63
83
|
const existing = tellFact(heart, tell.id);
|
|
@@ -73,37 +93,40 @@ export async function recordTell(paths, tell, options = {}) {
|
|
|
73
93
|
const sequence = insertTellFact(heart, tell);
|
|
74
94
|
pruneActivityFacts(heart, ACTIVITY_LIMIT);
|
|
75
95
|
return { kind: "recorded", tell: { sequence, ...tell, state: "pending", deliveries: [] } };
|
|
76
|
-
})
|
|
96
|
+
});
|
|
77
97
|
}
|
|
78
98
|
export async function readTell(paths, id) {
|
|
79
|
-
return await
|
|
99
|
+
return await withReadOnlyHeart(paths, (heart) => tellFact(heart, id));
|
|
80
100
|
}
|
|
81
101
|
export async function recordTellDeliveries(paths, inputs) {
|
|
82
|
-
await
|
|
102
|
+
await withHeartTransaction(paths, (heart) => {
|
|
83
103
|
for (const input of inputs) {
|
|
84
104
|
const current = tellFact(heart, input.tellId);
|
|
85
105
|
if (current === null)
|
|
86
106
|
throw new Error(`unknown tell ${input.tellId}`);
|
|
87
107
|
}
|
|
108
|
+
let changed = false;
|
|
88
109
|
for (const input of inputs)
|
|
89
|
-
insertTellDeliveryFact(heart, input);
|
|
90
|
-
pruneActivityFacts(heart, ACTIVITY_LIMIT);
|
|
91
|
-
})
|
|
110
|
+
changed = insertTellDeliveryFact(heart, input) || changed;
|
|
111
|
+
pruneActivityFacts(heart, ACTIVITY_LIMIT, changed);
|
|
112
|
+
});
|
|
92
113
|
}
|
|
93
114
|
export async function recordTellReceipt(paths, input) {
|
|
94
|
-
await
|
|
115
|
+
await withHeartTransaction(paths, (heart) => {
|
|
95
116
|
const tellIds = input.evidence === "exact" ? [input.tellId] : tellIdsForFence(heart, input.turnSequence, input.fence);
|
|
96
117
|
if (tellIds.length === 0)
|
|
97
118
|
throw new Error("tell receipt has no delivery mapping");
|
|
98
|
-
insertTellReceiptFact(heart, input);
|
|
99
|
-
pruneActivityFacts(heart, ACTIVITY_LIMIT);
|
|
100
|
-
})
|
|
119
|
+
const changed = insertTellReceiptFact(heart, input);
|
|
120
|
+
pruneActivityFacts(heart, ACTIVITY_LIMIT, changed);
|
|
121
|
+
});
|
|
101
122
|
}
|
|
102
123
|
export async function decidePendingTellDisposition(paths, input) {
|
|
103
|
-
return await
|
|
124
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
104
125
|
const body = latestBodyFact(heart);
|
|
105
126
|
if (body === null || body.sequence !== input.bodySequence)
|
|
106
127
|
return null;
|
|
128
|
+
if (tellDispositionResolved(heart, input.bodySequence) === true)
|
|
129
|
+
return null;
|
|
107
130
|
const existing = openTellDispositionIds(heart, input.bodySequence);
|
|
108
131
|
if (existing !== null)
|
|
109
132
|
return { bodySequence: input.bodySequence, tellIds: existing };
|
|
@@ -122,46 +145,33 @@ export async function decidePendingTellDisposition(paths, input) {
|
|
|
122
145
|
insertTellDispositionSnapshot(heart, input.bodySequence, tellIds, input.at);
|
|
123
146
|
pruneActivityFacts(heart, ACTIVITY_LIMIT);
|
|
124
147
|
return { bodySequence: input.bodySequence, tellIds };
|
|
125
|
-
})
|
|
148
|
+
});
|
|
126
149
|
}
|
|
127
150
|
export async function readOpenPendingTellDisposition(paths) {
|
|
128
|
-
return await
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
151
|
+
return await withReadOnlyHeart(paths, (heart) => latestOpenTellDisposition(heart));
|
|
152
|
+
}
|
|
153
|
+
/** Prove and consume the persisted decision in one transaction, never a caller's stale member list. */
|
|
154
|
+
export async function resolvePendingTellDisposition(paths, bodySequence, at, outcome = "custody") {
|
|
155
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
156
|
+
if (tellDispositionResolved(heart, bodySequence) === true)
|
|
157
|
+
return true;
|
|
158
|
+
const tellIds = openTellDispositionIds(heart, bodySequence);
|
|
159
|
+
if (tellIds === null)
|
|
160
|
+
throw new HeartAuthorityCorruptionError(`Akuma disposition ${bodySequence} is missing`);
|
|
161
|
+
if (outcome === "undelivered")
|
|
162
|
+
insertUndeliveredTellReceipts(heart, tellIds, at);
|
|
163
|
+
if (!dispositionSnapshotProven(heart, tellIds))
|
|
164
|
+
return false;
|
|
132
165
|
resolveTellDispositionSnapshot(heart, bodySequence, at);
|
|
133
|
-
pruneActivityFacts(heart, ACTIVITY_LIMIT);
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Heart-owned disposition custody proof. Sequence growth, spawn resolution, and
|
|
138
|
-
* an unqualified held leash are never proof. Returns the successor Body sequence
|
|
139
|
-
* when that exact Body took the frozen Tell-id snapshot by delivery, or true when
|
|
140
|
-
* the snapshot is already fully settled (no longer pending).
|
|
141
|
-
*/
|
|
142
|
-
export async function provePendingTellDispositionCustody(paths, disposition) {
|
|
143
|
-
return await withHeart(paths, (heart) => readTransaction(heart, () => {
|
|
144
|
-
if (!dispositionSnapshotProven(heart, disposition))
|
|
145
|
-
return { kind: "unproven" };
|
|
146
|
-
const successorBodySequence = successorBodyHoldingDisposition(heart, disposition);
|
|
147
|
-
return successorBodySequence === null
|
|
148
|
-
? { kind: "proven" }
|
|
149
|
-
: { kind: "proven", successorBodySequence };
|
|
150
|
-
}));
|
|
151
|
-
}
|
|
152
|
-
export async function recordUndeliveredPendingTells(paths, at, tellIds) {
|
|
153
|
-
if (tellIds.length === 0)
|
|
154
|
-
return;
|
|
155
|
-
await withHeart(paths, (heart) => transaction(heart, () => {
|
|
156
|
-
insertUndeliveredTellReceipts(heart, tellIds, at);
|
|
157
|
-
pruneActivityFacts(heart, ACTIVITY_LIMIT);
|
|
158
|
-
}));
|
|
166
|
+
pruneActivityFacts(heart, ACTIVITY_LIMIT, true);
|
|
167
|
+
return true;
|
|
168
|
+
});
|
|
159
169
|
}
|
|
160
170
|
export async function readKill(paths, bodySequence) {
|
|
161
|
-
return await
|
|
171
|
+
return await withReadOnlyHeart(paths, (heart) => killFactForBody(heart, bodySequence));
|
|
162
172
|
}
|
|
163
|
-
export async function requestStop(paths, at) {
|
|
164
|
-
return await
|
|
173
|
+
export async function requestStop(paths, at, signal) {
|
|
174
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
165
175
|
const body = latestBodyFact(heart);
|
|
166
176
|
if (body === null)
|
|
167
177
|
throw new Error("Akuma has no Body to kill");
|
|
@@ -175,10 +185,10 @@ export async function requestStop(paths, at) {
|
|
|
175
185
|
}
|
|
176
186
|
insertStopControl(heart, body.sequence, at);
|
|
177
187
|
return { kind: "requested", body };
|
|
178
|
-
})
|
|
188
|
+
}, signal);
|
|
179
189
|
}
|
|
180
|
-
export async function requestPause(paths, at) {
|
|
181
|
-
return await
|
|
190
|
+
export async function requestPause(paths, at, signal) {
|
|
191
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
182
192
|
if (soulFact(heart) === null)
|
|
183
193
|
return { kind: "not-born" };
|
|
184
194
|
const body = latestBodyFact(heart);
|
|
@@ -186,45 +196,45 @@ export async function requestPause(paths, at) {
|
|
|
186
196
|
throw new Error("Akuma has no Body to interrupt");
|
|
187
197
|
insertPauseControl(heart, at);
|
|
188
198
|
return { kind: "requested", body };
|
|
189
|
-
})
|
|
199
|
+
}, signal);
|
|
190
200
|
}
|
|
191
201
|
export async function stopRequested(paths, bodySequence) {
|
|
192
|
-
return await
|
|
202
|
+
return await withReadOnlyHeart(paths, (heart) => {
|
|
193
203
|
const target = stopFact(heart);
|
|
194
204
|
return target !== null && (bodySequence === undefined || target.bodySequence === bodySequence);
|
|
195
205
|
});
|
|
196
206
|
}
|
|
197
207
|
export async function pauseRequested(paths, bodySequence) {
|
|
198
|
-
return await
|
|
208
|
+
return await withReadOnlyHeart(paths, (heart) => {
|
|
199
209
|
const target = pauseFact(heart);
|
|
200
210
|
return target !== null && (bodySequence === undefined || target.bodySequence === bodySequence);
|
|
201
211
|
});
|
|
202
212
|
}
|
|
203
213
|
export async function breakBody(paths, input) {
|
|
204
|
-
await
|
|
214
|
+
await withHeartTransaction(paths, (heart) => endBodyFact(heart, input));
|
|
205
215
|
}
|
|
206
216
|
export async function beginTurn(paths, input) {
|
|
207
|
-
return await
|
|
217
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
208
218
|
const fact = insertTurnStartFact(heart, input);
|
|
209
219
|
pruneActivityFacts(heart, ACTIVITY_LIMIT);
|
|
210
220
|
return fact;
|
|
211
|
-
})
|
|
221
|
+
});
|
|
212
222
|
}
|
|
213
223
|
export async function bindTellsToTurn(paths, input) {
|
|
214
|
-
await
|
|
224
|
+
await withHeartTransaction(paths, (heart) => {
|
|
215
225
|
for (const tellId of input.tellIds) {
|
|
216
226
|
if (tellFact(heart, tellId) === null)
|
|
217
227
|
throw new Error(`unknown tell ${tellId}`);
|
|
218
228
|
insertTellBindingFact(heart, { tellId, turnSequence: input.turnSequence, boundAt: input.boundAt });
|
|
219
229
|
}
|
|
220
230
|
pruneActivityFacts(heart, ACTIVITY_LIMIT);
|
|
221
|
-
})
|
|
231
|
+
});
|
|
222
232
|
}
|
|
223
233
|
export async function readOpenBoundTurns(paths, bodySequence) {
|
|
224
|
-
return await
|
|
234
|
+
return await withReadOnlyHeart(paths, (heart) => openBoundTurns(heart, bodySequence));
|
|
225
235
|
}
|
|
226
236
|
export async function failOpenBoundTurns(paths, input) {
|
|
227
|
-
return await
|
|
237
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
228
238
|
const sequences = openBoundTurns(heart, input.bodySequence);
|
|
229
239
|
for (const turnSequence of sequences) {
|
|
230
240
|
insertTurnEndFact(heart, {
|
|
@@ -234,22 +244,22 @@ export async function failOpenBoundTurns(paths, input) {
|
|
|
234
244
|
completedAt: input.completedAt,
|
|
235
245
|
});
|
|
236
246
|
}
|
|
237
|
-
pruneActivityFacts(heart, ACTIVITY_LIMIT);
|
|
247
|
+
pruneActivityFacts(heart, ACTIVITY_LIMIT, sequences.length > 0);
|
|
238
248
|
return sequences;
|
|
239
|
-
})
|
|
249
|
+
});
|
|
240
250
|
}
|
|
241
251
|
export async function readTurn(paths, sequence) {
|
|
242
|
-
return await
|
|
252
|
+
return await withReadOnlyHeart(paths, (heart) => turnFact(heart, sequence));
|
|
243
253
|
}
|
|
244
254
|
export async function endTurn(paths, input) {
|
|
245
|
-
return await
|
|
255
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
246
256
|
const fact = insertTurnEndFact(heart, { kind: "turn-end", ...input });
|
|
247
|
-
pruneActivityFacts(heart, ACTIVITY_LIMIT);
|
|
257
|
+
pruneActivityFacts(heart, ACTIVITY_LIMIT, true);
|
|
248
258
|
return fact;
|
|
249
|
-
})
|
|
259
|
+
});
|
|
250
260
|
}
|
|
251
261
|
export async function finishBodyIfIdle(paths, input) {
|
|
252
|
-
return await
|
|
262
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
253
263
|
if (stopFact(heart)?.bodySequence === input.sequence || pauseFact(heart)?.bodySequence === input.sequence) {
|
|
254
264
|
endBodyFact(heart, { ...input, end: "put-down" });
|
|
255
265
|
return { kind: "controlled" };
|
|
@@ -259,11 +269,11 @@ export async function finishBodyIfIdle(paths, input) {
|
|
|
259
269
|
return { kind: "pending", tells: pending.map((tell) => tell.id) };
|
|
260
270
|
finishBodyFact(heart, input);
|
|
261
271
|
return { kind: "finished" };
|
|
262
|
-
})
|
|
272
|
+
});
|
|
263
273
|
}
|
|
264
274
|
export async function readHeart(paths) {
|
|
265
275
|
try {
|
|
266
|
-
return await withReadOnlyHeart(paths, (heart) =>
|
|
276
|
+
return await withReadOnlyHeart(paths, (heart) => ({
|
|
267
277
|
soul: soulFact(heart),
|
|
268
278
|
latestBody: latestBodyFact(heart),
|
|
269
279
|
latestSession: latestSessionFact(heart),
|
|
@@ -272,7 +282,7 @@ export async function readHeart(paths) {
|
|
|
272
282
|
stop: stopFact(heart),
|
|
273
283
|
pause: pauseFact(heart),
|
|
274
284
|
lastActivityAt: readLastActivityAt(heart),
|
|
275
|
-
}))
|
|
285
|
+
}));
|
|
276
286
|
}
|
|
277
287
|
catch (error) {
|
|
278
288
|
if (isHeartAbsent(error)) {
|
|
@@ -291,14 +301,14 @@ export async function readHeart(paths) {
|
|
|
291
301
|
}
|
|
292
302
|
}
|
|
293
303
|
export async function readLastAnsweredTurn(paths) {
|
|
294
|
-
return await
|
|
304
|
+
return await withReadOnlyHeart(paths, lastAnsweredTurnFact);
|
|
295
305
|
}
|
|
296
306
|
export async function readForkPoint(paths, historyId) {
|
|
297
307
|
const turnSequence = parsePublicHistoryId(historyId);
|
|
298
308
|
if (turnSequence === null)
|
|
299
309
|
return null;
|
|
300
310
|
try {
|
|
301
|
-
return await
|
|
311
|
+
return await withReadOnlyHeart(paths, (heart) => {
|
|
302
312
|
const turn = answeredTurnFact(heart, turnSequence);
|
|
303
313
|
const outcome = turn?.end?.outcome;
|
|
304
314
|
if (outcome?.kind !== "answered" || outcome.historyId === undefined)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseAkuId } from "../identity.js";
|
|
2
2
|
import { insertRequestFact, nonterminalRequestFacts, requestFact, updateRequestRefused, updateRequestBegun, updateRequestReserved, updateRequestServed, updateRequestUnproven, updateRequestVoided, updateUpstreamRequestServed, } from "./request-rows.js";
|
|
3
|
-
import {
|
|
3
|
+
import { withHeartTransaction, withReadOnlyHeart } from "./storage.js";
|
|
4
4
|
import { soulFact } from "./soul.js";
|
|
5
5
|
import { requestPayloadJson } from "./request-rows.js";
|
|
6
6
|
// This stays out of the public Heart index: only transport service projects it as a refusal.
|
|
@@ -14,7 +14,7 @@ export function isRequestInputConflict(error) {
|
|
|
14
14
|
return error instanceof RequestInputConflictError;
|
|
15
15
|
}
|
|
16
16
|
export async function admitRequest(paths, input) {
|
|
17
|
-
return await
|
|
17
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
18
18
|
const soul = soulFact(heart);
|
|
19
19
|
if (soul === null)
|
|
20
20
|
throw new Error("Akuma request admission requires a born Soul");
|
|
@@ -34,10 +34,10 @@ export async function admitRequest(paths, input) {
|
|
|
34
34
|
throw new RequestInputConflictError(input.id);
|
|
35
35
|
}
|
|
36
36
|
return fact;
|
|
37
|
-
})
|
|
37
|
+
});
|
|
38
38
|
}
|
|
39
39
|
export async function reserveRequest(paths, id, child) {
|
|
40
|
-
return await
|
|
40
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
41
41
|
const before = requestFact(heart, id);
|
|
42
42
|
if (before === null)
|
|
43
43
|
throw new Error(`unknown Akuma request ${id}`);
|
|
@@ -49,13 +49,13 @@ export async function reserveRequest(paths, id, child) {
|
|
|
49
49
|
throw new Error(`Akuma request ${id} cannot reserve ${child}`);
|
|
50
50
|
}
|
|
51
51
|
return requestFact(heart, id);
|
|
52
|
-
})
|
|
52
|
+
});
|
|
53
53
|
}
|
|
54
54
|
export async function serveRequest(paths, id, child) {
|
|
55
55
|
const parsed = parseAkuId(child);
|
|
56
56
|
if (parsed.id !== child)
|
|
57
57
|
throw new Error(`Akuma request ${id} has a noncanonical child`);
|
|
58
|
-
return await
|
|
58
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
59
59
|
const before = requestFact(heart, id);
|
|
60
60
|
if (before === null)
|
|
61
61
|
throw new Error(`unknown Akuma request ${id}`);
|
|
@@ -65,10 +65,10 @@ export async function serveRequest(paths, id, child) {
|
|
|
65
65
|
throw new Error(`Akuma request ${id} cannot serve ${child}`);
|
|
66
66
|
}
|
|
67
67
|
return requestFact(heart, id);
|
|
68
|
-
})
|
|
68
|
+
});
|
|
69
69
|
}
|
|
70
70
|
export async function serveUpstreamRequest(paths, id, serviceJson) {
|
|
71
|
-
return await
|
|
71
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
72
72
|
const before = requestFact(heart, id);
|
|
73
73
|
if (before === null)
|
|
74
74
|
throw new Error(`unknown Akuma request ${id}`);
|
|
@@ -78,10 +78,10 @@ export async function serveUpstreamRequest(paths, id, serviceJson) {
|
|
|
78
78
|
throw new Error(`Akuma request ${id} cannot be served with this service reference`);
|
|
79
79
|
}
|
|
80
80
|
return requestFact(heart, id);
|
|
81
|
-
})
|
|
81
|
+
});
|
|
82
82
|
}
|
|
83
83
|
export async function refuseRequest(paths, id, diagnostic) {
|
|
84
|
-
return await
|
|
84
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
85
85
|
const before = requestFact(heart, id);
|
|
86
86
|
if (before === null)
|
|
87
87
|
throw new Error(`unknown Akuma request ${id}`);
|
|
@@ -91,10 +91,10 @@ export async function refuseRequest(paths, id, diagnostic) {
|
|
|
91
91
|
throw new Error(`Akuma request ${id} cannot be refused`);
|
|
92
92
|
}
|
|
93
93
|
return requestFact(heart, id);
|
|
94
|
-
})
|
|
94
|
+
});
|
|
95
95
|
}
|
|
96
96
|
export async function voidRequest(paths, id, evidence) {
|
|
97
|
-
return await
|
|
97
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
98
98
|
const before = requestFact(heart, id);
|
|
99
99
|
if (before === null)
|
|
100
100
|
throw new Error(`unknown Akuma request ${id}`);
|
|
@@ -105,10 +105,10 @@ export async function voidRequest(paths, id, evidence) {
|
|
|
105
105
|
throw new Error(`Akuma request ${id} cannot be voided`);
|
|
106
106
|
}
|
|
107
107
|
return requestFact(heart, id);
|
|
108
|
-
})
|
|
108
|
+
});
|
|
109
109
|
}
|
|
110
110
|
export async function beginRequest(paths, id) {
|
|
111
|
-
return await
|
|
111
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
112
112
|
const before = requestFact(heart, id);
|
|
113
113
|
if (before === null)
|
|
114
114
|
throw new Error(`unknown Akuma request ${id}`);
|
|
@@ -117,10 +117,10 @@ export async function beginRequest(paths, id) {
|
|
|
117
117
|
else if (before.state !== "begun")
|
|
118
118
|
throw new Error(`Akuma request ${id} cannot begin`);
|
|
119
119
|
return requestFact(heart, id);
|
|
120
|
-
})
|
|
120
|
+
});
|
|
121
121
|
}
|
|
122
122
|
export async function unproveRequest(paths, id, evidence) {
|
|
123
|
-
return await
|
|
123
|
+
return await withHeartTransaction(paths, (heart) => {
|
|
124
124
|
const before = requestFact(heart, id);
|
|
125
125
|
if (before === null)
|
|
126
126
|
throw new Error(`unknown Akuma request ${id}`);
|
|
@@ -130,11 +130,11 @@ export async function unproveRequest(paths, id, evidence) {
|
|
|
130
130
|
throw new Error(`Akuma request ${id} cannot be marked unproven`);
|
|
131
131
|
}
|
|
132
132
|
return requestFact(heart, id);
|
|
133
|
-
})
|
|
133
|
+
});
|
|
134
134
|
}
|
|
135
135
|
export async function readRequest(paths, id) {
|
|
136
|
-
return await
|
|
136
|
+
return await withReadOnlyHeart(paths, (heart) => requestFact(heart, id));
|
|
137
137
|
}
|
|
138
138
|
export async function readNonterminalRequests(paths) {
|
|
139
|
-
return await
|
|
139
|
+
return await withReadOnlyHeart(paths, nonterminalRequestFacts);
|
|
140
140
|
}
|
|
@@ -2,5 +2,5 @@ import type { DatabaseSync } from "node:sqlite";
|
|
|
2
2
|
export declare function assertHeartSchemaVersion(database: DatabaseSync): void;
|
|
3
3
|
export declare function assertLeashSchemaVersion(database: DatabaseSync): void;
|
|
4
4
|
export declare function heartSchemaIsCurrent(database: DatabaseSync): boolean;
|
|
5
|
-
export declare const HEART_SCHEMA = "\n CREATE TABLE IF NOT EXISTS akuma_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version =
|
|
5
|
+
export declare const HEART_SCHEMA = "\n CREATE TABLE IF NOT EXISTS akuma_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version = 28)\n ) STRICT;\n INSERT OR IGNORE INTO akuma_schema(singleton, version) VALUES (1, 28);\n CREATE TABLE IF NOT EXISTS soul (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n soul_json TEXT NOT NULL CHECK (json_valid(soul_json))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS bodies (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n leash_taken_at TEXT NOT NULL,\n hung_diagnostic TEXT,\n hung_at TEXT,\n end TEXT CHECK (end IN ('exited', 'broke-off', 'put-down')),\n ended_at TEXT,\n CHECK ((hung_diagnostic IS NULL AND hung_at IS NULL)\n OR (hung_diagnostic IS NOT NULL AND hung_at IS NOT NULL))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS sessions (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n provider TEXT NOT NULL,\n coordinate_json TEXT NOT NULL CHECK (json_valid(coordinate_json)),\n cwd TEXT NOT NULL,\n options_json TEXT NOT NULL CHECK (json_valid(options_json)),\n admitted_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS turns (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n body_sequence INTEGER NOT NULL REFERENCES bodies(sequence),\n started_at TEXT NOT NULL,\n end_sequence INTEGER UNIQUE REFERENCES timeline(sequence) ON DELETE SET NULL,\n outcome TEXT CHECK (outcome IN ('answered', 'failed', 'invalid-output')),\n history_id TEXT UNIQUE,\n session_json TEXT CHECK (session_json IS NULL OR json_valid(session_json)),\n answer TEXT,\n answer_json TEXT CHECK (answer_json IS NULL OR json_valid(answer_json)),\n schema_json TEXT CHECK (schema_json IS NULL OR json_valid(schema_json)),\n diagnostic TEXT,\n completed_at TEXT,\n CHECK (\n (outcome = 'answered' AND session_json IS NOT NULL AND answer IS NOT NULL AND diagnostic IS NULL\n AND ((schema_json IS NULL AND answer_json IS NULL) OR (schema_json IS NOT NULL AND answer_json IS NOT NULL)))\n OR (outcome = 'failed' AND history_id IS NULL AND session_json IS NULL AND answer IS NULL\n AND answer_json IS NULL AND diagnostic IS NOT NULL)\n OR (outcome = 'invalid-output' AND answer IS NOT NULL AND answer_json IS NULL\n AND diagnostic IS NOT NULL AND schema_json IS NOT NULL)\n OR (outcome IS NULL AND end_sequence IS NULL AND history_id IS NULL AND session_json IS NULL\n AND answer IS NULL AND answer_json IS NULL AND diagnostic IS NULL AND completed_at IS NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS timeline (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL CHECK (kind IN ('turn-start', 'call', 'activity', 'tell', 'turn-end'))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS activity_retention (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n checked_sequence INTEGER NOT NULL\n ) STRICT;\n INSERT OR IGNORE INTO activity_retention(singleton, checked_sequence) VALUES (1, 0);\n CREATE TABLE IF NOT EXISTS calls (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS activity (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n event_json TEXT NOT NULL CHECK (json_valid(event_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tells (\n id TEXT PRIMARY KEY,\n sequence INTEGER NOT NULL UNIQUE REFERENCES timeline(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n schema_json TEXT CHECK (schema_json IS NULL OR json_valid(schema_json)),\n recorded_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_bindings (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n tell_id TEXT NOT NULL REFERENCES tells(id) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n bound_at TEXT NOT NULL,\n UNIQUE (tell_id, turn_sequence)\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_deliveries (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n tell_id TEXT NOT NULL REFERENCES tells(id) ON DELETE CASCADE,\n route TEXT NOT NULL CHECK (route IN ('launch', 'live')),\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT NOT NULL,\n receipt TEXT CHECK (receipt IN ('unavailable', 'required')),\n delivered_at TEXT NOT NULL,\n CHECK ((route = 'launch' AND receipt IS NULL) OR (route = 'live' AND receipt IS NOT NULL)),\n UNIQUE (tell_id, turn_sequence, fence)\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_receipts (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n evidence TEXT NOT NULL CHECK (evidence IN ('exact', 'fence')),\n tell_id TEXT REFERENCES tells(id) ON DELETE CASCADE,\n turn_sequence INTEGER REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT,\n kind TEXT NOT NULL,\n received_at TEXT NOT NULL,\n CHECK (\n (evidence = 'exact' AND tell_id IS NOT NULL AND turn_sequence IS NULL AND fence IS NULL)\n OR (evidence = 'fence' AND tell_id IS NULL AND turn_sequence IS NOT NULL AND fence IS NOT NULL)\n )\n ) STRICT;\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_exact\n ON tell_receipts(tell_id, kind);\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_fence\n ON tell_receipts(turn_sequence, fence, kind);\n CREATE TABLE IF NOT EXISTS tell_dispositions (\n body_sequence INTEGER PRIMARY KEY REFERENCES bodies(sequence),\n decided_at TEXT NOT NULL,\n resolved_at TEXT\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_disposition_members (\n body_sequence INTEGER NOT NULL REFERENCES tell_dispositions(body_sequence) ON DELETE CASCADE,\n tell_id TEXT NOT NULL REFERENCES tells(id),\n PRIMARY KEY (body_sequence, tell_id)\n ) STRICT;\n CREATE INDEX IF NOT EXISTS calls_by_turn ON calls(turn_sequence);\n CREATE INDEX IF NOT EXISTS activity_by_turn ON activity(turn_sequence);\n CREATE INDEX IF NOT EXISTS tell_bindings_by_turn ON tell_bindings(turn_sequence);\n CREATE INDEX IF NOT EXISTS tell_deliveries_by_turn ON tell_deliveries(turn_sequence);\n CREATE INDEX IF NOT EXISTS tell_disposition_members_by_tell ON tell_disposition_members(tell_id);\n CREATE TABLE IF NOT EXISTS requests (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n requester TEXT NOT NULL,\n action TEXT NOT NULL,\n payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),\n admitted_at TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('admitted', 'reserved', 'begun', 'served', 'refused', 'voided', 'unproven')),\n child TEXT,\n service_json TEXT CHECK (service_json IS NULL OR json_valid(service_json)),\n diagnostic TEXT,\n evidence TEXT,\n CHECK (\n (state = 'admitted' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'reserved' AND child IS NOT NULL AND service_json IS NULL\n AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'begun' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'served'\n AND ((child IS NOT NULL AND service_json IS NULL)\n OR (child IS NULL AND service_json IS NOT NULL))\n AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'refused' AND child IS NULL AND service_json IS NULL AND diagnostic IS NOT NULL AND evidence IS NULL)\n OR (state = 'voided' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NOT NULL)\n OR (state = 'unproven' AND child IS NULL AND service_json IS NULL AND diagnostic IS NULL AND evidence IS NOT NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS control (\n kind TEXT PRIMARY KEY CHECK (kind IN ('stop', 'pause')),\n value_json TEXT NOT NULL CHECK (json_valid(value_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS kills (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n body_sequence INTEGER NOT NULL UNIQUE REFERENCES bodies(sequence),\n evidence TEXT NOT NULL CHECK (evidence = 'killed'),\n at TEXT NOT NULL\n ) STRICT;\n";
|
|
6
6
|
export declare const LEASH_SCHEMA = "\n PRAGMA journal_mode=DELETE;\n CREATE TABLE IF NOT EXISTS leash_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version = 4)\n ) STRICT;\n INSERT OR IGNORE INTO leash_schema(singleton, version) VALUES (1, 4);\n CREATE TABLE IF NOT EXISTS seal (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n evidence TEXT NOT NULL,\n at TEXT NOT NULL\n ) STRICT;\n";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const HEART_SCHEMA_VERSION =
|
|
1
|
+
const HEART_SCHEMA_VERSION = 28;
|
|
2
2
|
const LEASH_SCHEMA_VERSION = 4;
|
|
3
3
|
function assertSchemaVersion(database, table, expected) {
|
|
4
4
|
const row = database.prepare(`SELECT version FROM ${table} WHERE singleton = 1`).get();
|
|
@@ -76,6 +76,11 @@ export const HEART_SCHEMA = `
|
|
|
76
76
|
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
77
77
|
kind TEXT NOT NULL CHECK (kind IN ('turn-start', 'call', 'activity', 'tell', 'turn-end'))
|
|
78
78
|
) STRICT;
|
|
79
|
+
CREATE TABLE IF NOT EXISTS activity_retention (
|
|
80
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
81
|
+
checked_sequence INTEGER NOT NULL
|
|
82
|
+
) STRICT;
|
|
83
|
+
INSERT OR IGNORE INTO activity_retention(singleton, checked_sequence) VALUES (1, 0);
|
|
79
84
|
CREATE TABLE IF NOT EXISTS calls (
|
|
80
85
|
sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,
|
|
81
86
|
turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,
|
|
@@ -127,16 +132,24 @@ export const HEART_SCHEMA = `
|
|
|
127
132
|
)
|
|
128
133
|
) STRICT;
|
|
129
134
|
CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_exact
|
|
130
|
-
ON tell_receipts(tell_id, kind)
|
|
135
|
+
ON tell_receipts(tell_id, kind);
|
|
131
136
|
CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_fence
|
|
132
|
-
ON tell_receipts(turn_sequence, fence, kind)
|
|
137
|
+
ON tell_receipts(turn_sequence, fence, kind);
|
|
133
138
|
CREATE TABLE IF NOT EXISTS tell_dispositions (
|
|
134
|
-
body_sequence INTEGER
|
|
135
|
-
tell_id TEXT NOT NULL REFERENCES tells(id),
|
|
139
|
+
body_sequence INTEGER PRIMARY KEY REFERENCES bodies(sequence),
|
|
136
140
|
decided_at TEXT NOT NULL,
|
|
137
|
-
resolved_at TEXT
|
|
141
|
+
resolved_at TEXT
|
|
142
|
+
) STRICT;
|
|
143
|
+
CREATE TABLE IF NOT EXISTS tell_disposition_members (
|
|
144
|
+
body_sequence INTEGER NOT NULL REFERENCES tell_dispositions(body_sequence) ON DELETE CASCADE,
|
|
145
|
+
tell_id TEXT NOT NULL REFERENCES tells(id),
|
|
138
146
|
PRIMARY KEY (body_sequence, tell_id)
|
|
139
147
|
) STRICT;
|
|
148
|
+
CREATE INDEX IF NOT EXISTS calls_by_turn ON calls(turn_sequence);
|
|
149
|
+
CREATE INDEX IF NOT EXISTS activity_by_turn ON activity(turn_sequence);
|
|
150
|
+
CREATE INDEX IF NOT EXISTS tell_bindings_by_turn ON tell_bindings(turn_sequence);
|
|
151
|
+
CREATE INDEX IF NOT EXISTS tell_deliveries_by_turn ON tell_deliveries(turn_sequence);
|
|
152
|
+
CREATE INDEX IF NOT EXISTS tell_disposition_members_by_tell ON tell_disposition_members(tell_id);
|
|
140
153
|
CREATE TABLE IF NOT EXISTS requests (
|
|
141
154
|
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
142
155
|
id TEXT NOT NULL UNIQUE,
|
|
@@ -8,10 +8,8 @@ export declare class HeartAbsentError extends Error {
|
|
|
8
8
|
}
|
|
9
9
|
export declare function isHeartAbsent(error: unknown): error is HeartAbsentError;
|
|
10
10
|
export declare function classifyHeartSchema(paths: AkumaPaths): Promise<"current" | "unsupported">;
|
|
11
|
-
export declare function
|
|
12
|
-
export declare function
|
|
13
|
-
export declare function transaction<T>(database: DatabaseSync, body: () => T): T;
|
|
14
|
-
export declare function readTransaction<T>(database: DatabaseSync, body: () => T): T;
|
|
11
|
+
export declare function withReadOnlyHeart<T>(paths: AkumaPaths, body: (database: DatabaseSync) => T, signal?: AbortSignal): Promise<T>;
|
|
12
|
+
export declare function withHeartTransaction<T>(paths: AkumaPaths, body: (database: DatabaseSync) => T, signal?: AbortSignal): Promise<T>;
|
|
15
13
|
export declare function initializeHeart(paths: AkumaPaths): Promise<void>;
|
|
16
14
|
export declare class HeldAkumaLeash {
|
|
17
15
|
private readonly database;
|