loki-mode 10.7.0 → 10.8.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.
Files changed (130) hide show
  1. package/README.md +1 -0
  2. package/SKILL.md +2 -2
  3. package/VERSION +1 -1
  4. package/autonomy/loki +4 -1
  5. package/autonomy/run.sh +9 -5
  6. package/dashboard/__init__.py +1 -1
  7. package/dashboard/server.py +6 -8
  8. package/docs/INSTALLATION.md +1 -1
  9. package/docs/v10/CONTROL-PLANE.md +7 -0
  10. package/docs/v10/CP-ENTERPRISE-UI.md +263 -2
  11. package/docs/v10/DECISIONS.md +41 -0
  12. package/docs/v10/ENGINE-LAWS.md +155 -0
  13. package/docs/v10/FAILURE-CLASSES.md +166 -0
  14. package/docs/v10/GUIDE.md +4 -2
  15. package/docs/v10/L0-WAVE1.md +81 -0
  16. package/docs/v10/METRICS.md +6 -0
  17. package/docs/v10/MORNING-BRIEF-1003.md +11 -25
  18. package/docs/v10/OPERATING-MODEL.md +1 -0
  19. package/docs/v10/PROGRESS.md +28 -0
  20. package/loki-ts/data/model-pricing.json +6 -6
  21. package/loki-ts/dist/loki.js +851 -837
  22. package/loki-ts/package.json +2 -1
  23. package/mcp/__init__.py +1 -1
  24. package/package.json +2 -2
  25. package/packages/control-plane/dist/server.js +17103 -5411
  26. package/packages/control-plane/drizzle/0002_tiresome_fixer.sql +7 -0
  27. package/packages/control-plane/drizzle/0003_integrity.sql +4 -0
  28. package/packages/control-plane/drizzle/0004_cpe03_local_repos_actions.sql +17 -0
  29. package/packages/control-plane/drizzle/meta/0002_snapshot.json +409 -0
  30. package/packages/control-plane/drizzle/meta/0003_snapshot.json +437 -0
  31. package/packages/control-plane/drizzle/meta/0004_snapshot.json +542 -0
  32. package/packages/control-plane/drizzle/meta/_journal.json +22 -1
  33. package/packages/control-plane/package.json +4 -1
  34. package/packages/control-plane/src/db/migrate.ts +6 -2
  35. package/packages/control-plane/src/db/prune.ts +91 -0
  36. package/packages/control-plane/src/db/schema.ts +32 -0
  37. package/packages/control-plane/src/server/app.ts +30 -22
  38. package/packages/control-plane/src/server/audit.ts +15 -0
  39. package/packages/control-plane/src/server/auth.ts +7 -1
  40. package/packages/control-plane/src/server/integrity.ts +147 -0
  41. package/packages/control-plane/src/server/legacy/routes-metrics.ts +18 -0
  42. package/packages/control-plane/src/server/legacy/routes.ts +237 -0
  43. package/packages/control-plane/src/server/legacy/shim.ts +195 -0
  44. package/packages/control-plane/src/server/repos.ts +27 -0
  45. package/packages/control-plane/src/server/routes/artifacts.ts +104 -0
  46. package/packages/control-plane/src/server/routes/audit.ts +17 -0
  47. package/packages/control-plane/src/server/routes/config.ts +319 -0
  48. package/packages/control-plane/src/server/routes/control.ts +75 -0
  49. package/packages/control-plane/src/server/routes/cost.ts +70 -0
  50. package/packages/control-plane/src/server/routes/cost_ledger.ts +210 -0
  51. package/packages/control-plane/src/server/routes/fleet.ts +101 -0
  52. package/packages/control-plane/src/server/routes/index.ts +68 -0
  53. package/packages/control-plane/src/server/routes/integrations.ts +123 -0
  54. package/packages/control-plane/src/server/routes/merge_risk.ts +209 -0
  55. package/packages/control-plane/src/server/routes/metrics.ts +93 -0
  56. package/packages/control-plane/src/server/routes/notify.ts +48 -0
  57. package/packages/control-plane/src/server/routes/providers.ts +111 -0
  58. package/packages/control-plane/src/server/routes/start.ts +39 -0
  59. package/packages/control-plane/src/server/routes/stats.ts +76 -0
  60. package/packages/control-plane/src/server/routes/stream.ts +85 -0
  61. package/packages/control-plane/src/server/routes/verify.ts +66 -0
  62. package/packages/control-plane/src/server/runs.ts +46 -4
  63. package/packages/control-plane/src/server/spawn.ts +79 -0
  64. package/packages/control-plane/src/shipper/discover.ts +50 -3
  65. package/packages/control-plane/src/shipper/ship.ts +15 -0
  66. package/packages/control-plane/src/shipper/watch.ts +3 -1
  67. package/packages/control-plane/ui/dist/assets/index-BQbFv65M.css +1 -0
  68. package/packages/control-plane/ui/dist/assets/index-Dbhu244h.js +155 -0
  69. package/packages/control-plane/ui/dist/index.html +2 -2
  70. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  71. package/web-app/dist/assets/{AdminPage-BQPb5rc5.js → AdminPage-DDDvUz0N.js} +1 -1
  72. package/web-app/dist/assets/{Avatar-Cttco6FC.js → Avatar-Dcdz46i6.js} +1 -1
  73. package/web-app/dist/assets/{Badge-CPb6kpbH.js → Badge-BB24cCpK.js} +1 -1
  74. package/web-app/dist/assets/{Button-caTL28a4.js → Button-D0-DmPZL.js} +1 -1
  75. package/web-app/dist/assets/{CockpitPage-bEbLx6ph.js → CockpitPage-CdjpU2JW.js} +1 -1
  76. package/web-app/dist/assets/{ComparePage-DNtJSij-.js → ComparePage-DT5U0y0d.js} +1 -1
  77. package/web-app/dist/assets/{ErrorBoundary-BrgC3K44.js → ErrorBoundary-lEHcl0EO.js} +1 -1
  78. package/web-app/dist/assets/{EvidenceReceiptPanel-Q-mSrUUQ.js → EvidenceReceiptPanel-GpnWvdCr.js} +1 -1
  79. package/web-app/dist/assets/{GitHubIssuesPanel-DbmYl_Er.js → GitHubIssuesPanel-C7VOh1B7.js} +1 -1
  80. package/web-app/dist/assets/{GitHubPRsPanel-C6PCHM4B.js → GitHubPRsPanel-G_kOuINv.js} +1 -1
  81. package/web-app/dist/assets/{HomePage-7zsL_k_e.js → HomePage-PqRisOny.js} +2 -2
  82. package/web-app/dist/assets/{LoginPage-Cl4ON8RQ.js → LoginPage-DU7pFAkf.js} +1 -1
  83. package/web-app/dist/assets/{MagicPage-BNEAhTqH.js → MagicPage-C5JWIWMk.js} +1 -1
  84. package/web-app/dist/assets/{MetricsPage-DyBH-88q.js → MetricsPage-CbIGS_qo.js} +1 -1
  85. package/web-app/dist/assets/{NotFoundPage-CIVr21yL.js → NotFoundPage-vU1pvdSt.js} +1 -1
  86. package/web-app/dist/assets/{ProjectPage-BMeVYocF.js → ProjectPage-SsixIPPv.js} +3 -3
  87. package/web-app/dist/assets/{ProjectsPage-D-H2WRVU.js → ProjectsPage-BfQKSR9k.js} +1 -1
  88. package/web-app/dist/assets/{SettingsPage-o0kR9RAt.js → SettingsPage-B8YfXAom.js} +1 -1
  89. package/web-app/dist/assets/{ShowcasePage-DtfMDMjp.js → ShowcasePage-Ba407f2-.js} +1 -1
  90. package/web-app/dist/assets/{SystemSettingsPage-CkXdpZ_T.js → SystemSettingsPage-DzNQhhBS.js} +1 -1
  91. package/web-app/dist/assets/{TeamsPage-Cu5H9cQ0.js → TeamsPage-tqUnpZ15.js} +1 -1
  92. package/web-app/dist/assets/{TemplatesPage-3wdbVwil.js → TemplatesPage-ClB5EpI3.js} +1 -1
  93. package/web-app/dist/assets/{TerminalOutput-CWn301xh.js → TerminalOutput-CBvpYfEu.js} +1 -1
  94. package/web-app/dist/assets/{activity-YGaAt4zJ.js → activity-C1s0L_X9.js} +1 -1
  95. package/web-app/dist/assets/{bell-C28q0GO7.js → bell-DMEiXr73.js} +1 -1
  96. package/web-app/dist/assets/{bot-DLcQGI92.js → bot-BF_xIOxu.js} +1 -1
  97. package/web-app/dist/assets/{check-w0sBNlcm.js → check-Dv1Ynvrs.js} +1 -1
  98. package/web-app/dist/assets/{chevron-left-W7O0vC_m.js → chevron-left-Ddhsod7S.js} +1 -1
  99. package/web-app/dist/assets/{circle-alert-Cxjq9fxB.js → circle-alert-OATHUkBZ.js} +1 -1
  100. package/web-app/dist/assets/{clock-C8EqzmNl.js → clock-C1Ltva07.js} +1 -1
  101. package/web-app/dist/assets/{cloud-xNgp4g3Y.js → cloud-B-S4oOy9.js} +1 -1
  102. package/web-app/dist/assets/{code-xml-B-lyNDLJ.js → code-xml-D3Femn_F.js} +1 -1
  103. package/web-app/dist/assets/{database-DoW5lmWc.js → database-DBSeFgO3.js} +1 -1
  104. package/web-app/dist/assets/{dollar-sign-BcARA5sC.js → dollar-sign-sgDgNuGE.js} +1 -1
  105. package/web-app/dist/assets/{file-code-corner-CehcII0J.js → file-code-corner-C-MbdZEc.js} +1 -1
  106. package/web-app/dist/assets/{file-plus-DI9Zo5hP.js → file-plus-CCB6yqXv.js} +1 -1
  107. package/web-app/dist/assets/{globe-CiZWBk3H.js → globe-DlbrNAj1.js} +1 -1
  108. package/web-app/dist/assets/{hammer-3qv_T2N-.js → hammer-B-DWm695.js} +1 -1
  109. package/web-app/dist/assets/{index-gF9grAz-.js → index-Dvny-1SU.js} +3 -3
  110. package/web-app/dist/assets/{layers-CN7d0By9.js → layers-99qsKpOT.js} +1 -1
  111. package/web-app/dist/assets/{loader-circle-DywcM-im.js → loader-circle-ZvZyZl3J.js} +1 -1
  112. package/web-app/dist/assets/{lock-_hdSmkU4.js → lock-DosGjcxP.js} +1 -1
  113. package/web-app/dist/assets/{package-BV5zgqxJ.js → package-C-PumesN.js} +1 -1
  114. package/web-app/dist/assets/{plus-CU_Hh4-x.js → plus-CTYYskTB.js} +1 -1
  115. package/web-app/dist/assets/{refresh-cw-DMK-fNl-.js → refresh-cw-qW2ztTFS.js} +1 -1
  116. package/web-app/dist/assets/{rotate-ccw-CEB3_v5_.js → rotate-ccw-ZIKrf-vO.js} +1 -1
  117. package/web-app/dist/assets/{scroll-text-BSdXxcRN.js → scroll-text-BamlXjAT.js} +1 -1
  118. package/web-app/dist/assets/{server-CAMlP1yJ.js → server-BljXb79I.js} +1 -1
  119. package/web-app/dist/assets/{shield-alert-B-r1Dt3h.js → shield-alert-Dr6eT9c_.js} +1 -1
  120. package/web-app/dist/assets/{thumbs-up-CYfxw9o4.js → thumbs-up-v3oBXZgU.js} +1 -1
  121. package/web-app/dist/assets/{trash-2-DX3nGPS8.js → trash-2-BOuIrCpi.js} +1 -1
  122. package/web-app/dist/assets/{trending-down-Bt-hSntd.js → trending-down-Dp7jMCgH.js} +1 -1
  123. package/web-app/dist/assets/{trending-up-DVSlvQp8.js → trending-up-CDiur-nP.js} +1 -1
  124. package/web-app/dist/assets/{upload-D45RRHAP.js → upload-DxIHXhNE.js} +1 -1
  125. package/web-app/dist/assets/{usePolling-fq_Xj8iw.js → usePolling-D2Uu-YZb.js} +1 -1
  126. package/web-app/dist/assets/{user-BWcJkLlr.js → user-BVWCUAfF.js} +1 -1
  127. package/web-app/dist/index.html +1 -1
  128. package/packages/control-plane/src/server/actions.ts +0 -60
  129. package/packages/control-plane/ui/dist/assets/index-CUQ65vJY.css +0 -1
  130. package/packages/control-plane/ui/dist/assets/index-CwwGfp6E.js +0 -129
