@kirrosh/zond 0.26.1 → 0.27.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.
Files changed (261) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +39 -22
  3. package/bin/zond.mjs +23 -0
  4. package/package.json +36 -23
  5. package/scripts/npm/postinstall.mjs +76 -0
  6. package/src/CLAUDE.md +0 -112
  7. package/src/bun-types.d.ts +0 -5
  8. package/src/cli/argv.ts +0 -122
  9. package/src/cli/commands/add-api.ts +0 -146
  10. package/src/cli/commands/api/annotate/idempotency.ts +0 -59
  11. package/src/cli/commands/api/annotate/index.ts +0 -880
  12. package/src/cli/commands/api/annotate/lifecycle.ts +0 -74
  13. package/src/cli/commands/api/annotate/overlay.ts +0 -206
  14. package/src/cli/commands/api/annotate/pagination.ts +0 -64
  15. package/src/cli/commands/api/annotate/prompts.ts +0 -220
  16. package/src/cli/commands/api/annotate/readback.ts +0 -58
  17. package/src/cli/commands/api/annotate/resources.ts +0 -91
  18. package/src/cli/commands/api/annotate/seed-bodies.ts +0 -61
  19. package/src/cli/commands/audit.ts +0 -786
  20. package/src/cli/commands/catalog.ts +0 -97
  21. package/src/cli/commands/check.ts +0 -361
  22. package/src/cli/commands/checks.ts +0 -1072
  23. package/src/cli/commands/ci-init.ts +0 -223
  24. package/src/cli/commands/clean.ts +0 -212
  25. package/src/cli/commands/cleanup.ts +0 -236
  26. package/src/cli/commands/completions.ts +0 -192
  27. package/src/cli/commands/coverage.ts +0 -872
  28. package/src/cli/commands/db.ts +0 -611
  29. package/src/cli/commands/describe.ts +0 -120
  30. package/src/cli/commands/discover.ts +0 -1356
  31. package/src/cli/commands/doctor.ts +0 -661
  32. package/src/cli/commands/fixtures.ts +0 -402
  33. package/src/cli/commands/generate.ts +0 -577
  34. package/src/cli/commands/init/agents-md.ts +0 -61
  35. package/src/cli/commands/init/bootstrap.ts +0 -111
  36. package/src/cli/commands/init/index.ts +0 -244
  37. package/src/cli/commands/init/skills.ts +0 -141
  38. package/src/cli/commands/init/templates/agents.md +0 -86
  39. package/src/cli/commands/init/templates/markdown.d.ts +0 -4
  40. package/src/cli/commands/init/templates/skills/warm-up-target.md +0 -122
  41. package/src/cli/commands/init/templates/skills/zond-checks.md +0 -621
  42. package/src/cli/commands/init/templates/skills/zond-seed.md +0 -114
  43. package/src/cli/commands/init/templates/skills/zond-triage.md +0 -272
  44. package/src/cli/commands/init/templates/skills/zond.md +0 -861
  45. package/src/cli/commands/init/templates/zond-config.yml +0 -14
  46. package/src/cli/commands/prepare-fixtures.ts +0 -97
  47. package/src/cli/commands/probe/_seed-bodies.ts +0 -52
  48. package/src/cli/commands/probe/mass-assignment.ts +0 -594
  49. package/src/cli/commands/probe/security.ts +0 -537
  50. package/src/cli/commands/probe/static.ts +0 -255
  51. package/src/cli/commands/probe/webhooks.ts +0 -163
  52. package/src/cli/commands/probe.ts +0 -535
  53. package/src/cli/commands/reference.ts +0 -87
  54. package/src/cli/commands/refresh-api.ts +0 -227
  55. package/src/cli/commands/remove-api.ts +0 -150
  56. package/src/cli/commands/report-bundle.ts +0 -310
  57. package/src/cli/commands/report.ts +0 -241
  58. package/src/cli/commands/request.ts +0 -548
  59. package/src/cli/commands/run.ts +0 -1162
  60. package/src/cli/commands/schema-from-runs.ts +0 -128
  61. package/src/cli/commands/secrets.ts +0 -133
  62. package/src/cli/commands/session.ts +0 -244
  63. package/src/cli/commands/use.ts +0 -74
  64. package/src/cli/index.ts +0 -55
  65. package/src/cli/json-envelope.ts +0 -108
  66. package/src/cli/json-schemas.ts +0 -314
  67. package/src/cli/output.ts +0 -40
  68. package/src/cli/program.ts +0 -219
  69. package/src/cli/resolve.ts +0 -105
  70. package/src/cli/runtime.ts +0 -7
  71. package/src/cli/safe-live.ts +0 -24
  72. package/src/cli/status-filter.ts +0 -114
  73. package/src/cli/util/api-context.ts +0 -85
  74. package/src/cli/version.ts +0 -8
  75. package/src/core/audit/persist.ts +0 -183
  76. package/src/core/checks/budget.ts +0 -59
  77. package/src/core/checks/checks/_crud-helpers.ts +0 -133
  78. package/src/core/checks/checks/_negative_mutator.ts +0 -133
  79. package/src/core/checks/checks/_readback-helpers.ts +0 -133
  80. package/src/core/checks/checks/content_type_conformance.ts +0 -39
  81. package/src/core/checks/checks/cross_call_references.ts +0 -147
  82. package/src/core/checks/checks/cursor_boundary_fuzzing.ts +0 -219
  83. package/src/core/checks/checks/ensure_resource_availability.ts +0 -62
  84. package/src/core/checks/checks/idempotency_replay.ts +0 -242
  85. package/src/core/checks/checks/ignored_auth.ts +0 -254
  86. package/src/core/checks/checks/index.ts +0 -68
  87. package/src/core/checks/checks/lifecycle_transitions.ts +0 -416
  88. package/src/core/checks/checks/missing_required_header.ts +0 -40
  89. package/src/core/checks/checks/negative_data_rejection.ts +0 -148
  90. package/src/core/checks/checks/not_a_server_error.ts +0 -35
  91. package/src/core/checks/checks/open_cors_on_sensitive.ts +0 -160
  92. package/src/core/checks/checks/pagination_invariants.ts +0 -419
  93. package/src/core/checks/checks/positive_data_acceptance.ts +0 -33
  94. package/src/core/checks/checks/rate_limit_headers_absent.ts +0 -77
  95. package/src/core/checks/checks/response_headers_conformance.ts +0 -74
  96. package/src/core/checks/checks/response_schema_conformance.ts +0 -30
  97. package/src/core/checks/checks/status_code_conformance.ts +0 -132
  98. package/src/core/checks/checks/unsupported_method.ts +0 -63
  99. package/src/core/checks/checks/use_after_free.ts +0 -78
  100. package/src/core/checks/index.ts +0 -30
  101. package/src/core/checks/mode.ts +0 -82
  102. package/src/core/checks/recommended-action.ts +0 -68
  103. package/src/core/checks/registry.ts +0 -78
  104. package/src/core/checks/runner.ts +0 -1461
  105. package/src/core/checks/sarif.ts +0 -230
  106. package/src/core/checks/spec-findings.ts +0 -308
  107. package/src/core/checks/stateful.ts +0 -121
  108. package/src/core/checks/types.ts +0 -305
  109. package/src/core/checks/zond-extensions.ts +0 -73
  110. package/src/core/classifier/recommended-action.ts +0 -251
  111. package/src/core/context/current.ts +0 -51
  112. package/src/core/context/session.ts +0 -78
  113. package/src/core/coverage/loader.ts +0 -216
  114. package/src/core/coverage/reasons.ts +0 -300
  115. package/src/core/diagnostics/db-analysis.ts +0 -666
  116. package/src/core/diagnostics/failure-class.ts +0 -140
  117. package/src/core/diagnostics/failure-hints.ts +0 -112
  118. package/src/core/diagnostics/spec-pointer.ts +0 -99
  119. package/src/core/diagnostics/suggested-fixes.ts +0 -155
  120. package/src/core/exporter/case-study/index.ts +0 -270
  121. package/src/core/exporter/curl.ts +0 -40
  122. package/src/core/exporter/exporter.ts +0 -48
  123. package/src/core/exporter/html-report/escape.ts +0 -24
  124. package/src/core/exporter/html-report/index.ts +0 -479
  125. package/src/core/exporter/html-report/script.ts +0 -100
  126. package/src/core/exporter/html-report/styles.ts +0 -408
  127. package/src/core/generator/catalog-builder.ts +0 -179
  128. package/src/core/generator/chunker.ts +0 -78
  129. package/src/core/generator/coverage-phase.ts +0 -0
  130. package/src/core/generator/coverage-scanner.ts +0 -87
  131. package/src/core/generator/data-factory.ts +0 -759
  132. package/src/core/generator/describe.ts +0 -302
  133. package/src/core/generator/endpoint-warnings.ts +0 -52
  134. package/src/core/generator/fixtures-builder.ts +0 -332
  135. package/src/core/generator/index.ts +0 -14
  136. package/src/core/generator/openapi-reader.ts +0 -297
  137. package/src/core/generator/path-param-disambig.ts +0 -140
  138. package/src/core/generator/resources-builder.ts +0 -898
  139. package/src/core/generator/schema-utils.ts +0 -112
  140. package/src/core/generator/serializer.ts +0 -343
  141. package/src/core/generator/suite-generator.ts +0 -1301
  142. package/src/core/generator/types.ts +0 -63
  143. package/src/core/identity/identity-file.ts +0 -0
  144. package/src/core/lint/affects.ts +0 -28
  145. package/src/core/lint/config.ts +0 -96
  146. package/src/core/lint/format.ts +0 -42
  147. package/src/core/lint/index.ts +0 -94
  148. package/src/core/lint/reporter.ts +0 -128
  149. package/src/core/lint/rules/consistency.ts +0 -158
  150. package/src/core/lint/rules/heuristics.ts +0 -97
  151. package/src/core/lint/rules/strictness.ts +0 -109
  152. package/src/core/lint/types.ts +0 -96
  153. package/src/core/lint/walker.ts +0 -248
  154. package/src/core/meta/meta-store.ts +0 -11
  155. package/src/core/output/README.md +0 -73
  156. package/src/core/output/index.ts +0 -13
  157. package/src/core/output/run.ts +0 -91
  158. package/src/core/output/types.ts +0 -122
  159. package/src/core/parser/dynamic-values.ts +0 -160
  160. package/src/core/parser/env-interpolation.ts +0 -104
  161. package/src/core/parser/filter.ts +0 -97
  162. package/src/core/parser/schema.ts +0 -359
  163. package/src/core/parser/types.ts +0 -117
  164. package/src/core/parser/variables.ts +0 -0
  165. package/src/core/parser/yaml-parser.ts +0 -181
  166. package/src/core/probe/bootstrap.ts +0 -34
  167. package/src/core/probe/dry-run-envelope.ts +0 -61
  168. package/src/core/probe/mass-assignment/classify.ts +0 -175
  169. package/src/core/probe/mass-assignment/cleanup.ts +0 -52
  170. package/src/core/probe/mass-assignment/digest.ts +0 -114
  171. package/src/core/probe/mass-assignment/orchestrator.ts +0 -459
  172. package/src/core/probe/mass-assignment/regression.ts +0 -141
  173. package/src/core/probe/mass-assignment/suspects.ts +0 -92
  174. package/src/core/probe/mass-assignment/types.ts +0 -135
  175. package/src/core/probe/mass-assignment-probe-class.ts +0 -198
  176. package/src/core/probe/mass-assignment-probe.ts +0 -27
  177. package/src/core/probe/mass-assignment-template.ts +0 -240
  178. package/src/core/probe/method-probe.ts +0 -164
  179. package/src/core/probe/method-shared.ts +0 -69
  180. package/src/core/probe/negative-probe.ts +0 -691
  181. package/src/core/probe/orphan-tracker.ts +0 -188
  182. package/src/core/probe/path-discovery.ts +0 -439
  183. package/src/core/probe/probe-harness.ts +0 -119
  184. package/src/core/probe/registry.ts +0 -89
  185. package/src/core/probe/runner.ts +0 -136
  186. package/src/core/probe/security/baseline.ts +0 -174
  187. package/src/core/probe/security/classify.ts +0 -341
  188. package/src/core/probe/security/cleanup.ts +0 -125
  189. package/src/core/probe/security/detectors.ts +0 -71
  190. package/src/core/probe/security/digest.ts +0 -104
  191. package/src/core/probe/security/orchestrator.ts +0 -398
  192. package/src/core/probe/security/regression.ts +0 -103
  193. package/src/core/probe/security/types.ts +0 -151
  194. package/src/core/probe/security-probe-class.ts +0 -207
  195. package/src/core/probe/security-probe.ts +0 -32
  196. package/src/core/probe/shared.ts +0 -531
  197. package/src/core/probe/static-probe-class.ts +0 -125
  198. package/src/core/probe/types.ts +0 -165
  199. package/src/core/probe/verdict-aggregator.ts +0 -33
  200. package/src/core/probe/webhooks-probe.ts +0 -282
  201. package/src/core/reporter/console.ts +0 -240
  202. package/src/core/reporter/index.ts +0 -22
  203. package/src/core/reporter/json.ts +0 -22
  204. package/src/core/reporter/junit.ts +0 -93
  205. package/src/core/reporter/ndjson.ts +0 -37
  206. package/src/core/reporter/types.ts +0 -15
  207. package/src/core/runner/assertions.ts +0 -383
  208. package/src/core/runner/async-pool.ts +0 -108
  209. package/src/core/runner/auth-path.ts +0 -8
  210. package/src/core/runner/ci-context.ts +0 -72
  211. package/src/core/runner/executor.ts +0 -652
  212. package/src/core/runner/expr-eval.ts +0 -41
  213. package/src/core/runner/form-encode.ts +0 -41
  214. package/src/core/runner/http-client.ts +0 -224
  215. package/src/core/runner/learn-drift.ts +0 -293
  216. package/src/core/runner/preflight-vars.ts +0 -153
  217. package/src/core/runner/progress-tracker.ts +0 -73
  218. package/src/core/runner/rate-limiter.ts +0 -185
  219. package/src/core/runner/run-kind.ts +0 -45
  220. package/src/core/runner/schema-validator.ts +0 -308
  221. package/src/core/runner/send-request.ts +0 -232
  222. package/src/core/runner/transforms.ts +0 -65
  223. package/src/core/runner/types.ts +0 -94
  224. package/src/core/secrets/registry.ts +0 -164
  225. package/src/core/secrets/secrets-file.ts +0 -115
  226. package/src/core/selectors/operation-filter.ts +0 -144
  227. package/src/core/setup-api.ts +0 -590
  228. package/src/core/severity/category.ts +0 -94
  229. package/src/core/severity/index.ts +0 -58
  230. package/src/core/spec/infer-schema.ts +0 -102
  231. package/src/core/spec/layers.ts +0 -154
  232. package/src/core/spec/merge-specs.ts +0 -156
  233. package/src/core/spec/schema-from-runs.ts +0 -117
  234. package/src/core/spec/schema-overlay.ts +0 -130
  235. package/src/core/util/ajv.ts +0 -13
  236. package/src/core/util/format-eta.ts +0 -21
  237. package/src/core/util/headers.ts +0 -9
  238. package/src/core/util/url.ts +0 -24
  239. package/src/core/utils.ts +0 -13
  240. package/src/core/workspace/config.ts +0 -129
  241. package/src/core/workspace/fixture-gap-report.ts +0 -84
  242. package/src/core/workspace/fixture-gaps.ts +0 -71
  243. package/src/core/workspace/manifest.ts +0 -283
  244. package/src/core/workspace/output-rotation.ts +0 -62
  245. package/src/core/workspace/root.ts +0 -96
  246. package/src/core/workspace/triage-path.ts +0 -87
  247. package/src/db/lint-runs.ts +0 -47
  248. package/src/db/migrate.ts +0 -128
  249. package/src/db/migrations/0001_run_kind.sql +0 -25
  250. package/src/db/migrations/0002_run_kind_request.sql +0 -59
  251. package/src/db/migrations/sql.d.ts +0 -4
  252. package/src/db/queries/collections.ts +0 -133
  253. package/src/db/queries/coverage.ts +0 -9
  254. package/src/db/queries/dashboard.ts +0 -59
  255. package/src/db/queries/results.ts +0 -216
  256. package/src/db/queries/runs.ts +0 -289
  257. package/src/db/queries/sessions.ts +0 -42
  258. package/src/db/queries/settings.ts +0 -28
  259. package/src/db/queries/types.ts +0 -172
  260. package/src/db/queries.ts +0 -75
  261. package/src/db/schema.ts +0 -298
