@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,130 +0,0 @@
1
- /**
2
- * ARV-176: response-schema overlay. `schema-from-runs` (ARV-175) emits a
3
- * `patch.schema.json` of inferred response schemas keyed by `METHOD /path` →
4
- * status. `refresh-api --merge-schema` folds that patch into a persistent
5
- * overlay (`apis/<name>/.api-schema.local.yaml`) and applies it onto the
6
- * freshly-pulled spec.json.
7
- *
8
- * Why a dedicated overlay file (not `.api-resources.local.yaml`): that file
9
- * is resource-shaped (ResourceYaml[]); response schemas are a different
10
- * dimension and shoehorning them in would muddy both. Same survives-refresh
11
- * contract as ARV-111's resource overlay — refresh-api re-applies it on every
12
- * run, so an upstream re-pull never loses the mined schemas.
13
- */
14
-
15
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
16
- import { join } from "node:path";
17
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
18
- import type { JsonSchema } from "./infer-schema.ts";
19
-
20
- export const SCHEMA_OVERLAY_FILENAME = ".api-schema.local.yaml";
21
-
22
- /** `METHOD /path` → status code → JSON Schema. */
23
- export type ResponseSchemaPatch = Record<string, Record<string, JsonSchema>>;
24
-
25
- interface SchemaOverlayFile {
26
- version: 1;
27
- response_schemas: ResponseSchemaPatch;
28
- }
29
-
30
- export function overlayPath(baseDir: string): string {
31
- return join(baseDir, SCHEMA_OVERLAY_FILENAME);
32
- }
33
-
34
- /** Load the overlay for an API, or null when absent. */
35
- export function loadSchemaOverlay(baseDir: string): ResponseSchemaPatch | null {
36
- const p = overlayPath(baseDir);
37
- if (!existsSync(p)) return null;
38
- try {
39
- const parsed = parseYaml(readFileSync(p, "utf-8")) as Partial<SchemaOverlayFile> | null;
40
- if (!parsed || typeof parsed !== "object") return null;
41
- return parsed.response_schemas ?? null;
42
- } catch {
43
- return null;
44
- }
45
- }
46
-
47
- /** Union two patches; entries in `incoming` win on collision. */
48
- export function mergePatch(base: ResponseSchemaPatch | null, incoming: ResponseSchemaPatch): ResponseSchemaPatch {
49
- const out: ResponseSchemaPatch = {};
50
- for (const [ep, byStatus] of Object.entries(base ?? {})) out[ep] = { ...byStatus };
51
- for (const [ep, byStatus] of Object.entries(incoming)) {
52
- out[ep] = { ...(out[ep] ?? {}), ...byStatus };
53
- }
54
- return out;
55
- }
56
-
57
- /** Persist the overlay (sorted for a stable, diff-friendly file). */
58
- export function saveSchemaOverlay(baseDir: string, patch: ResponseSchemaPatch): void {
59
- const sorted: ResponseSchemaPatch = {};
60
- for (const ep of Object.keys(patch).sort()) {
61
- const byStatus = patch[ep]!;
62
- sorted[ep] = {};
63
- for (const st of Object.keys(byStatus).sort()) sorted[ep]![st] = byStatus[st]!;
64
- }
65
- const file: SchemaOverlayFile = { version: 1, response_schemas: sorted };
66
- writeFileSync(overlayPath(baseDir), stringifyYaml(file), "utf-8");
67
- }
68
-
69
- export interface ApplyOverlayResult {
70
- /** `METHOD /path status` labels that got a schema written. */
71
- applied: string[];
72
- /** Labels skipped because a schema already existed (no --force). */
73
- preserved: string[];
74
- /** Labels skipped because the endpoint no longer exists upstream. */
75
- conflicts: string[];
76
- }
77
-
78
- const HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
79
-
80
- /**
81
- * Apply a response-schema patch onto a dereferenced OpenAPI doc, in place.
82
- * Fills `responses.<status>.content['application/json'].schema` where it is
83
- * absent (or with `force`). An endpoint/method missing from the doc is a
84
- * conflict (upstream drift) and is skipped, not fabricated.
85
- */
86
- export function applySchemaOverlay(
87
- doc: unknown,
88
- patch: ResponseSchemaPatch,
89
- opts: { force?: boolean } = {},
90
- ): ApplyOverlayResult {
91
- const result: ApplyOverlayResult = { applied: [], preserved: [], conflicts: [] };
92
- const paths = (doc as { paths?: Record<string, unknown> }).paths;
93
- if (!paths) {
94
- for (const [ep, byStatus] of Object.entries(patch)) {
95
- for (const st of Object.keys(byStatus)) result.conflicts.push(`${ep} ${st}`);
96
- }
97
- return result;
98
- }
99
-
100
- for (const [endpoint, byStatus] of Object.entries(patch)) {
101
- const sp = endpoint.indexOf(" ");
102
- const method = endpoint.slice(0, sp).toLowerCase();
103
- const path = endpoint.slice(sp + 1);
104
- const pathItem = paths[path] as Record<string, unknown> | undefined;
105
- const op = pathItem && HTTP_METHODS.includes(method)
106
- ? (pathItem[method] as { responses?: Record<string, unknown> } | undefined)
107
- : undefined;
108
-
109
- for (const [status, schema] of Object.entries(byStatus)) {
110
- const label = `${endpoint} ${status}`;
111
- if (!op) {
112
- result.conflicts.push(label);
113
- continue;
114
- }
115
- if (!op.responses) op.responses = {};
116
- const responses = op.responses as Record<string, { content?: Record<string, { schema?: unknown }> }>;
117
- if (!responses[status]) responses[status] = { content: {} } as { content: Record<string, { schema?: unknown }> };
118
- const resp = responses[status]!;
119
- if (!resp.content) resp.content = {};
120
- const mt = resp.content["application/json"] ?? (resp.content["application/json"] = {});
121
- if (mt.schema !== undefined && !opts.force) {
122
- result.preserved.push(label);
123
- continue;
124
- }
125
- mt.schema = schema;
126
- result.applied.push(label);
127
- }
128
- }
129
- return result;
130
- }
@@ -1,13 +0,0 @@
1
- import Ajv2020 from "ajv/dist/2020.js";
2
- import Ajv from "ajv";
3
- import addFormats from "ajv-formats";
4
-
5
- /** OpenAPI 3.1 → JSON Schema Draft 2020-12 (Ajv2020); 3.0 → Draft 4/7-ish
6
- * (plain Ajv). Both get the `ajv-formats` format keywords registered. */
7
- export function makeAjv(isV31: boolean, opts: ConstructorParameters<typeof Ajv>[0] = {}): Ajv {
8
- const ajv = isV31
9
- ? new (Ajv2020 as unknown as typeof Ajv)(opts)
10
- : new Ajv(opts);
11
- addFormats(ajv);
12
- return ajv;
13
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * ARV-249: human-readable duration formatter for ETA / progress lines.
3
- *
4
- * Distinct from `core/reporter/console.formatDuration`, which is geared to
5
- * per-step latencies (sub-second resolution, single unit). Here we drop
6
- * sub-second precision but emit two units once we cross a minute, so a
7
- * five-minute ETA reads `5m12s` instead of `5m 12s`.
8
- */
9
- export function formatEta(seconds: number): string {
10
- if (!Number.isFinite(seconds) || seconds < 0) return "?";
11
- const s = Math.round(seconds);
12
- if (s < 60) return `${s}s`;
13
- if (s < 3600) {
14
- const m = Math.floor(s / 60);
15
- const rem = s % 60;
16
- return rem > 0 ? `${m}m${rem}s` : `${m}m`;
17
- }
18
- const h = Math.floor(s / 3600);
19
- const m = Math.floor((s % 3600) / 60);
20
- return m > 0 ? `${h}h${m}m` : `${h}h`;
21
- }
@@ -1,9 +0,0 @@
1
- /** Case-insensitive check whether a header is already present in the map.
2
- * Deliberately not `new Headers(headers).has(name)` — the WHATWG Headers
3
- * constructor throws on values with invalid characters (e.g. raw CRLF),
4
- * which real interpolated var values can contain; a plain key scan never
5
- * throws on the value shape. */
6
- export function hasHeaderCI(headers: Record<string, string>, name: string): boolean {
7
- const lower = name.toLowerCase();
8
- return Object.keys(headers).some((k) => k.toLowerCase() === lower);
9
- }
@@ -1,24 +0,0 @@
1
- /** Concatenate baseUrl and path, stripping any trailing slashes from base. */
2
- export function joinBaseAndPath(baseUrl: string | undefined, path: string): string {
3
- if (!baseUrl) return path;
4
- return `${baseUrl.replace(/\/+$/, "")}${path}`;
5
- }
6
-
7
- export type QueryValue = string | number | boolean;
8
-
9
- /** Build a URL from base + path with an optional query record. Values are
10
- * coerced to strings via String(); URL-encoding is delegated to
11
- * URLSearchParams. */
12
- export function buildUrl(
13
- baseUrl: string | undefined,
14
- path: string,
15
- query?: Record<string, QueryValue>,
16
- ): string {
17
- let url = joinBaseAndPath(baseUrl, path);
18
- if (query && Object.keys(query).length > 0) {
19
- const params = new URLSearchParams();
20
- for (const [k, v] of Object.entries(query)) params.append(k, String(v));
21
- url += `?${params.toString()}`;
22
- }
23
- return url;
24
- }
package/src/core/utils.ts DELETED
@@ -1,13 +0,0 @@
1
- export function getByPath(obj: unknown, path: string, defaultVal?: unknown): unknown {
2
- // Normalize JSONPath-like bracket indexing (`data[0].id`) to dotted form
3
- // (`data.0.id`) so callers can use either spelling. Numeric segments also
4
- // index arrays correctly because `array["0"]` is equivalent to `array[0]`.
5
- const normalized = path.replace(/\[(\d+)\]/g, ".$1").replace(/^\./, "");
6
- const keys = normalized.split(".");
7
- let result: unknown = obj;
8
- for (const key of keys) {
9
- result = (result as Record<string, unknown>)?.[key];
10
- if (result === undefined) return defaultVal;
11
- }
12
- return result;
13
- }
@@ -1,129 +0,0 @@
1
- /**
2
- * Workspace `zond.config.yml` loader (TASK-301).
3
- *
4
- * Centralises read-only access to workspace-level defaults. Right now only
5
- * two fields are honoured:
6
- *
7
- * defaults:
8
- * timeout_ms: 30000 # used by cleanup / prepare-fixtures / probe
9
- * # mass-assignment / probe security / request
10
- * rate_limit: 5 # used by `zond run` (number, or "auto")
11
- *
12
- * Resolution chain across the CLI (highest wins):
13
- *
14
- * CLI flag → per-API .env.yaml meta → workspace defaults → hard-coded fallback
15
- *
16
- * Per-API overrides live in `apis/<name>/.env.yaml` as `rateLimit:` /
17
- * `timeoutMs:` (see `loadEnvMeta`); we deliberately don't carve a second
18
- * channel into this file to avoid two ways of saying the same thing.
19
- *
20
- * Read-once-and-cache: the file is parsed at most once per process from
21
- * the workspace root resolved by `findWorkspaceRoot`. Tests can call
22
- * `_resetWorkspaceConfigCache()` between runs.
23
- */
24
-
25
- import { existsSync, readFileSync } from "node:fs";
26
- import { join } from "node:path";
27
- import { findWorkspaceRoot } from "./root.ts";
28
-
29
- export interface WorkspaceDefaults {
30
- /** Per-request timeout in ms applied when the CLI flag and `.env.yaml` are silent. */
31
- timeoutMs?: number;
32
- /** Run-time rate limit (rps) or `"auto"`. */
33
- rateLimit?: number | "auto";
34
- }
35
-
36
- let cache: { root: string; defaults: WorkspaceDefaults } | null = null;
37
-
38
- function parseTimeoutMs(v: unknown): number | undefined {
39
- if (typeof v === "number" && Number.isFinite(v) && v > 0) return v;
40
- if (typeof v === "string") {
41
- const n = Number.parseInt(v, 10);
42
- if (Number.isFinite(n) && n > 0) return n;
43
- }
44
- return undefined;
45
- }
46
-
47
- function parseRateLimit(v: unknown): number | "auto" | undefined {
48
- if (v === "auto") return "auto";
49
- if (typeof v === "number" && Number.isFinite(v) && v > 0) return v;
50
- if (typeof v === "string") {
51
- if (v === "auto") return "auto";
52
- const n = Number.parseFloat(v);
53
- if (Number.isFinite(n) && n > 0) return n;
54
- }
55
- return undefined;
56
- }
57
-
58
- function readDefaults(configPath: string): WorkspaceDefaults {
59
- if (!existsSync(configPath)) return {};
60
- let parsed: unknown;
61
- try {
62
- const text = readFileSync(configPath, "utf8");
63
- parsed = Bun.YAML.parse(text);
64
- } catch {
65
- return {};
66
- }
67
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
68
- const obj = parsed as Record<string, unknown>;
69
- const defaultsRaw = obj.defaults;
70
- if (typeof defaultsRaw !== "object" || defaultsRaw === null || Array.isArray(defaultsRaw)) return {};
71
- const d = defaultsRaw as Record<string, unknown>;
72
- const out: WorkspaceDefaults = {};
73
- const t = parseTimeoutMs(d.timeout_ms ?? d.timeoutMs);
74
- if (t !== undefined) out.timeoutMs = t;
75
- const r = parseRateLimit(d.rate_limit ?? d.rateLimit);
76
- if (r !== undefined) out.rateLimit = r;
77
- return out;
78
- }
79
-
80
- /**
81
- * Returns the workspace defaults block, cached for the lifetime of the
82
- * process. When no workspace marker is found, returns `{}` (no defaults).
83
- */
84
- export function loadWorkspaceDefaults(cwd?: string): WorkspaceDefaults {
85
- const ws = findWorkspaceRoot(cwd);
86
- if (ws.fromFallback) return {};
87
- if (cache && cache.root === ws.root) return cache.defaults;
88
- const defaults = readDefaults(join(ws.root, "zond.config.yml"));
89
- cache = { root: ws.root, defaults };
90
- return defaults;
91
- }
92
-
93
- /** Test helper: drop the parse cache so the next call re-reads from disk. */
94
- export function _resetWorkspaceConfigCache(): void {
95
- cache = null;
96
- }
97
-
98
- export const HARD_DEFAULT_TIMEOUT_MS = 30000;
99
-
100
- /**
101
- * Resolve `--timeout` via CLI > per-API `.env.yaml` (`timeoutMs`) > workspace
102
- * `defaults.timeout_ms` > 30000. Each layer accepts `undefined` to mean
103
- * "fall through".
104
- */
105
- export function resolveTimeoutMs(
106
- cliFlag: number | undefined,
107
- envMetaTimeout: number | undefined,
108
- cwd?: string,
109
- ): number {
110
- if (cliFlag !== undefined) return cliFlag;
111
- if (envMetaTimeout !== undefined) return envMetaTimeout;
112
- const ws = loadWorkspaceDefaults(cwd);
113
- return ws.timeoutMs ?? HARD_DEFAULT_TIMEOUT_MS;
114
- }
115
-
116
- /**
117
- * Resolve `--rate-limit` via CLI > per-API `.env.yaml` (`rateLimit`) >
118
- * workspace `defaults.rate_limit` > undefined (no throttling).
119
- */
120
- export function resolveRateLimit(
121
- cliFlag: number | "auto" | undefined,
122
- envMetaRateLimit: number | "auto" | undefined,
123
- cwd?: string,
124
- ): number | "auto" | undefined {
125
- if (cliFlag !== undefined) return cliFlag;
126
- if (envMetaRateLimit !== undefined) return envMetaRateLimit;
127
- const ws = loadWorkspaceDefaults(cwd);
128
- return ws.rateLimit;
129
- }
@@ -1,84 +0,0 @@
1
- /**
2
- * ARV-349/350: deterministic fixture-gap report for `prepare-fixtures`.
3
- *
4
- * prepare-fixtures is a single-pass discover (ARV-336 removed the autonomous
5
- * seed engine). It fills FK ids it can resolve from list endpoints, but it
6
- * used to neither fill NOR flag two other classes of gap, so suites ran with
7
- * unresolved placeholders and produced noisy 400/404s:
8
- *
9
- * - undefinedVars (ARV-349): a suite references {{bank_code}} / {{tax_id}}
10
- * that nothing produces — not an env value, not a prior-step capture, not
11
- * a manifest entry. The user / agent must supply it.
12
- * - unseededRoots (ARV-350): a REQUIRED manifest var (e.g. {{account}}) that
13
- * is empty in env, referenced by a suite, and captured by no step — the
14
- * dependency-chain HEAD that gates dependent CRUD suites (persons-crud,
15
- * external_accounts-crud all skip: "required fixture {{account}} is empty").
16
- * Source-agnostic on purpose: real manifests model this root as `path`
17
- * required:true with an empty default, not necessarily `capture-chain`.
18
- *
19
- * REPORT ONLY — never invents a value (ARV-349 #2) and never auto-seeds
20
- * (ARV-350 #2). Deterministic (same suites+env+manifest → same report), so it
21
- * belongs in zond; supplying the values is the agent's/user's job.
22
- */
23
-
24
- import type { TestSuite } from "../parser/types.ts";
25
- import {
26
- preflightCheckVars,
27
- collectCapturesAndSets,
28
- collectStepRefs,
29
- } from "../runner/preflight-vars.ts";
30
-
31
- export interface FixtureGapReport {
32
- /** Suite {{vars}} with no producer (not env, capture, param, or generator). */
33
- undefinedVars: { variable: string; refs: number; suites: string[] }[];
34
- /** Required manifest vars that are empty, suite-referenced, and step-unseeded. */
35
- unseededRoots: { variable: string }[];
36
- }
37
-
38
- export function reportFixtureGaps(
39
- suites: TestSuite[],
40
- env: Record<string, string>,
41
- requiredEmptyVars: Set<string>,
42
- ): FixtureGapReport {
43
- // Match runtime capture scoping: only `setup: true` suites share their
44
- // captures into other suites (TestSuite.setup); a regular suite's captures
45
- // stay local to itself. So a var is "seeded" for suite S iff env holds it,
46
- // a setup suite captures it, or S itself captures it.
47
- const setupProduced = new Set<string>();
48
- for (const suite of suites) {
49
- if (!suite.setup) continue;
50
- for (const step of suite.tests) collectCapturesAndSets(step, setupProduced);
51
- }
52
-
53
- // Unseeded root = required + empty in env + SOME referencing suite cannot
54
- // produce it (not a setup capture, not captured within that suite). This
55
- // catches cross-suite roots like {{account}}: crud-accounts creates it, but
56
- // persons-crud references it without a create → that suite skips at runtime.
57
- const rootSet = new Set<string>();
58
- for (const suite of suites) {
59
- const seeded = new Set<string>(setupProduced);
60
- for (const step of suite.tests) collectCapturesAndSets(step, seeded);
61
- for (const step of suite.tests) {
62
- for (const v of collectStepRefs(step)) {
63
- if (requiredEmptyVars.has(v) && !seeded.has(v)) rootSet.add(v);
64
- }
65
- }
66
- }
67
- const unseededRoots = [...rootSet].sort().map(variable => ({ variable }));
68
-
69
- // Undefined vars = preflight hits, minus the roots we already called out
70
- // (a root absent from env would otherwise land in both buckets).
71
- const byVar = new Map<string, { refs: number; suites: Set<string> }>();
72
- for (const h of preflightCheckVars(suites, env)) {
73
- if (rootSet.has(h.variable)) continue;
74
- let e = byVar.get(h.variable);
75
- if (!e) { e = { refs: 0, suites: new Set() }; byVar.set(h.variable, e); }
76
- e.refs++;
77
- e.suites.add(h.suite);
78
- }
79
- const undefinedVars = [...byVar.entries()]
80
- .map(([variable, e]) => ({ variable, refs: e.refs, suites: [...e.suites].sort() }))
81
- .sort((a, b) => a.variable.localeCompare(b.variable));
82
-
83
- return { undefinedVars, unseededRoots };
84
- }
@@ -1,71 +0,0 @@
1
- /**
2
- * ARV-324: `.fixture-gaps.yaml` — per-API sidecar recording operations
3
- * that `prepare-fixtures`/`discover` already confirmed return a client
4
- * error (or an empty list) while hunting for fixture values — e.g. an
5
- * empty/under-provisioned test account.
6
- *
7
- * `checks run` runs as a separate process invocation and, without this
8
- * file, has no way to tell "known fixture gap" apart from "new backend
9
- * bug" — both would land on the same operation with
10
- * `recommended_action: report_backend_bug`. Loading this file lets the
11
- * classifier downgrade to `fix_fixture` instead (see
12
- * `core/classifier/recommended-action.ts`).
13
- *
14
- * Not the manifest (`.api-fixtures.yaml` stays spec-derived/read-only,
15
- * regenerated only by `refresh-api`) — this is runtime-observed state,
16
- * so it gets its own file, rewritten wholesale on every
17
- * discover/prepare-fixtures run so a since-fixed gap doesn't linger.
18
- */
19
-
20
- import { join } from "node:path";
21
- import { writeFile } from "node:fs/promises";
22
-
23
- export interface FixtureGap {
24
- method: string;
25
- path: string;
26
- resource: string;
27
- var: string;
28
- reason: string;
29
- }
30
-
31
- const FILENAME = ".fixture-gaps.yaml";
32
-
33
- export async function readFixtureGaps(apiDir: string): Promise<FixtureGap[]> {
34
- const file = Bun.file(join(apiDir, FILENAME));
35
- if (!(await file.exists())) return [];
36
- let parsed: unknown;
37
- try {
38
- parsed = Bun.YAML.parse(await file.text());
39
- } catch {
40
- return [];
41
- }
42
- if (!parsed || typeof parsed !== "object" || !Array.isArray((parsed as { gaps?: unknown }).gaps)) {
43
- return [];
44
- }
45
- return (parsed as { gaps: FixtureGap[] }).gaps;
46
- }
47
-
48
- export async function writeFixtureGaps(apiDir: string, gaps: FixtureGap[]): Promise<void> {
49
- const path = join(apiDir, FILENAME);
50
- const header = `# .fixture-gaps.yaml — auto-written by prepare-fixtures/discover (ARV-324)
51
- #
52
- # Operations that returned a client error (or an empty list) while zond
53
- # hunted for a fixture value — a resource that's empty/inaccessible in
54
- # the target API. Read by \`checks run\` so it doesn't relabel the same
55
- # known gap as report_backend_bug. Safe to delete — regenerated on the
56
- # next prepare-fixtures / discover run.
57
- `;
58
- const { stringify } = await import("yaml");
59
- const body = stringify({ gaps }, { lineWidth: 0, defaultStringType: "PLAIN" });
60
- await writeFile(path, header + "\n" + body, "utf-8");
61
- }
62
-
63
- /** Key an operation the same way on both the write side (discover/bootstrap,
64
- * always GET list-probes) and the read side (`CheckFinding.operation`). */
65
- export function gapKey(method: string, path: string): string {
66
- return `${method.toUpperCase()} ${path}`;
67
- }
68
-
69
- export function gapIndex(gaps: FixtureGap[]): Set<string> {
70
- return new Set(gaps.map((g) => gapKey(g.method, g.path)));
71
- }