@kirrosh/zond 0.26.0 → 0.27.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 (261) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +22 -21
  3. package/bin/zond.mjs +23 -0
  4. package/package.json +13 -16
  5. package/scripts/npm/postinstall.mjs +76 -0
  6. package/src/CLAUDE.md +0 -112
  7. package/src/bun-types.d.ts +0 -5
  8. package/src/cli/argv.ts +0 -122
  9. package/src/cli/commands/add-api.ts +0 -146
  10. package/src/cli/commands/api/annotate/idempotency.ts +0 -59
  11. package/src/cli/commands/api/annotate/index.ts +0 -880
  12. package/src/cli/commands/api/annotate/lifecycle.ts +0 -74
  13. package/src/cli/commands/api/annotate/overlay.ts +0 -206
  14. package/src/cli/commands/api/annotate/pagination.ts +0 -64
  15. package/src/cli/commands/api/annotate/prompts.ts +0 -220
  16. package/src/cli/commands/api/annotate/readback.ts +0 -58
  17. package/src/cli/commands/api/annotate/resources.ts +0 -91
  18. package/src/cli/commands/api/annotate/seed-bodies.ts +0 -61
  19. package/src/cli/commands/audit.ts +0 -786
  20. package/src/cli/commands/catalog.ts +0 -97
  21. package/src/cli/commands/check.ts +0 -361
  22. package/src/cli/commands/checks.ts +0 -1072
  23. package/src/cli/commands/ci-init.ts +0 -223
  24. package/src/cli/commands/clean.ts +0 -212
  25. package/src/cli/commands/cleanup.ts +0 -236
  26. package/src/cli/commands/completions.ts +0 -192
  27. package/src/cli/commands/coverage.ts +0 -872
  28. package/src/cli/commands/db.ts +0 -611
  29. package/src/cli/commands/describe.ts +0 -120
  30. package/src/cli/commands/discover.ts +0 -1356
  31. package/src/cli/commands/doctor.ts +0 -661
  32. package/src/cli/commands/fixtures.ts +0 -402
  33. package/src/cli/commands/generate.ts +0 -577
  34. package/src/cli/commands/init/agents-md.ts +0 -61
  35. package/src/cli/commands/init/bootstrap.ts +0 -111
  36. package/src/cli/commands/init/index.ts +0 -244
  37. package/src/cli/commands/init/skills.ts +0 -141
  38. package/src/cli/commands/init/templates/agents.md +0 -86
  39. package/src/cli/commands/init/templates/markdown.d.ts +0 -4
  40. package/src/cli/commands/init/templates/skills/warm-up-target.md +0 -122
  41. package/src/cli/commands/init/templates/skills/zond-checks.md +0 -621
  42. package/src/cli/commands/init/templates/skills/zond-seed.md +0 -114
  43. package/src/cli/commands/init/templates/skills/zond-triage.md +0 -272
  44. package/src/cli/commands/init/templates/skills/zond.md +0 -861
  45. package/src/cli/commands/init/templates/zond-config.yml +0 -14
  46. package/src/cli/commands/prepare-fixtures.ts +0 -97
  47. package/src/cli/commands/probe/_seed-bodies.ts +0 -52
  48. package/src/cli/commands/probe/mass-assignment.ts +0 -594
  49. package/src/cli/commands/probe/security.ts +0 -537
  50. package/src/cli/commands/probe/static.ts +0 -255
  51. package/src/cli/commands/probe/webhooks.ts +0 -163
  52. package/src/cli/commands/probe.ts +0 -535
  53. package/src/cli/commands/reference.ts +0 -87
  54. package/src/cli/commands/refresh-api.ts +0 -227
  55. package/src/cli/commands/remove-api.ts +0 -150
  56. package/src/cli/commands/report-bundle.ts +0 -310
  57. package/src/cli/commands/report.ts +0 -241
  58. package/src/cli/commands/request.ts +0 -548
  59. package/src/cli/commands/run.ts +0 -1142
  60. package/src/cli/commands/schema-from-runs.ts +0 -128
  61. package/src/cli/commands/secrets.ts +0 -133
  62. package/src/cli/commands/session.ts +0 -244
  63. package/src/cli/commands/use.ts +0 -74
  64. package/src/cli/index.ts +0 -55
  65. package/src/cli/json-envelope.ts +0 -108
  66. package/src/cli/json-schemas.ts +0 -314
  67. package/src/cli/output.ts +0 -40
  68. package/src/cli/program.ts +0 -219
  69. package/src/cli/resolve.ts +0 -105
  70. package/src/cli/runtime.ts +0 -7
  71. package/src/cli/safe-live.ts +0 -24
  72. package/src/cli/status-filter.ts +0 -114
  73. package/src/cli/util/api-context.ts +0 -85
  74. package/src/cli/version.ts +0 -8
  75. package/src/core/audit/persist.ts +0 -183
  76. package/src/core/checks/budget.ts +0 -59
  77. package/src/core/checks/checks/_crud-helpers.ts +0 -133
  78. package/src/core/checks/checks/_negative_mutator.ts +0 -133
  79. package/src/core/checks/checks/_readback-helpers.ts +0 -133
  80. package/src/core/checks/checks/content_type_conformance.ts +0 -39
  81. package/src/core/checks/checks/cross_call_references.ts +0 -147
  82. package/src/core/checks/checks/cursor_boundary_fuzzing.ts +0 -219
  83. package/src/core/checks/checks/ensure_resource_availability.ts +0 -62
  84. package/src/core/checks/checks/idempotency_replay.ts +0 -242
  85. package/src/core/checks/checks/ignored_auth.ts +0 -254
  86. package/src/core/checks/checks/index.ts +0 -68
  87. package/src/core/checks/checks/lifecycle_transitions.ts +0 -416
  88. package/src/core/checks/checks/missing_required_header.ts +0 -40
  89. package/src/core/checks/checks/negative_data_rejection.ts +0 -148
  90. package/src/core/checks/checks/not_a_server_error.ts +0 -35
  91. package/src/core/checks/checks/open_cors_on_sensitive.ts +0 -160
  92. package/src/core/checks/checks/pagination_invariants.ts +0 -419
  93. package/src/core/checks/checks/positive_data_acceptance.ts +0 -33
  94. package/src/core/checks/checks/rate_limit_headers_absent.ts +0 -77
  95. package/src/core/checks/checks/response_headers_conformance.ts +0 -74
  96. package/src/core/checks/checks/response_schema_conformance.ts +0 -30
  97. package/src/core/checks/checks/status_code_conformance.ts +0 -132
  98. package/src/core/checks/checks/unsupported_method.ts +0 -63
  99. package/src/core/checks/checks/use_after_free.ts +0 -78
  100. package/src/core/checks/index.ts +0 -30
  101. package/src/core/checks/mode.ts +0 -82
  102. package/src/core/checks/recommended-action.ts +0 -68
  103. package/src/core/checks/registry.ts +0 -78
  104. package/src/core/checks/runner.ts +0 -1461
  105. package/src/core/checks/sarif.ts +0 -230
  106. package/src/core/checks/spec-findings.ts +0 -308
  107. package/src/core/checks/stateful.ts +0 -121
  108. package/src/core/checks/types.ts +0 -305
  109. package/src/core/checks/zond-extensions.ts +0 -73
  110. package/src/core/classifier/recommended-action.ts +0 -251
  111. package/src/core/context/current.ts +0 -51
  112. package/src/core/context/session.ts +0 -78
  113. package/src/core/coverage/loader.ts +0 -216
  114. package/src/core/coverage/reasons.ts +0 -300
  115. package/src/core/diagnostics/db-analysis.ts +0 -666
  116. package/src/core/diagnostics/failure-class.ts +0 -140
  117. package/src/core/diagnostics/failure-hints.ts +0 -112
  118. package/src/core/diagnostics/spec-pointer.ts +0 -99
  119. package/src/core/diagnostics/suggested-fixes.ts +0 -155
  120. package/src/core/exporter/case-study/index.ts +0 -270
  121. package/src/core/exporter/curl.ts +0 -40
  122. package/src/core/exporter/exporter.ts +0 -48
  123. package/src/core/exporter/html-report/escape.ts +0 -24
  124. package/src/core/exporter/html-report/index.ts +0 -479
  125. package/src/core/exporter/html-report/script.ts +0 -100
  126. package/src/core/exporter/html-report/styles.ts +0 -408
  127. package/src/core/generator/catalog-builder.ts +0 -179
  128. package/src/core/generator/chunker.ts +0 -78
  129. package/src/core/generator/coverage-phase.ts +0 -0
  130. package/src/core/generator/coverage-scanner.ts +0 -87
  131. package/src/core/generator/data-factory.ts +0 -759
  132. package/src/core/generator/describe.ts +0 -302
  133. package/src/core/generator/endpoint-warnings.ts +0 -52
  134. package/src/core/generator/fixtures-builder.ts +0 -332
  135. package/src/core/generator/index.ts +0 -14
  136. package/src/core/generator/openapi-reader.ts +0 -297
  137. package/src/core/generator/path-param-disambig.ts +0 -140
  138. package/src/core/generator/resources-builder.ts +0 -898
  139. package/src/core/generator/schema-utils.ts +0 -112
  140. package/src/core/generator/serializer.ts +0 -343
  141. package/src/core/generator/suite-generator.ts +0 -1301
  142. package/src/core/generator/types.ts +0 -63
  143. package/src/core/identity/identity-file.ts +0 -0
  144. package/src/core/lint/affects.ts +0 -28
  145. package/src/core/lint/config.ts +0 -96
  146. package/src/core/lint/format.ts +0 -42
  147. package/src/core/lint/index.ts +0 -94
  148. package/src/core/lint/reporter.ts +0 -128
  149. package/src/core/lint/rules/consistency.ts +0 -158
  150. package/src/core/lint/rules/heuristics.ts +0 -97
  151. package/src/core/lint/rules/strictness.ts +0 -109
  152. package/src/core/lint/types.ts +0 -96
  153. package/src/core/lint/walker.ts +0 -248
  154. package/src/core/meta/meta-store.ts +0 -11
  155. package/src/core/output/README.md +0 -73
  156. package/src/core/output/index.ts +0 -13
  157. package/src/core/output/run.ts +0 -91
  158. package/src/core/output/types.ts +0 -122
  159. package/src/core/parser/dynamic-values.ts +0 -160
  160. package/src/core/parser/env-interpolation.ts +0 -104
  161. package/src/core/parser/filter.ts +0 -97
  162. package/src/core/parser/schema.ts +0 -359
  163. package/src/core/parser/types.ts +0 -117
  164. package/src/core/parser/variables.ts +0 -0
  165. package/src/core/parser/yaml-parser.ts +0 -181
  166. package/src/core/probe/bootstrap.ts +0 -34
  167. package/src/core/probe/dry-run-envelope.ts +0 -61
  168. package/src/core/probe/mass-assignment/classify.ts +0 -175
  169. package/src/core/probe/mass-assignment/cleanup.ts +0 -52
  170. package/src/core/probe/mass-assignment/digest.ts +0 -114
  171. package/src/core/probe/mass-assignment/orchestrator.ts +0 -459
  172. package/src/core/probe/mass-assignment/regression.ts +0 -141
  173. package/src/core/probe/mass-assignment/suspects.ts +0 -92
  174. package/src/core/probe/mass-assignment/types.ts +0 -135
  175. package/src/core/probe/mass-assignment-probe-class.ts +0 -198
  176. package/src/core/probe/mass-assignment-probe.ts +0 -27
  177. package/src/core/probe/mass-assignment-template.ts +0 -240
  178. package/src/core/probe/method-probe.ts +0 -164
  179. package/src/core/probe/method-shared.ts +0 -69
  180. package/src/core/probe/negative-probe.ts +0 -691
  181. package/src/core/probe/orphan-tracker.ts +0 -188
  182. package/src/core/probe/path-discovery.ts +0 -439
  183. package/src/core/probe/probe-harness.ts +0 -119
  184. package/src/core/probe/registry.ts +0 -89
  185. package/src/core/probe/runner.ts +0 -136
  186. package/src/core/probe/security/baseline.ts +0 -174
  187. package/src/core/probe/security/classify.ts +0 -341
  188. package/src/core/probe/security/cleanup.ts +0 -125
  189. package/src/core/probe/security/detectors.ts +0 -71
  190. package/src/core/probe/security/digest.ts +0 -104
  191. package/src/core/probe/security/orchestrator.ts +0 -398
  192. package/src/core/probe/security/regression.ts +0 -103
  193. package/src/core/probe/security/types.ts +0 -151
  194. package/src/core/probe/security-probe-class.ts +0 -207
  195. package/src/core/probe/security-probe.ts +0 -32
  196. package/src/core/probe/shared.ts +0 -531
  197. package/src/core/probe/static-probe-class.ts +0 -125
  198. package/src/core/probe/types.ts +0 -165
  199. package/src/core/probe/verdict-aggregator.ts +0 -33
  200. package/src/core/probe/webhooks-probe.ts +0 -282
  201. package/src/core/reporter/console.ts +0 -240
  202. package/src/core/reporter/index.ts +0 -22
  203. package/src/core/reporter/json.ts +0 -22
  204. package/src/core/reporter/junit.ts +0 -93
  205. package/src/core/reporter/ndjson.ts +0 -37
  206. package/src/core/reporter/types.ts +0 -15
  207. package/src/core/runner/assertions.ts +0 -383
  208. package/src/core/runner/async-pool.ts +0 -108
  209. package/src/core/runner/auth-path.ts +0 -8
  210. package/src/core/runner/ci-context.ts +0 -72
  211. package/src/core/runner/executor.ts +0 -652
  212. package/src/core/runner/expr-eval.ts +0 -41
  213. package/src/core/runner/form-encode.ts +0 -41
  214. package/src/core/runner/http-client.ts +0 -224
  215. package/src/core/runner/learn-drift.ts +0 -293
  216. package/src/core/runner/preflight-vars.ts +0 -153
  217. package/src/core/runner/progress-tracker.ts +0 -73
  218. package/src/core/runner/rate-limiter.ts +0 -185
  219. package/src/core/runner/run-kind.ts +0 -45
  220. package/src/core/runner/schema-validator.ts +0 -308
  221. package/src/core/runner/send-request.ts +0 -232
  222. package/src/core/runner/transforms.ts +0 -65
  223. package/src/core/runner/types.ts +0 -94
  224. package/src/core/secrets/registry.ts +0 -164
  225. package/src/core/secrets/secrets-file.ts +0 -115
  226. package/src/core/selectors/operation-filter.ts +0 -144
  227. package/src/core/setup-api.ts +0 -590
  228. package/src/core/severity/category.ts +0 -94
  229. package/src/core/severity/index.ts +0 -58
  230. package/src/core/spec/infer-schema.ts +0 -102
  231. package/src/core/spec/layers.ts +0 -154
  232. package/src/core/spec/merge-specs.ts +0 -156
  233. package/src/core/spec/schema-from-runs.ts +0 -117
  234. package/src/core/spec/schema-overlay.ts +0 -130
  235. package/src/core/util/ajv.ts +0 -13
  236. package/src/core/util/format-eta.ts +0 -21
  237. package/src/core/util/headers.ts +0 -9
  238. package/src/core/util/url.ts +0 -24
  239. package/src/core/utils.ts +0 -13
  240. package/src/core/workspace/config.ts +0 -129
  241. package/src/core/workspace/fixture-gap-report.ts +0 -84
  242. package/src/core/workspace/fixture-gaps.ts +0 -71
  243. package/src/core/workspace/manifest.ts +0 -283
  244. package/src/core/workspace/output-rotation.ts +0 -62
  245. package/src/core/workspace/root.ts +0 -96
  246. package/src/core/workspace/triage-path.ts +0 -87
  247. package/src/db/lint-runs.ts +0 -47
  248. package/src/db/migrate.ts +0 -128
  249. package/src/db/migrations/0001_run_kind.sql +0 -25
  250. package/src/db/migrations/0002_run_kind_request.sql +0 -59
  251. package/src/db/migrations/sql.d.ts +0 -4
  252. package/src/db/queries/collections.ts +0 -133
  253. package/src/db/queries/coverage.ts +0 -9
  254. package/src/db/queries/dashboard.ts +0 -59
  255. package/src/db/queries/results.ts +0 -216
  256. package/src/db/queries/runs.ts +0 -289
  257. package/src/db/queries/sessions.ts +0 -42
  258. package/src/db/queries/settings.ts +0 -28
  259. package/src/db/queries/types.ts +0 -172
  260. package/src/db/queries.ts +0 -75
  261. package/src/db/schema.ts +0 -298
