omp-conductor 0.10.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -19
- package/package.json +1 -1
- package/src/board.ts +125 -11
- package/src/briefs/orchestrator.md +50 -4
- package/src/briefs/policy.md +4 -1
- package/src/chain-check.ts +157 -0
- package/src/cli.ts +188 -19
- package/src/config.ts +195 -15
- package/src/daemon.ts +1017 -119
- package/src/diff-flags.ts +48 -3
- package/src/digest-schedule.ts +59 -0
- package/src/escalate.ts +17 -0
- package/src/failure-class.ts +31 -4
- package/src/fleet.ts +8 -3
- package/src/gitops.ts +49 -0
- package/src/omp.ts +44 -9
- package/src/orchestrator-tick.ts +94 -9
- package/src/orchestrator.ts +3 -2
- package/src/plugin.ts +46 -4
- package/src/release-policy.ts +66 -6
- package/src/reports.ts +5 -6
- package/src/session-host.ts +23 -6
- package/src/setup.ts +43 -10
- package/src/store.ts +263 -31
- package/src/tracker/github.ts +261 -56
- package/src/types.ts +251 -32
- package/src/verbs/actions.ts +127 -15
- package/src/verbs/protocol.ts +7 -6
- package/src/verbs/server.ts +144 -13
- package/src/worker.ts +183 -17
- package/src/worktree.ts +5 -0
package/src/store.ts
CHANGED
|
@@ -26,6 +26,8 @@ import type {
|
|
|
26
26
|
FrictionKind,
|
|
27
27
|
FrictionObservation,
|
|
28
28
|
FrictionSignal,
|
|
29
|
+
HeldNotice,
|
|
30
|
+
HeldNoticeDraft,
|
|
29
31
|
LabelOp,
|
|
30
32
|
MergeLock,
|
|
31
33
|
ReportDeliveryState,
|
|
@@ -39,6 +41,7 @@ import type {
|
|
|
39
41
|
SessionRole,
|
|
40
42
|
SettlementFlag,
|
|
41
43
|
Store,
|
|
44
|
+
TurnOverride,
|
|
42
45
|
VerbDecision,
|
|
43
46
|
VerbLedgerDraft,
|
|
44
47
|
VerbLedgerEntry,
|
|
@@ -71,6 +74,7 @@ const FRICTION_HOLD_REASONS: ReadonlySet<FrictionAdmissionReason> = new Set([
|
|
|
71
74
|
"failed-attempts",
|
|
72
75
|
"continuations",
|
|
73
76
|
"parent-lookup-error",
|
|
77
|
+
"issue-state-lookup-error",
|
|
74
78
|
"open-pr-lookup-error",
|
|
75
79
|
"unroutable:no-repo-label",
|
|
76
80
|
"unroutable:multiple-repo-labels",
|
|
@@ -105,6 +109,10 @@ const UPDATABLE_COLUMNS: Record<string, true> = {
|
|
|
105
109
|
sessionFile: true,
|
|
106
110
|
prUrl: true,
|
|
107
111
|
headSha: true,
|
|
112
|
+
mergeSha: true,
|
|
113
|
+
baseRef: true,
|
|
114
|
+
baseCheck: true,
|
|
115
|
+
baseCheckAt: true,
|
|
108
116
|
salvageSha: true,
|
|
109
117
|
salvageError: true,
|
|
110
118
|
salvageAckAt: true,
|
|
@@ -137,6 +145,10 @@ interface RunRow {
|
|
|
137
145
|
sessionFile: string | null;
|
|
138
146
|
prUrl: string | null;
|
|
139
147
|
headSha: string | null;
|
|
148
|
+
mergeSha: string | null;
|
|
149
|
+
baseRef: string | null;
|
|
150
|
+
baseCheck: string | null;
|
|
151
|
+
baseCheckAt: number | null;
|
|
140
152
|
salvageSha: string | null;
|
|
141
153
|
salvageError: string | null;
|
|
142
154
|
salvageAckAt: number | null;
|
|
@@ -254,6 +266,10 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
254
266
|
sessionFile TEXT,
|
|
255
267
|
prUrl TEXT,
|
|
256
268
|
headSha TEXT,
|
|
269
|
+
mergeSha TEXT,
|
|
270
|
+
baseRef TEXT,
|
|
271
|
+
baseCheck TEXT,
|
|
272
|
+
baseCheckAt INTEGER,
|
|
257
273
|
salvageSha TEXT,
|
|
258
274
|
salvageError TEXT,
|
|
259
275
|
salvageAckAt INTEGER,
|
|
@@ -274,6 +290,28 @@ CREATE TABLE IF NOT EXISTS notifications (
|
|
|
274
290
|
at INTEGER NOT NULL
|
|
275
291
|
);
|
|
276
292
|
|
|
293
|
+
-- A one-shot operator-selected ceiling for an issue's next claimed attempt.
|
|
294
|
+
-- The run insert and consumption happen in one transaction.
|
|
295
|
+
CREATE TABLE IF NOT EXISTS turn_overrides (
|
|
296
|
+
project TEXT NOT NULL,
|
|
297
|
+
issue INTEGER NOT NULL CHECK (issue > 0),
|
|
298
|
+
maxTurns INTEGER NOT NULL CHECK (maxTurns > 0),
|
|
299
|
+
setAt INTEGER NOT NULL CHECK (setAt >= 0),
|
|
300
|
+
PRIMARY KEY (project, issue)
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
-- Append-only audit of every operator-selected ceiling. Consuming or replacing
|
|
304
|
+
-- the pending value never erases what was requested.
|
|
305
|
+
CREATE TABLE IF NOT EXISTS turn_override_ledger (
|
|
306
|
+
id TEXT PRIMARY KEY,
|
|
307
|
+
project TEXT NOT NULL,
|
|
308
|
+
issue INTEGER NOT NULL CHECK (issue > 0),
|
|
309
|
+
maxTurns INTEGER NOT NULL CHECK (maxTurns > 0),
|
|
310
|
+
setAt INTEGER NOT NULL CHECK (setAt >= 0)
|
|
311
|
+
);
|
|
312
|
+
CREATE INDEX IF NOT EXISTS turn_override_ledger_project_at
|
|
313
|
+
ON turn_override_ledger (project, setAt);
|
|
314
|
+
|
|
277
315
|
CREATE TABLE IF NOT EXISTS dispatch_summaries (
|
|
278
316
|
project TEXT PRIMARY KEY,
|
|
279
317
|
summary TEXT NOT NULL
|
|
@@ -332,6 +370,24 @@ CREATE UNIQUE INDEX IF NOT EXISTS reports_dedupe
|
|
|
332
370
|
CREATE INDEX IF NOT EXISTS reports_project_state
|
|
333
371
|
ON reports (project, state, nextAttemptAt);
|
|
334
372
|
|
|
373
|
+
-- Interrupts a project's reporting.interruptOn policy deferred to the digest
|
|
374
|
+
-- (#229): an escalation that must not page the operator's phone now is held
|
|
375
|
+
-- here so the daily rollup can surface it. digestedAt set -> re-surfaced by a
|
|
376
|
+
-- digest pass and no longer owed. A table rather than a flag on reports: these
|
|
377
|
+
-- are escalations that never reached a send, distinct from reports the model
|
|
378
|
+
-- wrote on purpose.
|
|
379
|
+
CREATE TABLE IF NOT EXISTS held_notices (
|
|
380
|
+
id TEXT PRIMARY KEY,
|
|
381
|
+
project TEXT NOT NULL,
|
|
382
|
+
category TEXT NOT NULL,
|
|
383
|
+
summary TEXT NOT NULL,
|
|
384
|
+
detail TEXT NOT NULL,
|
|
385
|
+
createdAt INTEGER NOT NULL,
|
|
386
|
+
digestedAt INTEGER
|
|
387
|
+
);
|
|
388
|
+
CREATE INDEX IF NOT EXISTS held_notices_project_undigested
|
|
389
|
+
ON held_notices (project, digestedAt) WHERE digestedAt IS NULL;
|
|
390
|
+
|
|
335
391
|
-- The action ledger for the conductor-owned mutation verbs (#126). Every
|
|
336
392
|
-- decided call lands here, refusals included: the question an escalation asks
|
|
337
393
|
-- is "what did this run try", and a table that only records what succeeded
|
|
@@ -499,6 +555,10 @@ function toRecord(row: RunRow): RunRecord {
|
|
|
499
555
|
if (row.sessionFile !== null) record.sessionFile = row.sessionFile;
|
|
500
556
|
if (row.prUrl !== null) record.prUrl = row.prUrl;
|
|
501
557
|
if (row.headSha !== null) record.headSha = row.headSha;
|
|
558
|
+
if (row.mergeSha !== null) record.mergeSha = row.mergeSha;
|
|
559
|
+
if (row.baseRef !== null) record.baseRef = row.baseRef;
|
|
560
|
+
if (row.baseCheck !== null) record.baseCheck = row.baseCheck as RunRecord["baseCheck"];
|
|
561
|
+
if (row.baseCheckAt !== null) record.baseCheckAt = row.baseCheckAt;
|
|
502
562
|
if (row.salvageSha !== null) record.salvageSha = row.salvageSha;
|
|
503
563
|
if (row.salvageError !== null) record.salvageError = row.salvageError;
|
|
504
564
|
if (row.salvageAckAt !== null) record.salvageAckAt = row.salvageAckAt;
|
|
@@ -724,6 +784,18 @@ export function openStore(dbPath: string): Store {
|
|
|
724
784
|
if (!columns.some((column) => column.name === "headSha")) {
|
|
725
785
|
db.exec("ALTER TABLE runs ADD COLUMN headSha TEXT");
|
|
726
786
|
}
|
|
787
|
+
// Historical merged rows were never watched after settlement, so NULL is
|
|
788
|
+
// "not observed", never permission to claim their base branch was green.
|
|
789
|
+
for (const [name, type] of [
|
|
790
|
+
["mergeSha", "TEXT"],
|
|
791
|
+
["baseRef", "TEXT"],
|
|
792
|
+
["baseCheck", "TEXT"],
|
|
793
|
+
["baseCheckAt", "INTEGER"],
|
|
794
|
+
] as const) {
|
|
795
|
+
if (!columns.some((column) => column.name === name)) {
|
|
796
|
+
db.exec(`ALTER TABLE runs ADD COLUMN ${name} ${type}`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
727
799
|
// v0.3.22 and earlier removed a blocked run's dirty tree without saving it
|
|
728
800
|
// (#118), so no row before this release has anywhere to record where the
|
|
729
801
|
// work went. A NULL `salvageSha` on a historical row therefore means "never
|
|
@@ -858,9 +930,10 @@ export function openStore(dbPath: string): Store {
|
|
|
858
930
|
const insertRun = db.query<unknown, SqlValue[]>(
|
|
859
931
|
`INSERT INTO runs (
|
|
860
932
|
id, project, issue, repo, branch, worktree, state, attempt, turns,
|
|
861
|
-
maxTurns, spendUsd, sessionFile, prUrl, headSha,
|
|
862
|
-
|
|
863
|
-
|
|
933
|
+
maxTurns, spendUsd, sessionFile, prUrl, headSha, mergeSha, baseRef,
|
|
934
|
+
baseCheck, baseCheckAt, salvageSha, salvageError, salvageAckAt, startedAt,
|
|
935
|
+
endedAt, lastError, settlementFlags, report
|
|
936
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
864
937
|
);
|
|
865
938
|
const selectRun = db.query<RunRow, [string]>(`SELECT * FROM runs WHERE id = ?`);
|
|
866
939
|
const selectActive = db.query<RunRow, SqlValue[]>(
|
|
@@ -890,6 +963,26 @@ export function openStore(dbPath: string): Store {
|
|
|
890
963
|
AND (state <> 'merged' OR COALESCE(endedAt, startedAt) >= ?)
|
|
891
964
|
ORDER BY startedAt DESC`,
|
|
892
965
|
);
|
|
966
|
+
const selectPendingBaseChecks = db.query<RunRow, [string, number]>(
|
|
967
|
+
`SELECT * FROM runs
|
|
968
|
+
WHERE project = ? AND state = 'merged' AND baseCheck = 'pending'
|
|
969
|
+
ORDER BY COALESCE(endedAt, startedAt) ASC, rowid ASC
|
|
970
|
+
LIMIT ?`,
|
|
971
|
+
);
|
|
972
|
+
const selectLatestBaseChecks = db.query<RunRow & { rank: number }, [string, number]>(
|
|
973
|
+
`SELECT * FROM (
|
|
974
|
+
SELECT runs.*,
|
|
975
|
+
ROW_NUMBER() OVER (
|
|
976
|
+
PARTITION BY repo
|
|
977
|
+
ORDER BY COALESCE(endedAt, startedAt) DESC, runs.rowid DESC
|
|
978
|
+
) AS rank
|
|
979
|
+
FROM runs
|
|
980
|
+
WHERE project = ? AND state = 'merged' AND baseCheck IS NOT NULL
|
|
981
|
+
AND COALESCE(baseCheckAt, endedAt, startedAt) >= ?
|
|
982
|
+
)
|
|
983
|
+
WHERE rank = 1
|
|
984
|
+
ORDER BY repo ASC`,
|
|
985
|
+
);
|
|
893
986
|
const countAttempts = db.query<{ n: number }, [string, number]>(
|
|
894
987
|
`SELECT COUNT(*) AS n FROM runs WHERE project = ? AND issue = ?`,
|
|
895
988
|
);
|
|
@@ -902,12 +995,12 @@ export function openStore(dbPath: string): Store {
|
|
|
902
995
|
const countFailures = db.query<{ n: number }, [string, number]>(
|
|
903
996
|
`SELECT COUNT(*) AS n FROM runs
|
|
904
997
|
WHERE project = ? AND issue = ? AND state = 'failed'
|
|
905
|
-
AND (failureClass IS NULL OR failureClass NOT IN ('ci-infra', 'settlement-stuck', 'env-start-failure', 'dispatch-infra', 'provider-credit'))`,
|
|
998
|
+
AND (failureClass IS NULL OR failureClass NOT IN ('ci-infra', 'settlement-stuck', 'env-start-failure', 'dispatch-infra', 'provider-credit', 'provider-transient'))`,
|
|
906
999
|
);
|
|
907
1000
|
const countContinuations = db.query<{ n: number }, [string, number]>(
|
|
908
1001
|
`SELECT COUNT(*) AS n FROM runs
|
|
909
1002
|
WHERE project = ? AND issue = ? AND state IN ('killed', 'orphaned', 'blocked')
|
|
910
|
-
AND (failureClass IS NULL OR failureClass NOT IN ('admin-kill', 'settlement-stuck', 'env-start-failure', 'dispatch-infra', 'provider-credit'))`,
|
|
1003
|
+
AND (failureClass IS NULL OR failureClass NOT IN ('admin-kill', 'settlement-stuck', 'env-start-failure', 'dispatch-infra', 'provider-credit', 'provider-transient'))`,
|
|
911
1004
|
);
|
|
912
1005
|
// How many times one issue reached a given class. Recovery uses it to bound a
|
|
913
1006
|
// retry loop whose cause is persistent (e.g. a mirror that will not refresh):
|
|
@@ -973,6 +1066,38 @@ export function openStore(dbPath: string): Store {
|
|
|
973
1066
|
ORDER BY startedAt DESC, rowid DESC
|
|
974
1067
|
LIMIT 1`,
|
|
975
1068
|
);
|
|
1069
|
+
const upsertTurnOverride = db.query<unknown, [string, number, number, number]>(
|
|
1070
|
+
`INSERT INTO turn_overrides (project, issue, maxTurns, setAt) VALUES (?, ?, ?, ?)
|
|
1071
|
+
ON CONFLICT(project, issue) DO UPDATE SET
|
|
1072
|
+
maxTurns = excluded.maxTurns,
|
|
1073
|
+
setAt = excluded.setAt`,
|
|
1074
|
+
);
|
|
1075
|
+
const insertTurnOverrideLedger = db.query<
|
|
1076
|
+
unknown,
|
|
1077
|
+
[string, string, number, number, number]
|
|
1078
|
+
>(
|
|
1079
|
+
`INSERT INTO turn_override_ledger (id, project, issue, maxTurns, setAt)
|
|
1080
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
1081
|
+
);
|
|
1082
|
+
const selectTurnOverride = db.query<{ maxTurns: number }, [string, number]>(
|
|
1083
|
+
`SELECT maxTurns FROM turn_overrides WHERE project = ? AND issue = ?`,
|
|
1084
|
+
);
|
|
1085
|
+
const deleteTurnOverride = db.query<unknown, [string, number]>(
|
|
1086
|
+
`DELETE FROM turn_overrides WHERE project = ? AND issue = ?`,
|
|
1087
|
+
);
|
|
1088
|
+
const selectTurnOverrides = db.query<TurnOverride, [string]>(
|
|
1089
|
+
`SELECT project, issue, maxTurns, setAt FROM turn_overrides
|
|
1090
|
+
WHERE project = ? ORDER BY issue ASC`,
|
|
1091
|
+
);
|
|
1092
|
+
const selectTurnOverrideLedger = db.query<
|
|
1093
|
+
TurnOverride,
|
|
1094
|
+
[string, number, number, number]
|
|
1095
|
+
>(
|
|
1096
|
+
`SELECT project, issue, maxTurns, setAt FROM turn_override_ledger
|
|
1097
|
+
WHERE project = ? AND (? = 0 OR issue = ?)
|
|
1098
|
+
ORDER BY setAt DESC, rowid DESC
|
|
1099
|
+
LIMIT ?`,
|
|
1100
|
+
);
|
|
976
1101
|
// Every attempt that settled with a report, in attempt order. NULLs are the
|
|
977
1102
|
// rows written before #199, or one killed before the worker returned a
|
|
978
1103
|
// report — skipping them makes a continuation read exactly today's priors.
|
|
@@ -1079,6 +1204,33 @@ export function openStore(dbPath: string): Store {
|
|
|
1079
1204
|
ORDER BY createdAt ASC, rowid ASC
|
|
1080
1205
|
LIMIT ?`,
|
|
1081
1206
|
);
|
|
1207
|
+
|
|
1208
|
+
// Held notices (#229): escalations deferred to the digest by the project's
|
|
1209
|
+
// `reporting.interruptOn` policy. `digestedAt` NULL = still owed.
|
|
1210
|
+
const insertHeldNotice = db.query<unknown, SqlValue[]>(
|
|
1211
|
+
`INSERT INTO held_notices (id, project, category, summary, detail, createdAt, digestedAt)
|
|
1212
|
+
VALUES (?, ?, ?, ?, ?, ?, NULL)`,
|
|
1213
|
+
);
|
|
1214
|
+
const selectUndigestedNotices = db.query<
|
|
1215
|
+
{ id: string; category: string; summary: string; detail: string; createdAt: number },
|
|
1216
|
+
[string]
|
|
1217
|
+
>(
|
|
1218
|
+
`SELECT id, category, summary, detail, createdAt FROM held_notices
|
|
1219
|
+
WHERE project = ? AND digestedAt IS NULL
|
|
1220
|
+
ORDER BY createdAt ASC`,
|
|
1221
|
+
);
|
|
1222
|
+
const markNoticesDigestedRow = db.query<unknown, [number, string]>(
|
|
1223
|
+
`UPDATE held_notices SET digestedAt = ? WHERE project = ? AND digestedAt IS NULL`,
|
|
1224
|
+
);
|
|
1225
|
+
// The newest digest dedupe key a project has actually run toward — an
|
|
1226
|
+
// `updatedAt`-newest row whose key is a digest prefix and that did not end in
|
|
1227
|
+
// failure. The CLI refuses an off-schedule digest by comparing this against
|
|
1228
|
+
// today's key under `digestDue` (#229).
|
|
1229
|
+
const selectLastDigestKey = db.query<{ key: string | null }, [string]>(
|
|
1230
|
+
`SELECT dedupeKey AS key FROM reports
|
|
1231
|
+
WHERE project = ? AND dedupeKey LIKE 'digest:%' AND state <> 'failed'
|
|
1232
|
+
ORDER BY createdAt DESC LIMIT 1`,
|
|
1233
|
+
);
|
|
1082
1234
|
const claimReportRow = db.query<unknown, [string, number, string, number]>(
|
|
1083
1235
|
`UPDATE reports
|
|
1084
1236
|
SET state = 'sending', attemptId = ?, attempts = attempts + 1, updatedAt = ?
|
|
@@ -1300,34 +1452,58 @@ export function openStore(dbPath: string): Store {
|
|
|
1300
1452
|
recordGhRefusalTx(at);
|
|
1301
1453
|
};
|
|
1302
1454
|
|
|
1455
|
+
const insertRunRecord = (r: Omit<RunRecord, "id">): RunRecord => {
|
|
1456
|
+
const record: RunRecord = { ...r, id: crypto.randomUUID() };
|
|
1457
|
+
insertRun.run(
|
|
1458
|
+
record.id,
|
|
1459
|
+
record.project,
|
|
1460
|
+
record.issue,
|
|
1461
|
+
record.repo,
|
|
1462
|
+
record.branch,
|
|
1463
|
+
record.worktree,
|
|
1464
|
+
record.state,
|
|
1465
|
+
record.attempt,
|
|
1466
|
+
record.turns,
|
|
1467
|
+
record.maxTurns,
|
|
1468
|
+
record.spendUsd,
|
|
1469
|
+
toSql(record.sessionFile),
|
|
1470
|
+
toSql(record.prUrl),
|
|
1471
|
+
toSql(record.headSha),
|
|
1472
|
+
toSql(record.mergeSha),
|
|
1473
|
+
toSql(record.baseRef),
|
|
1474
|
+
toSql(record.baseCheck),
|
|
1475
|
+
toSql(record.baseCheckAt),
|
|
1476
|
+
toSql(record.salvageSha),
|
|
1477
|
+
toSql(record.salvageError),
|
|
1478
|
+
toSql(record.salvageAckAt),
|
|
1479
|
+
record.startedAt,
|
|
1480
|
+
toSql(record.endedAt),
|
|
1481
|
+
toSql(record.lastError),
|
|
1482
|
+
toSql(record.settlementFlags),
|
|
1483
|
+
toSql(record.report),
|
|
1484
|
+
);
|
|
1485
|
+
return record;
|
|
1486
|
+
};
|
|
1487
|
+
const createRunTx = db.transaction((r: Omit<RunRecord, "id">): RunRecord => {
|
|
1488
|
+
if (r.state !== "claimed") return insertRunRecord(r);
|
|
1489
|
+
const turnOverride = selectTurnOverride.get(r.project, r.issue)?.maxTurns;
|
|
1490
|
+
const record = insertRunRecord({
|
|
1491
|
+
...r,
|
|
1492
|
+
maxTurns: Math.max(r.maxTurns, turnOverride ?? r.maxTurns),
|
|
1493
|
+
});
|
|
1494
|
+
if (turnOverride !== undefined) deleteTurnOverride.run(r.project, r.issue);
|
|
1495
|
+
return record;
|
|
1496
|
+
});
|
|
1497
|
+
const setTurnOverrideTx = db.transaction(
|
|
1498
|
+
(project: string, issue: number, maxTurns: number, setAt: number): void => {
|
|
1499
|
+
upsertTurnOverride.run(project, issue, maxTurns, setAt);
|
|
1500
|
+
insertTurnOverrideLedger.run(crypto.randomUUID(), project, issue, maxTurns, setAt);
|
|
1501
|
+
},
|
|
1502
|
+
);
|
|
1503
|
+
|
|
1303
1504
|
return {
|
|
1304
1505
|
createRun(r: Omit<RunRecord, "id">): RunRecord {
|
|
1305
|
-
|
|
1306
|
-
insertRun.run(
|
|
1307
|
-
record.id,
|
|
1308
|
-
record.project,
|
|
1309
|
-
record.issue,
|
|
1310
|
-
record.repo,
|
|
1311
|
-
record.branch,
|
|
1312
|
-
record.worktree,
|
|
1313
|
-
record.state,
|
|
1314
|
-
record.attempt,
|
|
1315
|
-
record.turns,
|
|
1316
|
-
record.maxTurns,
|
|
1317
|
-
record.spendUsd,
|
|
1318
|
-
toSql(record.sessionFile),
|
|
1319
|
-
toSql(record.prUrl),
|
|
1320
|
-
toSql(record.headSha),
|
|
1321
|
-
toSql(record.salvageSha),
|
|
1322
|
-
toSql(record.salvageError),
|
|
1323
|
-
toSql(record.salvageAckAt),
|
|
1324
|
-
record.startedAt,
|
|
1325
|
-
toSql(record.endedAt),
|
|
1326
|
-
toSql(record.lastError),
|
|
1327
|
-
toSql(record.settlementFlags),
|
|
1328
|
-
toSql(record.report),
|
|
1329
|
-
);
|
|
1330
|
-
return record;
|
|
1506
|
+
return createRunTx(r);
|
|
1331
1507
|
},
|
|
1332
1508
|
|
|
1333
1509
|
updateRun(id: string, patch: Partial<RunRecord>): void {
|
|
@@ -1367,6 +1543,14 @@ export function openStore(dbPath: string): Store {
|
|
|
1367
1543
|
recentRuns(project: string, mergedSinceEpochMs: number): RunRecord[] {
|
|
1368
1544
|
return selectRecentRuns.all(project, mergedSinceEpochMs).map(toRecord);
|
|
1369
1545
|
},
|
|
1546
|
+
runsNeedingBaseCheck(project: string, limit = 20): RunRecord[] {
|
|
1547
|
+
return selectPendingBaseChecks.all(project, limit).map(toRecord);
|
|
1548
|
+
},
|
|
1549
|
+
|
|
1550
|
+
latestBaseChecks(project: string, sinceEpochMs: number): RunRecord[] {
|
|
1551
|
+
return selectLatestBaseChecks.all(project, sinceEpochMs).map(toRecord);
|
|
1552
|
+
},
|
|
1553
|
+
|
|
1370
1554
|
|
|
1371
1555
|
salvagedRuns(project: string): RunRecord[] {
|
|
1372
1556
|
return selectSalvaged.all(project).map(toRecord);
|
|
@@ -1393,6 +1577,30 @@ export function openStore(dbPath: string): Store {
|
|
|
1393
1577
|
return row ? toRecord(row) : undefined;
|
|
1394
1578
|
},
|
|
1395
1579
|
|
|
1580
|
+
setTurnOverride(project: string, issue: number, maxTurns: number, setAt = Date.now()): void {
|
|
1581
|
+
setTurnOverrideTx(project, issue, maxTurns, setAt);
|
|
1582
|
+
},
|
|
1583
|
+
|
|
1584
|
+
turnOverride(project: string, issue: number): number | undefined {
|
|
1585
|
+
return selectTurnOverride.get(project, issue)?.maxTurns;
|
|
1586
|
+
},
|
|
1587
|
+
|
|
1588
|
+
clearTurnOverride(project: string, issue: number): void {
|
|
1589
|
+
deleteTurnOverride.run(project, issue);
|
|
1590
|
+
},
|
|
1591
|
+
|
|
1592
|
+
listTurnOverrides(project: string): TurnOverride[] {
|
|
1593
|
+
return selectTurnOverrides.all(project);
|
|
1594
|
+
},
|
|
1595
|
+
|
|
1596
|
+
turnOverrideLedger(
|
|
1597
|
+
project: string,
|
|
1598
|
+
opts: { issue?: number; limit?: number } = {},
|
|
1599
|
+
): TurnOverride[] {
|
|
1600
|
+
const issue = opts.issue ?? 0;
|
|
1601
|
+
return selectTurnOverrideLedger.all(project, issue, issue, opts.limit ?? 50);
|
|
1602
|
+
},
|
|
1603
|
+
|
|
1396
1604
|
attemptReports(project: string, issue: number): { attempt: number; report: string }[] {
|
|
1397
1605
|
return selectAttemptReports.all(project, issue);
|
|
1398
1606
|
},
|
|
@@ -1445,6 +1653,30 @@ export function openStore(dbPath: string): Store {
|
|
|
1445
1653
|
return row === null ? undefined : toDispatchSummary(row.summary);
|
|
1446
1654
|
},
|
|
1447
1655
|
|
|
1656
|
+
lastDigestDedupeKey(project: string): string | undefined {
|
|
1657
|
+
const row = selectLastDigestKey.get(project);
|
|
1658
|
+
return row?.key ?? undefined;
|
|
1659
|
+
},
|
|
1660
|
+
|
|
1661
|
+
addHeldNotice(notice: HeldNoticeDraft): void {
|
|
1662
|
+
insertHeldNotice.run(
|
|
1663
|
+
crypto.randomUUID(),
|
|
1664
|
+
notice.project,
|
|
1665
|
+
notice.category,
|
|
1666
|
+
notice.summary,
|
|
1667
|
+
notice.detail,
|
|
1668
|
+
notice.createdAt,
|
|
1669
|
+
);
|
|
1670
|
+
},
|
|
1671
|
+
|
|
1672
|
+
undigestedNotices(project: string): HeldNotice[] {
|
|
1673
|
+
return selectUndigestedNotices.all(project).map((row) => ({ ...row }) as HeldNotice);
|
|
1674
|
+
},
|
|
1675
|
+
|
|
1676
|
+
markNoticesDigested(project: string, at: number): void {
|
|
1677
|
+
markNoticesDigestedRow.run(at, project);
|
|
1678
|
+
},
|
|
1679
|
+
|
|
1448
1680
|
recordFriction,
|
|
1449
1681
|
|
|
1450
1682
|
recordGhRefusal,
|