@@ -0,0 +1,542 @@
1
+ {
2
+ "version": "6",
3
+ "dialect": "sqlite",
4
+ "id": "df1eef76-5d0f-48f4-a4e7-909789b0bfed",
5
+ "prevId": "c1492d55-5d08-4208-940a-38fba6b33b3c",
6
+ "tables": {
7
+ "actions": {
8
+ "name": "actions",
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
+ "actor": {
25
+ "name": "actor",
26
+ "type": "text",
27
+ "primaryKey": false,
28
+ "notNull": true,
29
+ "autoincrement": false
30
+ },
31
+ "kind": {
32
+ "name": "kind",
33
+ "type": "text",
34
+ "primaryKey": false,
35
+ "notNull": true,
36
+ "autoincrement": false
37
+ },
38
+ "target": {
39
+ "name": "target",
40
+ "type": "text",
41
+ "primaryKey": false,
42
+ "notNull": false,
43
+ "autoincrement": false
44
+ },
45
+ "result": {
46
+ "name": "result",
47
+ "type": "text",
48
+ "primaryKey": false,
49
+ "notNull": true,
50
+ "autoincrement": false
51
+ },
52
+ "detail": {
53
+ "name": "detail",
54
+ "type": "text",
55
+ "primaryKey": false,
56
+ "notNull": false,
57
+ "autoincrement": false
58
+ }
59
+ },
60
+ "indexes": {
61
+ "actions_ts": {
62
+ "name": "actions_ts",
63
+ "columns": [
64
+ "ts"
65
+ ],
66
+ "isUnique": false
67
+ }
68
+ },
69
+ "foreignKeys": {},
70
+ "compositePrimaryKeys": {},
71
+ "uniqueConstraints": {},
72
+ "checkConstraints": {}
73
+ },
74
+ "audit": {
75
+ "name": "audit",
76
+ "columns": {
77
+ "id": {
78
+ "name": "id",
79
+ "type": "integer",
80
+ "primaryKey": true,
81
+ "notNull": true,
82
+ "autoincrement": true
83
+ },
84
+ "ts": {
85
+ "name": "ts",
86
+ "type": "text",
87
+ "primaryKey": false,
88
+ "notNull": true,
89
+ "autoincrement": false
90
+ },
91
+ "action": {
92
+ "name": "action",
93
+ "type": "text",
94
+ "primaryKey": false,
95
+ "notNull": true,
96
+ "autoincrement": false
97
+ },
98
+ "actor": {
99
+ "name": "actor",
100
+ "type": "text",
101
+ "primaryKey": false,
102
+ "notNull": true,
103
+ "autoincrement": false
104
+ },
105
+ "detail": {
106
+ "name": "detail",
107
+ "type": "text",
108
+ "primaryKey": false,
109
+ "notNull": true,
110
+ "autoincrement": false
111
+ }
112
+ },
113
+ "indexes": {},
114
+ "foreignKeys": {},
115
+ "compositePrimaryKeys": {},
116
+ "uniqueConstraints": {},
117
+ "checkConstraints": {}
118
+ },
119
+ "events": {
120
+ "name": "events",
121
+ "columns": {
122
+ "source_id": {
123
+ "name": "source_id",
124
+ "type": "text",
125
+ "primaryKey": false,
126
+ "notNull": true,
127
+ "autoincrement": false
128
+ },
129
+ "run_id": {
130
+ "name": "run_id",
131
+ "type": "text",
132
+ "primaryKey": false,
133
+ "notNull": true,
134
+ "autoincrement": false
135
+ },
136
+ "seq": {
137
+ "name": "seq",
138
+ "type": "integer",
139
+ "primaryKey": false,
140
+ "notNull": true,
141
+ "autoincrement": false
142
+ },
143
+ "ts": {
144
+ "name": "ts",
145
+ "type": "text",
146
+ "primaryKey": false,
147
+ "notNull": true,
148
+ "autoincrement": false
149
+ },
150
+ "type": {
151
+ "name": "type",
152
+ "type": "text",
153
+ "primaryKey": false,
154
+ "notNull": true,
155
+ "autoincrement": false
156
+ },
157
+ "stage": {
158
+ "name": "stage",
159
+ "type": "text",
160
+ "primaryKey": false,
161
+ "notNull": false,
162
+ "autoincrement": false
163
+ },
164
+ "data": {
165
+ "name": "data",
166
+ "type": "text",
167
+ "primaryKey": false,
168
+ "notNull": true,
169
+ "autoincrement": false
170
+ },
171
+ "line_sha256": {
172
+ "name": "line_sha256",
173
+ "type": "text",
174
+ "primaryKey": false,
175
+ "notNull": true,
176
+ "autoincrement": false
177
+ },
178
+ "received_at": {
179
+ "name": "received_at",
180
+ "type": "text",
181
+ "primaryKey": false,
182
+ "notNull": true,
183
+ "autoincrement": false
184
+ }
185
+ },
186
+ "indexes": {
187
+ "events_run_seq": {
188
+ "name": "events_run_seq",
189
+ "columns": [
190
+ "run_id",
191
+ "seq"
192
+ ],
193
+ "isUnique": false
194
+ },
195
+ "events_type_ts": {
196
+ "name": "events_type_ts",
197
+ "columns": [
198
+ "type",
199
+ "ts"
200
+ ],
201
+ "isUnique": false
202
+ }
203
+ },
204
+ "foreignKeys": {},
205
+ "compositePrimaryKeys": {
206
+ "events_source_id_run_id_seq_pk": {
207
+ "columns": [
208
+ "source_id",
209
+ "run_id",
210
+ "seq"
211
+ ],
212
+ "name": "events_source_id_run_id_seq_pk"
213
+ }
214
+ },
215
+ "uniqueConstraints": {},
216
+ "checkConstraints": {}
217
+ },
218
+ "local_repos": {
219
+ "name": "local_repos",
220
+ "columns": {
221
+ "source_id": {
222
+ "name": "source_id",
223
+ "type": "text",
224
+ "primaryKey": true,
225
+ "notNull": true,
226
+ "autoincrement": false
227
+ },
228
+ "realpath": {
229
+ "name": "realpath",
230
+ "type": "text",
231
+ "primaryKey": false,
232
+ "notNull": true,
233
+ "autoincrement": false
234
+ },
235
+ "name": {
236
+ "name": "name",
237
+ "type": "text",
238
+ "primaryKey": false,
239
+ "notNull": true,
240
+ "autoincrement": false
241
+ },
242
+ "discovered_at": {
243
+ "name": "discovered_at",
244
+ "type": "text",
245
+ "primaryKey": false,
246
+ "notNull": true,
247
+ "autoincrement": false
248
+ }
249
+ },
250
+ "indexes": {},
251
+ "foreignKeys": {},
252
+ "compositePrimaryKeys": {},
253
+ "uniqueConstraints": {},
254
+ "checkConstraints": {}
255
+ },
256
+ "runs": {
257
+ "name": "runs",
258
+ "columns": {
259
+ "source_id": {
260
+ "name": "source_id",
261
+ "type": "text",
262
+ "primaryKey": false,
263
+ "notNull": true,
264
+ "autoincrement": false
265
+ },
266
+ "run_id": {
267
+ "name": "run_id",
268
+ "type": "text",
269
+ "primaryKey": false,
270
+ "notNull": true,
271
+ "autoincrement": false
272
+ },
273
+ "origin_repo": {
274
+ "name": "origin_repo",
275
+ "type": "text",
276
+ "primaryKey": false,
277
+ "notNull": false,
278
+ "autoincrement": false
279
+ },
280
+ "issue_ref": {
281
+ "name": "issue_ref",
282
+ "type": "text",
283
+ "primaryKey": false,
284
+ "notNull": false,
285
+ "autoincrement": false
286
+ },
287
+ "task_source": {
288
+ "name": "task_source",
289
+ "type": "text",
290
+ "primaryKey": false,
291
+ "notNull": false,
292
+ "autoincrement": false
293
+ },
294
+ "provider": {
295
+ "name": "provider",
296
+ "type": "text",
297
+ "primaryKey": false,
298
+ "notNull": false,
299
+ "autoincrement": false
300
+ },
301
+ "model": {
302
+ "name": "model",
303
+ "type": "text",
304
+ "primaryKey": false,
305
+ "notNull": false,
306
+ "autoincrement": false
307
+ },
308
+ "started_at": {
309
+ "name": "started_at",
310
+ "type": "text",
311
+ "primaryKey": false,
312
+ "notNull": false,
313
+ "autoincrement": false
314
+ },
315
+ "ended_at": {
316
+ "name": "ended_at",
317
+ "type": "text",
318
+ "primaryKey": false,
319
+ "notNull": false,
320
+ "autoincrement": false
321
+ },
322
+ "verdict": {
323
+ "name": "verdict",
324
+ "type": "text",
325
+ "primaryKey": false,
326
+ "notNull": false,
327
+ "autoincrement": false
328
+ },
329
+ "pr_url": {
330
+ "name": "pr_url",
331
+ "type": "text",
332
+ "primaryKey": false,
333
+ "notNull": false,
334
+ "autoincrement": false
335
+ },
336
+ "pr_draft": {
337
+ "name": "pr_draft",
338
+ "type": "integer",
339
+ "primaryKey": false,
340
+ "notNull": false,
341
+ "autoincrement": false
342
+ },
343
+ "cost_usd": {
344
+ "name": "cost_usd",
345
+ "type": "real",
346
+ "primaryKey": false,
347
+ "notNull": false,
348
+ "autoincrement": false
349
+ },
350
+ "partial_usd": {
351
+ "name": "partial_usd",
352
+ "type": "real",
353
+ "primaryKey": false,
354
+ "notNull": true,
355
+ "autoincrement": false
356
+ },
357
+ "measured_sessions": {
358
+ "name": "measured_sessions",
359
+ "type": "integer",
360
+ "primaryKey": false,
361
+ "notNull": true,
362
+ "autoincrement": false
363
+ },
364
+ "total_sessions": {
365
+ "name": "total_sessions",
366
+ "type": "integer",
367
+ "primaryKey": false,
368
+ "notNull": true,
369
+ "autoincrement": false
370
+ },
371
+ "input_tokens": {
372
+ "name": "input_tokens",
373
+ "type": "integer",
374
+ "primaryKey": false,
375
+ "notNull": true,
376
+ "autoincrement": false
377
+ },
378
+ "output_tokens": {
379
+ "name": "output_tokens",
380
+ "type": "integer",
381
+ "primaryKey": false,
382
+ "notNull": true,
383
+ "autoincrement": false
384
+ },
385
+ "wall_s": {
386
+ "name": "wall_s",
387
+ "type": "real",
388
+ "primaryKey": false,
389
+ "notNull": false,
390
+ "autoincrement": false
391
+ },
392
+ "last_seq": {
393
+ "name": "last_seq",
394
+ "type": "integer",
395
+ "primaryKey": false,
396
+ "notNull": true,
397
+ "autoincrement": false
398
+ },
399
+ "last_event_at": {
400
+ "name": "last_event_at",
401
+ "type": "text",
402
+ "primaryKey": false,
403
+ "notNull": false,
404
+ "autoincrement": false
405
+ },
406
+ "tampered": {
407
+ "name": "tampered",
408
+ "type": "integer",
409
+ "primaryKey": false,
410
+ "notNull": true,
411
+ "autoincrement": false
412
+ },
413
+ "conflict": {
414
+ "name": "conflict",
415
+ "type": "integer",
416
+ "primaryKey": false,
417
+ "notNull": true,
418
+ "autoincrement": false,
419
+ "default": 0
420
+ },
421
+ "group_id": {
422
+ "name": "group_id",
423
+ "type": "text",
424
+ "primaryKey": false,
425
+ "notNull": false,
426
+ "autoincrement": false
427
+ },
428
+ "unit_id": {
429
+ "name": "unit_id",
430
+ "type": "text",
431
+ "primaryKey": false,
432
+ "notNull": false,
433
+ "autoincrement": false
434
+ },
435
+ "attested": {
436
+ "name": "attested",
437
+ "type": "integer",
438
+ "primaryKey": false,
439
+ "notNull": false,
440
+ "autoincrement": false
441
+ },
442
+ "integrity_reasons": {
443
+ "name": "integrity_reasons",
444
+ "type": "text",
445
+ "primaryKey": false,
446
+ "notNull": false,
447
+ "autoincrement": false
448
+ },
449
+ "sig_checked": {
450
+ "name": "sig_checked",
451
+ "type": "integer",
452
+ "primaryKey": false,
453
+ "notNull": false,
454
+ "autoincrement": false
455
+ },
456
+ "integrity_key_fp": {
457
+ "name": "integrity_key_fp",
458
+ "type": "text",
459
+ "primaryKey": false,
460
+ "notNull": false,
461
+ "autoincrement": false
462
+ }
463
+ },
464
+ "indexes": {
465
+ "runs_started": {
466
+ "name": "runs_started",
467
+ "columns": [
468
+ "started_at"
469
+ ],
470
+ "isUnique": false
471
+ },
472
+ "runs_verdict": {
473
+ "name": "runs_verdict",
474
+ "columns": [
475
+ "verdict"
476
+ ],
477
+ "isUnique": false
478
+ },
479
+ "runs_group": {
480
+ "name": "runs_group",
481
+ "columns": [
482
+ "group_id"
483
+ ],
484
+ "isUnique": false
485
+ }
486
+ },
487
+ "foreignKeys": {},
488
+ "compositePrimaryKeys": {
489
+ "runs_source_id_run_id_pk": {
490
+ "columns": [
491
+ "source_id",
492
+ "run_id"
493
+ ],
494
+ "name": "runs_source_id_run_id_pk"
495
+ }
496
+ },
497
+ "uniqueConstraints": {},
498
+ "checkConstraints": {}
499
+ },
500
+ "sources": {
501
+ "name": "sources",
502
+ "columns": {
503
+ "id": {
504
+ "name": "id",
505
+ "type": "text",
506
+ "primaryKey": true,
507
+ "notNull": true,
508
+ "autoincrement": false
509
+ },
510
+ "first_seen": {
511
+ "name": "first_seen",
512
+ "type": "text",
513
+ "primaryKey": false,
514
+ "notNull": true,
515
+ "autoincrement": false
516
+ },
517
+ "last_seen": {
518
+ "name": "last_seen",
519
+ "type": "text",
520
+ "primaryKey": false,
521
+ "notNull": true,
522
+ "autoincrement": false
523
+ }
524
+ },
525
+ "indexes": {},
526
+ "foreignKeys": {},
527
+ "compositePrimaryKeys": {},
528
+ "uniqueConstraints": {},
529
+ "checkConstraints": {}
530
+ }
531
+ },
532
+ "views": {},
533
+ "enums": {},
534
+ "_meta": {
535
+ "schemas": {},
536
+ "tables": {},
537
+ "columns": {}
538
+ },
539
+ "internal": {
540
+ "indexes": {}
541
+ }
542
+ }
@@ -15,6 +15,27 @@
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": 1791049000000,
30
+ "tag": "0003_integrity",
31
+ "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "6",
36
+ "when": 1791050000000,
37
+ "tag": "0004_cpe03_local_repos_actions",
38
+ "breakpoints": true
18
39
  }
19
40
  ]
20
- }
41
+ }
@@ -12,9 +12,12 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "drizzle-orm": "^0.45.3",
15
- "hono": "^4.13.12"
15
+ "hono": "^4.13.12",
16
+ "yaml": "^2.9.1"
16
17
  },
17
18
  "devDependencies": {
19
+ "@playwright/test": "1.58.1",
20
+ "axe-core": "4.13.0",
18
21
  "drizzle-kit": "^0.31.11"
19
22
  }
20
23
  }
@@ -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
- // src/db/ (source) and dist/ (bundled server) sit at different depths under packages/control-plane
16
- const migrationsFolder = [join(import.meta.dir, "../../drizzle"), join(import.meta.dir, "../drizzle")].find((d) => existsSync(join(d, "meta/_journal.json")));
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
+ }