mayhem-cli 0.5.1__py3-none-any.whl

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 (107) hide show
  1. mayhem/agent/__init__.py +1 -0
  2. mayhem/agent/cli.py +36 -0
  3. mayhem/agents/__init__.py +1 -0
  4. mayhem/agents/capabilities.py +106 -0
  5. mayhem/agents/executors.py +430 -0
  6. mayhem/agents/impact.py +729 -0
  7. mayhem/agents/lease_client.py +141 -0
  8. mayhem/agents/probes.py +284 -0
  9. mayhem/agents/protocol.py +134 -0
  10. mayhem/agents/server.py +281 -0
  11. mayhem/agents/sinks.py +60 -0
  12. mayhem/agents/transports.py +134 -0
  13. mayhem/agents/watchdog.py +140 -0
  14. mayhem/cli/__init__.py +11 -0
  15. mayhem/cli/app.py +154 -0
  16. mayhem/cli/campaign.py +496 -0
  17. mayhem/cli/config_cmd.py +47 -0
  18. mayhem/cli/context.py +23 -0
  19. mayhem/cli/dependency.py +429 -0
  20. mayhem/cli/exit_codes.py +24 -0
  21. mayhem/cli/experiment.py +24 -0
  22. mayhem/cli/lifecycle.py +805 -0
  23. mayhem/cli/resolver.py +72 -0
  24. mayhem/cli/services.py +459 -0
  25. mayhem/cli/style.py +101 -0
  26. mayhem/cli/toolkit.py +41 -0
  27. mayhem/cli/topology.py +127 -0
  28. mayhem/config.py +208 -0
  29. mayhem/controller/__init__.py +1 -0
  30. mayhem/controller/compensation.py +2156 -0
  31. mayhem/controller/executor.py +1719 -0
  32. mayhem/controller/janitor.py +196 -0
  33. mayhem/controller/observability_collector.py +382 -0
  34. mayhem/controller/observations.py +102 -0
  35. mayhem/controller/planner.py +715 -0
  36. mayhem/controller/recovery.py +245 -0
  37. mayhem/controller/resilience_report.py +585 -0
  38. mayhem/controller/resource_manager.py +457 -0
  39. mayhem/controller/safety.py +392 -0
  40. mayhem/domain/__init__.py +6 -0
  41. mayhem/domain/campaigns.py +118 -0
  42. mayhem/domain/cancellation.py +110 -0
  43. mayhem/domain/candidates.py +101 -0
  44. mayhem/domain/capabilities.py +86 -0
  45. mayhem/domain/catalog.py +727 -0
  46. mayhem/domain/checks.py +173 -0
  47. mayhem/domain/common.py +104 -0
  48. mayhem/domain/coverage.py +106 -0
  49. mayhem/domain/decisions.py +57 -0
  50. mayhem/domain/errors.py +87 -0
  51. mayhem/domain/events.py +61 -0
  52. mayhem/domain/execution_context.py +120 -0
  53. mayhem/domain/execution_loci.py +94 -0
  54. mayhem/domain/experiments.py +370 -0
  55. mayhem/domain/faults.py +239 -0
  56. mayhem/domain/identity.py +200 -0
  57. mayhem/domain/k8s_adapter.py +132 -0
  58. mayhem/domain/leases.py +186 -0
  59. mayhem/domain/load_strategy.py +98 -0
  60. mayhem/domain/m5_campaign.py +120 -0
  61. mayhem/domain/maniac.py +93 -0
  62. mayhem/domain/observability.py +146 -0
  63. mayhem/domain/outcomes.py +92 -0
  64. mayhem/domain/remote_agent_interface.py +70 -0
  65. mayhem/domain/resources.py +245 -0
  66. mayhem/domain/risks.py +61 -0
  67. mayhem/domain/run_outcome.py +146 -0
  68. mayhem/domain/runtime_adapter.py +256 -0
  69. mayhem/domain/success.py +329 -0
  70. mayhem/domain/topology.py +452 -0
  71. mayhem/infra/__init__.py +1 -0
  72. mayhem/infra/campaign_engine.py +205 -0
  73. mayhem/infra/candidate_gates.py +124 -0
  74. mayhem/infra/candidate_generator.py +110 -0
  75. mayhem/infra/coverage_repository.py +101 -0
  76. mayhem/infra/lease_repository.py +129 -0
  77. mayhem/infra/maniac.py +103 -0
  78. mayhem/infra/migrations.py +596 -0
  79. mayhem/infra/migrator.py +149 -0
  80. mayhem/infra/report.py +227 -0
  81. mayhem/infra/store.py +200 -0
  82. mayhem/py.typed +0 -0
  83. mayhem/spec.py +52 -0
  84. mayhem/toolkit/__init__.py +1 -0
  85. mayhem/toolkit/fingerprint.py +69 -0
  86. mayhem/toolkit/hashing.py +32 -0
  87. mayhem/toolkit/manifests/docker.yaml +11 -0
  88. mayhem/toolkit/manifests/podman.yaml +11 -0
  89. mayhem/toolkit/manifests/stress-ng.yaml +11 -0
  90. mayhem/toolkit/manifests/tc-netem.yaml +11 -0
  91. mayhem/toolkit/manifests/toxiproxy.yaml +10 -0
  92. mayhem/toolkit/registry.py +185 -0
  93. mayhem/toolkit/tool_runner.py +129 -0
  94. mayhem/topology/__init__.py +10 -0
  95. mayhem/topology/providers/__init__.py +0 -0
  96. mayhem/topology/providers/adapter_registry.py +60 -0
  97. mayhem/topology/providers/base.py +31 -0
  98. mayhem/topology/providers/compose.py +207 -0
  99. mayhem/topology/providers/docker_adapter.py +277 -0
  100. mayhem/topology/providers/docker_runtime.py +461 -0
  101. mayhem/topology/providers/podman_adapter.py +328 -0
  102. mayhem/topology/resolve.py +196 -0
  103. mayhem/topology/service.py +158 -0
  104. mayhem_cli-0.5.1.dist-info/METADATA +555 -0
  105. mayhem_cli-0.5.1.dist-info/RECORD +107 -0
  106. mayhem_cli-0.5.1.dist-info/WHEEL +4 -0
  107. mayhem_cli-0.5.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,596 @@
