@kirrosh/zond 0.22.0 → 0.26.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 (282) hide show
  1. package/CHANGELOG.md +811 -0
  2. package/README.md +59 -6
  3. package/package.json +9 -7
  4. package/src/CLAUDE.md +112 -0
  5. package/src/cli/argv.ts +122 -0
  6. package/src/cli/commands/add-api.ts +146 -0
  7. package/src/cli/commands/api/annotate/idempotency.ts +59 -0
  8. package/src/cli/commands/api/annotate/index.ts +880 -0
  9. package/src/cli/commands/api/annotate/lifecycle.ts +74 -0
  10. package/src/cli/commands/api/annotate/overlay.ts +206 -0
  11. package/src/cli/commands/api/annotate/pagination.ts +64 -0
  12. package/src/cli/commands/api/annotate/prompts.ts +220 -0
  13. package/src/cli/commands/api/annotate/readback.ts +58 -0
  14. package/src/cli/commands/api/annotate/resources.ts +91 -0
  15. package/src/cli/commands/api/annotate/seed-bodies.ts +61 -0
  16. package/src/cli/commands/audit.ts +786 -0
  17. package/src/cli/commands/catalog.ts +35 -0
  18. package/src/cli/commands/check.ts +361 -0
  19. package/src/cli/commands/checks.ts +1072 -0
  20. package/src/cli/commands/ci-init.ts +43 -0
  21. package/src/cli/commands/clean.ts +212 -0
  22. package/src/cli/commands/cleanup.ts +236 -0
  23. package/src/cli/commands/completions.ts +16 -0
  24. package/src/cli/commands/coverage.ts +823 -132
  25. package/src/cli/commands/db.ts +486 -12
  26. package/src/cli/commands/describe.ts +37 -2
  27. package/src/cli/commands/discover.ts +1356 -0
  28. package/src/cli/commands/doctor.ts +661 -0
  29. package/src/cli/commands/fixtures.ts +402 -0
  30. package/src/cli/commands/generate.ts +438 -47
  31. package/src/cli/commands/init/bootstrap.ts +34 -2
  32. package/src/cli/commands/{init.ts → init/index.ts} +99 -5
  33. package/src/cli/commands/init/skills.ts +99 -3
  34. package/src/cli/commands/init/templates/agents.md +77 -64
  35. package/src/cli/commands/init/templates/skills/warm-up-target.md +122 -0
  36. package/src/cli/commands/init/templates/skills/zond-checks.md +621 -0
  37. package/src/cli/commands/init/templates/skills/zond-seed.md +114 -0
  38. package/src/cli/commands/init/templates/skills/zond-triage.md +272 -0
  39. package/src/cli/commands/init/templates/skills/zond.md +802 -125
  40. package/src/cli/commands/init/templates/zond-config.yml +8 -9
  41. package/src/cli/commands/prepare-fixtures.ts +97 -0
  42. package/src/cli/commands/probe/_seed-bodies.ts +52 -0
  43. package/src/cli/commands/probe/mass-assignment.ts +594 -0
  44. package/src/cli/commands/probe/security.ts +537 -0
  45. package/src/cli/commands/probe/static.ts +255 -0
  46. package/src/cli/commands/probe/webhooks.ts +163 -0
  47. package/src/cli/commands/probe.ts +535 -0
  48. package/src/cli/commands/reference.ts +87 -0
  49. package/src/cli/commands/refresh-api.ts +227 -0
  50. package/src/cli/commands/remove-api.ts +150 -0
  51. package/src/cli/commands/report-bundle.ts +310 -0
  52. package/src/cli/commands/report.ts +241 -0
  53. package/src/cli/commands/request.ts +495 -4
  54. package/src/cli/commands/run.ts +870 -53
  55. package/src/cli/commands/schema-from-runs.ts +128 -0
  56. package/src/cli/commands/secrets.ts +133 -0
  57. package/src/cli/commands/session.ts +244 -0
  58. package/src/cli/commands/use.ts +18 -1
  59. package/src/cli/index.ts +20 -3
  60. package/src/cli/json-envelope.ts +92 -3
  61. package/src/cli/json-schemas.ts +314 -0
  62. package/src/cli/output.ts +17 -1
  63. package/src/cli/program.ts +199 -635
  64. package/src/cli/resolve.ts +105 -0
  65. package/src/cli/safe-live.ts +24 -0
  66. package/src/cli/status-filter.ts +114 -0
  67. package/src/cli/util/api-context.ts +85 -0
  68. package/src/cli/version.ts +5 -0
  69. package/src/core/audit/persist.ts +183 -0
  70. package/src/core/checks/budget.ts +59 -0
  71. package/src/core/checks/checks/_crud-helpers.ts +133 -0
  72. package/src/core/checks/checks/_negative_mutator.ts +133 -0
  73. package/src/core/checks/checks/_readback-helpers.ts +133 -0
  74. package/src/core/checks/checks/content_type_conformance.ts +39 -0
  75. package/src/core/checks/checks/cross_call_references.ts +147 -0
  76. package/src/core/checks/checks/cursor_boundary_fuzzing.ts +219 -0
  77. package/src/core/checks/checks/ensure_resource_availability.ts +62 -0
  78. package/src/core/checks/checks/idempotency_replay.ts +242 -0
  79. package/src/core/checks/checks/ignored_auth.ts +254 -0
  80. package/src/core/checks/checks/index.ts +68 -0
  81. package/src/core/checks/checks/lifecycle_transitions.ts +416 -0
  82. package/src/core/checks/checks/missing_required_header.ts +40 -0
  83. package/src/core/checks/checks/negative_data_rejection.ts +148 -0
  84. package/src/core/checks/checks/not_a_server_error.ts +35 -0
  85. package/src/core/checks/checks/open_cors_on_sensitive.ts +160 -0
  86. package/src/core/checks/checks/pagination_invariants.ts +419 -0
  87. package/src/core/checks/checks/positive_data_acceptance.ts +33 -0
  88. package/src/core/checks/checks/rate_limit_headers_absent.ts +77 -0
  89. package/src/core/checks/checks/response_headers_conformance.ts +74 -0
  90. package/src/core/checks/checks/response_schema_conformance.ts +30 -0
  91. package/src/core/checks/checks/status_code_conformance.ts +132 -0
  92. package/src/core/checks/checks/unsupported_method.ts +63 -0
  93. package/src/core/checks/checks/use_after_free.ts +78 -0
  94. package/src/core/checks/index.ts +30 -0
  95. package/src/core/checks/mode.ts +82 -0
  96. package/src/core/checks/recommended-action.ts +68 -0
  97. package/src/core/checks/registry.ts +78 -0
  98. package/src/core/checks/runner.ts +1461 -0
  99. package/src/core/checks/sarif.ts +230 -0
  100. package/src/core/checks/spec-findings.ts +308 -0
  101. package/src/core/checks/stateful.ts +121 -0
  102. package/src/core/checks/types.ts +305 -0
  103. package/src/core/checks/zond-extensions.ts +73 -0
  104. package/src/core/classifier/recommended-action.ts +251 -0
  105. package/src/core/context/current.ts +22 -6
  106. package/src/core/context/session.ts +78 -0
  107. package/src/core/coverage/loader.ts +216 -0
  108. package/src/core/coverage/reasons.ts +300 -0
  109. package/src/core/diagnostics/db-analysis.ts +293 -59
  110. package/src/core/diagnostics/failure-class.ts +140 -0
  111. package/src/core/diagnostics/failure-hints.ts +88 -89
  112. package/src/core/diagnostics/spec-pointer.ts +99 -0
  113. package/src/core/diagnostics/suggested-fixes.ts +155 -0
  114. package/src/core/exporter/case-study/index.ts +270 -0
  115. package/src/core/exporter/curl.ts +40 -0
  116. package/src/core/exporter/exporter.ts +48 -0
  117. package/src/core/exporter/html-report/escape.ts +24 -0
  118. package/src/core/exporter/html-report/index.ts +479 -0
  119. package/src/core/exporter/html-report/script.ts +100 -0
  120. package/src/core/exporter/html-report/styles.ts +408 -0
  121. package/src/core/generator/chunker.ts +38 -19
  122. package/src/core/generator/coverage-phase.ts +0 -0
  123. package/src/core/generator/data-factory.ts +586 -22
  124. package/src/core/generator/describe.ts +1 -1
  125. package/src/core/generator/fixtures-builder.ts +332 -0
  126. package/src/core/generator/index.ts +5 -5
  127. package/src/core/generator/openapi-reader.ts +135 -7
  128. package/src/core/generator/path-param-disambig.ts +140 -0
  129. package/src/core/generator/resources-builder.ts +898 -0
  130. package/src/core/generator/schema-utils.ts +33 -3
  131. package/src/core/generator/serializer.ts +103 -13
  132. package/src/core/generator/suite-generator.ts +583 -122
  133. package/src/core/generator/types.ts +14 -0
  134. package/src/core/identity/identity-file.ts +0 -0
  135. package/src/core/lint/affects.ts +28 -0
  136. package/src/core/lint/config.ts +96 -0
  137. package/src/core/lint/format.ts +42 -0
  138. package/src/core/lint/index.ts +94 -0
  139. package/src/core/lint/reporter.ts +128 -0
  140. package/src/core/lint/rules/consistency.ts +158 -0
  141. package/src/core/lint/rules/heuristics.ts +97 -0
  142. package/src/core/lint/rules/strictness.ts +109 -0
  143. package/src/core/lint/types.ts +96 -0
  144. package/src/core/lint/walker.ts +248 -0
  145. package/src/core/meta/meta-store.ts +6 -73
  146. package/src/core/output/README.md +73 -0
  147. package/src/core/output/index.ts +13 -0
  148. package/src/core/output/run.ts +91 -0
  149. package/src/core/output/types.ts +122 -0
  150. package/src/core/parser/dynamic-values.ts +160 -0
  151. package/src/core/parser/env-interpolation.ts +104 -0
  152. package/src/core/parser/filter.ts +57 -0
  153. package/src/core/parser/schema.ts +129 -4
  154. package/src/core/parser/types.ts +19 -1
  155. package/src/core/parser/variables.ts +0 -0
  156. package/src/core/parser/yaml-parser.ts +58 -12
  157. package/src/core/probe/bootstrap.ts +34 -0
  158. package/src/core/probe/dry-run-envelope.ts +61 -0
  159. package/src/core/probe/mass-assignment/classify.ts +175 -0
  160. package/src/core/probe/mass-assignment/cleanup.ts +52 -0
  161. package/src/core/probe/mass-assignment/digest.ts +114 -0
  162. package/src/core/probe/mass-assignment/orchestrator.ts +459 -0
  163. package/src/core/probe/mass-assignment/regression.ts +141 -0
  164. package/src/core/probe/mass-assignment/suspects.ts +92 -0
  165. package/src/core/probe/mass-assignment/types.ts +135 -0
  166. package/src/core/probe/mass-assignment-probe-class.ts +198 -0
  167. package/src/core/probe/mass-assignment-probe.ts +27 -0
  168. package/src/core/probe/mass-assignment-template.ts +240 -0
  169. package/src/core/probe/method-probe.ts +43 -76
  170. package/src/core/probe/method-shared.ts +69 -0
  171. package/src/core/probe/negative-probe.ts +183 -149
  172. package/src/core/probe/orphan-tracker.ts +188 -0
  173. package/src/core/probe/path-discovery.ts +439 -0
  174. package/src/core/probe/probe-harness.ts +119 -0
  175. package/src/core/probe/registry.ts +89 -0
  176. package/src/core/probe/runner.ts +136 -0
  177. package/src/core/probe/security/baseline.ts +174 -0
  178. package/src/core/probe/security/classify.ts +341 -0
  179. package/src/core/probe/security/cleanup.ts +125 -0
  180. package/src/core/probe/security/detectors.ts +71 -0
  181. package/src/core/probe/security/digest.ts +104 -0
  182. package/src/core/probe/security/orchestrator.ts +398 -0
  183. package/src/core/probe/security/regression.ts +103 -0
  184. package/src/core/probe/security/types.ts +151 -0
  185. package/src/core/probe/security-probe-class.ts +207 -0
  186. package/src/core/probe/security-probe.ts +32 -0
  187. package/src/core/probe/shared.ts +531 -0
  188. package/src/core/probe/static-probe-class.ts +125 -0
  189. package/src/core/probe/types.ts +165 -0
  190. package/src/core/probe/verdict-aggregator.ts +33 -0
  191. package/src/core/probe/webhooks-probe.ts +282 -0
  192. package/src/core/reporter/console.ts +41 -2
  193. package/src/core/reporter/index.ts +2 -3
  194. package/src/core/reporter/json.ts +11 -1
  195. package/src/core/reporter/junit.ts +27 -12
  196. package/src/core/reporter/ndjson.ts +37 -0
  197. package/src/core/reporter/types.ts +3 -0
  198. package/src/core/runner/assertions.ts +59 -2
  199. package/src/core/runner/async-pool.ts +108 -0
  200. package/src/core/runner/auth-path.ts +8 -0
  201. package/src/core/runner/ci-context.ts +72 -0
  202. package/src/core/runner/executor.ts +265 -36
  203. package/src/core/runner/form-encode.ts +41 -0
  204. package/src/core/runner/http-client.ts +112 -2
  205. package/src/core/runner/learn-drift.ts +293 -0
  206. package/src/core/runner/preflight-vars.ts +153 -0
  207. package/src/core/runner/progress-tracker.ts +73 -0
  208. package/src/core/runner/rate-limiter.ts +87 -33
  209. package/src/core/runner/run-kind.ts +45 -0
  210. package/src/core/runner/schema-validator.ts +308 -0
  211. package/src/core/runner/send-request.ts +158 -20
  212. package/src/core/runner/types.ts +44 -0
  213. package/src/core/secrets/registry.ts +164 -0
  214. package/src/core/secrets/secrets-file.ts +115 -0
  215. package/src/core/selectors/operation-filter.ts +144 -0
  216. package/src/core/setup-api.ts +457 -20
  217. package/src/core/severity/category.ts +94 -0
  218. package/src/core/severity/index.ts +58 -0
  219. package/src/core/spec/infer-schema.ts +102 -0
  220. package/src/core/spec/layers.ts +154 -0
  221. package/src/core/spec/merge-specs.ts +156 -0
  222. package/src/core/spec/schema-from-runs.ts +117 -0
  223. package/src/core/spec/schema-overlay.ts +130 -0
  224. package/src/core/util/ajv.ts +13 -0
  225. package/src/core/util/format-eta.ts +21 -0
  226. package/src/core/util/headers.ts +9 -0
  227. package/src/core/util/url.ts +24 -0
  228. package/src/core/utils.ts +5 -1
  229. package/src/core/workspace/config.ts +129 -0
  230. package/src/core/workspace/fixture-gap-report.ts +84 -0
  231. package/src/core/workspace/fixture-gaps.ts +71 -0
  232. package/src/core/workspace/manifest.ts +283 -0
  233. package/src/core/workspace/output-rotation.ts +62 -0
  234. package/src/core/workspace/root.ts +13 -11
  235. package/src/core/workspace/triage-path.ts +87 -0
  236. package/src/db/lint-runs.ts +47 -0
  237. package/src/db/migrate.ts +128 -0
  238. package/src/db/migrations/0001_run_kind.sql +25 -0
  239. package/src/db/migrations/0002_run_kind_request.sql +59 -0
  240. package/src/db/migrations/sql.d.ts +4 -0
  241. package/src/db/queries/collections.ts +133 -0
  242. package/src/db/queries/coverage.ts +9 -0
  243. package/src/db/queries/dashboard.ts +59 -0
  244. package/src/db/queries/results.ts +216 -0
  245. package/src/db/queries/runs.ts +289 -0
  246. package/src/db/queries/sessions.ts +42 -0
  247. package/src/db/queries/settings.ts +28 -0
  248. package/src/db/queries/types.ts +172 -0
  249. package/src/db/queries.ts +75 -802
  250. package/src/db/schema.ts +178 -50
  251. package/src/cli/commands/export.ts +0 -144
  252. package/src/cli/commands/guide.ts +0 -127
  253. package/src/cli/commands/init/templates/skills/scenarios.md +0 -97
  254. package/src/cli/commands/probe-methods.ts +0 -108
  255. package/src/cli/commands/probe-validation.ts +0 -124
  256. package/src/cli/commands/serve.ts +0 -114
  257. package/src/cli/commands/sync.ts +0 -268
  258. package/src/cli/commands/update.ts +0 -189
  259. package/src/cli/commands/validate.ts +0 -34
  260. package/src/core/diagnostics/render-md.ts +0 -112
  261. package/src/core/exporter/postman.ts +0 -963
  262. package/src/core/generator/guide-builder.ts +0 -253
  263. package/src/core/meta/types.ts +0 -19
  264. package/src/core/parser/index.ts +0 -21
  265. package/src/core/runner/execute-run.ts +0 -132
  266. package/src/core/runner/index.ts +0 -12
  267. package/src/core/sync/spec-differ.ts +0 -38
  268. package/src/web/data/collection-state.ts +0 -362
  269. package/src/web/routes/api.ts +0 -314
  270. package/src/web/routes/dashboard.ts +0 -350
  271. package/src/web/routes/runs.ts +0 -64
  272. package/src/web/schemas.ts +0 -121
  273. package/src/web/server.ts +0 -134
  274. package/src/web/static/htmx.min.cjs +0 -1
  275. package/src/web/static/style.css +0 -1148
  276. package/src/web/views/endpoints-tab.ts +0 -174
  277. package/src/web/views/explorer-tab.ts +0 -402
  278. package/src/web/views/health-strip.ts +0 -92
  279. package/src/web/views/layout.ts +0 -48
  280. package/src/web/views/results.ts +0 -210
  281. package/src/web/views/runs-tab.ts +0 -126
  282. package/src/web/views/suites-tab.ts +0 -181
