@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,383 +0,0 @@
1
- import type { TestStepExpect, AssertionRule } from "../parser/types.ts";
2
- import type { HttpResponse, AssertionResult } from "./types.ts";
3
- import { getByPath } from "../utils.ts";
4
-
5
- function checkType(value: unknown, expectedType: string): boolean {
6
- switch (expectedType) {
7
- case "string": return typeof value === "string";
8
- case "integer": return typeof value === "number" && Number.isInteger(value);
9
- case "number": return typeof value === "number";
10
- case "boolean": return typeof value === "boolean";
11
- case "array": return Array.isArray(value);
12
- case "object": return typeof value === "object" && value !== null && !Array.isArray(value);
13
- case "null": return value === null;
14
- default: return false;
15
- }
16
- }
17
-
18
- function describeType(value: unknown): string {
19
- if (value === null) return "null";
20
- if (value === undefined) return "undefined";
21
- if (Array.isArray(value)) return "array";
22
- if (typeof value === "number" && Number.isInteger(value)) return "integer";
23
- return typeof value;
24
- }
25
-
26
- function deepEquals(a: unknown, b: unknown): boolean {
27
- if (a === b) return true;
28
- // Loose numeric comparison: "123" == 123
29
- if (typeof a === "number" && typeof b === "string") return a === Number(b);
30
- if (typeof a === "string" && typeof b === "number") return Number(a) === b;
31
- if (typeof a !== typeof b) return false;
32
- if (typeof a !== "object" || a === null || b === null) return false;
33
- return Bun.deepEquals(a, b);
34
- }
35
-
36
- function checkRule(path: string, rule: AssertionRule, actual: unknown): AssertionResult[] {
37
- const results: AssertionResult[] = [];
38
- const field = `body.${path}`;
39
-
40
- if (rule.exists !== undefined) {
41
- // Key-presence semantics: null counts as "exists" (key present in response).
42
- // Use `not_equals: null` or `type: "null"` to assert non-null specifically.
43
- const doesExist = actual !== undefined;
44
- results.push({
45
- field, rule: `exists ${rule.exists}`,
46
- passed: doesExist === rule.exists, actual: doesExist, expected: rule.exists,
47
- });
48
- }
49
-
50
- if (rule.type !== undefined) {
51
- results.push({
52
- field, rule: `type ${rule.type}`,
53
- passed: checkType(actual, rule.type), actual: describeType(actual), expected: rule.type,
54
- });
55
- }
56
-
57
- if (rule.equals !== undefined) {
58
- results.push({
59
- field, rule: `equals ${JSON.stringify(rule.equals)}`,
60
- passed: deepEquals(actual, rule.equals), actual, expected: rule.equals,
61
- });
62
- }
63
-
64
- if (rule.contains !== undefined) {
65
- const passed = typeof actual === "string" && actual.includes(rule.contains);
66
- results.push({
67
- field, rule: `contains "${rule.contains}"`,
68
- passed, actual, expected: rule.contains,
69
- });
70
- }
71
-
72
- if (rule.matches !== undefined) {
73
- const passed = typeof actual === "string" && new RegExp(rule.matches).test(actual);
74
- results.push({
75
- field, rule: `matches ${rule.matches}`,
76
- passed, actual, expected: rule.matches,
77
- });
78
- }
79
-
80
- if (rule.gt !== undefined) {
81
- const passed = typeof actual === "number" && actual > rule.gt;
82
- results.push({
83
- field, rule: `gt ${rule.gt}`,
84
- passed, actual, expected: rule.gt,
85
- });
86
- }
87
-
88
- if (rule.lt !== undefined) {
89
- const passed = typeof actual === "number" && actual < rule.lt;
90
- results.push({
91
- field, rule: `lt ${rule.lt}`,
92
- passed, actual, expected: rule.lt,
93
- });
94
- }
95
-
96
- if (rule.not_equals !== undefined) {
97
- results.push({
98
- field, rule: `not_equals ${JSON.stringify(rule.not_equals)}`,
99
- passed: !deepEquals(actual, rule.not_equals), actual, expected: rule.not_equals,
100
- });
101
- }
102
-
103
- if (rule.not_contains !== undefined) {
104
- const passed = typeof actual === "string" && !actual.includes(rule.not_contains);
105
- results.push({
106
- field, rule: `not_contains "${rule.not_contains}"`,
107
- passed, actual, expected: rule.not_contains,
108
- });
109
- }
110
-
111
- if (rule.gte !== undefined) {
112
- const passed = typeof actual === "number" && actual >= rule.gte;
113
- results.push({
114
- field, rule: `gte ${rule.gte}`,
115
- passed, actual, expected: rule.gte,
116
- });
117
- }
118
-
119
- if (rule.lte !== undefined) {
120
- const passed = typeof actual === "number" && actual <= rule.lte;
121
- results.push({
122
- field, rule: `lte ${rule.lte}`,
123
- passed, actual, expected: rule.lte,
124
- });
125
- }
126
-
127
- if (rule.length !== undefined) {
128
- const hasLength = (Array.isArray(actual) || typeof actual === "string");
129
- const passed = hasLength && (actual as string | unknown[]).length === rule.length;
130
- results.push({
131
- field, rule: `length ${rule.length}`,
132
- passed, actual: hasLength ? (actual as string | unknown[]).length : describeType(actual), expected: rule.length,
133
- });
134
- }
135
-
136
- if (rule.length_gt !== undefined) {
137
- const hasLength = (Array.isArray(actual) || typeof actual === "string");
138
- const passed = hasLength && (actual as string | unknown[]).length > rule.length_gt;
139
- results.push({
140
- field, rule: `length_gt ${rule.length_gt}`,
141
- passed, actual: hasLength ? (actual as string | unknown[]).length : describeType(actual), expected: rule.length_gt,
142
- });
143
- }
144
-
145
- if (rule.length_gte !== undefined) {
146
- const hasLength = (Array.isArray(actual) || typeof actual === "string");
147
- const passed = hasLength && (actual as string | unknown[]).length >= rule.length_gte;
148
- results.push({
149
- field, rule: `length_gte ${rule.length_gte}`,
150
- passed, actual: hasLength ? (actual as string | unknown[]).length : describeType(actual), expected: rule.length_gte,
151
- });
152
- }
153
-
154
- if (rule.length_lt !== undefined) {
155
- const hasLength = (Array.isArray(actual) || typeof actual === "string");
156
- const passed = hasLength && (actual as string | unknown[]).length < rule.length_lt;
157
- results.push({
158
- field, rule: `length_lt ${rule.length_lt}`,
159
- passed, actual: hasLength ? (actual as string | unknown[]).length : describeType(actual), expected: rule.length_lt,
160
- });
161
- }
162
-
163
- if (rule.length_lte !== undefined) {
164
- const hasLength = (Array.isArray(actual) || typeof actual === "string");
165
- const passed = hasLength && (actual as string | unknown[]).length <= rule.length_lte;
166
- results.push({
167
- field, rule: `length_lte ${rule.length_lte}`,
168
- passed, actual: hasLength ? (actual as string | unknown[]).length : describeType(actual), expected: rule.length_lte,
169
- });
170
- }
171
-
172
- if (rule.each !== undefined) {
173
- if (!Array.isArray(actual)) {
174
- results.push({ field, rule: "each", passed: false, actual: describeType(actual), expected: "array" });
175
- } else {
176
- for (let i = 0; i < actual.length; i++) {
177
- for (const [subPath, subRule] of Object.entries(rule.each)) {
178
- const subActual = getByPath(actual[i], subPath);
179
- const subResults = checkRule(`${path}[${i}].${subPath}`, subRule, subActual);
180
- results.push(...subResults);
181
- }
182
- }
183
- }
184
- }
185
-
186
- if (rule.contains_item !== undefined) {
187
- if (!Array.isArray(actual)) {
188
- results.push({ field, rule: "contains_item", passed: false, actual: describeType(actual), expected: "array" });
189
- } else {
190
- const found = actual.some((item) => {
191
- for (const [subPath, subRule] of Object.entries(rule.contains_item!)) {
192
- const subActual = getByPath(item, subPath);
193
- const subResults = checkRule("", subRule, subActual);
194
- if (subResults.some(r => !r.passed)) return false;
195
- }
196
- return true;
197
- });
198
- results.push({
199
- field, rule: "contains_item",
200
- passed: found, actual: `array(${actual.length})`, expected: "at least one matching item",
201
- });
202
- }
203
- }
204
-
205
- if (rule.set_equals !== undefined) {
206
- if (!Array.isArray(actual) || !Array.isArray(rule.set_equals)) {
207
- results.push({
208
- field, rule: "set_equals",
209
- passed: false, actual: describeType(actual), expected: "both must be arrays",
210
- });
211
- } else {
212
- const actualSet = new Set(actual.map(v => JSON.stringify(v)));
213
- const expectedSet = new Set((rule.set_equals as unknown[]).map(v => JSON.stringify(v)));
214
- const passed = actualSet.size === expectedSet.size && [...actualSet].every(v => expectedSet.has(v));
215
- results.push({
216
- field, rule: "set_equals",
217
- passed, actual, expected: rule.set_equals,
218
- });
219
- }
220
- }
221
-
222
- return results;
223
- }
224
-
225
- export function checkAssertions(expect: TestStepExpect, response: HttpResponse): AssertionResult[] {
226
- const results: AssertionResult[] = [];
227
-
228
- if (expect.status !== undefined) {
229
- const allowed = Array.isArray(expect.status) ? expect.status : [expect.status];
230
- results.push({
231
- field: "status",
232
- rule: allowed.length === 1 ? `equals ${allowed[0]}` : `one of [${allowed.join(", ")}]`,
233
- passed: allowed.includes(response.status),
234
- actual: response.status,
235
- expected: expect.status,
236
- kind: "primary",
237
- });
238
- }
239
-
240
- if (expect.duration !== undefined) {
241
- results.push({
242
- field: "duration",
243
- rule: `lte ${expect.duration}ms`,
244
- passed: response.duration_ms <= expect.duration,
245
- actual: response.duration_ms,
246
- expected: expect.duration,
247
- kind: "auxiliary",
248
- });
249
- }
250
-
251
- if (expect.headers) {
252
- for (const [key, rule] of Object.entries(expect.headers)) {
253
- const actual = response.headers[key.toLowerCase()];
254
- if (typeof rule === "string") {
255
- results.push({
256
- field: `headers.${key}`,
257
- rule: `equals "${rule}"`,
258
- passed: actual === rule,
259
- actual,
260
- expected: rule,
261
- kind: "auxiliary",
262
- });
263
- } else {
264
- // AssertionRule in header — supports capture and other checks
265
- const ruleResults = checkRule(key, rule, actual).map(r => ({
266
- ...r,
267
- field: r.field.replace(/^body\./, "headers."),
268
- kind: "auxiliary" as const,
269
- }));
270
- results.push(...ruleResults);
271
- }
272
- }
273
- }
274
-
275
- if (expect.body && response.body_parsed !== undefined) {
276
- for (const [path, rule] of Object.entries(expect.body)) {
277
- let actual: unknown;
278
- if (path === "_body") {
279
- actual = response.body_parsed;
280
- } else if (path.startsWith("_body.")) {
281
- actual = getByPath(response.body_parsed, path.slice(6));
282
- } else {
283
- actual = getByPath(response.body_parsed, path);
284
- }
285
- const bodyResults = checkRule(path, rule, actual).map((r) => ({
286
- ...r,
287
- kind: "primary" as const,
288
- }));
289
- results.push(...bodyResults);
290
- }
291
- }
292
-
293
- return results;
294
- }
295
-
296
- export function extractCaptures(
297
- bodyRules: Record<string, AssertionRule> | undefined,
298
- responseBody: unknown,
299
- headerRules?: Record<string, string | AssertionRule>,
300
- responseHeaders?: Record<string, string>,
301
- ): Record<string, unknown> {
302
- const captures: Record<string, unknown> = {};
303
-
304
- if (bodyRules && responseBody !== undefined) {
305
- for (const [path, rule] of Object.entries(bodyRules)) {
306
- if (rule.capture) {
307
- let value: unknown;
308
- if (path === "_body") {
309
- value = responseBody;
310
- } else if (path.startsWith("_body.")) {
311
- value = getByPath(responseBody, path.slice(6));
312
- } else {
313
- value = getByPath(responseBody, path);
314
- }
315
- if (value !== undefined) {
316
- captures[rule.capture] = value;
317
- }
318
- }
319
- }
320
- }
321
-
322
- if (headerRules && responseHeaders) {
323
- for (const [key, rule] of Object.entries(headerRules)) {
324
- if (typeof rule !== "string" && rule.capture) {
325
- const value = responseHeaders[key.toLowerCase()];
326
- if (value !== undefined) {
327
- captures[rule.capture] = value;
328
- }
329
- }
330
- }
331
- }
332
-
333
- return captures;
334
- }
335
-
336
- /**
337
- * Find capture rules whose path didn't resolve in the response, returning
338
- * `{ var, source, path }` per miss. Surfaced as auxiliary assertion failures
339
- * so the user sees "captures: {}" with a reason instead of silent emptiness —
340
- * the empty-captures pitfall is the #1 footgun in CRUD chains because the
341
- * downstream step is silently skipped or runs with `undefined`. TASK-256.
342
- */
343
- export function findMissedCaptures(
344
- bodyRules: Record<string, AssertionRule> | undefined,
345
- responseBody: unknown,
346
- headerRules?: Record<string, string | AssertionRule>,
347
- responseHeaders?: Record<string, string>,
348
- ): Array<{ var: string; source: "body" | "header"; path: string }> {
349
- const misses: Array<{ var: string; source: "body" | "header"; path: string }> = [];
350
-
351
- if (bodyRules) {
352
- for (const [path, rule] of Object.entries(bodyRules)) {
353
- if (!rule.capture) continue;
354
- if (responseBody === undefined) {
355
- misses.push({ var: rule.capture, source: "body", path });
356
- continue;
357
- }
358
- let value: unknown;
359
- if (path === "_body") {
360
- value = responseBody;
361
- } else if (path.startsWith("_body.")) {
362
- value = getByPath(responseBody, path.slice(6));
363
- } else {
364
- value = getByPath(responseBody, path);
365
- }
366
- if (value === undefined) {
367
- misses.push({ var: rule.capture, source: "body", path });
368
- }
369
- }
370
- }
371
-
372
- if (headerRules) {
373
- for (const [key, rule] of Object.entries(headerRules)) {
374
- if (typeof rule === "string" || !rule.capture) continue;
375
- const value = responseHeaders ? responseHeaders[key.toLowerCase()] : undefined;
376
- if (value === undefined) {
377
- misses.push({ var: rule.capture, source: "header", path: key });
378
- }
379
- }
380
- }
381
-
382
- return misses;
383
- }
@@ -1,108 +0,0 @@
1
- /**
2
- * ARV-8 (m-15): bounded async-pool for `zond checks run --workers N`.
3
- *
4
- * Cooperative concurrency on a single Bun event loop — no threading,
5
- * no Workers, just N coroutines that pull from a shared cursor. The
6
- * design choice is deliberate:
7
- *
8
- * * Threads/Workers would need request-context plumbing (auth headers,
9
- * rate-limiter state, per-run schema validators are not transferable
10
- * without serialization), and Bun's HTTP client is already
11
- * non-blocking — so cooperative concurrency is faster *and* simpler.
12
- * * `Promise.all(items.map(fn))` would saturate at items.length, which
13
- * defeats the rate-limiter and tends to drown small mock servers.
14
- *
15
- * `runPool` preserves input order in the result array — callers (the
16
- * checks runner, mainly) want to merge per-op findings deterministically
17
- * regardless of which worker finished first.
18
- */
19
- import os from "node:os";
20
-
21
- const WORKERS_MIN = 1;
22
- const WORKERS_MAX = 64;
23
- /** `--workers auto` ceiling — beyond ~8 the gains on a typical mock
24
- * server are dominated by network/IO contention, not parallelism. */
25
- const WORKERS_AUTO_CEILING = 8;
26
-
27
- /**
28
- * Run `fn` over `items` with at most `workers` in-flight at once.
29
- *
30
- * Results are returned in input order, *not* completion order — keep
31
- * call-sites that compose findings/snapshots deterministic.
32
- *
33
- * Errors propagate: the first rejection cancels remaining workers'
34
- * dispatch (they finish their in-flight task and exit). Caller should
35
- * `try/catch` if it wants partial results — none of zond's callers do
36
- * today (a runner crash is fatal anyway).
37
- */
38
- export async function runPool<T, R>(
39
- items: readonly T[],
40
- workers: number,
41
- fn: (item: T, index: number) => Promise<R>,
42
- ): Promise<R[]> {
43
- if (items.length === 0) return [];
44
- // Effective worker count is clamped both ways: never more than items
45
- // (idle workers waste a closure), never below 1 (else nothing runs).
46
- const effective = Math.max(1, Math.min(workers, items.length));
47
- // Sequential fast-path — preserves the *exact* old behaviour
48
- // (microtask ordering, error timing) for AC #4 backward-compat.
49
- if (effective === 1) {
50
- const out: R[] = new Array(items.length);
51
- for (let i = 0; i < items.length; i++) {
52
- out[i] = await fn(items[i]!, i);
53
- }
54
- return out;
55
- }
56
- const out: R[] = new Array(items.length);
57
- let cursor = 0;
58
- let aborted: unknown = null;
59
- async function worker(): Promise<void> {
60
- while (true) {
61
- if (aborted !== null) return;
62
- const i = cursor++;
63
- if (i >= items.length) return;
64
- try {
65
- out[i] = await fn(items[i]!, i);
66
- } catch (err) {
67
- // First rejection wins — store it, drain in-flight tasks, then
68
- // re-throw at the join point.
69
- if (aborted === null) aborted = err;
70
- return;
71
- }
72
- }
73
- }
74
- const swarm: Promise<void>[] = [];
75
- for (let w = 0; w < effective; w++) swarm.push(worker());
76
- await Promise.all(swarm);
77
- if (aborted !== null) throw aborted;
78
- return out;
79
- }
80
-
81
- /**
82
- * Parse a `--workers` flag value:
83
- *
84
- * undefined → 1 (backward-compat default — AC #4)
85
- * "auto" / "AUTO"→ min(cpus, WORKERS_AUTO_CEILING) (AC #5)
86
- * numeric → clamp [1, 64] (AC #5)
87
- * anything else → throws (caller maps to a friendly CLI error)
88
- */
89
- export function parseWorkers(value: string | number | undefined): number {
90
- if (value === undefined || value === null || value === "") return 1;
91
- if (typeof value === "string") {
92
- const trimmed = value.trim().toLowerCase();
93
- if (trimmed === "auto") {
94
- return Math.max(WORKERS_MIN, Math.min(os.cpus().length, WORKERS_AUTO_CEILING));
95
- }
96
- const n = Number.parseInt(trimmed, 10);
97
- if (!Number.isFinite(n)) throw new Error(`Invalid --workers value: "${value}"`);
98
- return Math.max(WORKERS_MIN, Math.min(n, WORKERS_MAX));
99
- }
100
- if (!Number.isFinite(value)) throw new Error(`Invalid --workers value: ${value}`);
101
- return Math.max(WORKERS_MIN, Math.min(Math.trunc(value), WORKERS_MAX));
102
- }
103
-
104
- export const WORKERS_LIMITS = {
105
- min: WORKERS_MIN,
106
- max: WORKERS_MAX,
107
- autoCeiling: WORKERS_AUTO_CEILING,
108
- } as const;
@@ -1,8 +0,0 @@
1
- /**
2
- * Heuristic for "auth-shaped" endpoint paths used by `--safe` mode and
3
- * the env-symptom diagnostics in `db-analysis.ts`. Anything matching is
4
- * treated as a login/refresh route — `--safe` whitelists it for live
5
- * runs, and the diagnostics flag concentrated POST failures here as
6
- * `auth_required` rather than per-endpoint bugs.
7
- */
8
- export const AUTH_PATH_RE = /\/(auth|login|signin|token|oauth)\b/i;
@@ -1,72 +0,0 @@
1
- /**
2
- * TASK-116: detect CI context (commit sha, branch, trigger) from common
3
- * environment variables. Returns `null` for fields when nothing is present
4
- * — caller decides whether to default `trigger` to `"manual"`.
5
- *
6
- * Supported providers (autodetected — no opt-in required):
7
- * GitHub Actions GITHUB_ACTIONS=true, GITHUB_SHA, GITHUB_REF_NAME
8
- * GitLab CI GITLAB_CI=true, CI_COMMIT_SHA, CI_COMMIT_REF_NAME
9
- * CircleCI CIRCLECI=true, CIRCLE_SHA1, CIRCLE_BRANCH
10
- * Buildkite BUILDKITE=true, BUILDKITE_COMMIT, BUILDKITE_BRANCH
11
- * Jenkins JENKINS_URL set, GIT_COMMIT, BRANCH_NAME / GIT_BRANCH
12
- * Generic CI=true triggers `trigger=ci` even when no provider
13
- * matches — caller can still pass nullable commit/branch.
14
- *
15
- * Manual override is via the explicit `--commit-sha` / `--branch` /
16
- * `--trigger` flags or the env vars `ZOND_COMMIT_SHA` / `ZOND_BRANCH` /
17
- * `ZOND_TRIGGER`. These win over autodetection.
18
- */
19
- export interface CiContext {
20
- trigger: "ci" | "manual";
21
- commit_sha: string | null;
22
- branch: string | null;
23
- /** Provider tag (github-actions / gitlab-ci / circleci / …) for diagnostics. */
24
- provider: string | null;
25
- }
26
-
27
- export function detectCiContext(env: NodeJS.ProcessEnv = process.env): CiContext {
28
- const overrideCommit = env.ZOND_COMMIT_SHA?.trim() || null;
29
- const overrideBranch = env.ZOND_BRANCH?.trim() || null;
30
- const overrideTrigger = env.ZOND_TRIGGER?.trim() || null;
31
-
32
- let provider: string | null = null;
33
- let commit: string | null = null;
34
- let branch: string | null = null;
35
-
36
- if (env.GITHUB_ACTIONS === "true") {
37
- provider = "github-actions";
38
- commit = env.GITHUB_SHA?.trim() || null;
39
- branch = env.GITHUB_REF_NAME?.trim() || env.GITHUB_HEAD_REF?.trim() || null;
40
- } else if (env.GITLAB_CI === "true") {
41
- provider = "gitlab-ci";
42
- commit = env.CI_COMMIT_SHA?.trim() || null;
43
- branch = env.CI_COMMIT_REF_NAME?.trim() || null;
44
- } else if (env.CIRCLECI === "true") {
45
- provider = "circleci";
46
- commit = env.CIRCLE_SHA1?.trim() || null;
47
- branch = env.CIRCLE_BRANCH?.trim() || null;
48
- } else if (env.BUILDKITE === "true") {
49
- provider = "buildkite";
50
- commit = env.BUILDKITE_COMMIT?.trim() || null;
51
- branch = env.BUILDKITE_BRANCH?.trim() || null;
52
- } else if (env.JENKINS_URL) {
53
- provider = "jenkins";
54
- commit = env.GIT_COMMIT?.trim() || null;
55
- branch = env.BRANCH_NAME?.trim() || env.GIT_BRANCH?.trim() || null;
56
- }
57
-
58
- const inferredCi = !!provider || env.CI === "true" || env.CI === "1";
59
- const trigger: "ci" | "manual" =
60
- overrideTrigger === "ci" || overrideTrigger === "manual"
61
- ? overrideTrigger
62
- : inferredCi
63
- ? "ci"
64
- : "manual";
65
-
66
- return {
67
- trigger,
68
- commit_sha: overrideCommit ?? commit,
69
- branch: overrideBranch ?? branch,
70
- provider: provider ?? (inferredCi ? "generic" : null),
71
- };
72
- }