1
+ """Schema v1 — canonical DDL from docs/reference/sqlite-schema.md."""
2
+
3
+ from mayhem.infra.migrator import Migration
4
+
5
+ M0001_INITIAL = Migration(
6
+ version=1,
7
+ name="initial",
8
+ statements=(
9
+ """
10
+ CREATE TABLE config_snapshots (
11
+ id TEXT PRIMARY KEY,
12
+ resolved_json TEXT NOT NULL,
13
+ source_map TEXT NOT NULL,
14
+ created_at TEXT NOT NULL
15
+ )
16
+ """,
17
+ """
18
+ CREATE TABLE topology_snapshots (
19
+ id TEXT PRIMARY KEY,
20
+ run_id TEXT REFERENCES runs(id),
21
+ graph_json TEXT NOT NULL,
22
+ drift_report TEXT NOT NULL,
23
+ fingerprint TEXT NOT NULL
24
+ )
25
+ """,
26
+ """
27
+ CREATE TABLE runs (
28
+ id TEXT PRIMARY KEY,
29
+ experiment_name TEXT NOT NULL,
30
+ kind TEXT NOT NULL CHECK (kind IN ('deterministic','random')),
31
+ spec_json TEXT NOT NULL,
32
+ plan_json TEXT NOT NULL,
33
+ seed INTEGER,
34
+ status TEXT NOT NULL CHECK (status IN
35
+ ('created','planning','validated','running','recovering',
36
+ 'completed','failed','aborted')),
37
+ environment_fingerprint TEXT NOT NULL,
38
+ config_snapshot_id TEXT NOT NULL REFERENCES config_snapshots(id),
39
+ topology_snapshot_id TEXT REFERENCES topology_snapshots(id),
40
+ started_at TEXT,
41
+ ended_at TEXT,
42
+ summary_md TEXT
43
+ )
44
+ """,
45
+ "CREATE INDEX idx_runs_status ON runs(status)",
46
+ "CREATE INDEX idx_runs_started ON runs(started_at DESC)",
47
+ """
48
+ CREATE TABLE step_runs (
49
+ id TEXT PRIMARY KEY,
50
+ run_id TEXT NOT NULL REFERENCES runs(id),
51
+ seq INTEGER NOT NULL,
52
+ parent_step_id TEXT REFERENCES step_runs(id),
53
+ action_type TEXT NOT NULL,
54
+ action_json TEXT NOT NULL,
55
+ status TEXT NOT NULL CHECK (status IN
56
+ ('pending','running','completed','failed','skipped','cancelled')),
57
+ started_at TEXT,
58
+ ended_at TEXT,
59
+ error TEXT
60
+ )
61
+ """,
62
+ """
63
+ CREATE TABLE fault_leases (
64
+ id TEXT PRIMARY KEY,
65
+ state TEXT NOT NULL CHECK (state IN
66
+ ('pending','active','releasing','released','expired','orphaned','dirty')),
67
+ owner_agent TEXT NOT NULL,
68
+ undo_json TEXT NOT NULL,
69
+ verify_json TEXT NOT NULL,
70
+ ttl_seconds REAL NOT NULL,
71
+ expires_at TEXT NOT NULL,
72
+ injected_at TEXT,
73
+ released_at TEXT,
74
+ release_mechanism TEXT,
75
+ escalation_notes TEXT
76
+ )
77
+ """,
78
+ "CREATE INDEX idx_leases_state ON fault_leases(state)",
79
+ """
80
+ CREATE TABLE fault_invocations (
81
+ id TEXT PRIMARY KEY,
82
+ run_id TEXT NOT NULL REFERENCES runs(id),
83
+ step_run_id TEXT NOT NULL REFERENCES step_runs(id),
84
+ fault_id TEXT NOT NULL,
85
+ targets_json TEXT NOT NULL,
86
+ params_json TEXT NOT NULL,
87
+ backend TEXT NOT NULL,
88
+ lease_id TEXT UNIQUE NOT NULL REFERENCES fault_leases(id)
89
+ )
90
+ """,
91
+ """
92
+ CREATE TABLE recovery_records (
93
+ id TEXT PRIMARY KEY,
94
+ lease_id TEXT NOT NULL REFERENCES fault_leases(id),
95
+ attempt INTEGER NOT NULL,
96
+ mechanism TEXT NOT NULL,
97
+ undo_results_json TEXT NOT NULL,
98
+ verified INTEGER NOT NULL CHECK (verified IN (0, 1)),
99
+ at TEXT NOT NULL
100
+ )
101
+ """,
102
+ """
103
+ CREATE TABLE tool_runs (
104
+ id TEXT PRIMARY KEY,
105
+ invocation_ref TEXT,
106
+ argv_digest TEXT NOT NULL,
107
+ argv_json TEXT NOT NULL,
108
+ env_digest TEXT NOT NULL,
109
+ host TEXT NOT NULL,
110
+ exit_code INTEGER,
111
+ duration_ms INTEGER,
112
+ truncated INTEGER NOT NULL DEFAULT 0 CHECK (truncated IN (0, 1)),
113
+ stdout_ref TEXT,
114
+ stderr_ref TEXT
115
+ )
116
+ """,
117
+ """
118
+ CREATE TABLE agent_states (
119
+ id TEXT PRIMARY KEY,
120
+ host TEXT NOT NULL,
121
+ roles_json TEXT NOT NULL,
122
+ capabilities_json TEXT NOT NULL,
123
+ state TEXT NOT NULL CHECK (state IN ('ready','busy','dead','retired')),
124
+ last_heartbeat TEXT NOT NULL
125
+ )
126
+ """,
127
+ """
128
+ CREATE TABLE events (
129
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
130
+ run_id TEXT REFERENCES runs(id),
131
+ ts TEXT NOT NULL,
132
+ kind TEXT NOT NULL,
133
+ payload_json TEXT NOT NULL
134
+ )
135
+ """,
136
+ "CREATE INDEX idx_events_run_ts ON events(run_id, ts)",
137
+ """
138
+ CREATE TABLE steady_state_evaluations (
139
+ id TEXT PRIMARY KEY,
140
+ run_id TEXT NOT NULL REFERENCES runs(id),
141
+ check_id TEXT NOT NULL,
142
+ phase TEXT NOT NULL CHECK (phase IN ('pre','during','post')),
143
+ passed INTEGER NOT NULL CHECK (passed IN (0, 1)),
144
+ measured_json TEXT NOT NULL,
145
+ expectation_json TEXT NOT NULL,
146
+ evaluated_at TEXT NOT NULL
147
+ )
148
+ """,
149
+ """
150
+ CREATE TABLE maniac_decisions (
151
+ id TEXT PRIMARY KEY,
152
+ run_id TEXT UNIQUE REFERENCES runs(id),
153
+ candidates_json TEXT NOT NULL,
154
+ weights_json TEXT NOT NULL,
155
+ rng_state TEXT NOT NULL,
156
+ chosen_plan_json TEXT NOT NULL,
157
+ decided_at TEXT NOT NULL
158
+ )
159
+ """,
160
+ ),
161
+ )
162
+
163
+ M0002_LEASE_CONTEXT = Migration(
164
+ version=2,
165
+ name="lease_context",
166
+ statements=(
167
+ "ALTER TABLE fault_leases ADD COLUMN run_id TEXT",
168
+ "ALTER TABLE fault_leases ADD COLUMN fault_id TEXT NOT NULL DEFAULT ''",
169
+ "ALTER TABLE fault_leases ADD COLUMN targets_json TEXT NOT NULL DEFAULT '[]'",
170
+ "ALTER TABLE fault_leases ADD COLUMN created_epoch_s REAL NOT NULL DEFAULT 0",
171
+ ),
172
+ )
173
+
174
+ M0003_CAMPAIGNS_OBSERVATIONS = Migration(
175
+ version=3,
176
+ name="campaigns_and_observations",
177
+ statements=(
178
+ """
179
+ CREATE TABLE campaigns (
180
+ id TEXT PRIMARY KEY,
181
+ name TEXT NOT NULL,
182
+ description TEXT NOT NULL DEFAULT '',
183
+ status TEXT NOT NULL DEFAULT 'draft'
184
+ CHECK (status IN ('draft','scheduled','running','paused','completed','aborted')),
185
+ experiments_json TEXT NOT NULL DEFAULT '[]',
186
+ window_json TEXT NOT NULL DEFAULT '{}',
187
+ policy_json TEXT NOT NULL DEFAULT '{}',
188
+ labels_json TEXT NOT NULL DEFAULT '{}',
189
+ created_at TEXT NOT NULL,
190
+ updated_at TEXT NOT NULL
191
+ )
192
+ """,
193
+ "CREATE INDEX idx_campaigns_status ON campaigns(status)",
194
+ """
195
+ CREATE TABLE observations (
196
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
197
+ kind TEXT NOT NULL,
198
+ run_id TEXT NOT NULL,
199
+ source TEXT NOT NULL DEFAULT '',
200
+ data_json TEXT NOT NULL DEFAULT '{}',
201
+ timestamp TEXT NOT NULL
202
+ )
203
+ """,
204
+ "CREATE INDEX idx_observations_run ON observations(run_id)",
205
+ "CREATE INDEX idx_observations_kind ON observations(kind)",
206
+ ),
207
+ )
208
+
209
+ M0004_DRILL_RUN_KIND = Migration(
210
+ version=4,
211
+ name="drill_run_kind",
212
+ statements=(
213
+ # Rebuild `runs` (no ALTER support for CHECK constraints in SQLite) so the
214
+ # `kind` column admits 'drill' plans (Phase 5). FK enforcement is disabled
215
+ # for the migration by run_migrations; id values are preserved.
216
+ """
217
+ CREATE TABLE runs_new (
218
+ id TEXT PRIMARY KEY,
219
+ experiment_name TEXT NOT NULL,
220
+ kind TEXT NOT NULL CHECK (kind IN ('deterministic','random','drill')),
221
+ spec_json TEXT NOT NULL,
222
+ plan_json TEXT NOT NULL,
223
+ seed INTEGER,
224
+ status TEXT NOT NULL CHECK (status IN
225
+ ('created','planning','validated','running','recovering',
226
+ 'completed','failed','aborted')),
227
+ environment_fingerprint TEXT NOT NULL,
228
+ config_snapshot_id TEXT NOT NULL REFERENCES config_snapshots(id),
229
+ topology_snapshot_id TEXT REFERENCES topology_snapshots(id),
230
+ started_at TEXT,
231
+ ended_at TEXT,
232
+ summary_md TEXT
233
+ )
234
+ """,
235
+ """
236
+ INSERT INTO runs_new (id, experiment_name, kind, spec_json, plan_json, seed,
237
+ status, environment_fingerprint, config_snapshot_id, topology_snapshot_id,
238
+ started_at, ended_at, summary_md)
239
+ SELECT id, experiment_name, kind, spec_json, plan_json, seed,
240
+ status, environment_fingerprint, config_snapshot_id, topology_snapshot_id,
241
+ started_at, ended_at, summary_md
242
+ FROM runs
243
+ """,
244
+ "DROP TABLE runs",
245
+ "ALTER TABLE runs_new RENAME TO runs",
246
+ "CREATE INDEX idx_runs_status ON runs(status)",
247
+ "CREATE INDEX idx_runs_started ON runs(started_at DESC)",
248
+ ),
249
+ )
250
+
251
+ M0005_STEP_RUN_BYPASS_STATUS = Migration(
252
+ version=5,
253
+ name="step_run_bypass_status",
254
+ statements=(
255
+ # Rebuild `step_runs` so `status` admits 'bypassed' — the per-fault
256
+ # fail-safe outcome where the impact gate proved tooling absent and the
257
+ # engine skipped the injection instead of failing the run.
258
+ """
259
+ CREATE TABLE step_runs_new (
260
+ id TEXT PRIMARY KEY,
261
+ run_id TEXT NOT NULL REFERENCES runs(id),
262
+ seq INTEGER NOT NULL,
263
+ parent_step_id TEXT REFERENCES step_runs(id),
264
+ action_type TEXT NOT NULL,
265
+ action_json TEXT NOT NULL,
266
+ status TEXT NOT NULL CHECK (status IN
267
+ ('pending','running','completed','failed','skipped','cancelled','bypassed')),
268
+ started_at TEXT,
269
+ ended_at TEXT,
270
+ error TEXT
271
+ )
272
+ """,
273
+ """
274
+ INSERT INTO step_runs_new (id, run_id, seq, parent_step_id, action_type,
275
+ action_json, status, started_at, ended_at, error)
276
+ SELECT id, run_id, seq, parent_step_id, action_type,
277
+ action_json, status, started_at, ended_at, error
278
+ FROM step_runs
279
+ """,
280
+ "DROP TABLE step_runs",
281
+ "ALTER TABLE step_runs_new RENAME TO step_runs",
282
+ ),
283
+ )
284
+
285
+ M0006_RUNTIME_IDENTITY = Migration(
286
+ version=6,
287
+ name="runtime_identity",
288
+ statements=(
289
+ # Rebuild `step_runs` adding the canonical `runtime_identity` column and
290
+ # admitting `'target_drift'` as a first-class persisted step status
291
+ # (ADR-M1-3 Phase 1.4). `'bypassed'` and all prior statuses are retained
292
+ # so pre-M1 rows read identically (ADR-M1-4).
293
+ """
294
+ CREATE TABLE step_runs_v6 (
295
+ id TEXT PRIMARY KEY,
296
+ run_id TEXT NOT NULL REFERENCES runs(id),
297
+ seq INTEGER NOT NULL,
298
+ parent_step_id TEXT REFERENCES step_runs(id),
299
+ action_type TEXT NOT NULL,
300
+ action_json TEXT NOT NULL,
301
+ status TEXT NOT NULL CHECK (status IN
302
+ ('pending','running','completed','failed','skipped','cancelled',
303
+ 'bypassed','target_drift')),
304
+ started_at TEXT,
305
+ ended_at TEXT,
306
+ error TEXT,
307
+ runtime_identity TEXT
308
+ )
309
+ """,
310
+ """
311
+ INSERT INTO step_runs_v6 (id, run_id, seq, parent_step_id, action_type,
312
+ action_json, status, started_at, ended_at, error, runtime_identity)
313
+ SELECT id, run_id, seq, parent_step_id, action_type,
314
+ action_json, status, started_at, ended_at, error, NULL
315
+ FROM step_runs
316
+ """,
317
+ "DROP TABLE step_runs",
318
+ "ALTER TABLE step_runs_v6 RENAME TO step_runs",
319
+ # Nullable identity columns on every persisted record (ADR-M1-3).
320
+ "ALTER TABLE runs ADD COLUMN runtime_identity TEXT",
321
+ "ALTER TABLE fault_leases ADD COLUMN runtime_identity TEXT",
322
+ "ALTER TABLE observations ADD COLUMN runtime_identity TEXT",
323
+ "ALTER TABLE recovery_records ADD COLUMN runtime_identity TEXT",
324
+ ),
325
+ )
326
+
327
+
328
+ M0007_FAULT_GROUPS = Migration(
329
+ version=7,
330
+ name="fault_groups",
331
+ statements=(
332
+ # Persistent fault-group attribution (ADR-M2-1/2-2): every executed
333
+ # step and fault invocation carries the group it belongs to. In-place
334
+ # column additions per Q9 (schema freeze enforced at M4).
335
+ "ALTER TABLE step_runs ADD COLUMN execution_group_id TEXT",
336
+ "ALTER TABLE step_runs ADD COLUMN group_mode TEXT",
337
+ "ALTER TABLE step_runs ADD COLUMN group_path TEXT",
338
+ "ALTER TABLE fault_invocations ADD COLUMN execution_group_id TEXT",
339
+ "CREATE INDEX idx_step_runs_group ON step_runs(execution_group_id)",
340
+ ),
341
+ )
342
+
343
+
344
+ M0008_FAILED_TO_APPLY = Migration(
345
+ version=8,
346
+ name="failed_to_apply",
347
+ statements=(
348
+ # Execution-time capability revalidation (ADR-M2 Phase 2.3): a fault
349
+ # that reaches the mutation boundary but whose capability no longer
350
+ # holds (engine binary gone, tooling removed after plan time) is
351
+ # recorded as `'failed_to_apply'` instead of mutating or bypassing.
352
+ # Rebuild `step_runs` to admit the new persisted status; every prior
353
+ # status remains so pre-M8 rows read identically (ADR-M1-4).
354
+ """
355
+ CREATE TABLE step_runs_v8 (
356
+ id TEXT PRIMARY KEY,
357
+ run_id TEXT NOT NULL REFERENCES runs(id),
358
+ seq INTEGER NOT NULL,
359
+ parent_step_id TEXT REFERENCES step_runs(id),
360
+ action_type TEXT NOT NULL,
361
+ action_json TEXT NOT NULL,
362
+ status TEXT NOT NULL CHECK (status IN
363
+ ('pending','running','completed','failed','skipped','cancelled',
364
+ 'bypassed','target_drift','failed_to_apply')),
365
+ started_at TEXT,
366
+ ended_at TEXT,
367
+ error TEXT,
368
+ runtime_identity TEXT,
369
+ execution_group_id TEXT,
370
+ group_mode TEXT,
371
+ group_path TEXT
372
+ )
373
+ """,
374
+ """
375
+ INSERT INTO step_runs_v8 (id, run_id, seq, parent_step_id, action_type,
376
+ action_json, status, started_at, ended_at, error, runtime_identity,
377
+ execution_group_id, group_mode, group_path)
378
+ SELECT id, run_id, seq, parent_step_id, action_type,
379
+ action_json, status, started_at, ended_at, error, runtime_identity,
380
+ execution_group_id, group_mode, group_path
381
+ FROM step_runs
382
+ """,
383
+ "DROP TABLE step_runs",
384
+ "ALTER TABLE step_runs_v8 RENAME TO step_runs",
385
+ "CREATE INDEX idx_step_runs_group ON step_runs(execution_group_id)",
386
+ ),
387
+ )
388
+
389
+
390
+ M0009_FORK_ATOMICITY = Migration(
391
+ version=9,
392
+ name="fork_atomicity",
393
+ statements=(
394
+ # ADR-M2 Phase 2.8 — fork atomicity. A run starts by snapshotting the
395
+ # topology fork and committing a plan *as a pair*; either both land or
396
+ # neither. The staging table records the commit phase so a crash
397
+ # between "fork durable" and "plan committed" leaves a durable marker
398
+ # the next startup can reconcile (orphaned fork -> cleaned up).
399
+ """
400
+ CREATE TABLE run_fork_staging (
401
+ run_id TEXT PRIMARY KEY,
402
+ topo_id TEXT NOT NULL,
403
+ phase TEXT NOT NULL CHECK (phase IN ('planning','committed')),
404
+ created_at TEXT NOT NULL,
405
+ committed_at TEXT
406
+ )
407
+ """,
408
+ # Rebuild step_runs to admit the persisted `resource_conflict` status
409
+ # (ADR-M2 Phase 2.7).
410
+ """
411
+ CREATE TABLE step_runs_v9 (
412
+ id TEXT PRIMARY KEY,
413
+ run_id TEXT NOT NULL REFERENCES runs(id),
414
+ seq INTEGER NOT NULL,
415
+ parent_step_id TEXT REFERENCES step_runs(id),
416
+ action_type TEXT NOT NULL,
417
+ action_json TEXT NOT NULL,
418
+ status TEXT NOT NULL CHECK (status IN
419
+ ('pending','running','completed','failed','skipped','cancelled',
420
+ 'bypassed','target_drift','failed_to_apply','resource_conflict')),
421
+ started_at TEXT,
422
+ ended_at TEXT,
423
+ error TEXT,
424
+ runtime_identity TEXT,
425
+ execution_group_id TEXT,
426
+ group_mode TEXT,
427
+ group_path TEXT
428
+ )
429
+ """,
430
+ """
431
+ INSERT INTO step_runs_v9 (id, run_id, seq, parent_step_id, action_type,
432
+ action_json, status, started_at, ended_at, error, runtime_identity,
433
+ execution_group_id, group_mode, group_path)
434
+ SELECT id, run_id, seq, parent_step_id, action_type,
435
+ action_json, status, started_at, ended_at, error, runtime_identity,
436
+ execution_group_id, group_mode, group_path
437
+ FROM step_runs
438
+ """,
439
+ "DROP TABLE step_runs",
440
+ "ALTER TABLE step_runs_v9 RENAME TO step_runs",
441
+ "CREATE INDEX idx_step_runs_group ON step_runs(execution_group_id)",
442
+ ),
443
+ )
444
+
445
+
446
+ # M0010 — NetworkPath target columns (ADR-M3-7).
447
+ #
448
+ # A fault step may target a first-class NetworkPath rather than a bare
449
+ # container name. The structured fault is already serialized in action_json;
450
+ # these three *nullable* columns make the path target queryable without parsing
451
+ # the blob and provide a stable place for the fingerprint. They are added with
452
+ # ALTER TABLE ADD COLUMN (nullable, no default) so existing step_runs rows are
453
+ # untouched — no table rebuild, no CHECK evolution needed.
454
+ M0010_NETWORK_PATH = Migration(
455
+ version=10,
456
+ name="network_path",
457
+ statements=(
458
+ "ALTER TABLE step_runs ADD COLUMN network_path TEXT",
459
+ "ALTER TABLE step_runs ADD COLUMN path_namespace TEXT",
460
+ "ALTER TABLE step_runs ADD COLUMN path_interface TEXT",
461
+ "ALTER TABLE step_runs ADD COLUMN network_fingerprint TEXT",
462
+ ),
463
+ )
464
+
465
+
466
+ # ── M4-3/4-4/4-5: Success criteria verdict + observability (ADR-M4-3/4-4) ─
467
+ M0013_M4_SUCCESS_OBSERVABILITY = Migration(
468
+ version=13,
469
+ name="m4_success_observability",
470
+ statements=(
471
+ # A drill's machine-evaluable verdict (ADR-M4-3) and the raw criteria
472
+ # evaluation, persisted on the run row; both nullable — runs without
473
+ # declared criteria keep a NULL verdict and the existing status flow.
474
+ "ALTER TABLE runs ADD COLUMN verdict TEXT",
475
+ "ALTER TABLE runs ADD COLUMN criteria_json TEXT",
476
+ ),
477
+ down_statements=(
478
+ # ADR-M4-5: a DB at the M4 schema can be restored to the frozen baseline.
479
+ "ALTER TABLE runs DROP COLUMN criteria_json",
480
+ "ALTER TABLE runs DROP COLUMN verdict",
481
+ ),
482
+ )
483
+
484
+
485
+ # ── M4-4/4-1: Observability evidence + governing-decision trace (ADR-M4-4) ──
486
+ M0014_M4_OBSERVABILITY_AND_DECISIONS = Migration(
487
+ version=14,
488
+ name="m4_observability_decisions",
489
+ statements=(
490
+ # Collected observability evidence (ADR-M4-4) and the decision refs
491
+ # (ADR ids + decided-on timestamps, ADR-M4-1) that produced the run,
492
+ # both persisted on the run row; additive and nullable.
493
+ "ALTER TABLE runs ADD COLUMN observability_json TEXT",
494
+ "ALTER TABLE runs ADD COLUMN governing_decisions_json TEXT",
495
+ ),
496
+ down_statements=(
497
+ "ALTER TABLE runs DROP COLUMN governing_decisions_json",
498
+ "ALTER TABLE runs DROP COLUMN observability_json",
499
+ ),
500
+ )
501
+
502
+
503
+ # ── M5-1: Run and Outcome domain persistence (ADR-M5-1) ──────────────
504
+ M0011_M5_RUN_OUTCOME = Migration(
505
+ version=11,
506
+ name="m5_run_outcome",
507
+ statements=(
508
+ """
509
+ CREATE TABLE m5_runs (
510
+ id TEXT PRIMARY KEY,
511
+ experiment_name TEXT NOT NULL,
512
+ spec_json TEXT NOT NULL,
513
+ plan_json TEXT NOT NULL,
514
+ seed INTEGER,
515
+ status TEXT NOT NULL DEFAULT 'pending',
516
+ environment_fingerprint TEXT NOT NULL DEFAULT '',
517
+ config_snapshot_id TEXT NOT NULL DEFAULT '',
518
+ started_at TEXT NOT NULL DEFAULT '',
519
+ ended_at TEXT NOT NULL DEFAULT '',
520
+ description TEXT NOT NULL DEFAULT '',
521
+ verdict TEXT NOT NULL DEFAULT 'pass',
522
+ tags_json TEXT NOT NULL DEFAULT '[]',
523
+ extra_json TEXT NOT NULL DEFAULT '{}'
524
+ )
525
+ """,
526
+ "CREATE INDEX idx_m5_runs_status ON m5_runs(status)",
527
+ "CREATE INDEX idx_m5_runs_experiment ON m5_runs(experiment_name)",
528
+ """
529
+ CREATE TABLE m5_outcomes (
530
+ run_id TEXT PRIMARY KEY REFERENCES m5_runs(id),
531
+ body_json TEXT NOT NULL DEFAULT '{}',
532
+ body_hash TEXT NOT NULL DEFAULT '',
533
+ checks_passed INTEGER NOT NULL DEFAULT 0,
534
+ checks_failed INTEGER NOT NULL DEFAULT 0,
535
+ metric_deltas_json TEXT NOT NULL DEFAULT '{}',
536
+ residual_effect TEXT NOT NULL DEFAULT '',
537
+ stability_signal TEXT NOT NULL DEFAULT '',
538
+ extra_json TEXT NOT NULL DEFAULT '{}'
539
+ )
540
+ """,
541
+ ),
542
+ )
543
+
544
+
545
+ # ── M5-2: Coverage accounting (ADR-M5-3) ─────────────────────────────
546
+ M0012_M5_COVERAGE = Migration(
547
+ version=12,
548
+ name="m5_coverage",
549
+ statements=(
550
+ """
551
+ CREATE TABLE m5_coverage (
552
+ cell_key TEXT PRIMARY KEY,
553
+ target TEXT NOT NULL,
554
+ fault_kind TEXT NOT NULL,
555
+ execution_context TEXT NOT NULL,
556
+ parameter_band TEXT NOT NULL,
557
+ run_id TEXT NOT NULL,
558
+ covered INTEGER NOT NULL DEFAULT 1,
559
+ extra_json TEXT NOT NULL DEFAULT '{}'
560
+ )
561
+ """,
562
+ "CREATE INDEX idx_m5_coverage_target ON m5_coverage(target)",
563
+ "CREATE INDEX idx_m5_coverage_fault ON m5_coverage(fault_kind)",
564
+ ),
565
+ )
566
+
567
+
568
+ # ── Run liveness: the controller records its pid so the janitor can tell
569
+ # ── a dead owner from a live one and reclaim within-TTL leases it left.
570
+ # ── Without this, a crashed ``run`` wedges its targets until TTL (the
571
+ # ── reported "janitor did nothing" pain).
572
+ M0015_RUN_CONTROLLER_PID = Migration(
573
+ version=15,
574
+ name="run_controller_pid",
575
+ statements=("ALTER TABLE runs ADD COLUMN controller_pid INTEGER",),
576
+ down_statements=("ALTER TABLE runs DROP COLUMN controller_pid",),
577
+ )
578
+
579
+
580
+ ALL_MIGRATIONS: tuple[Migration, ...] = (
581
+ M0001_INITIAL,
582
+ M0002_LEASE_CONTEXT,
583
+ M0003_CAMPAIGNS_OBSERVATIONS,
584
+ M0004_DRILL_RUN_KIND,
585
+ M0005_STEP_RUN_BYPASS_STATUS,
586
+ M0006_RUNTIME_IDENTITY,
587
+ M0007_FAULT_GROUPS,
588
+ M0008_FAILED_TO_APPLY,
589
+ M0009_FORK_ATOMICITY,
590
+ M0010_NETWORK_PATH,
591
+ M0011_M5_RUN_OUTCOME,
592
+ M0012_M5_COVERAGE,
593
+ M0013_M4_SUCCESS_OBSERVABILITY,
594
+ M0014_M4_OBSERVABILITY_AND_DECISIONS,
595
+ M0015_RUN_CONTROLLER_PID,
596
+ )