package/src/db/migrate.ts DELETED
@@ -1,128 +0,0 @@
1
- /**
2
- * ARV-127 (m-19): file-based SQLite migration runner.
3
- *
4
- * Why a new runner. The legacy migration path in `schema.ts`
5
- * (`runMigrations` + `PRAGMA user_version`) is fine for the additive
6
- * column changes shipped through v10, but the knowledge-base work
7
- * planned past m-19 will need richer migrations (multi-statement,
8
- * data backfills, optional rollback notes). Inlining those as `if
9
- * (ver >= N && ver < N+1)` blocks in TypeScript stops scaling once
10
- * each migration becomes a small project of its own.
11
- *
12
- * This module sits on top of the legacy path:
13
- * - `runMigrations()` is untouched — it owns the PRAGMA-version era
14
- * and keeps fresh DBs / older snapshots correct.
15
- * - `applyMigrations()` runs *after* `runMigrations()`, walks the
16
- * registered migration list, and applies anything not yet recorded
17
- * in `schema_migrations`. New work (v11+) lands as files; the
18
- * 0001_run_kind.sql file mirrors the most recent legacy migration
19
- * so the two systems agree on the post-v10 schema for fresh DBs.
20
- *
21
- * Existing-DB compatibility (AC#5). On a `.zond/zond.db` that already
22
- * survived the legacy `runMigrations` path (user_version >= 10), the
23
- * `run_kind` column already exists — re-running `0001_run_kind.sql`
24
- * would throw a `duplicate column` error. We seed the legacy ids into
25
- * `schema_migrations` once, on first contact with the new runner, so
26
- * those rows are treated as "already applied" without executing.
27
- *
28
- * Distribution. The SQL bodies are imported as embedded text so
29
- * `bun build --compile` packs them into the binary (no on-disk
30
- * lookup at runtime — same pattern as the init/templates skills).
31
- */
32
- import type { Database } from "bun:sqlite";
33
-
34
- import migration_0001_run_kind from "./migrations/0001_run_kind.sql" with { type: "text" };
35
- import migration_0002_run_kind_request from "./migrations/0002_run_kind_request.sql" with { type: "text" };
36
-
37
- /** Migration manifest. Each entry is a `{ id, sql }` pair; order in
38
- * this array is the apply order, matching the lexical sort that the
39
- * Django / Rails-style `<id>_<slug>.sql` convention would produce on
40
- * disk. Adding a new migration = add a text-import + push to this
41
- * list. The runner reads this constant, not the filesystem. */
42
- const MIGRATIONS: ReadonlyArray<{ id: string; sql: string }> = [
43
- { id: "0001_run_kind", sql: migration_0001_run_kind },
44
- { id: "0002_run_kind_request", sql: migration_0002_run_kind_request },
45
- ];
46
-
47
- /** Pre-existing migration ids that were already applied by the legacy
48
- * PRAGMA-version path. When the new runner first encounters a DB
49
- * whose `user_version >= 10`, we record these as applied without
50
- * running them — the inline `runMigrations` already did. */
51
- const LEGACY_SEED_IDS: ReadonlyArray<{ id: string; minUserVersion: number }> = [
52
- { id: "0001_run_kind", minUserVersion: 10 },
53
- ];
54
-
55
- function currentUserVersion(db: Database): number {
56
- const row = db.query("PRAGMA user_version").get() as
57
- | { user_version: number }
58
- | undefined;
59
- return row?.user_version ?? 0;
60
- }
61
-
62
- /**
63
- * Idempotently apply every pending migration. Safe to call on every
64
- * DB open — the registry table makes the no-op case cheap.
65
- *
66
- * Failure semantics: each migration runs in its own transaction. A
67
- * script that throws (bad SQL, constraint violation) rolls its own
68
- * statements back and re-raises; later migrations don't run. The
69
- * caller (DB open path) treats this as fatal — there is no partial
70
- * upgrade.
71
- *
72
- * The optional `overrides` parameter lets tests inject a synthetic
73
- * migration list (e.g. to exercise a migration order or a failing
74
- * script) without touching the shipped manifest.
75
- */
76
- export function applyMigrations(
77
- db: Database,
78
- overrides?: { migrations?: ReadonlyArray<{ id: string; sql: string }>; legacySeed?: ReadonlyArray<{ id: string; minUserVersion: number }> },
79
- ): { applied: string[]; skipped: string[] } {
80
- const migrations = overrides?.migrations ?? MIGRATIONS;
81
- const legacySeed = overrides?.legacySeed ?? LEGACY_SEED_IDS;
82
-
83
- db.exec(`
84
- CREATE TABLE IF NOT EXISTS schema_migrations (
85
- id TEXT PRIMARY KEY,
86
- applied_at TEXT NOT NULL DEFAULT (datetime('now'))
87
- )
88
- `);
89
-
90
- // Legacy seed: mark already-applied-by-the-PRAGMA-runner ids as done.
91
- const userVersion = currentUserVersion(db);
92
- const insertSeed = db.prepare(
93
- "INSERT OR IGNORE INTO schema_migrations (id) VALUES (?)",
94
- );
95
- for (const seed of legacySeed) {
96
- if (userVersion >= seed.minUserVersion) {
97
- insertSeed.run(seed.id);
98
- }
99
- }
100
-
101
- const appliedRows = db
102
- .query("SELECT id FROM schema_migrations")
103
- .all() as Array<{ id: string }>;
104
- const alreadyApplied = new Set(appliedRows.map((r) => r.id));
105
-
106
- const applied: string[] = [];
107
- const skipped: string[] = [];
108
-
109
- for (const migration of migrations) {
110
- if (alreadyApplied.has(migration.id)) {
111
- skipped.push(migration.id);
112
- continue;
113
- }
114
- db.transaction(() => {
115
- db.exec(migration.sql);
116
- db.prepare("INSERT INTO schema_migrations (id) VALUES (?)").run(migration.id);
117
- })();
118
- applied.push(migration.id);
119
- }
120
-
121
- return { applied, skipped };
122
- }
123
-
124
- /** Exported for tests + downstream tooling that wants to know which
125
- * migration ids ship with the binary. */
126
- export function listShippedMigrations(): string[] {
127
- return MIGRATIONS.map((m) => m.id);
128
- }
@@ -1,25 +0,0 @@
1
- -- ARV-127 (m-19): captures the legacy v9→v10 inline migration as the
2
- -- first file-based migration of the new runner. Mirrors the SQL block
3
- -- previously written in src/db/schema.ts `runMigrations()`. Existing
4
- -- `.zond/zond.db` files that already ran the inline migration are
5
- -- pre-seeded as "applied" by `applyMigrations`, so this script never
6
- -- re-executes the ALTER on a DB where `run_kind` already exists.
7
- --
8
- -- Source: ARV-55 — classify each historical run by suite kind so the
9
- -- coverage default query becomes a column compare.
10
-
11
- ALTER TABLE runs ADD COLUMN run_kind TEXT NOT NULL DEFAULT 'regular';
12
-
13
- UPDATE runs SET run_kind = 'probe'
14
- WHERE id IN (
15
- SELECT r.id FROM runs r
16
- WHERE EXISTS (SELECT 1 FROM results WHERE run_id = r.id AND suite_file IS NOT NULL AND suite_file LIKE '%probes/%')
17
- AND NOT EXISTS (SELECT 1 FROM results WHERE run_id = r.id AND suite_file IS NOT NULL AND suite_file NOT LIKE '%probes/%')
18
- );
19
-
20
- UPDATE runs SET run_kind = 'check'
21
- WHERE id IN (
22
- SELECT r.id FROM runs r
23
- WHERE EXISTS (SELECT 1 FROM results WHERE run_id = r.id AND suite_file IS NOT NULL AND suite_file LIKE '%checks/%')
24
- AND NOT EXISTS (SELECT 1 FROM results WHERE run_id = r.id AND suite_file IS NOT NULL AND suite_file NOT LIKE '%checks/%')
25
- );
@@ -1,59 +0,0 @@
1
- -- ARV-265 (m-22): extend `runs.run_kind` CHECK constraint to include
2
- -- 'request' (zond request in-session) and 'fixture' (prepare-fixtures
3
- -- cascade list-calls), so audit-coverage can attribute HTTP touches
4
- -- back to those producers.
5
- --
6
- -- The CHECK constraint can't be added with ALTER TABLE in SQLite. Bun's
7
- -- bun:sqlite also locks `sqlite_master` against direct UPDATEs, so the
8
- -- usual `PRAGMA writable_schema=1` rewrite is rejected. We use the
9
- -- canonical SQLite 12-step rebuild: create a parallel table with the
10
- -- new constraint, copy rows over, drop the old, rename.
11
- --
12
- -- The migration runner wraps this in a transaction. FK references from
13
- -- `results.run_id` to `runs(id)` survive the DROP+RENAME because SQLite
14
- -- resolves FK targets by name at row-modification time, not at table
15
- -- definition time — when the new `runs` ends up with the same name the
16
- -- soft reference is reattached transparently. Indexes are explicitly
17
- -- recreated post-rename (they don't survive a DROP).
18
-
19
- CREATE TABLE runs_new (
20
- id INTEGER PRIMARY KEY AUTOINCREMENT,
21
- started_at TEXT NOT NULL,
22
- finished_at TEXT,
23
- total INTEGER NOT NULL DEFAULT 0,
24
- passed INTEGER NOT NULL DEFAULT 0,
25
- failed INTEGER NOT NULL DEFAULT 0,
26
- skipped INTEGER NOT NULL DEFAULT 0,
27
- trigger TEXT DEFAULT 'manual',
28
- commit_sha TEXT,
29
- branch TEXT,
30
- environment TEXT,
31
- duration_ms INTEGER,
32
- collection_id INTEGER REFERENCES collections(id),
33
- session_id TEXT,
34
- tags TEXT,
35
- run_kind TEXT NOT NULL DEFAULT 'regular'
36
- CHECK (run_kind IN ('regular','probe','check','request','fixture'))
37
- );
38
-
39
- INSERT INTO runs_new (
40
- id, started_at, finished_at, total, passed, failed, skipped, trigger,
41
- commit_sha, branch, environment, duration_ms, collection_id, session_id,
42
- tags, run_kind
43
- )
44
- SELECT
45
- id, started_at, finished_at, total, passed, failed, skipped, trigger,
46
- commit_sha, branch, environment, duration_ms, collection_id, session_id,
47
- tags,
48
- CASE
49
- WHEN run_kind IN ('regular','probe','check','request','fixture') THEN run_kind
50
- ELSE 'regular'
51
- END
52
- FROM runs;
53
-
54
- DROP TABLE runs;
55
- ALTER TABLE runs_new RENAME TO runs;
56
-
57
- CREATE INDEX IF NOT EXISTS idx_runs_started ON runs(started_at DESC);
58
- CREATE INDEX IF NOT EXISTS idx_runs_collection ON runs(collection_id);
59
- CREATE INDEX IF NOT EXISTS idx_runs_session ON runs(session_id, started_at DESC);
@@ -1,4 +0,0 @@
1
- declare module "*.sql" {
2
- const content: string;
3
- export default content;
4
- }
@@ -1,133 +0,0 @@
1
- import { getDb } from "../schema.ts";
2
- import {
3
- normalizePath,
4
- type CollectionRecord,
5
- type CollectionSummary,
6
- type CreateCollectionOpts,
7
- type RunRecord,
8
- } from "./types.ts";
9
-
10
- export function createCollection(opts: CreateCollectionOpts): number {
11
- const db = getDb();
12
- const stmt = db.prepare(`
13
- INSERT INTO collections (name, base_dir, test_path, openapi_spec)
14
- VALUES ($name, $base_dir, $test_path, $openapi_spec)
15
- `);
16
- const result = stmt.run({
17
- $name: opts.name,
18
- $base_dir: opts.base_dir ?? null,
19
- $test_path: opts.test_path,
20
- $openapi_spec: opts.openapi_spec ?? null,
21
- });
22
- return Number(result.lastInsertRowid);
23
- }
24
-
25
- export function getCollectionById(id: number): CollectionRecord | null {
26
- const db = getDb();
27
- return db.query("SELECT * FROM collections WHERE id = ?").get(id) as CollectionRecord | null;
28
- }
29
-
30
- export function getLatestRunByCollection(
31
- collectionId: number,
32
- opts: { runKind?: import("../../core/runner/run-kind.ts").RunKind | "any" } = {},
33
- ): RunRecord | null {
34
- const db = getDb();
35
- // ARV-55: 'regular' is the default so coverage skips probe-only runs
36
- // without an explicit predicate. 'any' opts back into the legacy
37
- // behaviour (used by `coverage`'s probe-run hint logic).
38
- const kind = opts.runKind ?? "regular";
39
- const kindClause = kind === "any" ? "" : "AND run_kind = ?";
40
- const params: (string | number)[] = [collectionId];
41
- if (kind !== "any") params.push(kind);
42
- const row = db.query(`
43
- SELECT * FROM runs
44
- WHERE collection_id = ? AND finished_at IS NOT NULL ${kindClause}
45
- ORDER BY started_at DESC
46
- LIMIT 1
47
- `).get(...params) as (Record<string, unknown> & { tags?: unknown }) | null;
48
- if (!row) return null;
49
- let tags: string[] | null = null;
50
- if (typeof row.tags === "string") {
51
- try {
52
- const v = JSON.parse(row.tags);
53
- if (Array.isArray(v) && v.every((x) => typeof x === "string")) tags = v;
54
- } catch {
55
- // legacy/corrupt — leave null
56
- }
57
- }
58
- // ARV-55: normalise run_kind alongside tags so RunRecord stays consistent.
59
- const rk = row.run_kind;
60
- const run_kind: import("../../core/runner/run-kind.ts").RunKind =
61
- rk === "probe" || rk === "check" || rk === "request" || rk === "fixture" ? rk : "regular";
62
- return { ...(row as unknown as RunRecord), tags, run_kind };
63
- }
64
-
65
- export function listCollections(): CollectionSummary[] {
66
- const db = getDb();
67
- return db.query(`
68
- SELECT
69
- c.id, c.name, c.base_dir, c.test_path, c.openapi_spec, c.created_at,
70
- COUNT(r.id) AS total_runs,
71
- CASE WHEN SUM(r.total) > 0
72
- THEN ROUND(SUM(r.passed) * 100.0 / SUM(r.total), 1)
73
- ELSE 0 END AS pass_rate,
74
- MAX(r.started_at) AS last_run_at,
75
- COALESCE((SELECT passed FROM runs WHERE collection_id = c.id ORDER BY started_at DESC LIMIT 1), 0) AS last_run_passed,
76
- COALESCE((SELECT failed FROM runs WHERE collection_id = c.id ORDER BY started_at DESC LIMIT 1), 0) AS last_run_failed,
77
- COALESCE((SELECT total FROM runs WHERE collection_id = c.id ORDER BY started_at DESC LIMIT 1), 0) AS last_run_total
78
- FROM collections c
79
- LEFT JOIN runs r ON r.collection_id = c.id AND r.finished_at IS NOT NULL
80
- GROUP BY c.id
81
- ORDER BY c.name
82
- `).all() as CollectionSummary[];
83
- }
84
-
85
- export function updateCollection(id: number, opts: Partial<CreateCollectionOpts>): boolean {
86
- const db = getDb();
87
- const sets: string[] = [];
88
- const params: Record<string, any> = { $id: id };
89
-
90
- if (opts.name !== undefined) { sets.push("name = $name"); params.$name = opts.name; }
91
- if (opts.base_dir !== undefined) { sets.push("base_dir = $base_dir"); params.$base_dir = opts.base_dir; }
92
- if (opts.test_path !== undefined) { sets.push("test_path = $test_path"); params.$test_path = opts.test_path; }
93
- if (opts.openapi_spec !== undefined) { sets.push("openapi_spec = $openapi_spec"); params.$openapi_spec = opts.openapi_spec; }
94
-
95
- if (sets.length === 0) return false;
96
-
97
- const result = db.prepare(`UPDATE collections SET ${sets.join(", ")} WHERE id = $id`).run(params);
98
- return result.changes > 0;
99
- }
100
-
101
- export function deleteCollection(id: number, deleteRuns = false): boolean {
102
- const db = getDb();
103
- if (deleteRuns) {
104
- const runIds = db.query("SELECT id FROM runs WHERE collection_id = ?").all(id) as { id: number }[];
105
- for (const row of runIds) {
106
- db.prepare("DELETE FROM results WHERE run_id = ?").run(row.id);
107
- }
108
- db.prepare("DELETE FROM runs WHERE collection_id = ?").run(id);
109
- } else {
110
- db.prepare("UPDATE runs SET collection_id = NULL WHERE collection_id = ?").run(id);
111
- }
112
- const result = db.prepare("DELETE FROM collections WHERE id = ?").run(id);
113
- return result.changes > 0;
114
- }
115
-
116
- export function findCollectionByTestPath(path: string): CollectionRecord | null {
117
- const db = getDb();
118
- const normalized = normalizePath(path);
119
- return db.query("SELECT * FROM collections WHERE test_path = ?").get(normalized) as CollectionRecord | null;
120
- }
121
-
122
- export function findCollectionByNameOrId(nameOrId: string): CollectionRecord | null {
123
- const db = getDb();
124
- // Try as numeric ID first
125
- const id = parseInt(nameOrId, 10);
126
- if (!isNaN(id)) {
127
- const byId = db.query("SELECT * FROM collections WHERE id = ?").get(id) as CollectionRecord | null;
128
- if (byId) return byId;
129
- }
130
- // Then by name (case-insensitive)
131
- return db.query("SELECT * FROM collections WHERE lower(name) = lower(?)").get(nameOrId) as CollectionRecord | null;
132
- }
133
-
@@ -1,9 +0,0 @@
1
- /**
2
- * Reserved for coverage-domain DB queries. As of TASK-187, coverage
3
- * tables (`coverage_runs`, etc.) are still managed entirely from
4
- * `src/core/coverage/`; nothing in `queries.ts` was coverage-specific to
5
- * move here. The file exists so the per-domain layout is future-proof
6
- * — when a coverage table-backed feature lands, queries land here and
7
- * the cli/UI imports stay stable.
8
- */
9
- export {};
@@ -1,59 +0,0 @@
1
- import { getDb } from "../schema.ts";
2
- import type {
3
- DashboardStats,
4
- PassRateTrendPoint,
5
- SlowestTest,
6
- FlakyTest,
7
- } from "./types.ts";
8
-
9
- export function getDashboardStats(): DashboardStats {
10
- const db = getDb();
11
- const row = db.query(`
12
- SELECT
13
- COUNT(*) AS totalRuns,
14
- COALESCE(SUM(total), 0) AS totalTests,
15
- CASE WHEN SUM(total) > 0
16
- THEN ROUND(SUM(passed) * 100.0 / SUM(total), 1)
17
- ELSE 0 END AS overallPassRate,
18
- COALESCE(ROUND(AVG(duration_ms), 0), 0) AS avgDuration
19
- FROM runs
20
- WHERE finished_at IS NOT NULL
21
- `).get() as { totalRuns: number; totalTests: number; overallPassRate: number; avgDuration: number };
22
- return row;
23
- }
24
-
25
- export function getPassRateTrend(limit = 30): PassRateTrendPoint[] {
26
- const db = getDb();
27
- return db.query(`
28
- SELECT id AS run_id, started_at,
29
- CASE WHEN total > 0 THEN ROUND(passed * 100.0 / total, 1) ELSE 0 END AS pass_rate
30
- FROM runs
31
- WHERE finished_at IS NOT NULL
32
- ORDER BY started_at DESC
33
- LIMIT ?
34
- `).all(limit) as PassRateTrendPoint[];
35
- }
36
-
37
- export function getSlowestTests(limit = 5): SlowestTest[] {
38
- const db = getDb();
39
- return db.query(`
40
- SELECT suite_name, test_name, ROUND(AVG(duration_ms), 0) AS avg_duration
41
- FROM results
42
- GROUP BY suite_name, test_name
43
- ORDER BY avg_duration DESC
44
- LIMIT ?
45
- `).all(limit) as SlowestTest[];
46
- }
47
-
48
- export function getFlakyTests(runsBack = 20, limit = 5): FlakyTest[] {
49
- const db = getDb();
50
- return db.query(`
51
- SELECT r.suite_name, r.test_name, COUNT(DISTINCT r.status) AS distinct_statuses
52
- FROM results r
53
- INNER JOIN (SELECT id FROM runs ORDER BY started_at DESC LIMIT ?) recent ON r.run_id = recent.id
54
- GROUP BY r.suite_name, r.test_name
55
- HAVING COUNT(DISTINCT r.status) > 1
56
- ORDER BY distinct_statuses DESC
57
- LIMIT ?
58
- `).all(runsBack, limit) as FlakyTest[];
59
- }
@@ -1,216 +0,0 @@
1
- import { getDb, withDbRetry } from "../schema.ts";
2
- import type { TestRunResult } from "../../core/runner/types.ts";
3
- import { getSecretRegistry } from "../../core/secrets/registry.ts";
4
- import type { StoredStepResult } from "./types.ts";
5
-
6
- function parseProvenance(raw: unknown): import("../../core/parser/types.ts").SourceMetadata | null {
7
- if (typeof raw !== "string" || raw.length === 0) return null;
8
- try {
9
- const parsed = JSON.parse(raw);
10
- return parsed && typeof parsed === "object" ? parsed : null;
11
- } catch {
12
- return null;
13
- }
14
- }
15
-
16
- export function saveResults(runId: number, suiteResults: TestRunResult[]): void {
17
- const db = getDb();
18
-
19
- const stmt = db.prepare(`
20
- INSERT INTO results
21
- (run_id, suite_name, test_name, status, duration_ms,
22
- request_method, request_url, request_body,
23
- response_status, response_body, response_headers, error_message, assertions, captures, suite_file, provenance, failure_class, failure_class_reason, spec_pointer, spec_excerpt)
24
- VALUES
25
- ($run_id, $suite_name, $test_name, $status, $duration_ms,
26
- $request_method, $request_url, $request_body,
27
- $response_status, $response_body, $response_headers, $error_message, $assertions, $captures, $suite_file, $provenance, $failure_class, $failure_class_reason, $spec_pointer, $spec_excerpt)
28
- `);
29
-
30
- // TASK-167 (m-10): every string field that can carry a leaked secret
31
- // (URL with token in query, body echo on 401, Set-Cookie header, etc.)
32
- // goes through the registry sanitizer before INSERT.
33
- const reg = getSecretRegistry();
34
- const redactString = (s: string | null | undefined): string | null =>
35
- s == null ? null : reg.redact(s);
36
- const redactJson = (v: unknown): string | null => {
37
- if (v == null) return null;
38
- if (typeof v === "string") return reg.redact(v);
39
- return reg.redact(JSON.stringify(v));
40
- };
41
-
42
- withDbRetry("saveResults", () => db.transaction(() => {
43
- for (const suite of suiteResults) {
44
- for (const step of suite.steps) {
45
- const maxBodySize = 50_000;
46
- const truncBody = (s: string | null | undefined) =>
47
- s && s.length > maxBodySize ? s.slice(0, maxBodySize) + "\n...[truncated]" : (s ?? null);
48
- stmt.run({
49
- $run_id: runId,
50
- $suite_name: suite.suite_name,
51
- $test_name: step.name,
52
- $status: step.status,
53
- $duration_ms: step.duration_ms,
54
- $request_method: step.request.method,
55
- $request_url: redactString(step.request.url),
56
- $request_body: redactString(truncBody(step.request.body)),
57
- $response_status: step.response?.status ?? null,
58
- $response_body: redactString(truncBody(step.response?.body)),
59
- $response_headers: step.response?.headers
60
- ? redactJson(step.response.headers)
61
- : null,
62
- $error_message: redactString(step.error ?? null),
63
- $assertions: step.assertions.length > 0 ? redactJson(step.assertions) : null,
64
- $captures: Object.keys(step.captures).length > 0 ? redactJson(step.captures) : null,
65
- $suite_file: suite.suite_file ?? null,
66
- $provenance: step.provenance ? JSON.stringify(step.provenance) : null,
67
- $failure_class: step.failure_class ?? null,
68
- $failure_class_reason: step.failure_class_reason ?? null,
69
- $spec_pointer: step.spec_pointer ?? null,
70
- $spec_excerpt: redactString(step.spec_excerpt ?? null),
71
- });
72
- }
73
- }
74
- })());
75
- }
76
-
77
- export function getResultsByRunId(runId: number): StoredStepResult[] {
78
- const db = getDb();
79
- const rows = db.query("SELECT * FROM results WHERE run_id = ? ORDER BY id").all(runId) as Array<
80
- Omit<StoredStepResult, "assertions" | "captures" | "provenance"> & {
81
- assertions: string | null;
82
- captures: string | null;
83
- provenance: string | null;
84
- }
85
- >;
86
- return rows.map((row) => ({
87
- ...row,
88
- assertions: row.assertions ? JSON.parse(row.assertions) : [],
89
- captures: row.captures ? JSON.parse(row.captures) : {},
90
- provenance: parseProvenance(row.provenance),
91
- }));
92
- }
93
-
94
- /**
95
- * Row shape for the fixture-kind POST history matched by
96
- * `getRecentFixturePosts`'s SQL LIKE pattern (typically the
97
- * create-endpoint URL with `{var}` path params replaced by `%`).
98
- */
99
- export interface LastFixtureAttempt {
100
- request_method: string;
101
- request_url: string;
102
- request_body: string | null;
103
- response_status: number | null;
104
- response_body: string | null;
105
- attempted_at: string;
106
- }
107
-
108
- /**
109
- * ARV-278: return the most recent N fixture-POST attempts (most recent
110
- * first). Powers `dump --with-last-attempt --history N` so the agent
111
- * sees the progression of errors as the overlay was iterated — e.g.
112
- * "first 400 said missing X, after fixing the body the next 400 said
113
- * resource_missing customer" surfaces the cascade-staleness issue (see
114
- * ARV-282) one level earlier than a single-snapshot view.
115
- */
116
- export function getRecentFixturePosts(
117
- urlLikePattern: string,
118
- limit: number,
119
- ): LastFixtureAttempt[] {
120
- if (!Number.isFinite(limit) || limit <= 0) return [];
121
- const db = getDb();
122
- const rows = db.query(`
123
- SELECT
124
- r.request_method AS request_method,
125
- r.request_url AS request_url,
126
- r.request_body AS request_body,
127
- r.response_status AS response_status,
128
- r.response_body AS response_body,
129
- ru.started_at AS attempted_at
130
- FROM results r
131
- JOIN runs ru ON r.run_id = ru.id
132
- WHERE ru.run_kind = 'fixture'
133
- AND r.request_method = 'POST'
134
- AND r.request_url LIKE ?
135
- ORDER BY ru.started_at DESC, r.id DESC
136
- LIMIT ?
137
- `).all(urlLikePattern, Math.floor(limit)) as LastFixtureAttempt[];
138
- return rows;
139
- }
140
-
141
- /**
142
- * ARV-330: like `getRecentFixturePosts` but across ALL run kinds
143
- * (`check`/`probe`/`run`/`request`/`fixture`). A root resource with no
144
- * seed_body is `skip-no-create` by prepare-fixtures, so it never records
145
- * a fixture-kind POST — yet a depth-check or probe may have POSTed the
146
- * same create-path and captured the account-level capability error. The
147
- * hard-blocked classifier reads this wider source so it can still
148
- * diagnose the gate. Caller is responsible for filtering auth-probe
149
- * noise (deliberately-broken-cred 401/403s).
150
- */
151
- export function getRecentCreatePosts(
152
- urlLikePattern: string,
153
- limit: number,
154
- excludeLikePattern?: string,
155
- ): LastFixtureAttempt[] {
156
- if (!Number.isFinite(limit) || limit <= 0) return [];
157
- const db = getDb();
158
- // `excludeLikePattern` filters out child sub-resource POSTs in SQL (e.g.
159
- // `%/v1/accounts/%` drops `/v1/accounts/{id}/reject`); without it the
160
- // loose trailing wildcard lets a burst of probe sub-calls monopolize the
161
- // LIMIT window and starve the real create-path attempts (ARV-330).
162
- const rows = excludeLikePattern
163
- ? db.query(`
164
- SELECT r.request_method AS request_method, r.request_url AS request_url,
165
- r.request_body AS request_body, r.response_status AS response_status,
166
- r.response_body AS response_body, ru.started_at AS attempted_at
167
- FROM results r JOIN runs ru ON r.run_id = ru.id
168
- WHERE r.request_method = 'POST' AND r.request_url LIKE ? AND r.request_url NOT LIKE ?
169
- ORDER BY ru.started_at DESC, r.id DESC LIMIT ?
170
- `).all(urlLikePattern, excludeLikePattern, Math.floor(limit))
171
- : db.query(`
172
- SELECT r.request_method AS request_method, r.request_url AS request_url,
173
- r.request_body AS request_body, r.response_status AS response_status,
174
- r.response_body AS response_body, ru.started_at AS attempted_at
175
- FROM results r JOIN runs ru ON r.run_id = ru.id
176
- WHERE r.request_method = 'POST' AND r.request_url LIKE ?
177
- ORDER BY ru.started_at DESC, r.id DESC LIMIT ?
178
- `).all(urlLikePattern, Math.floor(limit));
179
- return rows as LastFixtureAttempt[];
180
- }
181
-
182
- export function getFilteredResults(
183
- runId: number,
184
- filters: {
185
- method?: string;
186
- /** Compiled SQL fragment for the `--status` filter (TASK-140). */
187
- statusSql?: { sql: string; params: number[] };
188
- },
189
- ): StoredStepResult[] {
190
- const db = getDb();
191
- const conditions = ["run_id = ?"];
192
- const params: (string | number)[] = [runId];
193
-
194
- if (filters.method) {
195
- conditions.push("request_method = ?");
196
- params.push(filters.method.toUpperCase());
197
- }
198
- if (filters.statusSql) {
199
- conditions.push(filters.statusSql.sql);
200
- params.push(...filters.statusSql.params);
201
- }
202
-
203
- const rows = db.query(`SELECT * FROM results WHERE ${conditions.join(" AND ")} ORDER BY id`).all(...params) as Array<
204
- Omit<StoredStepResult, "assertions" | "captures" | "provenance"> & {
205
- assertions: string | null;
206
- captures: string | null;
207
- provenance: string | null;
208
- }
209
- >;
210
- return rows.map((row) => ({
211
- ...row,
212
- assertions: row.assertions ? JSON.parse(row.assertions) : [],
213
- captures: row.captures ? JSON.parse(row.captures) : {},
214
- provenance: parseProvenance(row.provenance),
215
- }));
216
- }