@@ -1,270 +0,0 @@
1
- import type { RunRecord, StoredStepResult } from "../../../db/queries.ts";
2
- import type { FailureClass } from "../../diagnostics/failure-class.ts";
3
- import { buildCurl } from "../curl.ts";
4
-
5
- export interface CaseStudyOptions {
6
- result: StoredStepResult;
7
- run: RunRecord;
8
- /** Pulled from OpenAPI `info.title` if available. */
9
- specTitle?: string | null;
10
- specVersion?: string | null;
11
- zondVersion: string;
12
- /** TASK-164 (m-9 P8): cap response/request bodies to N bytes. 0 or
13
- * unset = no cap. The CLI wrapper defaults to 8 KB. */
14
- bodyCapBytes?: number;
15
- /** ARV-106/107: short registry slug (`--api <name>`). When set, the
16
- * case-study renders a `zond request --api <name>` alternative below the
17
- * curl repro and falls back to it for the "API" field when specTitle is
18
- * null. */
19
- apiName?: string | null;
20
- /** ARV-107: loaded OpenAPI spec. When provided, the renderer auto-extracts
21
- * the relevant operation block for the "What the spec says" section instead
22
- * of leaving the `<TODO: paste …>` placeholder. */
23
- specDoc?: OpenApiDocLike | null;
24
- }
25
-
26
- /** Minimal shape we touch from an OpenAPI spec — keeps this module
27
- * framework-free. */
28
- interface OpenApiDocLike {
29
- paths?: Record<string, Record<string, unknown>> | null;
30
- components?: { schemas?: Record<string, unknown> } | null;
31
- }
32
-
33
- function capBody(content: string | null | undefined, capBytes: number | undefined): string | null {
34
- if (!content) return content ?? null;
35
- if (!capBytes || capBytes <= 0 || content.length <= capBytes) return content;
36
- const dropped = content.length - capBytes;
37
- return `${content.slice(0, capBytes)}\n[truncated ${dropped} bytes; first ${capBytes} shown; full body in run DB]`;
38
- }
39
-
40
- const CLASS_HUMAN: Record<FailureClass, string> = {
41
- definitely_bug: "definitely_bug",
42
- likely_bug: "likely_bug",
43
- quirk: "quirk",
44
- env_issue: "env_issue",
45
- cascade: "cascade",
46
- };
47
-
48
- const TODO = (hint: string): string => `<TODO: ${hint}>`;
49
-
50
- function tryPretty(s: string | null | undefined): string {
51
- if (!s) return "";
52
- try {
53
- return JSON.stringify(JSON.parse(s), null, 2);
54
- } catch {
55
- return s;
56
- }
57
- }
58
-
59
- function extractPath(url: string | null): string {
60
- if (!url) return TODO("path");
61
- try {
62
- return new URL(url).pathname;
63
- } catch {
64
- return url;
65
- }
66
- }
67
-
68
- function shortDescription(result: StoredStepResult): string {
69
- if (result.failure_class_reason) return result.failure_class_reason;
70
- if (result.error_message) return result.error_message;
71
- if (result.response_status != null && result.response_status >= 500) {
72
- return `unexpected ${result.response_status} from server`;
73
- }
74
- if (result.response_status != null && result.response_status >= 400) {
75
- return `unexpected ${result.response_status} response`;
76
- }
77
- return TODO("one-line summary of the failure");
78
- }
79
-
80
- function tldrLine(result: StoredStepResult): string {
81
- switch (result.failure_class) {
82
- case "definitely_bug":
83
- return "Backend bug — endpoint contradicts its own contract.";
84
- case "likely_bug":
85
- return "Suspicious behaviour — contract leaves it ambiguous, but the response is hard to defend.";
86
- case "quirk":
87
- return "Documented quirk worth flagging in onboarding docs / a spec PR.";
88
- case "env_issue":
89
- return "Environment / fixture issue — not an API bug, but blocks the test from running.";
90
- default:
91
- return TODO("one-sentence takeaway");
92
- }
93
- }
94
-
95
- function whyItMatters(result: StoredStepResult): string {
96
- switch (result.failure_class) {
97
- case "definitely_bug":
98
- case "likely_bug": {
99
- const reason = result.failure_class_reason ?? TODO("explain why this is a bug — what user-visible impact?");
100
- const failedAsserts = result.assertions.filter((a) => !a.passed);
101
- const detail = failedAsserts.length > 0
102
- ? failedAsserts
103
- .map((a) => `- \`${a.rule}\` at \`${a.field}\`: expected \`${JSON.stringify(a.expected)}\`, got \`${JSON.stringify(a.actual)}\``)
104
- .join("\n")
105
- : "";
106
- return detail ? `${reason}\n\nFailed assertions:\n\n${detail}` : reason;
107
- }
108
- case "quirk":
109
- return `Spec implies one thing, server does another:\n\n- Expected: ${TODO("what the spec promised")}\n- Actual: ${TODO("what the server did")}`;
110
- case "env_issue":
111
- return result.failure_class_reason ?? TODO("describe what the test setup needed and didn't get");
112
- default:
113
- return TODO("explain why this finding matters to the reader");
114
- }
115
- }
116
-
117
- function howZondFoundIt(result: StoredStepResult): string {
118
- const prov = result.provenance;
119
- if (!prov || prov.type === "manual") {
120
- return `Manually authored test case: \`${result.suite_name}\` → \`${result.test_name}\`.`;
121
- }
122
- const generator = prov.generator ?? "openapi-generated";
123
- const lines: string[] = [];
124
- lines.push(`Generated by \`${generator}\` (${prov.type ?? "unknown"}).`);
125
- if (prov.endpoint) lines.push(`Targeted endpoint: \`${prov.endpoint}\`.`);
126
- if (prov.response_branch) lines.push(`Asserted response branch: \`${prov.response_branch}\`.`);
127
- lines.push(`Suite: \`${result.suite_name}\` → \`${result.test_name}\`.`);
128
- return lines.join("\n");
129
- }
130
-
131
- function matchSpecOperation(
132
- spec: OpenApiDocLike,
133
- method: string,
134
- concretePath: string,
135
- ): { template: string; operation: Record<string, unknown> } | null {
136
- const paths = spec.paths;
137
- if (!paths) return null;
138
- const want = method.toLowerCase();
139
- // Prefer an exact path hit (no path-params), then fall back to a regex-based
140
- // template match. Both passes ignore query strings.
141
- const cleanPath = concretePath.split("?")[0] ?? concretePath;
142
- if (paths[cleanPath]) {
143
- const op = (paths[cleanPath] as Record<string, unknown>)[want];
144
- if (op && typeof op === "object") return { template: cleanPath, operation: op as Record<string, unknown> };
145
- }
146
- for (const [template, item] of Object.entries(paths)) {
147
- if (template === cleanPath) continue;
148
- if (!template.includes("{")) continue;
149
- const re = new RegExp("^" + template.replace(/\{[^}]+\}/g, "[^/]+") + "$");
150
- if (!re.test(cleanPath)) continue;
151
- const op = (item as Record<string, unknown>)[want];
152
- if (op && typeof op === "object") return { template, operation: op as Record<string, unknown> };
153
- }
154
- return null;
155
- }
156
-
157
- function specSnippet(result: StoredStepResult, specDoc?: OpenApiDocLike | null): string {
158
- if (!result.spec_pointer && !result.spec_excerpt) {
159
- // ARV-107: try to recover the operation slice from the loaded spec.
160
- if (specDoc && result.request_method && result.request_url) {
161
- const path = extractPath(result.request_url);
162
- const match = matchSpecOperation(specDoc, result.request_method, path);
163
- if (match) {
164
- const ptr = `JSON pointer: \`#/paths/${match.template.replace(/\//g, "~1")}/${result.request_method.toLowerCase()}\`\n\n`;
165
- return ptr + "```json\n" + JSON.stringify(match.operation, null, 2) + "\n```";
166
- }
167
- }
168
- return TODO("paste the relevant slice of the OpenAPI spec");
169
- }
170
- const ptr = result.spec_pointer ? `JSON pointer: \`${result.spec_pointer}\`\n\n` : "";
171
- const excerpt = result.spec_excerpt
172
- ? "```json\n" + tryPretty(result.spec_excerpt) + "\n```"
173
- : TODO("snippet not captured at run time");
174
- return ptr + excerpt;
175
- }
176
-
177
- function provenanceLine(result: StoredStepResult): string {
178
- const prov = result.provenance;
179
- if (!prov) return `Test source: \`${result.suite_name}\` (no provenance metadata).`;
180
- const parts: string[] = [];
181
- if (prov.generator) parts.push(`generator \`${prov.generator}\``);
182
- if (prov.spec) parts.push(`spec \`${prov.spec}\``);
183
- if (prov.response_branch) parts.push(`branch \`${prov.response_branch}\``);
184
- return parts.length > 0
185
- ? `Provenance: ${parts.join(", ")}.`
186
- : `Provenance: \`${prov.type ?? "unknown"}\`.`;
187
- }
188
-
189
- function buildZondRequestLine(apiName: string, result: StoredStepResult): string {
190
- const method = (result.request_method ?? "GET").toUpperCase();
191
- const path = extractPath(result.request_url);
192
- const parts: string[] = [`zond request --api ${apiName} ${method} ${path}`];
193
- if (result.request_body) {
194
- const escaped = result.request_body.replace(/'/g, `'\\''`);
195
- parts.push(` --json '${escaped}'`);
196
- }
197
- return parts.join(" \\\n");
198
- }
199
-
200
- export function renderCaseStudy(opts: CaseStudyOptions): string {
201
- const { result, run, specTitle, specVersion, zondVersion, apiName } = opts;
202
- const method = (result.request_method ?? TODO("HTTP method")).toUpperCase();
203
- const path = extractPath(result.request_url);
204
- const fc = result.failure_class ? CLASS_HUMAN[result.failure_class] : TODO("classification");
205
-
206
- // ARV-107: cascade — explicit spec title, then registry slug (`--api`), then
207
- // collection-id breadcrumb, then unrecoverable.
208
- const apiLine = specTitle
209
- ? `${specTitle}${specVersion ? ` ${specVersion}` : ""}`
210
- : apiName
211
- ? apiName
212
- : (run.collection_id != null ? TODO("API title — spec was not loadable at export time") : TODO("API name"));
213
-
214
- const responseStatus = result.response_status != null ? String(result.response_status) : "no response";
215
- const cappedBody = capBody(result.response_body, opts.bodyCapBytes);
216
- const responseBody = cappedBody
217
- ? "```json\n" + tryPretty(cappedBody) + "\n```"
218
- : (result.error_message ? `Network/runtime error: \`${result.error_message}\`` : "_(empty body)_");
219
-
220
- // ARV-106: prefer the `zond request` form when an api slug is known
221
- // (skill anti-curl rule). The curl block stays as a copy-paste fallback for
222
- // recipients without zond installed, with a redacted Authorization header.
223
- const reproAlt = apiName
224
- ? `\n\n_Or with zond:_\n\n\`\`\`bash\n${buildZondRequestLine(apiName, result)}\n\`\`\``
225
- : "";
226
-
227
- return `# ${method} ${path} — ${shortDescription(result)}
228
-
229
- ## TL;DR
230
-
231
- - **What we found:** ${fc}
232
- - **What went wrong:** ${tldrLine(result)}
233
- - **Repro:** see below
234
-
235
- ## Context
236
-
237
- - **API:** ${apiLine}
238
- - **Endpoint:** \`${method} ${path}\`
239
- - ${provenanceLine(result)}
240
-
241
- ### What the spec says
242
-
243
- ${specSnippet(result, opts.specDoc)}
244
-
245
- ## Repro
246
-
247
- \`\`\`bash
248
- ${buildCurl(result)}
249
- \`\`\`${reproAlt}
250
-
251
- ## What happened
252
-
253
- - **Status:** ${responseStatus}
254
- - **Duration:** ${result.duration_ms} ms
255
-
256
- ${responseBody}
257
-
258
- ## Why it matters
259
-
260
- ${whyItMatters(result)}
261
-
262
- ## How zond found it
263
-
264
- ${howZondFoundIt(result)}
265
-
266
- ---
267
-
268
- _Generated by zond ${zondVersion} from run #${run.id} (result #${result.id})._
269
- `;
270
- }
@@ -1,40 +0,0 @@
1
- import type { StoredStepResult } from "../../db/queries.ts";
2
-
3
- export interface BuildCurlOptions {
4
- /** "redacted" — emit `Authorization: Bearer <REDACTED>` placeholder so the
5
- * reader knows the original request was authenticated (ARV-106). "omit" —
6
- * legacy behaviour. Defaults to "redacted". */
7
- authHeader?: "redacted" | "omit";
8
- }
9
-
10
- function isRemoteUrl(url: string | null | undefined): boolean {
11
- if (!url) return false;
12
- try {
13
- const u = new URL(url);
14
- if (u.protocol !== "http:" && u.protocol !== "https:") return false;
15
- const host = u.hostname.toLowerCase();
16
- return host !== "localhost" && host !== "127.0.0.1" && host !== "::1" && host !== "0.0.0.0";
17
- } catch {
18
- return false;
19
- }
20
- }
21
-
22
- /** Single-quote-wrapped curl, safe-ish for shells. */
23
- export function buildCurl(step: StoredStepResult, options: BuildCurlOptions = {}): string {
24
- const parts: string[] = ["curl"];
25
- const method = step.request_method?.toUpperCase();
26
- if (method && method !== "GET") {
27
- parts.push("-X", method);
28
- }
29
- const wantAuth = (options.authHeader ?? "redacted") === "redacted" && isRemoteUrl(step.request_url);
30
- if (wantAuth) {
31
- parts.push("-H", "'Authorization: Bearer <REDACTED — replace with your token>'");
32
- }
33
- if (step.request_body) {
34
- const escaped = step.request_body.replace(/'/g, `'\\''`);
35
- parts.push("-H", "'Content-Type: application/json'");
36
- parts.push("-d", `'${escaped}'`);
37
- }
38
- parts.push(`'${step.request_url ?? ""}'`);
39
- return parts.join(" ");
40
- }
@@ -1,48 +0,0 @@
1
- /**
2
- * Single sanitization seam for everything zond writes to disk or stdout.
3
- *
4
- * Background: m-10 (TASK-166..168) introduced a secrets registry and
5
- * `redact()` helper. The first wave of work added `redact(...)` calls
6
- * inside individual exporters and reporters, which means a new exporter
7
- * is one missing call away from leaking a secret. TASK-186 collapses
8
- * that risk: every exporter declares a pure `render()` and goes through
9
- * the {@link runExporter} pipeline, which applies the sanitizer exactly
10
- * once at the boundary.
11
- *
12
- * The interface is deliberately string-out only — every consumer
13
- * (writeFile, console.log, HTTP body) accepts a string anyway.
14
- */
15
-
16
- import { redact } from "../secrets/registry.ts";
17
-
18
- export interface Exporter<I, O = void> {
19
- /** Stable identifier — used in logs and tests. */
20
- readonly name: string;
21
- /** Mime hint for the rendered payload. */
22
- readonly mime: string;
23
- /** Pure render — no I/O, no redaction. Receives caller-supplied opts. */
24
- render(input: I, opts?: O): string;
25
- }
26
-
27
- /**
28
- * Run an exporter's render through the sanitizer pipeline. This is the
29
- * only place sanitization happens for exporter output — render() must
30
- * NOT call `redact()` itself, and callers must NOT redact again on top.
31
- *
32
- * Sanitization is currently a single pass of {@link redact}; future
33
- * sanitizer rules (e.g. identity scrubbing) will plug in here so every
34
- * exporter inherits them automatically.
35
- */
36
- export function runExporter<I, O>(exporter: Exporter<I, O>, input: I, opts?: O): string {
37
- return applySanitizer(exporter.render(input, opts));
38
- }
39
-
40
- /**
41
- * Sanitizer pipeline used by {@link runExporter}. Exposed for the
42
- * handful of sites that build their payload outside the exporter
43
- * interface (e.g. probe digests assembled inline) so they can opt into
44
- * the same single-pass redaction without duplicating logic.
45
- */
46
- export function applySanitizer(payload: string): string {
47
- return redact(payload);
48
- }
@@ -1,24 +0,0 @@
1
- // Minimal HTML escaper for safe inlining of user-controlled strings
2
- // (request URLs, response bodies, error messages) into the single-file report.
3
-
4
- const HTML_ESCAPES: Record<string, string> = {
5
- "&": "&amp;",
6
- "<": "&lt;",
7
- ">": "&gt;",
8
- '"': "&quot;",
9
- "'": "&#39;",
10
- };
11
-
12
- export function escapeHtml(input: string | null | undefined): string {
13
- if (input == null) return "";
14
- return String(input).replace(/[&<>"']/g, (ch) => HTML_ESCAPES[ch] ?? ch);
15
- }
16
-
17
- export function tryPrettyJson(s: string | null | undefined): string {
18
- if (!s) return "";
19
- try {
20
- return JSON.stringify(JSON.parse(s), null, 2);
21
- } catch {
22
- return s;
23
- }
24
- }