package/CHANGELOG.md CHANGED
@@ -2,6 +2,817 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ## [0.26.0] — 2026-07-09
8
+
9
+ Deterministic gap-fills surfaced by a docgen-core-service audit — all pass the
10
+ litmus test (same input → same output, no severity/FP/blame judgment). Plus an
11
+ m-25 distribution cleanup that collapses the workspace layout to one convention.
12
+
13
+ ### Added
14
+ - **Multi-spec merge (ARV-375):** `zond add api <name> --spec a --spec b …`
15
+ unions N specs into one audit target. Deterministic last-wins on path /
16
+ component collisions, surfaced as warnings (path collisions + same-name /
17
+ different-shape schema collisions). Core in `src/core/spec/merge-specs.ts`.
18
+ - **`zond secrets set <key> <value>` (ARV-377):** writes `apis/<name>/.secrets.yaml`
19
+ with a `.bak` backup, never echoes the value, and auto-strips a pasted
20
+ `Bearer ` prefix (`--literal` keeps it verbatim). Closes the "hand-rolled
21
+ python to rotate an expired token" gap and ARV-367/AC3.
22
+ - **`zond db diagnose --union <spec>` (ARV-380):** aggregate
23
+ `by_recommended_action` (+ summary counts, deduped examples) across a run
24
+ set, reusing coverage's `--union` vocabulary (`session` / `since:<dur>` /
25
+ `tag:<name>` / `runs:<ids>`). `--api` scopes `since:`/`tag:` to a collection.
26
+ - **Coverage deprecated flags (ARV-379):** `coverage --json` now carries a
27
+ per-entry `deprecated` boolean on the `*Endpoints` bucket arrays plus a
28
+ top-level `deprecatedEndpoints` list, so a consumer can split "real gap"
29
+ from "deprecated, skip by design" without re-reading spec.json.
30
+
31
+ ### Fixed
32
+ - **`db diagnose --union` version follow-up + candidate-surfacing (ARV-381/382):**
33
+ disambig's parent-walk now skips version segments (`/v30/{code}`), aligning
34
+ with `owningCollectionForPathParam` which already strips them — resolves the
35
+ `_v30_code` miss-no-list class. And when prepare-fixtures can't confidently
36
+ derive an owner list, the item carries `candidates[]` — plausible GET/list
37
+ endpoints ranked by proximity (deprecated ones marked) — instead of
38
+ dead-ending; zond surfaces the evidence, the agent picks the value. On
39
+ docgen-core-merged: `miss-no-list` 40 → 22 total, 9 of those now carry
40
+ candidates, the rest are honest dead-ends (no listable source in the spec).
41
+ - **Resource graph misses `/list`-style owners (ARV-376):** `resolveOwnerListPaths`
42
+ now links `/list` (and `/search`, `/find`) endpoints to their sibling
43
+ `/{code}` and `/byid/{id}` params; the `byid` accessor marker no longer
44
+ collapses every read-by-id param into one global `byid_id`; a malformed spec
45
+ whose path template (`/byid/{id}`) disagrees with the declared param name
46
+ (`byid_id`) is reconciled to the template; secondary lookup keys (`{code}`)
47
+ surface as candidates. On the docgen-core-merged spec this cut
48
+ prepare-fixtures `miss-no-list` from 40 → 30.
49
+ - **`fixtures add` batch (ARV-378):** confirmed `fixtures add <pairs...>`
50
+ already applies N `key=value` pairs in one call (one write + one `.bak`) —
51
+ no shell loop needed.
52
+
53
+ ### Removed
54
+ - **Flat `zond.db` workspace layout (m-25):** dropped the legacy root-level
55
+ `zond.db` marker and its implicit default-DB resolution. The DB now lives
56
+ only at `.zond/zond.db`; `zond.config.yml` / `.zond/` / `apis/` remain the
57
+ workspace markers. Migrate an old workspace with `mv zond.db .zond/` (or
58
+ re-`zond init`); an explicit `--db <path>` still opens any file. Removes the
59
+ dual-layout confusion that let a root `zond.db` and `.zond/zond.db` coexist.
60
+ - **Dead `benchmarks/` references (m-25):** pruned the never-committed
61
+ `benchmarks/**` entries from `knip.json` and the dead `bench:api` script from
62
+ `package.json` (`knip` is now clean).
63
+
64
+ ## [0.25.0] — 2026-07-07
65
+
66
+ m-24: **rebuild around the agent.** zond becomes a set of deterministic
67
+ dumb tools — it sends requests, validates schemas, stores and diffs runs,
68
+ and reports gaps. The autonomous heuristic layer that produced most of the
69
+ 0.24 bug-stream (auto-discovery/seed/cascade, annotate-auto guess-engine,
70
+ severity calibrators, anti-FP suppression) is removed; the agent now owns
71
+ every judgment call (severity, spec-vs-backend blame, "is this a false
72
+ positive?", which value fills a fixture). The deterministic core —
73
+ send → validate → store → diff — survives intact. A litmus test in
74
+ `src/CLAUDE.md` keeps the heuristic layer from creeping back one
75
+ "reasonable" fix at a time: same input → same output, or it's the agent's.
76
+
77
+ ### Removed (heuristic layer)
78
+ - **Autonomous fixture seed/cascade engine gone (ARV-336):** `bootstrap.ts`
79
+ + `create-body.ts` deleted. Live-POST resource creation guessed bodies and
80
+ cascaded parent→child (1% success on Stripe). prepare-fixtures is now
81
+ single-pass verify + gap-report; the agent (or user) creates resources.
82
+ - **Severity calibrators + anti-FP suppression gate removed (ARV-337 Cut A):**
83
+ severity is agent judgment; anti-FP `applyAntiFp` no longer gates emission —
84
+ the rule reasons survive as raw evidence, not as a suppress-or-emit verdict.
85
+ - **annotate-auto guess-engine removed (ARV-337 Cut B):** `inferSeedBody`
86
+ fabricated bodies from format/name fallbacks and self-ranked confidence.
87
+ annotate now dumps the spec slice; the agent authors the YAML, zond
88
+ validates and merges.
89
+
90
+ ### Agent-facing artifacts & new mechanics
91
+ - **YAML run summary (ARV-338):** `zond db diagnose` drops prose hints (keeps
92
+ the mechanical counts/grouping spine); `db … --report yaml` emits
93
+ agent-friendly output.
94
+ - **Field-level run diff (ARV-339):** `zond db compare` now diffs response
95
+ body/schema per field, not just status.
96
+ - **prepare-fixtures gap report (ARV-349/350):** undefined vars + unseeded
97
+ capture-chain roots are reported deterministically — no auto-seed.
98
+ - **Agent-orchestrated auto-seed (ARV-355):** `zond request --capture` +
99
+ `zond-seed` skill — the agent reasons about creation order, zond executes.
100
+ - **Ambiguous-id guard (ARV-334):** the hint-less fixture harvest reports
101
+ `miss-ambiguous-id` (naming the candidate string field) instead of silently
102
+ putting a numeric `id` into a string slot like `{owner}` — zond flags the
103
+ ambiguity, the agent/annotation picks the field.
104
+ - **generate preserves hand-edits (ARV-361):** suite files whose auto-gen
105
+ header was removed are treated as agent-owned and preserved on regenerate;
106
+ `--force` overwrites them (was a documented no-op).
107
+
108
+ ### Checks, probes & safety
109
+ - **`--safe`/`--live` parity (ARV-299)** across `checks run` and `probe`.
110
+ - **stateful scope (ARV-325):** `--check stateful` expands to the
111
+ state-machine set only.
112
+ - **cascade fast-fail (ARV-326):** prepare-fixtures aborts early on a
113
+ dead/scoped-wrong token instead of grinding through futile attempts.
114
+ - **input-triggered 5xx caught (ARV-340/341):** self-generated body rejects
115
+ route to `fix_spec`, not `report_backend_bug`.
116
+ - **child-exclude wired (ARV-330):** the last-attempt history lookup uses the
117
+ all-run-kinds query + child-exclude pattern so probe sub-resource POSTs
118
+ (`/v1/accounts/{id}/reject`) don't starve the real create-path window.
119
+ - **checks reporting fixes (ARV-322/323/328):** suppressed-count accuracy,
120
+ SIGTERM ndjson counting, throttled stderr progress on long runs.
121
+
122
+ ### Sweeps & infrastructure
123
+ - **Bounded/resumable sweeps (ARV-342):** `--skip-ops` / `--max-ops` op-window.
124
+ - **DB retention (ARV-266):** `zond db prune` + `zond db stats`, per-run-kind
125
+ cutoffs, VACUUM after delete.
126
+ - **Schema from observed runs (ARV-175/176):** `schema-from-runs` command +
127
+ `refresh-api --merge-schema` — union/required-intersection from real
128
+ responses, not guessing.
129
+
130
+ ### Report, triage & audit-run cleanup
131
+ - Report-noise, severity and UX cleanup (ARV-343/344/345/346/348/351/353);
132
+ compare scope tag, partial summary on SIGTERM, triage depth aggregate
133
+ (ARV-352/354).
134
+ - Empty-dir `zond run --output` writes a `0 tests` envelope instead of no
135
+ file (ARV-357); cross-phase double-emit documented for triage (ARV-358).
136
+ - Workspace-leak fix (env-independent audit paths) + self-compare guard
137
+ (ARV-359/360), from a petstore audit-run.
138
+
139
+ ## [0.24.0] — 2026-07-03
140
+
141
+ m-22 validation sprint. No new milestone surface — this release hardens the
142
+ 0.23.0 depth-checks/probe/audit stack against real APIs. ~40 fixes (ARV-260..332)
143
+ were found by running `zond audit`/`zond-scan` live against Stripe, GitHub,
144
+ Resend and docgen-core, not by unit tests.
145
+
146
+ ### Safety & contracts
147
+ - **Safe-mode leak closed (ARV-332):** `checks run --check stateful --include
148
+ method:GET` no longer fires POST create-chains — CRUD groups are built from
149
+ the filtered op set, so `ensure_resource_availability`/`use_after_free`
150
+ self-skip when no write is in scope. Critical for scanning APIs you don't own.
151
+ - Exit-code contract hardened (ARV-303/307/308/309/310): audit judges stages on
152
+ `envelope.ok`, not raw exit code; budget-exhaust distinguished from network error.
153
+ - `open_cors_on_sensitive` HIGH only on 2xx + ambient (cookie) credential
154
+ (ARV-312/316) — kills a class of false HIGHs.
155
+
156
+ ### Severity calibration
157
+ - Probe-side severity calibrator (ARV-283/300): `SecuritySeverity ↔ Severity`
158
+ adapter with sentinel passthrough; wired into security probe. mass-assignment/
159
+ static/webhooks tracked in ARV-311.
160
+
161
+ ### Fixture / seed pipeline
162
+ - FK-aware body builder wires reference fields to fixtures (ARV-45).
163
+ - Topological seed order + body-FK deferral (ARV-324/327); gap-report flags
164
+ unfillable complex root resources instead of silently skipping (ARV-329/330).
165
+
166
+ ### Ops & workflow
167
+ - `ZOND_WORKSPACE` honored; `zond-audit` workflow added for reproducible runs.
168
+ - ndjson reporter stability (ARV-314/318/320/322); path-casing canonicalized
169
+ before workspace-root walk (ARV-315).
170
+ - Repo-wide ponytail cleanup: dead code removed, probe monoliths deduped.
171
+
172
+ ## [0.23.0] — 2026-05-15
173
+
174
+ Big release covering m-15 → m-21 (155 ARV tickets across depth-checks,
175
+ schemathesis-comparison, m-20 stateful probes, agent-augmented annotation,
176
+ deep-testing-and-tuning). The detailed per-task list survives below
177
+ under the legacy TASK-* headings (m-13 carry-over); this section is the
178
+ release-summary for the m-15..m-21 epic series.
179
+
180
+ ### Highlights — m-15 to m-21
181
+
182
+ #### `zond checks` — schemathesis-style depth checks (m-15, ARV-1..12)
183
+
184
+ - **17 registered checks** across two registries: per-response
185
+ (`status_code_conformance`, `content_type_conformance`,
186
+ `response_headers_conformance`, `response_schema_conformance`,
187
+ `missing_required_header`, `unsupported_method`, `negative_data_rejection`,
188
+ `positive_data_acceptance`, `not_a_server_error`,
189
+ `rate_limit_headers_absent`) and stateful (`ignored_auth`,
190
+ `use_after_free`, `ensure_resource_availability`, `cross_call_references`,
191
+ `idempotency_replay`, `pagination_invariants`, `lifecycle_transitions`,
192
+ `open_cors_on_sensitive`).
193
+ - **Anti-FP infrastructure** (`core/anti-fp/`) with per-rule registry,
194
+ 6 documented schemathesis-FP fixture-pack regressions
195
+ (`tests/regression/schemathesis-fps/`), and per-finding `recommended_action`
196
+ enum (`fix_test_data` / `fix_test_logic` / `report_to_api_owner` / …).
197
+ - **Coverage phase** (`--phase coverage`) for deterministic boundary-value
198
+ enumeration over body + param schemas; complements `--phase examples`.
199
+ - **SARIF v2.1.0 reporter** with stable `partialFingerprints` for GitHub
200
+ Code Scanning integration.
201
+ - **NDJSON streaming reporter** (`--report ndjson`) with published
202
+ JSON Schema (`docs/json-schema/ndjsonEvent.schema.json`).
203
+ - **`--workers` async-pool** for op-level concurrency, gated by an
204
+ optional `--rate-limit auto` adaptive limiter (RFC 9568 RateLimit-* headers).
205
+ - **`--include` / `--exclude` selectors** unified across `generate`, `run`,
206
+ `checks` (path/method/tag/operation-id grammar).
207
+
208
+ #### m-18 — schemathesis parity baselines (ARV-174..186)
209
+
210
+ - Stripe / Resend / Sentry parity baselines (3 baseline-fixed bugs:
211
+ ARV-179 unsupported_method exhaustive enumeration, ARV-180
212
+ status_code_conformance param-axis coverage, ARV-181 ignored_auth
213
+ pathVars + strict-401, ARV-183 phantom-findings fix, ARV-184
214
+ missing_required_header exhaustive). Documented schemathesis-V4
215
+ comparison matrix.
216
+
217
+ #### m-20 — stateful probes (ARV-169..173, 187, 191)
218
+
219
+ - **`cross_call_references`** (ARV-169) — POST→GET shape-diff probe.
220
+ Surfaces `state_not_persisted` (HIGH) when the server echoes a field on
221
+ create but drops it on read.
222
+ - **`idempotency_replay`** (ARV-170) — `Idempotency-Key` honor probe.
223
+ Two POSTs with the same key must return the same id and bit-identical
224
+ response (`duplicate_resource` / `non_bit_identical`).
225
+ - **`pagination_invariants`** (ARV-171) — cursor-style page consistency.
226
+ Detects off-by-one duplicates across pages, partial-page-with-has_more,
227
+ and inconsistent has_more.
228
+ - **`lifecycle_transitions`** (ARV-172) — declared state machine
229
+ verification with action-replay idempotency probe.
230
+ - **`probe webhooks`** (ARV-173) — webhook shape-conformance against
231
+ `spec.webhooks` event log; recipe in `docs/recipes/webhook-receiver.md`.
232
+ - **`zond api annotate`** (ARV-187) — agent-augmented annotation flow:
233
+ `dump` slices spec for the agent, `apply` merges its YAML answers into
234
+ `.api-resources.local.yaml` (no LLM inside zond — see
235
+ `feedback_zond_no_llm_calls` memory).
236
+ - **Form-encoded stateful checks** (ARV-191) — stateful probes honor
237
+ `requestBodyContentType` so Stripe-style APIs aren't broken-baseline.
238
+ - **`.api-resources.local.yaml` `patches:` block** (ARV-169) — field-level
239
+ overlay survives `refresh-api`; replaces the deprecated re-declaration
240
+ pattern.
241
+
242
+ #### m-21 — deep-testing-and-tuning (ARV-188..256)
243
+
244
+ - **Severity rebalance** (ARV-250..256) under "no-OOB" constraint —
245
+ small-team API hygiene scanner positioning. SSRF / CORS / rate-limit
246
+ / missing-auth probes recalibrated; `report categorization` into
247
+ security / reliability / contract / hygiene buckets (ARV-251).
248
+ - **Spec-lint cap at LOW/INFO** (ARV-255) — dedicated `zond lint` command
249
+ separates static spec hygiene from runtime probes.
250
+ - **Mock-API testbed** (ARV-193) — `apis/_mock/` with 4 intentional bugs
251
+ per m-20 stateful probe; `tests/regression/mock-testbed.test.ts` is the
252
+ regression-floor for probe-quality.
253
+ - **`zond fixtures add` / `import --from-curl`** (ARV-195) — manual
254
+ fixture-bootstrap for path-FK ids that auto-discover/--seed can't reach
255
+ (vendor-dashboard ids).
256
+ - **Stripe form-encoding fix** (ARV-196) — bootstrap seed POST honors
257
+ `application/x-www-form-urlencoded` with bracket nesting
258
+ (`card[number]`, `items[0][price]`).
259
+
260
+ ### Performance & operability
261
+
262
+ - **`--max-requests` cap** for `zond checks run` (ARV-227) — shared
263
+ budget for per-response + stateful phases bounds long runs against
264
+ large specs (github / kubernetes).
265
+ - **Schema-validation safety net** (ARV-214) — oversized response schemas
266
+ in `--validate-schema` skip with a stderr warning instead of hanging
267
+ for 15+ min on AJV.compile. Configurable via
268
+ `ZOND_VALIDATE_SCHEMA_MAX_BYTES` (default 1 MiB) and
269
+ `ZOND_VALIDATE_SCHEMA_SLOW_COMPILE_MS` (default 1000ms).
270
+ - **Rate-limiter adaptive mode** (ARV-8 follow-ups) — paces from
271
+ `RateLimit-*` response headers (RFC 9568) so multi-worker runs respect
272
+ vendor budgets globally.
273
+
274
+ ### Skills & workflow
275
+
276
+ - Skills consolidated 5 → 3 (`zond`, `zond-checks`, `zond-triage`) with
277
+ `zond init --prune-stale-skills` (ARV-197) for upgrades.
278
+ - Skill `update-on-feature-change` ritual formalized; per-feature CLI
279
+ changes gate on skill update (`feedback_update_skills_per_feature`
280
+ memory).
281
+ - Iron rules in `skills/zond.md`: `--dry-run` for destructive ops,
282
+ `--redact-identity` for triage artefacts, mandatory
283
+ `zond doctor --missing-only` first step.
284
+
285
+ ### Fixed (selected)
286
+
287
+ - ARV-145: `zond add api` no longer crashes on cyclic OpenAPI specs
288
+ (Stripe). `decycleSchema` writes `x-circular` sentinel; downstream
289
+ parsers skip cleanly.
290
+ - ARV-200: extractEndpoints filters `x-circular` param stubs (R10/F1
291
+ feedback-loop crash).
292
+ - ARV-209: `--validate-schema` auto-resolves spec from
293
+ `apis/<name>/tests/` path (R12/F11 — manual `--spec` no longer
294
+ required for skill-driven runs).
295
+ - ARV-244: `cleanup --orphans` percent-encodes unsafe characters in
296
+ `deletePath`.
297
+ - ARV-238: `clean --api <name>` resolves global `--api` fallback.
298
+ - 50+ feedback-loop bug fixes from R01..R18 against Resend / Sentry /
299
+ Stripe / GitHub baselines (search `git log --grep "R[0-9]\+/F"` for the full list).
300
+
301
+ ### Workspace contract
302
+
303
+ - **Manifest vs values** (m-17, decision-7) — `.api-fixtures.yaml` is
304
+ the manifest of required vars; `.env.yaml` carries values. The split
305
+ is enforced by `prepare-fixtures` ("not in manifest, ignored" warning)
306
+ and documented in `skills/zond.md`.
307
+ - **`.api-resources.local.yaml`** (ARV-111) — survives
308
+ `add-api`/`refresh-api`. Use `extensions:` for full resource entries,
309
+ `patches:` for field-level overlays (readback_diff, idempotency,
310
+ pagination, lifecycle, seed_body).
311
+ - **Single API-resolution chain** (TASK-290) — `--api` flag → `ZOND_API`
312
+ env → `.zond/current-api` (set by `zond use <name>`).
313
+
314
+ ---
315
+
316
+ The legacy m-13 TASK-* changelog continues below; entries originally in
317
+ `[Unreleased]` are now part of 0.23.0.
318
+
319
+ ### Added
320
+
321
+ - **TASK-301: workspace defaults for `--timeout` and `--rate-limit` in `zond.config.yml`.**
322
+ New `defaults.timeout_ms` and `defaults.rate_limit` (alias `timeoutMs`,
323
+ `rateLimit`, `rate_limit: auto`) feed `cleanup`, `prepare-fixtures`,
324
+ `probe mass-assignment`, `probe security`, `request`, and `run`.
325
+ Resolution chain: **CLI flag → `apis/<name>/.env.yaml` meta → workspace
326
+ defaults → built-in fallback** (30000 ms / undefined rate limit).
327
+ `.env.yaml` already supported `rateLimit:`; now also supports
328
+ `timeoutMs:`. The init template's `zond-config.yml` documents the
329
+ `defaults` block. New helpers: `loadWorkspaceDefaults`,
330
+ `resolveTimeoutMs`, `resolveRateLimit` in `core/workspace/config.ts`.
331
+
332
+ ### Changed (breaking)
333
+
334
+ - **TASK-300: `zond probe validation` and `zond probe methods` are merged into `zond probe static`.**
335
+ Both classes are static-input checks (no HTTP) and now share one entry
336
+ point: `zond probe static --output <dir>` runs both by default. Filter
337
+ via `--include validation,methods` (or `--exclude`). The old subcommands
338
+ are removed without a deprecation alias — same model as TASK-298
339
+ (`validate` + `lint-spec` → `check`). `zond audit` now spawns a single
340
+ `probe static` stage (output dir `apis/<name>/probes/static/`) in place
341
+ of the two former stages.
342
+
343
+ - **TASK-299: `zond discover` and `zond bootstrap` are merged into `zond prepare-fixtures`.**
344
+ Single-pass discover is now `zond prepare-fixtures --api <name>` (the
345
+ former `zond discover` flow). Multi-pass cascade is `--cascade`, with
346
+ `--seed` / `--force` / `--max-passes` (the former `zond bootstrap`).
347
+ `--seed` implies `--cascade`. The old top-level commands are removed
348
+ without deprecation. `zond audit --seed` now spawns
349
+ `prepare-fixtures --apply --seed` instead of `bootstrap`.
350
+
351
+ - **TASK-298: `zond validate` and `zond lint-spec` are merged into `zond check`.**
352
+ Use `zond check tests <path>` for the YAML-test schema validator and
353
+ `zond check spec [spec]` for the OpenAPI static analyser. The old
354
+ top-level commands are removed (no deprecation alias). Flag surface is
355
+ unchanged. Single mental model — both surfaces are conformance checks
356
+ on workspace inputs, neither makes HTTP calls.
357
+
358
+ ### Removed (breaking)
359
+
360
+ - **TASK-284: `zond serve` and the WebUI are removed.** Agent-first / CLI-only
361
+ surface per vector-3. Use `zond report export` for shareable HTML reports.
362
+ `src/ui/` is gone along with hono / react / tanstack / tailwind dependencies.
363
+ - **TASK-285: `zond update` (and `self-update` alias) is removed.** Use the
364
+ system package manager: re-run `install.sh`, or `npm install -g @kirrosh/zond@latest`,
365
+ or `bun install -g @kirrosh/zond@latest`. README has the upgrade section.
366
+ - **TASK-286: `zond export postman` is removed (decision-4 reversed).** The
367
+ parallel YAML→Postman exporter (`src/cli/commands/export.ts` +
368
+ `src/core/exporter/postman.ts`, ~963 LOC) had no measured demand;
369
+ OpenAPI-driven tooling already covers the round-trip use case.
370
+ - **TASK-287: `zond report case-study` standalone subcommand is removed.**
371
+ The case-study markdown drafts are still produced by `zond report bundle`
372
+ (default `--include case-study`); the per-failure CLI surface and its flags
373
+ collapse into the bundle path. `renderCaseStudy` core renderer is unchanged.
374
+
375
+ ### Removed (breaking)
376
+
377
+ - **TASK-288: deprecated top-level `probe-*` aliases removed.** The
378
+ one-release deprecation window for `probe-validation`, `probe-methods`,
379
+ `probe-mass-assignment`, `probe-security` (TASK-182) is closed. Use
380
+ `zond probe <class>` instead. `warnDeprecatedProbe` helper removed.
381
+
382
+ ### Changed (breaking)
383
+
384
+ - **TASK-296: `--json` envelope `errors[]` is now structured.** Every
385
+ error is `{ code: ZondErrorCode, message: string, details?: object }`
386
+ instead of a flat string. `code` is a closed enum
387
+ (`unknown_error`, `env_missing`, `fixture_missing`, `network_timeout`,
388
+ `network_error`, `sandbox_blocked`, `spec_load_failure`,
389
+ `yaml_parse_error`, `workspace_not_found`, `file_not_found`,
390
+ `permission_denied`, `argument_invalid`, `api_not_registered`,
391
+ `db_error`, `auth_config_error`) so an agent can route on `code`
392
+ without parsing the human message. Agents that previously did
393
+ `errors[0]` now read `errors[0].message`. Sites not yet classified
394
+ emit `code: "unknown_error"` (still structured, still routable).
395
+
396
+ ### Added
397
+
398
+ - **TASK-294: `recommended_action` field on every Issue / SecurityFinding /
399
+ mass-assignment / discover finding.** Closes the agent-routing gap
400
+ for findings outside `db diagnose`. New enum values: `fix_spec`,
401
+ `fix_fixture`. See `skills/zond.md` for the full table.
402
+
403
+ - **TASK-292: 5 iron rules in `skills/zond.md`.** Promotes from
404
+ audit-and-consolidation §6: NEVER destructive ops on shared/prod org
405
+ without `--dry-run`; NEVER report cleanup-failure as API bug; NEVER
406
+ share triage artefacts without `--redact-identity`; MUST timeout
407
+ bootstrap cascade (default 8 passes); MUST run
408
+ `zond doctor --api <name> --missing-only` first. Each has a one-line
409
+ rationale embedded next to the rule.
410
+
411
+ - **TASK-290: global `--api` flag + `ZOND_API` env + `.zond/current-api` file.**
412
+ `zond` now resolves the active API from a single chain (highest wins):
413
+ per-command `--api` > root `--api` > `ZOND_API` env > `.zond/current-api`
414
+ (set by `zond use <name>`; was `.zond-current` at workspace root).
415
+ The root `--api` value is mirrored into `ZOND_API_GLOBAL` by a preAction
416
+ hook so deeply-nested code can read it without a `cmd` reference.
417
+
418
+ ### Deprecated
419
+
420
+ - **TASK-289: `zond run --no-real-parents` → `--use-synthetic-parents`.**
421
+ Double-negative renamed to a positive flag. The old name still works
422
+ one release with a stderr warning, then drops.
423
+ - **TASK-291: `zond lint-spec --filter-rule` is a deprecated alias for
424
+ the whitelist subset of `--rule`.** The two flags are unified: `--rule`
425
+ now accepts `B1` (whitelist), `!B2` (disable), `B3=high|low|off` (override).
426
+ `--filter-rule` still works one release with a stderr warning.
427
+
428
+ ### Added
429
+
430
+ - **TASK-29: `zond db diagnose --json` now surfaces `suggested_fixes`.**
431
+ Two actionable signals on top of the existing `agent_directive` /
432
+ `recommended_action` / `env_issue` envelope:
433
+ (1) **placeholder path-params on 404s** — when a 404 hits a URL still
434
+ containing literal `example`, all-zero UUIDs, `your-…-here`,
435
+ `replace-me`, or sentinel hex tails (`…dead/beef/cafe`), the segment
436
+ is flagged with a fix message pointing at `zond discover --apply` or
437
+ the matching fixture in `.env.yaml`. Deduplicated across failures so
438
+ one broken segment doesn't repeat N times.
439
+ (2) **unfilled `.env.yaml` keys** — reads the API's `.env.yaml` and
440
+ flags values that are empty, `<TODO>` / `<…>`, `example`,
441
+ `your-…-here`, or `replace-me`. The agent gets a concrete list of
442
+ keys to fill before re-running, instead of guessing from a 404 burst.
443
+
444
+ - **TASK-36: tagless endpoints fall back to per-resource grouping.**
445
+ `groupEndpointsByTag` previously piled every untagged endpoint into a
446
+ single `untagged` bucket — Resend's `/audiences` POST/GET/DELETE all
447
+ ended up in one fat `smoke-untagged.yaml` instead of a focused
448
+ `audiences-smoke` / `crud-audiences` pair. Untagged endpoints now key
449
+ by their first non-templated path segment (`/audiences/{id}` →
450
+ `audiences`, `/{tenant}/jobs/{id}` → `jobs`), so tagless specs
451
+ produce the same per-resource suite layout as tagged ones. Path of
452
+ `/` keeps the legacy `untagged` key.
453
+
454
+ - **TASK-116: `zond run --all` + CI context autodetection.** `--all`
455
+ discovers every `apis/<name>/tests/` directory in the workspace and
456
+ merges them into a single `runs.id` — one run row per CI invocation,
457
+ even with multiple registered APIs (without it, each `zond run` lands
458
+ on its own row, so cross-build comparison is impossible). On every
459
+ `zond run` the CLI now also stamps the run row with CI context
460
+ auto-detected from env vars (GitHub Actions, GitLab CI, CircleCI,
461
+ Buildkite, Jenkins, or generic `CI=true`): `trigger=ci`, `commit_sha`,
462
+ `branch`. Manual runs still default to `trigger=manual` with no
463
+ commit/branch. `ZOND_TRIGGER` / `ZOND_COMMIT_SHA` / `ZOND_BRANCH`
464
+ override autodetection for wrappers that strip the native vars.
465
+ `RunFilters` gained `trigger`, so `listRuns({ trigger: "ci" })` /
466
+ `zond db runs --trigger ci` (UI/CLI filter) limits the dashboard to
467
+ CI rows.
468
+
469
+ - **TASK-142: `zond request --validate-schema` and `--validate-against "METHOD:/path"`.**
470
+ One-off ad-hoc requests can now check the response body against the
471
+ OpenAPI response schema without wrapping the call in YAML. Auto-resolves
472
+ the endpoint from request method + URL.path (templated paths like
473
+ `/users/{id}` matched via the same regex used by `run --validate-schema`).
474
+ Selects the response branch from the actual status code (200 → 200 schema,
475
+ 404 → 404 schema, anything else → `default`). Output adds a
476
+ `Schema validation: PASS / FAIL` block with the matched endpoint,
477
+ response branch, and human-readable schema errors (`schema.required`,
478
+ `schema.type`, etc.). FAIL → exit 1; `no-endpoint`/`no-spec`/`no-schema`
479
+ → soft no-op with a one-line hint. `--validate-against` overrides the
480
+ auto-resolver when the URL doesn't fit the spec template (e.g.
481
+ parameterized resources fetched by slug). Requires `--api <name>` —
482
+ the spec is loaded from the registered collection.
483
+
484
+ - **TASK-143: `zond report bundle <range>` — batch triage exporter.**
485
+ One command instead of `4 runs × 2 formats = 8 calls`. Range forms:
486
+ `A..B` (inclusive numeric range), `A,B,C` (comma list), or
487
+ `--session <id>` (resolve all runs from a CLI session via `runs.session_id`).
488
+ For each run writes `<dir>/<run-id>/case-study.md` (only when failures
489
+ exist), `<dir>/<run-id>/report.html` (single-file HTML), and
490
+ `<dir>/<run-id>/diagnose.json`. A top-level `index.md` lists run-id /
491
+ spec / totals / artefact links / agent_directive snippet from
492
+ `diagnose`. `--include` filters the artefact set (subset of
493
+ `case-study`, `export`, `diagnose`); `--body-cap`/`--no-body-cap`
494
+ forwards to both case-study and HTML renderers. Default output dir
495
+ is `triage/bundle/<timestamp>/` when `--output` is omitted.
496
+
497
+ - **TASK-146: `probe mass-assignment --emit-template "METHOD:/path"`.**
498
+ Generates a ready-to-edit YAML probe template for one endpoint, so the
499
+ user doesn't have to copy-paste the boilerplate from the skill (Phase
500
+ 5.1) when the auto-prober marked a verdict INCONCLUSIVE / INCONCLUSIVE-5XX.
501
+ For POST endpoints with discoverable item path (GET-by-id / DELETE
502
+ counterpart) the emitter produces a full `create → verify → cleanup`
503
+ chain with `always: true` cleanup. Privileged-field injection is the
504
+ union of (a) classic mass-assignment vectors (`is_admin`, `role`,
505
+ `owner_id`, …) and (b) `readOnly: true` / `x-zond-protected` properties
506
+ lifted from the request body schema. Output to stdout by default, or
507
+ `--output <file>` to write a YAML file directly. Note: the body
508
+ serializer was tightened so `not_equals: true` (boolean) no longer
509
+ silently emits as `not_equals: "true"` (string) — assertions against
510
+ real boolean fields now compare correctly.
511
+
512
+ - **TASK-153: `probe security` fuzzy-echo classifier for CRLF.** The echo
513
+ detector previously used verbatim substring match, which missed real
514
+ stored-CRLF bugs when the backend stripped `\r`, URL-decoded `%0d%0a`
515
+ before saving, or truncated the field at the first newline (only the
516
+ tail landed in storage). The classifier now branches by class: SSRF and
517
+ open-redirect stay verbatim (URL preserved as-is), CRLF additionally
518
+ tries URL-decoded pairs, CR/LF normalization variants, and tail-only
519
+ match after a newline. The match kind (`verbatim` / `url-decoded` /
520
+ `CRLF→LF` / `CR stripped` / `tail after CRLF` / …) is recorded in
521
+ `finding.reason` for investigation. Bodies are walked as a tree of
522
+ string leaves so CR/LF chars aren't hidden behind JSON escape
523
+ sequences in the haystack.
524
+
525
+ - **TASK-145: `zond doctor --missing-only` + `--query` + canonical `--json` shape.**
526
+ The `--json` envelope is now documented as the canonical contract — all
527
+ diagnostic data lives under `.data` (no `.diagnostics` wrapper). `--help`
528
+ spells out every dot-path (`.data.fixtures.required[]`,
529
+ `.data.staleArtifacts[]`, …) so agents stop guessing. **`--missing-only`**
530
+ hides rows already healthy in both text and JSON: required fixtures with
531
+ values, fresh artifacts, optional fixtures, and `extraInEnv` are dropped.
532
+ **`--query <dotpath>`** resolves a subtree of the report and emits it as
533
+ raw JSON to stdout (no envelope), so pipelines no longer need `jq` for
534
+ the common cases (`zond doctor --query fixtures.required`,
535
+ `--query staleArtifacts`). Unknown paths fail with exit 2 and a list of
536
+ the canonical entry points.
537
+ - **TASK-140: `zond db run --status` now accepts ranges & classes.**
538
+ In addition to the existing exact-code form (`--status 502`), the flag
539
+ parses class wildcards (`5xx`, `4xx`, …), inclusive ranges
540
+ (`500-599`), open-ended comparisons (`>=500`, `<400`, `>500`, `<=400`),
541
+ and any comma-separated mix of those (`5xx,429`, `500,502,504`).
542
+ Triage of large failure runs (e.g. 2000+-step Sentry hunts) no longer
543
+ needs `jq` over `--json`. Invalid syntax produces a one-line error;
544
+ the parser is unit-tested in `tests/cli/status-filter.test.ts`.
545
+ - **TASK-144: `zond run --retry-on-network <N>`.** Auto-retry on transient
546
+ TCP/transport errors (`ECONNRESET`, `EPIPE`, `socket hang up`,
547
+ `fetch failed`, abort/timeout without HTTP response) with exponential
548
+ backoff + full jitter (base 250 ms, cap 8 s). Default `1`, set `0` to
549
+ disable. **HTTP status codes (incl. 5xx) are NOT retried by this path**
550
+ — 5xx is a real server response, not a flaky socket; rate-limited 429
551
+ retries continue to flow through the rate-limiter. Retried steps surface
552
+ `network_retry: <count>` in `--report json` and the `--json` envelope so
553
+ flaky-network shells stay visible during triage.
554
+ - **TASK-186: unified `Exporter` interface + sanitizer pipeline.**
555
+ `src/core/exporter/exporter.ts` now defines `Exporter<I, O>` plus a
556
+ `runExporter()` pipeline; `applySanitizer()` is the one place
557
+ sanitization happens. `generateJsonReport` and `generateJunitXml` are
558
+ pure renderers that hand off to `runExporter`. The cli sites that
559
+ used to call `redact()` directly (HTML report, case-study draft,
560
+ probe-mass-assignment digest, probe-security digest) now call
561
+ `applySanitizer()`, signalling the single sanitization seam. New
562
+ exporters get sanitization for free; `redact()` is no longer imported
563
+ from cli/exporter code.
564
+ - **TASK-184: typed `--json` envelope helpers (closes TASK-73 / TASK-74).**
565
+ `src/cli/json-envelope.ts` now exports a discriminated-union
566
+ `EnvelopeResult<T>` plus two new entry points: `writeEnvelope(cmd, result)`
567
+ (writes the envelope and returns the exit code) and `withEnvelope(cmd, produce)`
568
+ (wraps an async producer, renders thrown errors as `ok: false`).
569
+ Existing `jsonOk` / `jsonError` / `printJson` keep working — the new
570
+ helpers are an opt-in convenience layer for new commands. Test suite
571
+ pins the success/error/meta/warnings shape end-to-end.
572
+
573
+ ### Changed
574
+
575
+ - **TASK-187: split `src/db/queries.ts` by domain.** The 750-line module
576
+ is now split across `src/db/queries/{types,runs,sessions,results,collections,dashboard,settings,coverage}.ts`.
577
+ `src/db/queries.ts` survives as a façade that re-exports everything,
578
+ so all 27 callers stay unchanged for one release; the façade will be
579
+ deleted in the next minor (callers should migrate to the per-domain
580
+ paths). `coverage.ts` and `settings.ts` are reserved placeholders for
581
+ future features and ignored by knip.
582
+ - **TASK-185: extract shared probe scaffolding into `core/probe/runner.ts`.**
583
+ The four probe cli commands (`validation`, `methods`, `mass-assignment`,
584
+ `security`) used to each repeat: `readOpenApiSpec` → `extractEndpoints`
585
+ → tag-filter / list-tags → mkdir output → write each suite with
586
+ `autoGenHeader` → record in manifest. That scaffolding now lives in
587
+ two helpers, `loadSpecForProbe` and `writeProbeSuites`, so each cli
588
+ command shrinks to ~100 lines and the suite-emit path is identical
589
+ across them. Live HTTP orchestration in `mass-assignment-probe.ts` /
590
+ `security-probe.ts` is untouched.
591
+ - **TASK-183: merge `init.ts` and `init/`.** The `init` command had two
592
+ files that looked like entry points: `src/cli/commands/init.ts`
593
+ (handler) and `src/cli/commands/init/` (helpers). Moved the handler
594
+ into `src/cli/commands/init/index.ts` so the directory is the
595
+ command's only home; behaviour unchanged.
596
+ - **TASK-181: sync `install.ps1` ↔ `install.sh`.** PowerShell installer
597
+ now detects ARM64 alongside x64 (was hard-coded to `win-x64`), wraps
598
+ the release-tag fetch in try/catch with a useful error message, and
599
+ matches the .sh installer's tone. The two scripts diverged in late
600
+ April when `install.sh` gained codesign/xattr / fallback-to-local
601
+ logic; this brings them in line for the cross-platform behaviour
602
+ that's actually shared (detection + download + verify).
603
+ - **TASK-179: knip cleanup.** Deleted three unused barrel modules
604
+ (`src/core/diagnostics/render-md.ts`, `src/core/parser/index.ts`,
605
+ `src/core/runner/index.ts`), trimmed the `tailwindcss` direct
606
+ dependency (provided transitively by `bun-plugin-tailwind`), and
607
+ stripped 25+ unused `export` keywords across `src/core` and `src/db`
608
+ so symbols become module-private. The historical `executeRun` runner
609
+ in `src/core/runner/execute-run.ts` (superseded by `run.ts`) was
610
+ dropped; only `AUTH_PATH_RE` survives. `knip.json` now treats
611
+ `tests/`, `scripts/`, and `benchmarks/` as entries and silences
612
+ noise on commander `*Options` types and shadcn `*Variants`.
613
+ - **TASK-178: build artefacts out of repo root.** `bun run build` now
614
+ emits the compiled binary to `dist/zond` (was `./zond`); the
615
+ codesign-darwin script's default arg follows. The default SQLite path
616
+ is now `<workspace>/.zond/zond.db` (next to `.zond/manifest.json`)
617
+ rather than `<workspace>/zond.db`. Legacy `<workspace>/zond.db` is
618
+ still honoured if present, so existing workspaces keep working without
619
+ a migration step.
620
+
621
+ ### Removed
622
+
623
+ - **TASK-180: collapse `docs/INDEX.md` and `docs/project-backlog.md`.**
624
+ Both duplicated content already in README + ZOND.md + AGENTS.md.
625
+ Removed; AGENTS.md and README.md updated to point straight at
626
+ `backlog/` and `backlog/decisions/`.
627
+ - **TASK-177: remove `.mcp.example.json`.** Leftover from the
628
+ pre-decision-2 MCP integration. MCP support was dropped entirely; the
629
+ example config no longer documents anything.
630
+ - **TASK-176: drop `CLAUDE.md`.** The file was a 13-line wrapper that
631
+ pointed Claude Code at `AGENTS.md`. Modern Claude Code reads
632
+ `AGENTS.md` directly, so the redirect is unnecessary. AGENTS.md
633
+ remains the single source of truth for AI agents.
634
+
635
+ ### Changed
636
+
637
+ - **TASK-151 round-5 follow-up: eventual-consistency retry on POST
638
+ cleanup.** SaaS APIs that route `POST` to a write replica and read
639
+ paths through a follower (Sentry observed this round-5) returned
640
+ 404 to immediate `DELETE` cleanup, even though the resource existed
641
+ ~10s later. `tryCleanup` now retries 404 with two short backoffs
642
+ (default 200ms / 1s, configurable via `cleanupRetryDelaysMs`). A
643
+ 404 that survives retries is flagged
644
+ `persisted across retries — likely real leak`. 5xx, network
645
+ errors, 401/403 fail fast (not transient).
646
+ - **Skill: documented CI exit codes.** Phase 5.2 now states that
647
+ `zond probe-security` exits non-zero on either `HIGH > 0` or
648
+ `cleanup.error > 0`, and that `grep -q "Cleanup failures"
649
+ digest.md` is a reliable signal for the latter.
650
+
651
+ - **TASK-151 round-4 follow-up: per-field restore + cleanup-failure
652
+ surfacing.** The first cut of snapshot+restore sent the full GET
653
+ body back as a single PUT, which `422`'d on partial-PUT APIs (Sentry,
654
+ Stripe) — the round-3 user re-ran the probe and found `org.name`,
655
+ `project.name`, `project.subjectPrefix` left as the attack payload.
656
+ Fixes:
657
+ 1. `restoreOriginal` now replays each dirty field as its own
658
+ single-key PUT, so partial-PUT APIs accept it. Caller passes the
659
+ set of mutated keys (full-baseline → all body keys, partial
660
+ baseline / per-attack → just the targeted field).
661
+ 2. `findDeleteCounterpart` / `findGetByIdCounterpart` are
662
+ trailing-slash tolerant. `POST /keys/` + `DELETE /keys/{id}/`
663
+ now matches; previously the regex required identical slash forms
664
+ and silently leaked DSN keys.
665
+ 3. POST cleanup failures (no DELETE counterpart, missing id, DELETE
666
+ 4xx, network error) accumulate into `verdict.cleanup.error`.
667
+ 4. `formatSecurityDigest` prints a mandatory
668
+ `## ⚠️ Cleanup failures` section first when any verdict has a
669
+ cleanup error, plus a `🧹 cleanup-failure` tag next to the
670
+ verdict line in its severity bucket. The CLI now exits non-zero
671
+ on cleanup failures (data-integrity gate, distinct from the
672
+ HIGH-finding gate).
673
+
674
+ - **TASK-151: `probe-security` snapshot+restore cleanup for PUT/PATCH.**
675
+ Cleanup used to be `DELETE-if-2xx`, which silently destroyed live data
676
+ on rename'ы — a probe overwrote a Sentry DSN-key with the attack
677
+ payload and left it that way. probe-security now does a `GET` before
678
+ baseline (when there's a GET counterpart on the same path),
679
+ caches the original body, and restores it via `PUT`/`PATCH` after
680
+ every 2xx response. Strips read-only fields (`id`, `created_at`,
681
+ `updated_at`) from the restore body, forwards `If-Match` when
682
+ `requiresEtag` is set, and surfaces restore failures in
683
+ `verdict.cleanup.error` so they show up in the digest. POST keeps
684
+ the existing `DELETE`-counterpart cleanup.
685
+
686
+ - **TASK-152: `probe-security` partial-body fallback on PUT/PATCH.**
687
+ Sentry / Stripe / GitHub-shaped APIs accept partial PUT — sending the
688
+ spec's full body returns `422` and the proven-HIGH CRLF on
689
+ `subjectPrefix` lands in `INCONCLUSIVE-BASELINE`. probe-security now
690
+ retries the baseline with a single-key body per detected field; if any
691
+ partial baseline succeeds, attacks proceed using that shape and the
692
+ finding `reason` is annotated `[partial-body]`. Only PUT/PATCH —
693
+ partial bodies on POST would just trip required-field validation.
694
+
695
+ ### Added
696
+
697
+ - **TASK-138: `zond probe-security <classes>` — live SSRF / CRLF /
698
+ open-redirect probes.** Replaces the markdown templates the audit skill
699
+ used to ship for Phase 5.2/5.3 (one HIGH stored CRLF on Sentry came
700
+ from one of those templates — but only after hand-copying it per
701
+ endpoint). Detects vulnerable fields by name + `format` hints
702
+ (`*_url` / `webhook` / `format: uri` for SSRF; `subject` / `*_prefix`
703
+ / `name` / `description` for CRLF; `redirect` / `next` / `return_to`
704
+ for open-redirect), sends a **baseline-OK** request first (skips the
705
+ endpoint with `INCONCLUSIVE-BASELINE` if baseline ≠ 2xx — eliminates
706
+ the 5×404 noise the markdown templates produced on scope-locked
707
+ endpoints), then attacks each detected field with the class's
708
+ payloads. Classifies HIGH (5xx **or** payload echoed in 2xx body —
709
+ stored injection candidate), LOW (2xx, no echo — verify manually),
710
+ OK (4xx). Idempotent cleanup via DELETE counterpart. `--dry-run`
711
+ enumerates fields without sending requests. `--emit-tests <dir>`
712
+ produces regression YAML suites with `always: true` cleanup.
713
+
714
+ - **TASK-137: `probe-mass-assignment` body-FK auto-discovery.** Required
715
+ body fields named `*_id` / `*_slug` / `*_uuid` / `*_key` are now resolved
716
+ pre-baseline by hitting the matching collection list endpoint
717
+ (`audience_id` → `GET /audiences`). Eliminates most
718
+ `inconclusive-baseline` noise, where the spec-generated random UUID was
719
+ rejected before extras ever reached validation. Enabled by default
720
+ (gated by the existing `--discover` / `--no-discover` flag — same as
721
+ the path-param discovery from TASK-92). When discovery still misses an
722
+ FK, the INCONCLUSIVE summary now lists the unresolved field names so
723
+ the user knows exactly what to add to env. Follow-up
724
+ `--retry-inconclusive <run-id>` tracked as TASK-150.
725
+
726
+ - **TASK-136: `zond discover --api <name>` — auto-fill `.env.yaml` FK ids
727
+ from list-endpoints.** Phase 2.5 of an audit used to be manual: hit
728
+ `GET /audiences`, `GET /projects`, etc., copy slugs into `.env.yaml`,
729
+ repeat for every FK. `discover` walks `.api-resources.yaml`, finds owner
730
+ list-endpoints for each path-FK var, calls them with the workspace
731
+ `auth_token`, and proposes a diff. Suffix-aware extraction (`*_slug` →
732
+ `slug`, `*_uuid` → `uuid`, `*_id` → `id`). Default dry-run; `--apply`
733
+ writes with a `.env.yaml.bak` backup. Skips vars already filled with a
734
+ non-placeholder value. v1 limitation: only collection-level list
735
+ endpoints (no nested paths — that's TASK-137 territory).
736
+
737
+ - **TASK-139: `zond generate --explain`.** Prints a per-POST diagnostic table
738
+ (`resource | post | get/{id} | put/patch | delete | list | verdict | reason`)
739
+ without writing files, so you can debug "why didn't `generate` emit a CRUD
740
+ chain for resource X?" against a real spec. Pairs with the relaxed
741
+ detector below.
742
+
743
+ ### Changed
744
+
745
+ - **TASK-139: relaxed CRUD detector — trailing slashes and id-like field
746
+ names.** `detectCrudGroups` now matches `POST /alerts/` against
747
+ `GET /alerts/{id}` (and any combination of trailing slashes), and
748
+ `getCaptureField` looks for the path-param name (`{slug}` → `slug`,
749
+ `{rule_id}` → `id`/`rule_id`) plus `slug`/`uuid`/`key`/`version`/`name`
750
+ string fields before falling back to type-shape heuristics. Together
751
+ these produce CRUD chains for Sentry-style resources (alert-rules,
752
+ dashboards, releases) that previously fell through the strict regex.
753
+
754
+ ### Changed
755
+
756
+ - **TASK-135: `probe-validation` no longer short-circuits on parent path
757
+ params.** Probes now emit non-attacked path parameters as runtime
758
+ placeholders (`{{organization_id_or_slug}}`) so `zond run` resolves them
759
+ from `.env.yaml`. Previously every parent slot was baked as the
760
+ synthetic sentinel `nonexistent-zzzzz`, which made nested-path probes
761
+ return 404 from the parent before the leaf validator ever fired —
762
+ hiding real 5xx bugs in `repos/{repo}/commits`-style endpoints. Use
763
+ `--no-real-parents` to keep the legacy fully-synthetic rendering.
764
+
765
+ ### Added
766
+
767
+ - **TASK-110: `zond report case-study <failure-id>` — markdown drafts for
768
+ one failure.** Companion to TASK-107: zooms into a single `results.id`
769
+ and produces a ready-to-edit case-study (TL;DR, spec snippet, curl,
770
+ response, "why it matters", provenance) primed for `gh issue create
771
+ --body-file -` or a Slack write-up. Powers a **Case study draft**
772
+ button on the Run detail UI (clipboard via
773
+ `GET /api/results/:id/case-study.md`). Missing fields become explicit
774
+ `<TODO: ...>` placeholders.
775
+
776
+ - **TASK-107: `zond report export <run-id>` — single-file HTML run reports.**
777
+ Materialises a stored run as a self-contained HTML (inline CSS + JS, no
778
+ external assets) you can attach to a GitHub issue, drop into Slack, or
779
+ archive offline. Includes pass-rate ring, KPI strip, collapsible failure
780
+ cards with provenance + frozen OpenAPI excerpts, **Copy curl** and
781
+ **Copy as GitHub issue** buttons, failure-class filter chips, and an
782
+ endpoint × method coverage map. Light/dark themes via
783
+ `prefers-color-scheme`; print-friendly for browser-PDF export.
784
+
785
+ ### Breaking
786
+
787
+ - **TASK-73: top-level `--json` removed.** `--json` was previously a global
788
+ option that propagated to every subcommand; on `run` it collided with
789
+ `--report json` and crashed (`paths[0] must be of type string`). It is now
790
+ a per-command option attached only to subcommands that produce a JSON
791
+ envelope. **Migration**: replace `zond run … --json` with
792
+ `zond run … --report json`. Other commands (`db diagnose --json`,
793
+ `validate --json`, `coverage --json`, …) keep working unchanged — only
794
+ the flag's scope changed, not its meaning.
795
+
796
+ ### Round-2 papercuts continued (TASK-70 / TASK-72 / TASK-75)
797
+
798
+ - **TASK-72: `--tag` no longer silently swallows YAML parse errors.** Tag
799
+ filter prints every parse error as a warning; if every file fails to parse
800
+ the run exits 2; if the tag filter empties to zero AND parse errors exist,
801
+ the run exits 1 with a message pointing at the parse failures instead of
802
+ the misleading "No suites match the specified tags".
803
+
804
+ - **TASK-75: pre-flight `{{var}}` check + `--strict-vars`.** Every `{{var}}`
805
+ reference is checked against env, parameterize, set keys and prior-step
806
+ captures before a request goes out. Missing references emit a warning by
807
+ default; `--strict-vars` makes them a hard-fail (exit 2) so CI catches
808
+ typos before the server returns "invalid email format".
809
+
810
+ - **TASK-70: env_issue overrides per-failure recommendation.** When
811
+ `db diagnose` detects a run-level env_issue, every non-5xx failure's
812
+ `recommended_action` becomes `fix_env` and the misleading per-failure
813
+ hint/schema_hint is suppressed. Real backend bugs (5xx) keep
814
+ `report_backend_bug`.
815
+
5
816
  ## [0.22.0] — 2026-04-29
6
817
 
7
818
  ### Round-2 papercuts (TASK-68 → TASK-86)