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