@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,97 +0,0 @@
1
- import type { OpenAPIV3 } from "openapi-types";
2
- import type { Issue, RuleId, Severity, HeuristicConfig } from "../types.ts";
3
- import { DEFAULT_SEVERITY } from "../types.ts";
4
- import type { ParamContext, SchemaContext, RequestBodyContext } from "../walker.ts";
5
- import { normalisedTypes } from "../walker.ts";
6
-
7
- interface RuleSink {
8
- push(rule: RuleId, severity: Severity, message: string, opts: Partial<Pick<Issue, "path" | "method" | "fix_hint">> & { jsonpointer: string }): void;
9
- }
10
-
11
- /**
12
- * B2 — path/query param named like an id (`*_id`, `id`) without `format: uuid`
13
- * or `pattern`. Heuristic: only on params whose name matches the id-suffix list.
14
- */
15
- export function runParamHeuristics(ctx: ParamContext, sink: RuleSink, h: HeuristicConfig): void {
16
- const p = ctx.param;
17
- if (p.in !== "path" && p.in !== "query") return;
18
- const schema = (p.schema ?? {}) as OpenAPIV3.SchemaObject;
19
- const types = normalisedTypes(schema);
20
- // B2 only applies to string-shaped ids (uuid). Integer ids are constrained
21
- // by minimum/maximum (B3 territory), not by format: uuid.
22
- if (types.length > 0 && !types.includes("string")) return;
23
- const looksLikeId = p.name === "id" || h.id_suffixes.some(s => p.name.endsWith(s));
24
- if (!looksLikeId) return;
25
- const fmt = (schema as { format?: string }).format;
26
- const hasPattern = !!(schema as { pattern?: string }).pattern;
27
- if (fmt !== "uuid" && !hasPattern) {
28
- sink.push("B2", DEFAULT_SEVERITY.B2, `id-like param "${p.name}" missing format: uuid or pattern (heuristic)`, {
29
- jsonpointer: ctx.jsonpointer, path: ctx.path, method: ctx.method,
30
- fix_hint: "if the id is a UUID, add format: uuid; otherwise add a pattern",
31
- });
32
- }
33
- }
34
-
35
- /**
36
- * B5/B6 — schema property name suggests a known semantic type, but `format`
37
- * is missing.
38
- * - `*_at`, `*_date`, `*_time`, `created`, `updated`, `timestamp` → `date-time`
39
- * - `email`, `url`, `website`, `homepage` → `email` / `uri`
40
- */
41
- export function runSchemaHeuristics(ctx: SchemaContext, sink: RuleSink, h: HeuristicConfig): void {
42
- if (ctx.origin !== "property" || !ctx.propertyName) return;
43
- const s = ctx.schema;
44
- const types = normalisedTypes(s);
45
- if (!types.includes("string")) return;
46
- const fmt = (s as { format?: string }).format;
47
- const name = ctx.propertyName;
48
-
49
- // B5 — timestamp fields
50
- const looksLikeTimestamp =
51
- h.timestamp_suffixes.some(suf => name.endsWith(suf)) ||
52
- ["created", "updated", "timestamp"].includes(name);
53
- if (looksLikeTimestamp && fmt !== "date-time" && fmt !== "date") {
54
- sink.push("B5", DEFAULT_SEVERITY.B5, `field "${name}" looks like a timestamp but has no format: date-time (heuristic)`, {
55
- jsonpointer: ctx.jsonpointer, path: ctx.path, method: ctx.method,
56
- fix_hint: "add format: date-time so --validate-schema enforces RFC3339",
57
- });
58
- }
59
-
60
- // B6 — email / url
61
- if (name === "email" && fmt !== "email") {
62
- sink.push("B6", DEFAULT_SEVERITY.B6, `field "${name}" missing format: email (heuristic)`, {
63
- jsonpointer: ctx.jsonpointer, path: ctx.path, method: ctx.method,
64
- fix_hint: "add format: email",
65
- });
66
- }
67
- if (h.url_names.includes(name) && fmt !== "uri" && fmt !== "url") {
68
- sink.push("B6", DEFAULT_SEVERITY.B6, `field "${name}" missing format: uri (heuristic)`, {
69
- jsonpointer: ctx.jsonpointer, path: ctx.path, method: ctx.method,
70
- fix_hint: "add format: uri",
71
- });
72
- }
73
- }
74
-
75
- /**
76
- * B9 — request-body schema declares semantically-required-looking properties
77
- * (`name`, `email`, `title`) but `required: []` is empty / absent. Heuristic.
78
- */
79
- export function runRequestBodyHeuristics(ctx: RequestBodyContext, sink: RuleSink, h: HeuristicConfig): void {
80
- if (!ctx.requestBody.content) return;
81
- for (const [ct, mt] of Object.entries(ctx.requestBody.content)) {
82
- if (!ct.includes("json")) continue;
83
- const schema = mt.schema as OpenAPIV3.SchemaObject | undefined;
84
- if (!schema || !schema.properties) continue;
85
- const required = (schema.required ?? []) as string[];
86
- const propNames = Object.keys(schema.properties);
87
- const semanticPresent = h.semantic_required.filter(n => propNames.includes(n));
88
- const semanticMissing = semanticPresent.filter(n => !required.includes(n));
89
- if (semanticPresent.length > 0 && semanticMissing.length === semanticPresent.length) {
90
- sink.push("B9", DEFAULT_SEVERITY.B9, `request body has properties [${semanticPresent.join(", ")}] but none are required (heuristic)`, {
91
- jsonpointer: `${ctx.jsonpointer}/content/${ct.replace(/~/g, "~0").replace(/\//g, "~1")}/schema/required`,
92
- path: ctx.path, method: ctx.method,
93
- fix_hint: `consider adding to required: [${semanticMissing.map(n => `"${n}"`).join(", ")}]`,
94
- });
95
- }
96
- }
97
- }
@@ -1,109 +0,0 @@
1
- import type { OpenAPIV3 } from "openapi-types";
2
- import type { Issue, RuleId, Severity, HeuristicConfig } from "../types.ts";
3
- import { DEFAULT_SEVERITY } from "../types.ts";
4
- import type { ParamContext, ResponseContext, RequestBodyContext, SchemaContext } from "../walker.ts";
5
- import { normalisedTypes } from "../walker.ts";
6
-
7
- interface RuleSink {
8
- push(rule: RuleId, severity: Severity, message: string, opts: Partial<Pick<Issue, "path" | "method" | "fix_hint">> & { jsonpointer: string }): void;
9
- }
10
-
11
- /**
12
- * Group B — formal strictness gaps on parameters.
13
- * B1 (path-param without format/pattern) → high.
14
- * B3 (integer-param without min/max — pagination names get medium, others low).
15
- * B4 (cursor-name string-param without minLength: 1) → low.
16
- */
17
- export function runParamStrictnessRules(ctx: ParamContext, sink: RuleSink, h: HeuristicConfig): void {
18
- const p = ctx.param;
19
- const schema = (p.schema ?? {}) as OpenAPIV3.SchemaObject;
20
- const types = normalisedTypes(schema);
21
-
22
- if (p.in === "path" && types.includes("string")) {
23
- // Only flag string path-params: integer/number params have type-level
24
- // constraints (minimum/maximum, multipleOf), so missing format/pattern
25
- // is benign there.
26
- const hasFormat = !!(schema as { format?: string }).format;
27
- const hasPattern = !!(schema as { pattern?: string }).pattern;
28
- if (!hasFormat && !hasPattern) {
29
- sink.push("B1", DEFAULT_SEVERITY.B1, `path-param "${p.name}" missing format/pattern`, {
30
- jsonpointer: ctx.jsonpointer, path: ctx.path, method: ctx.method,
31
- fix_hint: "add format: uuid (or pattern: ^...$) so SDKs reject malformed values client-side",
32
- });
33
- }
34
- }
35
-
36
- if (types.includes("integer") || types.includes("number")) {
37
- const hasMin = typeof (schema as { minimum?: unknown }).minimum === "number";
38
- const hasMax = typeof (schema as { maximum?: unknown }).maximum === "number";
39
- if (!hasMin || !hasMax) {
40
- const isPagination = h.pagination_names.some(n => n.toLowerCase() === p.name.toLowerCase());
41
- const sev: Severity = isPagination ? "medium" : "low";
42
- const which = !hasMin && !hasMax ? "minimum/maximum" : !hasMin ? "minimum" : "maximum";
43
- sink.push("B3", sev, `${p.in}-param "${p.name}" (${types.join("|")}) missing ${which}`, {
44
- jsonpointer: ctx.jsonpointer, path: ctx.path, method: ctx.method,
45
- fix_hint: `add ${which} so out-of-range values are rejected before reaching the server`,
46
- });
47
- }
48
- }
49
-
50
- if (types.includes("string") && h.cursor_names.some(n => n.toLowerCase() === p.name.toLowerCase())) {
51
- const minLen = (schema as { minLength?: unknown }).minLength;
52
- if (typeof minLen !== "number" || minLen < 1) {
53
- sink.push("B4", DEFAULT_SEVERITY.B4, `cursor-param "${p.name}" missing minLength: 1`, {
54
- jsonpointer: ctx.jsonpointer, path: ctx.path, method: ctx.method,
55
- fix_hint: "add minLength: 1 so empty cursor strings are rejected client-side",
56
- });
57
- }
58
- }
59
- }
60
-
61
- /**
62
- * B7 — 2xx response without a JSON schema. `--validate-schema` silently skips
63
- * such endpoints, masking real type drift.
64
- */
65
- export function runResponseStrictnessRules(ctx: ResponseContext, sink: RuleSink): void {
66
- const status = parseInt(ctx.status, 10);
67
- if (!Number.isFinite(status) || status < 200 || status >= 300) return;
68
- // 204 No Content / 205 Reset Content / 304 Not Modified by definition carry no body.
69
- if (status === 204 || status === 205) return;
70
- const r = ctx.response;
71
- const hasJsonSchema = r.content && Object.entries(r.content).some(([ct, mt]) => {
72
- return ct.includes("json") && (mt.schema !== undefined);
73
- });
74
- if (!hasJsonSchema) {
75
- sink.push("B7", DEFAULT_SEVERITY.B7, `${ctx.status} response missing JSON schema`, {
76
- jsonpointer: ctx.jsonpointer, path: ctx.path, method: ctx.method,
77
- fix_hint: "declare content.application/json.schema so --validate-schema can verify the response",
78
- });
79
- }
80
- }
81
-
82
- /**
83
- * B8 — request-body root schema without explicit `additionalProperties`.
84
- * Informational; tied to mass-assignment risk (T58).
85
- */
86
- export function runRequestBodyStrictnessRules(ctx: RequestBodyContext, sink: RuleSink): void {
87
- if (!ctx.requestBody.content) return;
88
- for (const [ct, mt] of Object.entries(ctx.requestBody.content)) {
89
- if (!ct.includes("json")) continue;
90
- const schema = mt.schema as OpenAPIV3.SchemaObject | undefined;
91
- if (!schema) continue;
92
- const ap = (schema as { additionalProperties?: unknown }).additionalProperties;
93
- if (ap === undefined) {
94
- sink.push("B8", DEFAULT_SEVERITY.B8, `request body schema does not set additionalProperties`, {
95
- jsonpointer: `${ctx.jsonpointer}/content/${ct.replace(/~/g, "~0").replace(/\//g, "~1")}/schema`,
96
- path: ctx.path, method: ctx.method,
97
- fix_hint: "set additionalProperties: false to make mass-assignment vectors explicit",
98
- });
99
- }
100
- }
101
- }
102
-
103
- /**
104
- * Schema-level B-rules that aren't on parameters: currently empty placeholder
105
- * for future expansion (e.g. response-body B5 picked up via heuristics).
106
- */
107
- export function runSchemaStrictnessRules(_ctx: SchemaContext, _sink: RuleSink): void {
108
- // intentionally empty — heuristic schema-level checks live in heuristics.ts
109
- }
@@ -1,96 +0,0 @@
1
- import type { RecommendedAction } from "../diagnostics/failure-hints.ts";
2
-
3
- // Severity unified in src/core/severity (ARV-250). Lint historically used a
4
- // 3-tier ladder (high/medium/low) without 'critical' or 'info'. ARV-255
5
- // will downgrade most lint findings to info/low; this re-export aligns the
6
- // type but does not yet change DEFAULT_SEVERITY values per rule.
7
- import type { Severity } from "../severity/index.ts";
8
- export type { Severity };
9
-
10
- export type { RecommendedAction };
11
-
12
- export type RuleId =
13
- | "A1" | "A2" | "A3" | "A4" | "A5" | "A6"
14
- | "B1" | "B2" | "B3" | "B4" | "B5" | "B6" | "B7" | "B8" | "B9";
15
-
16
- export const ALL_RULES: RuleId[] = [
17
- "A1", "A2", "A3", "A4", "A5", "A6",
18
- "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9",
19
- ];
20
-
21
- /**
22
- * ARV-255 (m-21 pivot): all lint findings cap at LOW/INFO. Static spec
23
- * analysis is hygiene — no runtime evidence, no exploit pathway, no
24
- * security or contract drift. The old "HIGH on missing additionalProperties"
25
- * inflation made the audit report unreadable; now lint lives in the
26
- * hygiene category and surfaces via `zond lint` separately.
27
- *
28
- * Tier assignment:
29
- * - `low`: real spec violations (format mismatch in example, missing
30
- * path-param format, response without schema). Worth fixing, but not
31
- * security.
32
- * - `info`: style and documentation gaps (additionalProperties, naming,
33
- * missing examples, optional descriptions). Could be intentional.
34
- */
35
- export const DEFAULT_SEVERITY: Record<RuleId, Severity> = {
36
- A1: "low", A2: "low", A3: "info", A4: "info", A5: "info", A6: "info",
37
- B1: "low", B2: "info", B3: "info", B4: "info", B5: "info",
38
- B6: "info", B7: "low", B8: "info", B9: "info",
39
- };
40
-
41
- export interface Issue {
42
- rule: RuleId;
43
- severity: Severity;
44
- path?: string;
45
- method?: string;
46
- jsonpointer: string;
47
- message: string;
48
- fix_hint?: string;
49
- affects?: string[];
50
- /** TASK-294: agent-routable action — always `fix_spec` for lint issues
51
- * (the spec is the source of truth and the only thing to edit). */
52
- recommended_action: RecommendedAction;
53
- }
54
-
55
- export type RuleSetting = "off" | Severity;
56
-
57
- export interface HeuristicConfig {
58
- id_suffixes: string[];
59
- timestamp_suffixes: string[];
60
- url_names: string[];
61
- cursor_names: string[];
62
- pagination_names: string[];
63
- semantic_required: string[];
64
- }
65
-
66
- export interface LintConfig {
67
- rules: Partial<Record<RuleId, RuleSetting>>;
68
- heuristics: HeuristicConfig;
69
- ignore_paths: string[];
70
- include_paths?: string[];
71
- max_issues?: number;
72
- }
73
-
74
- export interface LintStats {
75
- total: number;
76
- critical: number;
77
- high: number;
78
- medium: number;
79
- low: number;
80
- info: number;
81
- endpoints: number;
82
- }
83
-
84
- export interface LintResult {
85
- issues: Issue[];
86
- stats: LintStats;
87
- }
88
-
89
- export const DEFAULT_HEURISTICS: HeuristicConfig = {
90
- id_suffixes: ["_id", "Id", "ID"],
91
- timestamp_suffixes: ["_at", "_date", "_time"],
92
- url_names: ["url", "website", "homepage", "callback_url", "webhook_url"],
93
- cursor_names: ["after", "before", "cursor", "token", "page_token", "next_token"],
94
- pagination_names: ["limit", "offset", "page", "size", "count", "per_page", "page_size"],
95
- semantic_required: ["name", "email", "title"],
96
- };
@@ -1,248 +0,0 @@
1
- import type { OpenAPIV3 } from "openapi-types";
2
-
3
- const HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"] as const;
4
-
5
- export type SchemaContextKind =
6
- | "param-schema"
7
- | "request-body"
8
- | "response-body"
9
- | "property";
10
-
11
- export interface ParamContext {
12
- kind: "parameter";
13
- jsonpointer: string;
14
- path: string;
15
- method: string;
16
- param: OpenAPIV3.ParameterObject;
17
- }
18
-
19
- export interface ResponseContext {
20
- kind: "response";
21
- jsonpointer: string;
22
- path: string;
23
- method: string;
24
- status: string;
25
- response: OpenAPIV3.ResponseObject;
26
- }
27
-
28
- export interface RequestBodyContext {
29
- kind: "requestBody";
30
- jsonpointer: string;
31
- path: string;
32
- method: string;
33
- requestBody: OpenAPIV3.RequestBodyObject;
34
- }
35
-
36
- export interface SchemaContext {
37
- kind: "schema";
38
- jsonpointer: string;
39
- path?: string;
40
- method?: string;
41
- origin: SchemaContextKind;
42
- /** Property name if this schema is a value of `properties.<name>`. */
43
- propertyName?: string;
44
- schema: OpenAPIV3.SchemaObject;
45
- }
46
-
47
- export type WalkContext = ParamContext | ResponseContext | RequestBodyContext | SchemaContext;
48
-
49
- export type Visitor = (ctx: WalkContext) => void;
50
-
51
- /**
52
- * RFC6901 segment encoder: `~` → `~0`, `/` → `~1`.
53
- */
54
- function escapePointerSegment(s: string | number): string {
55
- return String(s).replace(/~/g, "~0").replace(/\//g, "~1");
56
- }
57
-
58
- /**
59
- * Walk an OpenAPI 3.x document, invoking `visit` for parameters, request bodies,
60
- * responses, and every nested schema (recursively through properties / items /
61
- * combinators). Each call carries a stable RFC6901 jsonpointer so issues can
62
- * point precisely to the source.
63
- *
64
- * Cycles (already-visited schema objects by reference) are short-circuited so
65
- * `@readme/openapi-parser`-resolved $ref-cycles don't loop.
66
- */
67
- export function walk(doc: OpenAPIV3.Document, visit: Visitor): void {
68
- if (!doc.paths) return;
69
- for (const [path, pathItem] of Object.entries(doc.paths)) {
70
- if (!pathItem) continue;
71
- const pathPtr = `/paths/${escapePointerSegment(path)}`;
72
-
73
- // Path-level parameters
74
- if (pathItem.parameters) {
75
- pathItem.parameters.forEach((p, idx) => {
76
- const param = p as OpenAPIV3.ParameterObject;
77
- const ptr = `${pathPtr}/parameters/${idx}`;
78
- visit({ kind: "parameter", jsonpointer: ptr, path, method: "*", param });
79
- if (param.schema) {
80
- walkSchema(
81
- param.schema as OpenAPIV3.SchemaObject,
82
- `${ptr}/schema`,
83
- { origin: "param-schema", path, method: "*" },
84
- visit,
85
- new Set(),
86
- );
87
- }
88
- });
89
- }
90
-
91
- for (const m of HTTP_METHODS) {
92
- const op = (pathItem as Record<string, unknown>)[m] as OpenAPIV3.OperationObject | undefined;
93
- if (!op) continue;
94
- const opPtr = `${pathPtr}/${m}`;
95
- const method = m.toUpperCase();
96
-
97
- // Operation-level parameters
98
- if (op.parameters) {
99
- op.parameters.forEach((p, idx) => {
100
- const param = p as OpenAPIV3.ParameterObject;
101
- const ptr = `${opPtr}/parameters/${idx}`;
102
- visit({ kind: "parameter", jsonpointer: ptr, path, method, param });
103
- if (param.schema) {
104
- walkSchema(
105
- param.schema as OpenAPIV3.SchemaObject,
106
- `${ptr}/schema`,
107
- { origin: "param-schema", path, method },
108
- visit,
109
- new Set(),
110
- );
111
- }
112
- });
113
- }
114
-
115
- // Request body
116
- if (op.requestBody) {
117
- const rb = op.requestBody as OpenAPIV3.RequestBodyObject;
118
- const rbPtr = `${opPtr}/requestBody`;
119
- visit({ kind: "requestBody", jsonpointer: rbPtr, path, method, requestBody: rb });
120
- if (rb.content) {
121
- for (const [ct, mt] of Object.entries(rb.content)) {
122
- if (mt.schema) {
123
- walkSchema(
124
- mt.schema as OpenAPIV3.SchemaObject,
125
- `${rbPtr}/content/${escapePointerSegment(ct)}/schema`,
126
- { origin: "request-body", path, method },
127
- visit,
128
- new Set(),
129
- );
130
- }
131
- }
132
- }
133
- }
134
-
135
- // Responses
136
- if (op.responses) {
137
- for (const [status, respObj] of Object.entries(op.responses)) {
138
- const resp = respObj as OpenAPIV3.ResponseObject;
139
- const respPtr = `${opPtr}/responses/${escapePointerSegment(status)}`;
140
- visit({ kind: "response", jsonpointer: respPtr, path, method, status, response: resp });
141
- if (resp.content) {
142
- for (const [ct, mt] of Object.entries(resp.content)) {
143
- if (mt.schema) {
144
- walkSchema(
145
- mt.schema as OpenAPIV3.SchemaObject,
146
- `${respPtr}/content/${escapePointerSegment(ct)}/schema`,
147
- { origin: "response-body", path, method },
148
- visit,
149
- new Set(),
150
- );
151
- }
152
- }
153
- }
154
- }
155
- }
156
- }
157
- }
158
- }
159
-
160
- interface WalkSchemaCtx {
161
- origin: SchemaContextKind;
162
- path?: string;
163
- method?: string;
164
- propertyName?: string;
165
- }
166
-
167
- function walkSchema(
168
- schema: OpenAPIV3.SchemaObject,
169
- pointer: string,
170
- ctx: WalkSchemaCtx,
171
- visit: Visitor,
172
- visited: Set<unknown>,
173
- ): void {
174
- if (!schema || typeof schema !== "object") return;
175
- if (visited.has(schema)) return;
176
- visited.add(schema);
177
-
178
- visit({
179
- kind: "schema",
180
- jsonpointer: pointer,
181
- path: ctx.path,
182
- method: ctx.method,
183
- origin: ctx.origin,
184
- propertyName: ctx.propertyName,
185
- schema,
186
- });
187
-
188
- if (schema.properties) {
189
- for (const [name, sub] of Object.entries(schema.properties)) {
190
- walkSchema(
191
- sub as OpenAPIV3.SchemaObject,
192
- `${pointer}/properties/${escapePointerSegment(name)}`,
193
- { origin: "property", path: ctx.path, method: ctx.method, propertyName: name },
194
- visit,
195
- visited,
196
- );
197
- }
198
- }
199
-
200
- const arraySchema = schema as OpenAPIV3.ArraySchemaObject;
201
- if (arraySchema.items) {
202
- walkSchema(
203
- arraySchema.items as OpenAPIV3.SchemaObject,
204
- `${pointer}/items`,
205
- { ...ctx, propertyName: undefined },
206
- visit,
207
- visited,
208
- );
209
- }
210
-
211
- for (const combinator of ["allOf", "anyOf", "oneOf"] as const) {
212
- const arr = (schema as Record<string, unknown>)[combinator] as OpenAPIV3.SchemaObject[] | undefined;
213
- if (Array.isArray(arr)) {
214
- arr.forEach((sub, idx) => {
215
- walkSchema(
216
- sub,
217
- `${pointer}/${combinator}/${idx}`,
218
- { ...ctx, propertyName: undefined },
219
- visit,
220
- visited,
221
- );
222
- });
223
- }
224
- }
225
-
226
- if (typeof schema.additionalProperties === "object" && schema.additionalProperties !== null) {
227
- walkSchema(
228
- schema.additionalProperties as OpenAPIV3.SchemaObject,
229
- `${pointer}/additionalProperties`,
230
- { ...ctx, propertyName: undefined },
231
- visit,
232
- visited,
233
- );
234
- }
235
- }
236
-
237
- /**
238
- * Normalise OpenAPI 3.0 `nullable: true` so callers can reason about a single
239
- * type list. Returns the (possibly array) type without mutating the schema.
240
- */
241
- export function normalisedTypes(schema: OpenAPIV3.SchemaObject): string[] {
242
- const t = (schema as { type?: string | string[] }).type;
243
- const list: string[] = Array.isArray(t) ? [...t] : t ? [t] : [];
244
- if ((schema as { nullable?: boolean }).nullable === true && !list.includes("null")) {
245
- list.push("null");
246
- }
247
- return list;
248
- }
@@ -1,11 +0,0 @@
1
- import { createHash } from "crypto";
2
-
3
- /**
4
- * SHA-256 of the canonical (decycled) JSON form of an OpenAPI document.
5
- * Used as the freshness hash recorded in `.api-catalog.yaml`,
6
- * `.api-resources.yaml`, and `.api-fixtures.yaml` so `zond doctor` can
7
- * detect drift between the local snapshot and its derived artifacts.
8
- */
9
- export function hashSpec(specContent: string): string {
10
- return createHash("sha256").update(specContent).digest("hex");
11
- }
@@ -1,73 +0,0 @@
1
- # core/output — typed `--report` / `--output` / `--json` policy
2
-
3
- `OutputSpec<Payload>` is the single source-of-truth for how a command
4
- produces output. Closes the seven divergent-output bugs collected in
5
- `strategy/lessons.md` §E (ARV-50, ARV-82, ARV-97, …) by replacing N
6
- ad-hoc parsers with one resolver.
7
-
8
- ## Policy matrix
9
-
10
- `resolveOutput(spec, opts)` reads `--report`, `--output`, and `--json`
11
- from the CLI layer and resolves them to a `ResolvedOutput` decision
12
- according to this matrix:
13
-
14
- | Input | Format | Channel | Notes |
15
- | ------------------------------------ | -------------- | ------------------- | ----- |
16
- | _(nothing)_ | `defaultFormat`| from format policy | bare invocation |
17
- | `--report <fmt>` | `<fmt>` (or alias) | from format policy | unknown → error |
18
- | `--json` | first format with `envelopeWrap: true` | from format policy | falls back to `defaultFormat` when no envelope-wrap format exists |
19
- | `--output <path>` | unchanged | `file` (path = resolved absolute) | `--output` always wins over `defaultChannel` |
20
- | `--report sarif` (defaults to file) | `sarif` | `file` (`defaultFilename` if no `--output`) | ARV-5 default `zond-checks.sarif` |
21
- | `--report ndjson` (defaults to stdout) | `ndjson` | `stdout` | event stream — see ARV-10 |
22
- | `--report ndjson --output <path>` | `ndjson` | `file` | ARV-97 — explicit `--output` redirects the stream |
23
- | `--json` + `--report <fmt>` | — | — | mutually exclusive (throws `OutputSpecError`) |
24
-
25
- Aliases (`spec.aliases`) let a single CLI flag value resolve to
26
- another format — used by `checks run` so `--report ndjson` continues to
27
- mean "the `--ndjson` streaming flag", matching skill-prompt
28
- expectations (ARV-63).
29
-
30
- ## Building a spec
31
-
32
- ```ts
33
- import type { OutputSpec } from "@/core/output";
34
-
35
- interface ChecksPayload { findings: Finding[]; summary: Summary }
36
-
37
- export const CHECKS_RUN_OUTPUT: OutputSpec<ChecksPayload> = {
38
- command: "checks run",
39
- defaultFormat: "json",
40
- formats: {
41
- json: { defaultChannel: "stdout", envelopeWrap: true, description: "JSON envelope" },
42
- sarif: { defaultChannel: "file", defaultFilename: "zond-checks.sarif" },
43
- ndjson: { defaultChannel: "stdout", description: "event stream — one JSON per line" },
44
- },
45
- aliases: {
46
- // `--report ndjson` is a friendly alias retained from skill prompts.
47
- ndjson: "ndjson",
48
- },
49
- };
50
- ```
51
-
52
- The CLI handler calls `resolveOutput()` and renders/writes the payload
53
- itself — each command's shape (streaming vs single-shot, envelope vs
54
- raw) differs enough that a shared render/write runner added indirection
55
- without removing per-command logic. `checks run` and `probe *` open an
56
- fd from `resolved.path`/`resolved.channel` and feed output into it
57
- incrementally; `run` renders a single payload and writes it once. See
58
- any of `src/cli/commands/{run,checks,probe}.ts` for the pattern.
59
-
60
- ## Why the format set is open
61
-
62
- Each command owns its own format vocabulary. `run` ships
63
- `json`/`junit`; `checks run` ships `json`/`sarif`/`ndjson`; `probe *`
64
- ships `json` only. Declaring formats per-spec instead of in a global
65
- union avoids a leaky enum and keeps `--help` accurate per command.
66
-
67
- ## Errors
68
-
69
- `resolveOutput` throws `OutputSpecError` for policy violations
70
- (`--json + --report`, unknown format, file-default without filename).
71
- The CLI handler catches it and produces either `jsonError` or a human
72
- `printError`, matching the rest of the codebase's input-error
73
- behaviour (exit code 2).
@@ -1,13 +0,0 @@
1
- /**
2
- * ARV-116 (m-19): public surface of the output-spec module.
3
- */
4
- export type {
5
- OutputChannel,
6
- OutputFormat,
7
- OutputOptions,
8
- OutputSpec,
9
- FormatPolicy,
10
- ResolvedOutput,
11
- } from "./types.ts";
12
- export { OutputSpecError } from "./types.ts";
13
- export { resolveOutput } from "./run.ts";