loki-mode 10.7.0 → 10.7.1
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 +1 -0
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/v10/CONTROL-PLANE.md +7 -0
- package/docs/v10/DECISIONS.md +41 -0
- package/docs/v10/ENGINE-LAWS.md +155 -0
- package/docs/v10/FAILURE-CLASSES.md +145 -0
- package/docs/v10/GUIDE.md +4 -2
- package/docs/v10/L0-WAVE1.md +81 -0
- package/docs/v10/MORNING-BRIEF-1003.md +11 -25
- package/docs/v10/OPERATING-MODEL.md +1 -0
- package/docs/v10/PROGRESS.md +28 -0
- package/loki-ts/dist/loki.js +802 -781
- package/loki-ts/package.json +1 -0
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/packages/control-plane/dist/server.js +293 -19
- package/packages/control-plane/drizzle/0002_tiresome_fixer.sql +7 -0
- package/packages/control-plane/drizzle/0003_integrity.sql +4 -0
- package/packages/control-plane/drizzle/meta/0002_snapshot.json +409 -0
- package/packages/control-plane/drizzle/meta/0003_snapshot.json +437 -0
- package/packages/control-plane/drizzle/meta/_journal.json +14 -0
- package/packages/control-plane/src/db/migrate.ts +6 -2
- package/packages/control-plane/src/db/prune.ts +91 -0
- package/packages/control-plane/src/db/schema.ts +13 -0
- package/packages/control-plane/src/server/app.ts +20 -1
- package/packages/control-plane/src/server/integrity.ts +147 -0
- package/packages/control-plane/src/server/runs.ts +46 -4
- package/packages/control-plane/src/shipper/discover.ts +32 -3
- package/packages/control-plane/src/shipper/ship.ts +15 -0
- package/packages/control-plane/src/shipper/watch.ts +3 -1
- package/packages/control-plane/ui/dist/assets/index-DHDI_dHM.css +1 -0
- package/packages/control-plane/ui/dist/assets/index-DaQr0MPS.js +129 -0
- package/packages/control-plane/ui/dist/index.html +2 -2
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-BQPb5rc5.js → AdminPage-BJSU5hY1.js} +1 -1
- package/web-app/dist/assets/{Avatar-Cttco6FC.js → Avatar-CSOSpw9c.js} +1 -1
- package/web-app/dist/assets/{Badge-CPb6kpbH.js → Badge-COr7vj0D.js} +1 -1
- package/web-app/dist/assets/{Button-caTL28a4.js → Button-DGoWFERp.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-bEbLx6ph.js → CockpitPage-CBpWkNXY.js} +1 -1
- package/web-app/dist/assets/{ComparePage-DNtJSij-.js → ComparePage-BwT8vqrL.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-BrgC3K44.js → ErrorBoundary-D7SicDDA.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-Q-mSrUUQ.js → EvidenceReceiptPanel-bx-5liG2.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-DbmYl_Er.js → GitHubIssuesPanel-BAeeWLqY.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-C6PCHM4B.js → GitHubPRsPanel-CssD8lGI.js} +1 -1
- package/web-app/dist/assets/{HomePage-7zsL_k_e.js → HomePage-SL5PT3m5.js} +2 -2
- package/web-app/dist/assets/{LoginPage-Cl4ON8RQ.js → LoginPage-C8hFOZOM.js} +1 -1
- package/web-app/dist/assets/{MagicPage-BNEAhTqH.js → MagicPage-xHbHkrPf.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-DyBH-88q.js → MetricsPage-B_2CKFcC.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-CIVr21yL.js → NotFoundPage-CXwXMq35.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-BMeVYocF.js → ProjectPage-AaisMQI-.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-D-H2WRVU.js → ProjectsPage-Cej5ResX.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-o0kR9RAt.js → SettingsPage-DKv4mn9H.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-DtfMDMjp.js → ShowcasePage-Dkiyzne4.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-CkXdpZ_T.js → SystemSettingsPage-DaBLQ5dU.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-Cu5H9cQ0.js → TeamsPage-B2yUr31k.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-3wdbVwil.js → TemplatesPage-CfYiANhU.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-CWn301xh.js → TerminalOutput-CMXoexLM.js} +1 -1
- package/web-app/dist/assets/{activity-YGaAt4zJ.js → activity-B0dzoZtY.js} +1 -1
- package/web-app/dist/assets/{bell-C28q0GO7.js → bell-DFOTaSoo.js} +1 -1
- package/web-app/dist/assets/{bot-DLcQGI92.js → bot-Dz-WSQyP.js} +1 -1
- package/web-app/dist/assets/{check-w0sBNlcm.js → check-L1QIRueJ.js} +1 -1
- package/web-app/dist/assets/{chevron-left-W7O0vC_m.js → chevron-left-1TrnIEZS.js} +1 -1
- package/web-app/dist/assets/{circle-alert-Cxjq9fxB.js → circle-alert-8Bf6xEDx.js} +1 -1
- package/web-app/dist/assets/{clock-C8EqzmNl.js → clock-DB8NE6P5.js} +1 -1
- package/web-app/dist/assets/{cloud-xNgp4g3Y.js → cloud-DUSL3wZS.js} +1 -1
- package/web-app/dist/assets/{code-xml-B-lyNDLJ.js → code-xml-DbjryWNl.js} +1 -1
- package/web-app/dist/assets/{database-DoW5lmWc.js → database-DrezrMSs.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-BcARA5sC.js → dollar-sign-CGbaLMzR.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-CehcII0J.js → file-code-corner-Dfypa-r4.js} +1 -1
- package/web-app/dist/assets/{file-plus-DI9Zo5hP.js → file-plus-CzV5L2Gd.js} +1 -1
- package/web-app/dist/assets/{globe-CiZWBk3H.js → globe-BoYjTwPQ.js} +1 -1
- package/web-app/dist/assets/{hammer-3qv_T2N-.js → hammer-WO-HuiyJ.js} +1 -1
- package/web-app/dist/assets/{index-gF9grAz-.js → index-CjIOUjnd.js} +3 -3
- package/web-app/dist/assets/{layers-CN7d0By9.js → layers-Bn6jtKNk.js} +1 -1
- package/web-app/dist/assets/{loader-circle-DywcM-im.js → loader-circle-gERWRnmW.js} +1 -1
- package/web-app/dist/assets/{lock-_hdSmkU4.js → lock-BIiMLJq8.js} +1 -1
- package/web-app/dist/assets/{package-BV5zgqxJ.js → package-dLvDSVys.js} +1 -1
- package/web-app/dist/assets/{plus-CU_Hh4-x.js → plus-BHM3ZLhZ.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-DMK-fNl-.js → refresh-cw-BAp_alv6.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-CEB3_v5_.js → rotate-ccw-CBhwq_MG.js} +1 -1
- package/web-app/dist/assets/{scroll-text-BSdXxcRN.js → scroll-text-DoS-6dfU.js} +1 -1
- package/web-app/dist/assets/{server-CAMlP1yJ.js → server-5JgdDWQO.js} +1 -1
- package/web-app/dist/assets/{shield-alert-B-r1Dt3h.js → shield-alert-DjmlLT34.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-CYfxw9o4.js → thumbs-up-BtiEXl3p.js} +1 -1
- package/web-app/dist/assets/{trash-2-DX3nGPS8.js → trash-2-ClsWU_Vg.js} +1 -1
- package/web-app/dist/assets/{trending-down-Bt-hSntd.js → trending-down-DPS4M7_X.js} +1 -1
- package/web-app/dist/assets/{trending-up-DVSlvQp8.js → trending-up-D6OJ38-V.js} +1 -1
- package/web-app/dist/assets/{upload-D45RRHAP.js → upload-DOCzLUpF.js} +1 -1
- package/web-app/dist/assets/{usePolling-fq_Xj8iw.js → usePolling-CljQTWj_.js} +1 -1
- package/web-app/dist/assets/{user-BWcJkLlr.js → user-C0G82Weu.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/packages/control-plane/ui/dist/assets/index-CUQ65vJY.css +0 -1
- package/packages/control-plane/ui/dist/assets/index-CwwGfp6E.js +0 -129
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "6",
|
|
3
|
+
"dialect": "sqlite",
|
|
4
|
+
"id": "c1492d55-5d08-4208-940a-38fba6b33b3c",
|
|
5
|
+
"prevId": "2b4a930a-5fb1-4e32-8387-fac651ba5fca",
|
|
6
|
+
"tables": {
|
|
7
|
+
"audit": {
|
|
8
|
+
"name": "audit",
|
|
9
|
+
"columns": {
|
|
10
|
+
"id": {
|
|
11
|
+
"name": "id",
|
|
12
|
+
"type": "integer",
|
|
13
|
+
"primaryKey": true,
|
|
14
|
+
"notNull": true,
|
|
15
|
+
"autoincrement": true
|
|
16
|
+
},
|
|
17
|
+
"ts": {
|
|
18
|
+
"name": "ts",
|
|
19
|
+
"type": "text",
|
|
20
|
+
"primaryKey": false,
|
|
21
|
+
"notNull": true,
|
|
22
|
+
"autoincrement": false
|
|
23
|
+
},
|
|
24
|
+
"action": {
|
|
25
|
+
"name": "action",
|
|
26
|
+
"type": "text",
|
|
27
|
+
"primaryKey": false,
|
|
28
|
+
"notNull": true,
|
|
29
|
+
"autoincrement": false
|
|
30
|
+
},
|
|
31
|
+
"actor": {
|
|
32
|
+
"name": "actor",
|
|
33
|
+
"type": "text",
|
|
34
|
+
"primaryKey": false,
|
|
35
|
+
"notNull": true,
|
|
36
|
+
"autoincrement": false
|
|
37
|
+
},
|
|
38
|
+
"detail": {
|
|
39
|
+
"name": "detail",
|
|
40
|
+
"type": "text",
|
|
41
|
+
"primaryKey": false,
|
|
42
|
+
"notNull": true,
|
|
43
|
+
"autoincrement": false
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"indexes": {},
|
|
47
|
+
"foreignKeys": {},
|
|
48
|
+
"compositePrimaryKeys": {},
|
|
49
|
+
"uniqueConstraints": {},
|
|
50
|
+
"checkConstraints": {}
|
|
51
|
+
},
|
|
52
|
+
"events": {
|
|
53
|
+
"name": "events",
|
|
54
|
+
"columns": {
|
|
55
|
+
"source_id": {
|
|
56
|
+
"name": "source_id",
|
|
57
|
+
"type": "text",
|
|
58
|
+
"primaryKey": false,
|
|
59
|
+
"notNull": true,
|
|
60
|
+
"autoincrement": false
|
|
61
|
+
},
|
|
62
|
+
"run_id": {
|
|
63
|
+
"name": "run_id",
|
|
64
|
+
"type": "text",
|
|
65
|
+
"primaryKey": false,
|
|
66
|
+
"notNull": true,
|
|
67
|
+
"autoincrement": false
|
|
68
|
+
},
|
|
69
|
+
"seq": {
|
|
70
|
+
"name": "seq",
|
|
71
|
+
"type": "integer",
|
|
72
|
+
"primaryKey": false,
|
|
73
|
+
"notNull": true,
|
|
74
|
+
"autoincrement": false
|
|
75
|
+
},
|
|
76
|
+
"ts": {
|
|
77
|
+
"name": "ts",
|
|
78
|
+
"type": "text",
|
|
79
|
+
"primaryKey": false,
|
|
80
|
+
"notNull": true,
|
|
81
|
+
"autoincrement": false
|
|
82
|
+
},
|
|
83
|
+
"type": {
|
|
84
|
+
"name": "type",
|
|
85
|
+
"type": "text",
|
|
86
|
+
"primaryKey": false,
|
|
87
|
+
"notNull": true,
|
|
88
|
+
"autoincrement": false
|
|
89
|
+
},
|
|
90
|
+
"stage": {
|
|
91
|
+
"name": "stage",
|
|
92
|
+
"type": "text",
|
|
93
|
+
"primaryKey": false,
|
|
94
|
+
"notNull": false,
|
|
95
|
+
"autoincrement": false
|
|
96
|
+
},
|
|
97
|
+
"data": {
|
|
98
|
+
"name": "data",
|
|
99
|
+
"type": "text",
|
|
100
|
+
"primaryKey": false,
|
|
101
|
+
"notNull": true,
|
|
102
|
+
"autoincrement": false
|
|
103
|
+
},
|
|
104
|
+
"line_sha256": {
|
|
105
|
+
"name": "line_sha256",
|
|
106
|
+
"type": "text",
|
|
107
|
+
"primaryKey": false,
|
|
108
|
+
"notNull": true,
|
|
109
|
+
"autoincrement": false
|
|
110
|
+
},
|
|
111
|
+
"received_at": {
|
|
112
|
+
"name": "received_at",
|
|
113
|
+
"type": "text",
|
|
114
|
+
"primaryKey": false,
|
|
115
|
+
"notNull": true,
|
|
116
|
+
"autoincrement": false
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"indexes": {
|
|
120
|
+
"events_run_seq": {
|
|
121
|
+
"name": "events_run_seq",
|
|
122
|
+
"columns": [
|
|
123
|
+
"run_id",
|
|
124
|
+
"seq"
|
|
125
|
+
],
|
|
126
|
+
"isUnique": false
|
|
127
|
+
},
|
|
128
|
+
"events_type_ts": {
|
|
129
|
+
"name": "events_type_ts",
|
|
130
|
+
"columns": [
|
|
131
|
+
"type",
|
|
132
|
+
"ts"
|
|
133
|
+
],
|
|
134
|
+
"isUnique": false
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"foreignKeys": {},
|
|
138
|
+
"compositePrimaryKeys": {
|
|
139
|
+
"events_source_id_run_id_seq_pk": {
|
|
140
|
+
"columns": [
|
|
141
|
+
"source_id",
|
|
142
|
+
"run_id",
|
|
143
|
+
"seq"
|
|
144
|
+
],
|
|
145
|
+
"name": "events_source_id_run_id_seq_pk"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"uniqueConstraints": {},
|
|
149
|
+
"checkConstraints": {}
|
|
150
|
+
},
|
|
151
|
+
"runs": {
|
|
152
|
+
"name": "runs",
|
|
153
|
+
"columns": {
|
|
154
|
+
"source_id": {
|
|
155
|
+
"name": "source_id",
|
|
156
|
+
"type": "text",
|
|
157
|
+
"primaryKey": false,
|
|
158
|
+
"notNull": true,
|
|
159
|
+
"autoincrement": false
|
|
160
|
+
},
|
|
161
|
+
"run_id": {
|
|
162
|
+
"name": "run_id",
|
|
163
|
+
"type": "text",
|
|
164
|
+
"primaryKey": false,
|
|
165
|
+
"notNull": true,
|
|
166
|
+
"autoincrement": false
|
|
167
|
+
},
|
|
168
|
+
"origin_repo": {
|
|
169
|
+
"name": "origin_repo",
|
|
170
|
+
"type": "text",
|
|
171
|
+
"primaryKey": false,
|
|
172
|
+
"notNull": false,
|
|
173
|
+
"autoincrement": false
|
|
174
|
+
},
|
|
175
|
+
"issue_ref": {
|
|
176
|
+
"name": "issue_ref",
|
|
177
|
+
"type": "text",
|
|
178
|
+
"primaryKey": false,
|
|
179
|
+
"notNull": false,
|
|
180
|
+
"autoincrement": false
|
|
181
|
+
},
|
|
182
|
+
"task_source": {
|
|
183
|
+
"name": "task_source",
|
|
184
|
+
"type": "text",
|
|
185
|
+
"primaryKey": false,
|
|
186
|
+
"notNull": false,
|
|
187
|
+
"autoincrement": false
|
|
188
|
+
},
|
|
189
|
+
"provider": {
|
|
190
|
+
"name": "provider",
|
|
191
|
+
"type": "text",
|
|
192
|
+
"primaryKey": false,
|
|
193
|
+
"notNull": false,
|
|
194
|
+
"autoincrement": false
|
|
195
|
+
},
|
|
196
|
+
"model": {
|
|
197
|
+
"name": "model",
|
|
198
|
+
"type": "text",
|
|
199
|
+
"primaryKey": false,
|
|
200
|
+
"notNull": false,
|
|
201
|
+
"autoincrement": false
|
|
202
|
+
},
|
|
203
|
+
"started_at": {
|
|
204
|
+
"name": "started_at",
|
|
205
|
+
"type": "text",
|
|
206
|
+
"primaryKey": false,
|
|
207
|
+
"notNull": false,
|
|
208
|
+
"autoincrement": false
|
|
209
|
+
},
|
|
210
|
+
"ended_at": {
|
|
211
|
+
"name": "ended_at",
|
|
212
|
+
"type": "text",
|
|
213
|
+
"primaryKey": false,
|
|
214
|
+
"notNull": false,
|
|
215
|
+
"autoincrement": false
|
|
216
|
+
},
|
|
217
|
+
"verdict": {
|
|
218
|
+
"name": "verdict",
|
|
219
|
+
"type": "text",
|
|
220
|
+
"primaryKey": false,
|
|
221
|
+
"notNull": false,
|
|
222
|
+
"autoincrement": false
|
|
223
|
+
},
|
|
224
|
+
"pr_url": {
|
|
225
|
+
"name": "pr_url",
|
|
226
|
+
"type": "text",
|
|
227
|
+
"primaryKey": false,
|
|
228
|
+
"notNull": false,
|
|
229
|
+
"autoincrement": false
|
|
230
|
+
},
|
|
231
|
+
"pr_draft": {
|
|
232
|
+
"name": "pr_draft",
|
|
233
|
+
"type": "integer",
|
|
234
|
+
"primaryKey": false,
|
|
235
|
+
"notNull": false,
|
|
236
|
+
"autoincrement": false
|
|
237
|
+
},
|
|
238
|
+
"cost_usd": {
|
|
239
|
+
"name": "cost_usd",
|
|
240
|
+
"type": "real",
|
|
241
|
+
"primaryKey": false,
|
|
242
|
+
"notNull": false,
|
|
243
|
+
"autoincrement": false
|
|
244
|
+
},
|
|
245
|
+
"partial_usd": {
|
|
246
|
+
"name": "partial_usd",
|
|
247
|
+
"type": "real",
|
|
248
|
+
"primaryKey": false,
|
|
249
|
+
"notNull": true,
|
|
250
|
+
"autoincrement": false
|
|
251
|
+
},
|
|
252
|
+
"measured_sessions": {
|
|
253
|
+
"name": "measured_sessions",
|
|
254
|
+
"type": "integer",
|
|
255
|
+
"primaryKey": false,
|
|
256
|
+
"notNull": true,
|
|
257
|
+
"autoincrement": false
|
|
258
|
+
},
|
|
259
|
+
"total_sessions": {
|
|
260
|
+
"name": "total_sessions",
|
|
261
|
+
"type": "integer",
|
|
262
|
+
"primaryKey": false,
|
|
263
|
+
"notNull": true,
|
|
264
|
+
"autoincrement": false
|
|
265
|
+
},
|
|
266
|
+
"input_tokens": {
|
|
267
|
+
"name": "input_tokens",
|
|
268
|
+
"type": "integer",
|
|
269
|
+
"primaryKey": false,
|
|
270
|
+
"notNull": true,
|
|
271
|
+
"autoincrement": false
|
|
272
|
+
},
|
|
273
|
+
"output_tokens": {
|
|
274
|
+
"name": "output_tokens",
|
|
275
|
+
"type": "integer",
|
|
276
|
+
"primaryKey": false,
|
|
277
|
+
"notNull": true,
|
|
278
|
+
"autoincrement": false
|
|
279
|
+
},
|
|
280
|
+
"wall_s": {
|
|
281
|
+
"name": "wall_s",
|
|
282
|
+
"type": "real",
|
|
283
|
+
"primaryKey": false,
|
|
284
|
+
"notNull": false,
|
|
285
|
+
"autoincrement": false
|
|
286
|
+
},
|
|
287
|
+
"last_seq": {
|
|
288
|
+
"name": "last_seq",
|
|
289
|
+
"type": "integer",
|
|
290
|
+
"primaryKey": false,
|
|
291
|
+
"notNull": true,
|
|
292
|
+
"autoincrement": false
|
|
293
|
+
},
|
|
294
|
+
"last_event_at": {
|
|
295
|
+
"name": "last_event_at",
|
|
296
|
+
"type": "text",
|
|
297
|
+
"primaryKey": false,
|
|
298
|
+
"notNull": false,
|
|
299
|
+
"autoincrement": false
|
|
300
|
+
},
|
|
301
|
+
"tampered": {
|
|
302
|
+
"name": "tampered",
|
|
303
|
+
"type": "integer",
|
|
304
|
+
"primaryKey": false,
|
|
305
|
+
"notNull": true,
|
|
306
|
+
"autoincrement": false
|
|
307
|
+
},
|
|
308
|
+
"conflict": {
|
|
309
|
+
"name": "conflict",
|
|
310
|
+
"type": "integer",
|
|
311
|
+
"primaryKey": false,
|
|
312
|
+
"notNull": true,
|
|
313
|
+
"autoincrement": false,
|
|
314
|
+
"default": 0
|
|
315
|
+
},
|
|
316
|
+
"group_id": {
|
|
317
|
+
"name": "group_id",
|
|
318
|
+
"type": "text",
|
|
319
|
+
"primaryKey": false,
|
|
320
|
+
"notNull": false,
|
|
321
|
+
"autoincrement": false
|
|
322
|
+
},
|
|
323
|
+
"unit_id": {
|
|
324
|
+
"name": "unit_id",
|
|
325
|
+
"type": "text",
|
|
326
|
+
"primaryKey": false,
|
|
327
|
+
"notNull": false,
|
|
328
|
+
"autoincrement": false
|
|
329
|
+
},
|
|
330
|
+
"attested": {
|
|
331
|
+
"name": "attested",
|
|
332
|
+
"type": "integer",
|
|
333
|
+
"primaryKey": false,
|
|
334
|
+
"notNull": false,
|
|
335
|
+
"autoincrement": false
|
|
336
|
+
},
|
|
337
|
+
"integrity_reasons": {
|
|
338
|
+
"name": "integrity_reasons",
|
|
339
|
+
"type": "text",
|
|
340
|
+
"primaryKey": false,
|
|
341
|
+
"notNull": false,
|
|
342
|
+
"autoincrement": false
|
|
343
|
+
},
|
|
344
|
+
"sig_checked": {
|
|
345
|
+
"name": "sig_checked",
|
|
346
|
+
"type": "integer",
|
|
347
|
+
"primaryKey": false,
|
|
348
|
+
"notNull": false,
|
|
349
|
+
"autoincrement": false
|
|
350
|
+
},
|
|
351
|
+
"integrity_key_fp": {
|
|
352
|
+
"name": "integrity_key_fp",
|
|
353
|
+
"type": "text",
|
|
354
|
+
"primaryKey": false,
|
|
355
|
+
"notNull": false,
|
|
356
|
+
"autoincrement": false
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
"indexes": {
|
|
360
|
+
"runs_started": {
|
|
361
|
+
"name": "runs_started",
|
|
362
|
+
"columns": [
|
|
363
|
+
"started_at"
|
|
364
|
+
],
|
|
365
|
+
"isUnique": false
|
|
366
|
+
},
|
|
367
|
+
"runs_verdict": {
|
|
368
|
+
"name": "runs_verdict",
|
|
369
|
+
"columns": [
|
|
370
|
+
"verdict"
|
|
371
|
+
],
|
|
372
|
+
"isUnique": false
|
|
373
|
+
},
|
|
374
|
+
"runs_group": {
|
|
375
|
+
"name": "runs_group",
|
|
376
|
+
"columns": [
|
|
377
|
+
"group_id"
|
|
378
|
+
],
|
|
379
|
+
"isUnique": false
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
"foreignKeys": {},
|
|
383
|
+
"compositePrimaryKeys": {
|
|
384
|
+
"runs_source_id_run_id_pk": {
|
|
385
|
+
"columns": [
|
|
386
|
+
"source_id",
|
|
387
|
+
"run_id"
|
|
388
|
+
],
|
|
389
|
+
"name": "runs_source_id_run_id_pk"
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
"uniqueConstraints": {},
|
|
393
|
+
"checkConstraints": {}
|
|
394
|
+
},
|
|
395
|
+
"sources": {
|
|
396
|
+
"name": "sources",
|
|
397
|
+
"columns": {
|
|
398
|
+
"id": {
|
|
399
|
+
"name": "id",
|
|
400
|
+
"type": "text",
|
|
401
|
+
"primaryKey": true,
|
|
402
|
+
"notNull": true,
|
|
403
|
+
"autoincrement": false
|
|
404
|
+
},
|
|
405
|
+
"first_seen": {
|
|
406
|
+
"name": "first_seen",
|
|
407
|
+
"type": "text",
|
|
408
|
+
"primaryKey": false,
|
|
409
|
+
"notNull": true,
|
|
410
|
+
"autoincrement": false
|
|
411
|
+
},
|
|
412
|
+
"last_seen": {
|
|
413
|
+
"name": "last_seen",
|
|
414
|
+
"type": "text",
|
|
415
|
+
"primaryKey": false,
|
|
416
|
+
"notNull": true,
|
|
417
|
+
"autoincrement": false
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
"indexes": {},
|
|
421
|
+
"foreignKeys": {},
|
|
422
|
+
"compositePrimaryKeys": {},
|
|
423
|
+
"uniqueConstraints": {},
|
|
424
|
+
"checkConstraints": {}
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
"views": {},
|
|
428
|
+
"enums": {},
|
|
429
|
+
"_meta": {
|
|
430
|
+
"schemas": {},
|
|
431
|
+
"tables": {},
|
|
432
|
+
"columns": {}
|
|
433
|
+
},
|
|
434
|
+
"internal": {
|
|
435
|
+
"indexes": {}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
@@ -15,6 +15,20 @@
|
|
|
15
15
|
"when": 1790956800000,
|
|
16
16
|
"tag": "0001_group_id",
|
|
17
17
|
"breakpoints": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"idx": 2,
|
|
21
|
+
"version": "6",
|
|
22
|
+
"when": 1791046012148,
|
|
23
|
+
"tag": "0002_tiresome_fixer",
|
|
24
|
+
"breakpoints": true
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"idx": 3,
|
|
28
|
+
"version": "6",
|
|
29
|
+
"when": 1791132412148,
|
|
30
|
+
"tag": "0003_integrity",
|
|
31
|
+
"breakpoints": true
|
|
18
32
|
}
|
|
19
33
|
]
|
|
20
34
|
}
|
|
@@ -12,8 +12,12 @@ export function openDb(path: string) {
|
|
|
12
12
|
const sqlite = new Database(path, { create: true });
|
|
13
13
|
sqlite.exec("PRAGMA journal_mode = WAL; PRAGMA busy_timeout = 5000;");
|
|
14
14
|
const db = drizzle(sqlite, { schema });
|
|
15
|
-
//
|
|
16
|
-
const migrationsFolder = [
|
|
15
|
+
// source, the bundled server and the bundled CLI sit at different depths
|
|
16
|
+
const migrationsFolder = [
|
|
17
|
+
join(import.meta.dir, "../../drizzle"), // src/db (source checkout)
|
|
18
|
+
join(import.meta.dir, "../drizzle"), // packages/control-plane/dist (bundled server)
|
|
19
|
+
join(import.meta.dir, "../../packages/control-plane/drizzle"), // loki-ts/dist (bundled CLI; also an installed npm package)
|
|
20
|
+
].find((d) => existsSync(join(d, "meta/_journal.json")));
|
|
17
21
|
if (!migrationsFolder) throw new Error("drizzle migrations folder not found");
|
|
18
22
|
migrate(db, { migrationsFolder });
|
|
19
23
|
return { db, sqlite };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Removal of runs from the control DB: shared by `loki control prune` (direct DB) and DELETE /v1/runs/:source/:run (server).
|
|
2
|
+
// One transaction deletes the runs, their events, and any source left with neither. An audit row is written first, inside the same transaction.
|
|
3
|
+
import { Database } from "bun:sqlite";
|
|
4
|
+
|
|
5
|
+
export interface PruneFilter { repo?: string; before?: string }
|
|
6
|
+
export interface PruneCounts { runs: number; events: number; sources: number }
|
|
7
|
+
|
|
8
|
+
/** Strict ISO 8601 parse: YYYY-MM-DD or a date-time with an explicit Z/offset. Returns a normalized UTC instant or null. */
|
|
9
|
+
export function parseBefore(s: string): string | null {
|
|
10
|
+
if (!/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2}))?$/.test(s)) return null;
|
|
11
|
+
const t = Date.parse(s);
|
|
12
|
+
if (Number.isNaN(t)) return null;
|
|
13
|
+
// reject rolled-over dates such as 2026-02-31
|
|
14
|
+
if (new Date(`${s.slice(0, 10)}T00:00:00Z`).toISOString().slice(0, 10) !== s.slice(0, 10)) return null;
|
|
15
|
+
return new Date(t).toISOString();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Key = { source_id: string; run_id: string };
|
|
19
|
+
|
|
20
|
+
function where(f: PruneFilter): { sql: string; args: string[] } {
|
|
21
|
+
const parts: string[] = [];
|
|
22
|
+
const args: string[] = [];
|
|
23
|
+
if (f.repo !== undefined) { parts.push("origin_repo = ?"); args.push(f.repo); }
|
|
24
|
+
if (f.before !== undefined) { parts.push("started_at IS NOT NULL AND julianday(started_at) < julianday(?)"); args.push(f.before); }
|
|
25
|
+
return { sql: parts.join(" AND "), args };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const n = (sqlite: Database, sql: string, ...args: string[]): number => (sqlite.query(sql).get(...args) as { n: number }).n;
|
|
29
|
+
|
|
30
|
+
/** What deleting these runs would remove: runs, their events, and sources that would be left holding nothing. */
|
|
31
|
+
function countFor(sqlite: Database, keys: Key[]): PruneCounts {
|
|
32
|
+
const bySource = new Map<string, string[]>();
|
|
33
|
+
for (const k of keys) bySource.set(k.source_id, [...(bySource.get(k.source_id) ?? []), k.run_id]);
|
|
34
|
+
let events = 0, sources = 0;
|
|
35
|
+
for (const [src, runIds] of bySource) {
|
|
36
|
+
let ev = 0;
|
|
37
|
+
for (const r of runIds) ev += n(sqlite, "select count(*) n from events where source_id = ? and run_id = ?", src, r);
|
|
38
|
+
events += ev;
|
|
39
|
+
const runsLeft = n(sqlite, "select count(*) n from runs where source_id = ?", src) - runIds.length;
|
|
40
|
+
const eventsLeft = n(sqlite, "select count(*) n from events where source_id = ?", src) - ev;
|
|
41
|
+
if (runsLeft <= 0 && eventsLeft <= 0 && sqlite.query("select 1 x from sources where id = ?").get(src)) sources++;
|
|
42
|
+
}
|
|
43
|
+
return { runs: keys.length, events, sources };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function audit(sqlite: Database, action: string, actor: string, detail: unknown): void {
|
|
47
|
+
sqlite.query("insert into audit (ts, action, actor, detail) values (?, ?, ?, ?)").run(new Date().toISOString(), action, actor, JSON.stringify(detail));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Drops only the given sources, and only when they now hold neither runs nor events (matches what countFor reported). */
|
|
51
|
+
const dropOrphanSources = (sqlite: Database, ids: Iterable<string>): void => {
|
|
52
|
+
for (const id of ids) sqlite.query("delete from sources where id = ? and not exists (select 1 from runs where source_id = ?) and not exists (select 1 from events where source_id = ?)").run(id, id, id);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Read-only handle for dry runs: no migrations, no journal-mode change, no writes. */
|
|
56
|
+
export const openReadOnly = (path: string): Database => new Database(path, { readonly: true });
|
|
57
|
+
|
|
58
|
+
/** Removes the matching runs. dryRun counts only and writes nothing. At least one filter is required. */
|
|
59
|
+
export function pruneRuns(sqlite: Database, f: PruneFilter, opts: { dryRun?: boolean; actor?: string } = {}): PruneCounts {
|
|
60
|
+
if (f.repo === undefined && f.before === undefined) throw new Error("prune needs --repo or --before");
|
|
61
|
+
const w = where(f);
|
|
62
|
+
const select = (): Key[] => sqlite.query(`select source_id, run_id from runs where ${w.sql}`).all(...w.args) as Key[];
|
|
63
|
+
if (opts.dryRun) return countFor(sqlite, select());
|
|
64
|
+
let out: PruneCounts = { runs: 0, events: 0, sources: 0 };
|
|
65
|
+
sqlite.transaction(() => {
|
|
66
|
+
const keys = select();
|
|
67
|
+
if (keys.length === 0) return;
|
|
68
|
+
out = countFor(sqlite, keys);
|
|
69
|
+
audit(sqlite, "prune", opts.actor ?? "cli", { filter: f, counts: out });
|
|
70
|
+
for (const k of keys) {
|
|
71
|
+
sqlite.query("delete from events where source_id = ? and run_id = ?").run(k.source_id, k.run_id);
|
|
72
|
+
sqlite.query("delete from runs where source_id = ? and run_id = ?").run(k.source_id, k.run_id);
|
|
73
|
+
}
|
|
74
|
+
dropOrphanSources(sqlite, new Set(keys.map((k) => k.source_id)));
|
|
75
|
+
}).immediate();
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Removes one run. Null when it does not exist (nothing audited, nothing deleted). Otherwise the counts removed. */
|
|
80
|
+
export function removeRun(sqlite: Database, source: string, run: string, actor = "http"): PruneCounts | null {
|
|
81
|
+
let out: PruneCounts | null = null;
|
|
82
|
+
sqlite.transaction(() => {
|
|
83
|
+
if (!sqlite.query("select 1 x from runs where source_id = ? and run_id = ?").get(source, run)) return;
|
|
84
|
+
out = countFor(sqlite, [{ source_id: source, run_id: run }]);
|
|
85
|
+
audit(sqlite, "run.remove", actor, { source_id: source, run_id: run, counts: out });
|
|
86
|
+
sqlite.query("delete from events where source_id = ? and run_id = ?").run(source, run);
|
|
87
|
+
sqlite.query("delete from runs where source_id = ? and run_id = ?").run(source, run);
|
|
88
|
+
dropOrphanSources(sqlite, [source]);
|
|
89
|
+
}).immediate();
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
@@ -49,9 +49,22 @@ export const runs = sqliteTable("runs", {
|
|
|
49
49
|
conflict: integer("conflict").notNull().default(0),
|
|
50
50
|
groupId: text("group_id"),
|
|
51
51
|
unitId: text("unit_id"),
|
|
52
|
+
attested: integer("attested"), // EL-FC08b: null = never evaluated (reads as unattested)
|
|
53
|
+
sigChecked: integer("sig_checked"), // log seal signature verified against a configured key
|
|
54
|
+
integrityKeyFp: text("integrity_key_fp"), // key-set fingerprint the integrity result was computed under; a change triggers a boot recompute
|
|
55
|
+
integrityReasons: text("integrity_reasons"), // JSON string[]
|
|
52
56
|
}, (t) => [
|
|
53
57
|
primaryKey({ columns: [t.sourceId, t.runId] }),
|
|
54
58
|
index("runs_started").on(t.startedAt),
|
|
55
59
|
index("runs_verdict").on(t.verdict),
|
|
56
60
|
index("runs_group").on(t.groupId),
|
|
57
61
|
]);
|
|
62
|
+
|
|
63
|
+
// Append-only record of destructive operator actions (run removal, prune). Written before the delete runs.
|
|
64
|
+
export const audit = sqliteTable("audit", {
|
|
65
|
+
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
66
|
+
ts: text("ts").notNull(),
|
|
67
|
+
action: text("action").notNull(),
|
|
68
|
+
actor: text("actor").notNull(),
|
|
69
|
+
detail: text("detail", { mode: "json" }).notNull(),
|
|
70
|
+
});
|
|
@@ -4,9 +4,10 @@ import { Hono, type Context } from "hono";
|
|
|
4
4
|
import { openDb } from "../db/migrate.ts";
|
|
5
5
|
import { ingest } from "./ingest.ts";
|
|
6
6
|
import { defaultAnswerDir, writeAnswer } from "./answer.ts";
|
|
7
|
-
import { listRuns, runDetail } from "./runs.ts";
|
|
7
|
+
import { listRuns, recomputeLegacy, runDetail } from "./runs.ts";
|
|
8
8
|
import { hostGuard, isLoopbackHost, tokenGuard } from "./auth.ts";
|
|
9
9
|
import { backfill } from "../shipper/backfill.ts";
|
|
10
|
+
import { removeRun } from "../db/prune.ts";
|
|
10
11
|
import { planStart, registryRepos, spawnStart } from "./actions.ts";
|
|
11
12
|
|
|
12
13
|
const MAX_BODY = 1_000_000;
|
|
@@ -18,6 +19,7 @@ const defaultUiDir = () => [join(import.meta.dir, "../../ui/dist"), join(import.
|
|
|
18
19
|
export function createApp(opts: { dbPath: string; uiDir?: string; answerDir?: string; token?: string; loopbackOnly?: boolean; repoDir?: string; startBin?: string; spawnImpl?: typeof spawnStart }) {
|
|
19
20
|
const uiDir = opts.uiDir ?? defaultUiDir();
|
|
20
21
|
const { db, sqlite } = openDb(opts.dbPath);
|
|
22
|
+
recomputeLegacy(db);
|
|
21
23
|
let ready = true;
|
|
22
24
|
const answerDir = opts.answerDir ?? defaultAnswerDir();
|
|
23
25
|
const app = new Hono();
|
|
@@ -87,6 +89,23 @@ export function createApp(opts: { dbPath: string; uiDir?: string; answerDir?: st
|
|
|
87
89
|
});
|
|
88
90
|
return c.json({ runs: r.runs, sent: r.sent, failed: r.failed });
|
|
89
91
|
});
|
|
92
|
+
// Remove one run (and its events). Loopback-only like /v1/start (never registered on a non-loopback server); bearer via tokenGuard when a token is set. JSON content type blocks cross-site form posts;
|
|
93
|
+
// a browser Origin must match the host this server is served on. Absent Origin (curl, the CLI) is allowed. The audit row is written inside the delete transaction, before the rows go.
|
|
94
|
+
act.delete("/v1/runs/:source/:run", async (c) => {
|
|
95
|
+
if (!peerIsLoopback(c) || !isLoopbackHost(c.req.header("host"))) return c.json({ error: "loopback only" }, 403);
|
|
96
|
+
if (!(c.req.header("content-type") ?? "").toLowerCase().startsWith("application/json")) return c.json({ error: "content-type must be application/json" }, 400);
|
|
97
|
+
const origin = c.req.header("origin");
|
|
98
|
+
if (origin !== undefined) {
|
|
99
|
+
let oh = "";
|
|
100
|
+
try { oh = new URL(origin).host.toLowerCase(); } catch { /* refused below */ }
|
|
101
|
+
if (!oh || oh !== (c.req.header("host") ?? "").toLowerCase()) return c.json({ error: "origin not allowed" }, 403);
|
|
102
|
+
}
|
|
103
|
+
const source = c.req.param("source"), run = c.req.param("run");
|
|
104
|
+
const r = removeRun(sqlite, source, run);
|
|
105
|
+
if (!r) return c.json({ error: "run not found" }, 404);
|
|
106
|
+
const left = (sqlite.query("select count(*) n from runs").get() as { n: number }).n;
|
|
107
|
+
return c.json({ ok: true, removed: r, remaining_runs: left });
|
|
108
|
+
});
|
|
90
109
|
act.get("/v1/repos", (c) => peerIsLoopback(c) ? c.json({ repos: [...new Set<string>([repoDir, ...registryRepos()])] }) : c.json({ error: "loopback only" }, 403));
|
|
91
110
|
act.post("/v1/start", async (c) => {
|
|
92
111
|
if (!local(c)) return c.json({ error: "loopback JSON requests only" }, 403);
|