@kirrosh/zond 0.23.0 → 0.26.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.
- package/CHANGELOG.md +164 -1
- package/README.md +8 -7
- package/package.json +2 -3
- package/src/CLAUDE.md +112 -0
- package/src/cli/commands/add-api.ts +19 -7
- package/src/cli/commands/api/annotate/index.ts +359 -4
- package/src/cli/commands/api/annotate/lifecycle.ts +1 -1
- package/src/cli/commands/api/annotate/overlay.ts +1 -1
- package/src/cli/commands/api/annotate/pagination.ts +10 -6
- package/src/cli/commands/api/annotate/prompts.ts +39 -2
- package/src/cli/commands/audit.ts +360 -54
- package/src/cli/commands/check.ts +15 -2
- package/src/cli/commands/checks.ts +352 -36
- package/src/cli/commands/cleanup.ts +4 -30
- package/src/cli/commands/coverage.ts +275 -57
- package/src/cli/commands/db.ts +311 -8
- package/src/cli/commands/discover.ts +281 -161
- package/src/cli/commands/doctor.ts +57 -3
- package/src/cli/commands/fixtures.ts +1 -1
- package/src/cli/commands/generate.ts +24 -7
- package/src/cli/commands/init/bootstrap.ts +4 -1
- package/src/cli/commands/init/index.ts +2 -2
- package/src/cli/commands/init/skills.ts +43 -0
- package/src/cli/commands/init/templates/agents.md +12 -3
- package/src/cli/commands/init/templates/skills/warm-up-target.md +122 -0
- package/src/cli/commands/init/templates/skills/zond-checks.md +268 -44
- package/src/cli/commands/init/templates/skills/zond-seed.md +114 -0
- package/src/cli/commands/init/templates/skills/zond-triage.md +88 -26
- package/src/cli/commands/init/templates/skills/zond.md +274 -64
- package/src/cli/commands/init/templates/zond-config.yml +1 -1
- package/src/cli/commands/prepare-fixtures.ts +14 -52
- package/src/cli/commands/probe/_seed-bodies.ts +52 -0
- package/src/cli/commands/probe/mass-assignment.ts +101 -10
- package/src/cli/commands/probe/security.ts +95 -12
- package/src/cli/commands/probe/webhooks.ts +2 -0
- package/src/cli/commands/probe.ts +87 -11
- package/src/cli/commands/refresh-api.ts +59 -1
- package/src/cli/commands/report-bundle.ts +3 -11
- package/src/cli/commands/request.ts +116 -0
- package/src/cli/commands/run.ts +33 -5
- package/src/cli/commands/schema-from-runs.ts +128 -0
- package/src/cli/commands/secrets.ts +133 -0
- package/src/cli/json-envelope.ts +0 -20
- package/src/cli/json-schemas.ts +51 -0
- package/src/cli/output.ts +17 -1
- package/src/cli/program.ts +5 -4
- package/src/cli/safe-live.ts +24 -0
- package/src/cli/status-filter.ts +0 -10
- package/src/core/audit/persist.ts +183 -0
- package/src/core/checks/budget.ts +59 -0
- package/src/core/checks/checks/cross_call_references.ts +17 -4
- package/src/core/checks/checks/cursor_boundary_fuzzing.ts +219 -0
- package/src/core/checks/checks/idempotency_replay.ts +1 -5
- package/src/core/checks/checks/ignored_auth.ts +44 -1
- package/src/core/checks/checks/index.ts +3 -0
- package/src/core/checks/checks/lifecycle_transitions.ts +169 -26
- package/src/core/checks/checks/negative_data_rejection.ts +119 -16
- package/src/core/checks/checks/not_a_server_error.ts +8 -0
- package/src/core/checks/checks/open_cors_on_sensitive.ts +47 -18
- package/src/core/checks/checks/pagination_invariants.ts +298 -117
- package/src/core/checks/checks/positive_data_acceptance.ts +1 -4
- package/src/core/checks/checks/status_code_conformance.ts +78 -7
- package/src/core/checks/mode.ts +3 -0
- package/src/core/checks/recommended-action.ts +5 -1
- package/src/core/checks/runner.ts +614 -27
- package/src/core/checks/spec-findings.ts +308 -0
- package/src/core/checks/types.ts +117 -1
- package/src/core/checks/zond-extensions.ts +73 -0
- package/src/core/classifier/recommended-action.ts +35 -6
- package/src/core/coverage/loader.ts +31 -0
- package/src/core/diagnostics/db-analysis.ts +200 -106
- package/src/core/diagnostics/failure-class.ts +21 -1
- package/src/core/diagnostics/failure-hints.ts +4 -208
- package/src/core/diagnostics/suggested-fixes.ts +2 -3
- package/src/core/generator/chunker.ts +1 -8
- package/src/core/generator/data-factory.ts +199 -61
- package/src/core/generator/fixtures-builder.ts +38 -31
- package/src/core/generator/index.ts +0 -2
- package/src/core/generator/openapi-reader.ts +98 -4
- package/src/core/generator/path-param-disambig.ts +30 -4
- package/src/core/generator/resources-builder.ts +276 -26
- package/src/core/generator/schema-utils.ts +22 -0
- package/src/core/generator/suite-generator.ts +168 -15
- package/src/core/generator/types.ts +6 -0
- package/src/core/identity/identity-file.ts +0 -0
- package/src/core/output/README.md +11 -29
- package/src/core/output/index.ts +1 -1
- package/src/core/output/run.ts +0 -35
- package/src/core/output/types.ts +0 -7
- package/src/core/parser/dynamic-values.ts +160 -0
- package/src/core/parser/variables.ts +0 -0
- package/src/core/probe/dry-run-envelope.ts +4 -0
- package/src/core/probe/mass-assignment/classify.ts +175 -0
- package/src/core/probe/mass-assignment/cleanup.ts +52 -0
- package/src/core/probe/mass-assignment/digest.ts +114 -0
- package/src/core/probe/mass-assignment/orchestrator.ts +459 -0
- package/src/core/probe/mass-assignment/regression.ts +141 -0
- package/src/core/probe/mass-assignment/suspects.ts +92 -0
- package/src/core/probe/mass-assignment/types.ts +135 -0
- package/src/core/probe/mass-assignment-probe.ts +23 -1118
- package/src/core/probe/mass-assignment-template.ts +32 -4
- package/src/core/probe/path-discovery.ts +3 -4
- package/src/core/probe/probe-harness.ts +21 -22
- package/src/core/probe/security/baseline.ts +174 -0
- package/src/core/probe/security/classify.ts +341 -0
- package/src/core/probe/security/cleanup.ts +125 -0
- package/src/core/probe/security/detectors.ts +71 -0
- package/src/core/probe/security/digest.ts +104 -0
- package/src/core/probe/security/orchestrator.ts +398 -0
- package/src/core/probe/security/regression.ts +103 -0
- package/src/core/probe/security/types.ts +151 -0
- package/src/core/probe/security-probe-class.ts +8 -2
- package/src/core/probe/security-probe.ts +28 -1449
- package/src/core/probe/shared.ts +26 -0
- package/src/core/probe/webhooks-probe.ts +5 -7
- package/src/core/runner/assertions.ts +1 -1
- package/src/core/runner/executor.ts +3 -18
- package/src/core/runner/form-encode.ts +8 -18
- package/src/core/runner/http-client.ts +38 -1
- package/src/core/runner/preflight-vars.ts +19 -15
- package/src/core/runner/rate-limiter.ts +11 -29
- package/src/core/runner/run-kind.ts +7 -1
- package/src/core/runner/schema-validator.ts +2 -6
- package/src/core/runner/send-request.ts +11 -6
- package/src/core/runner/types.ts +6 -0
- package/src/core/setup-api.ts +53 -15
- package/src/core/severity/index.ts +0 -63
- package/src/core/spec/infer-schema.ts +102 -0
- package/src/core/spec/merge-specs.ts +156 -0
- package/src/core/spec/schema-from-runs.ts +117 -0
- package/src/core/spec/schema-overlay.ts +130 -0
- package/src/core/util/ajv.ts +13 -0
- package/src/core/util/headers.ts +9 -0
- package/src/core/util/url.ts +24 -0
- package/src/core/workspace/fixture-gap-report.ts +84 -0
- package/src/core/workspace/fixture-gaps.ts +71 -0
- package/src/core/workspace/root.ts +13 -11
- package/src/db/migrate.ts +2 -0
- package/src/db/migrations/0002_run_kind_request.sql +59 -0
- package/src/db/queries/collections.ts +2 -2
- package/src/db/queries/results.ts +88 -0
- package/src/db/queries/runs.ts +56 -2
- package/src/db/queries.ts +3 -0
- package/src/db/schema.ts +7 -7
- package/src/cli/commands/bootstrap.ts +0 -710
- package/src/core/anti-fp/bootstrap.ts +0 -34
- package/src/core/anti-fp/index.ts +0 -33
- package/src/core/anti-fp/registry.ts +0 -44
- package/src/core/anti-fp/rules/baseline-echo.ts +0 -74
- package/src/core/anti-fp/rules/schemathesis/body_negation_becomes_valid.ts +0 -52
- package/src/core/anti-fp/rules/schemathesis/coverage_phase_boundary_positive.ts +0 -38
- package/src/core/anti-fp/rules/schemathesis/has_unverifiable_mutations.ts +0 -35
- package/src/core/anti-fp/rules/schemathesis/index.ts +0 -24
- package/src/core/anti-fp/rules/schemathesis/string_type_mutation_becomes_valid.ts +0 -53
- package/src/core/anti-fp/rules/subscription-gated/index.ts +0 -11
- package/src/core/anti-fp/rules/subscription-gated/paid-plan-403.ts +0 -75
- package/src/core/anti-fp/types.ts +0 -68
- package/src/core/generator/create-body.ts +0 -89
|
@@ -29,16 +29,28 @@ For triage of a failing run — see `zond-triage`.
|
|
|
29
29
|
- **NEVER `curl` or `wget`** — use `zond request <method> <url> --api <name>`
|
|
30
30
|
for ad-hoc HTTP so it lands in the run DB and respects auth. Never
|
|
31
31
|
shell-substitute the token by hand (`$(yq …)` is blocked by the sandbox).
|
|
32
|
+
`--body '{…}'` always sends `application/json`. For form-encoded APIs
|
|
33
|
+
(Stripe v1, classic webforms) pass `--form` — zond URL-encodes the
|
|
34
|
+
JSON body. Auto-detect from spec: if `requestBody.content` in
|
|
35
|
+
`.api-catalog.yaml` declares only `application/x-www-form-urlencoded`,
|
|
36
|
+
use `--form`; mixed content-types → pick by `Content-Type` header you
|
|
37
|
+
want to test.
|
|
32
38
|
- **NEVER hardcode tokens** — put them in `apis/<name>/.secrets.yaml`
|
|
33
39
|
(auto-gitignored), reference from `.env.yaml` as `@secret:auth_token`.
|
|
34
40
|
Plain `${SHELL_VAR}` references also work. Tests read the resolved
|
|
35
41
|
value as `{{auth_token}}`.
|
|
36
42
|
- **NEVER read `.secrets.yaml` directly.** Use `zond doctor --api <name> --json`
|
|
37
|
-
— reports `set | unset` and value length only.
|
|
43
|
+
— reports `set | unset` and value length only. To WRITE / rotate a secret
|
|
44
|
+
use `zond secrets set <key> <value> --api <name>` (writes a `.bak`, never
|
|
45
|
+
echoes the value, auto-strips a pasted `Bearer ` prefix — pass `--literal`
|
|
46
|
+
to keep it verbatim).
|
|
38
47
|
- **NEVER edit `.api-*.yaml`** (catalog / resources / fixtures) by hand —
|
|
39
48
|
regenerated by `zond refresh-api`. `.api-resources.local.yaml` IS the
|
|
40
|
-
hand-editable / annotate-target overlay; safe to edit.
|
|
41
|
-
|
|
49
|
+
hand-editable / annotate-target overlay; safe to edit.
|
|
50
|
+
`.api-schema.local.yaml` is the response-schema overlay (ARV-176) —
|
|
51
|
+
written by `refresh-api --merge-schema`, also survives refresh; edit by
|
|
52
|
+
re-merging a `patch.schema.json`, not by hand. `.env.yaml` values are
|
|
53
|
+
editable (see ARV-114).
|
|
42
54
|
- **NEVER invent endpoints.** Only use entries from `.api-catalog.yaml`.
|
|
43
55
|
- **NEVER run destructive ops on a shared / production org without
|
|
44
56
|
`--dry-run` first.** Probes, `prepare-fixtures --apply`, `cleanup` hit
|
|
@@ -55,12 +67,15 @@ For triage of a failing run — see `zond-triage`.
|
|
|
55
67
|
In autonomous / loop / audit-sweep mode (no user-in-the-loop), log to
|
|
56
68
|
`api-bugs-<NN>.md`, continue the sweep — bailing on bug #1 forfeits #2..N.
|
|
57
69
|
- **CRUD-run ≥80% 401/403 / `permission_denied` → `env_issue`, not bug.**
|
|
58
|
-
Token scope issue. Confirm via `zond db diagnose <run-id> --
|
|
59
|
-
|
|
70
|
+
Token scope issue. Confirm via `zond db diagnose <run-id> --json`
|
|
71
|
+
(401/403 dominate `by_recommended_action`); do not generate
|
|
72
|
+
case-studies, do not `expect:`-mask.
|
|
60
73
|
- **MUST run `zond doctor --api <name> --missing-only` before generating
|
|
61
74
|
fixtures or touching `.env.yaml`** — identifies unfilled keys early.
|
|
62
|
-
-
|
|
63
|
-
—
|
|
75
|
+
- **`prepare-fixtures` is single-pass, never POST-creates, and never
|
|
76
|
+
harvests values** (ARV-362) — it verifies existing fixtures and reports
|
|
77
|
+
unfilled vars as gaps. Which record/field fills a slot is your call:
|
|
78
|
+
fill fixtures by hand (`fixtures add` / editing `.env.yaml`).
|
|
64
79
|
|
|
65
80
|
## Workspace artifact model
|
|
66
81
|
|
|
@@ -101,12 +116,13 @@ probes/ ← probe-emitted suites
|
|
|
101
116
|
- "Generate should sync `.env.yaml`" is a rejected design (decision-7,
|
|
102
117
|
m-17).
|
|
103
118
|
|
|
104
|
-
### Manual fixture-bootstrap (
|
|
119
|
+
### Manual fixture-bootstrap (you fill every value)
|
|
105
120
|
|
|
106
|
-
`prepare-fixtures
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
121
|
+
ARV-362: `prepare-fixtures` **reports** FK gaps — it never harvests a
|
|
122
|
+
value. Every fixture is filled by hand: read the gap (empty list → create
|
|
123
|
+
a record first; non-empty → pick a value; 404/403 → auth/drift), then
|
|
124
|
+
supply it. Vendor-dashboard ids (Stripe `cus_*`, GitHub PR numbers,
|
|
125
|
+
Sentry issue ids) have no list endpoint at all — same hand-off:
|
|
110
126
|
|
|
111
127
|
| Input you have | Command |
|
|
112
128
|
|---|---|
|
|
@@ -116,7 +132,7 @@ hand-off:
|
|
|
116
132
|
`--validate` GETs the spec's read-by-id endpoint and classifies the
|
|
117
133
|
fixture as `live` / `stale` / `unknown` so dead ids don't silently
|
|
118
134
|
break later runs (ARV-32). Both commands write to `apis/<name>/.env.yaml`
|
|
119
|
-
with a `.env.yaml.bak` backup
|
|
135
|
+
with a `.env.yaml.bak` backup.
|
|
120
136
|
|
|
121
137
|
### When to read which file
|
|
122
138
|
|
|
@@ -153,7 +169,9 @@ accept `--json` — use `--report json`.
|
|
|
153
169
|
|
|
154
170
|
| User asked… | Start at |
|
|
155
171
|
|---|---|
|
|
156
|
-
| "
|
|
172
|
+
| "smoke this API", "quick first pass", "demo / CI gate" | `zond audit --api <name>` (safe-mode breadth pass — no live mutations) |
|
|
173
|
+
| "audit this API", "test the whole API", "raise coverage", "deep pass" | walk Phase 0–9 — audit covers smoke; depth (stateful, security, learn-apply) needs phase-level decisions |
|
|
174
|
+
| "full live audit against sandbox", "include mass-assignment/security probes" | `zond audit --api <name> --live --with-mass-assignment --with-security` (REQUIRES throwaway/sandbox account) |
|
|
157
175
|
| "find bugs", "test for 5xx", "probe sweep" | Phase 0 → Phase 7 (Probes) |
|
|
158
176
|
| "security only / SSRF / CRLF" | Phase 7.2 directly with `--dry-run` first |
|
|
159
177
|
| "deep depth-checks", "SARIF", "stateful invariants" | Hand off → `zond-checks` |
|
|
@@ -181,32 +199,85 @@ If doctor reports stale → `zond refresh-api <name>`.
|
|
|
181
199
|
|
|
182
200
|
## Phase 1 — Fixture pack
|
|
183
201
|
|
|
184
|
-
`zond prepare-fixtures`
|
|
185
|
-
|
|
186
|
-
|
|
202
|
+
`zond prepare-fixtures` is **single-pass and deterministic**: it walks
|
|
203
|
+
`.api-resources.yaml`, hits each owner list/read endpoint, and **reports**
|
|
204
|
+
which FK vars are unfilled (empty list → create one first; non-empty →
|
|
205
|
+
pick a value; 404/403 → auth/drift). It does **not** POST-create resources
|
|
206
|
+
and (ARV-362) **never harvests a value** — which record/field fills a slot
|
|
207
|
+
is your judgment. Fill every gap by hand (`fixtures add` / `.env.yaml`).
|
|
187
208
|
|
|
188
209
|
```bash
|
|
189
|
-
zond prepare-fixtures --api <name>
|
|
190
|
-
zond prepare-fixtures --api <name> --apply
|
|
191
|
-
zond prepare-fixtures --api <name> --
|
|
192
|
-
zond prepare-fixtures --api <name> --refresh # = --verify --apply: drop stale ids, re-resolve
|
|
210
|
+
zond prepare-fixtures --api <name> # report gaps (never writes values)
|
|
211
|
+
zond prepare-fixtures --api <name> --apply # only unsets stale ids under --refresh; never harvests
|
|
212
|
+
zond prepare-fixtures --api <name> --refresh # = --verify --apply: drop (unset) stale 404 ids → you refill
|
|
193
213
|
```
|
|
194
214
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
215
|
+
It also scans the generated suites and reports two named gap buckets
|
|
216
|
+
(warnings in text, `summary.fixtureGaps` in `--json`) — report only, no
|
|
217
|
+
values invented, no auto-seed (ARV-349/350):
|
|
218
|
+
- **`unseededRoots`** — required manifest vars that are empty, referenced by
|
|
219
|
+
a suite, and seeded by no step (e.g. `{{account}}` created in `crud-accounts`
|
|
220
|
+
but referenced uncaptured in `persons-crud`). These are chain-heads: one id
|
|
221
|
+
un-skips a whole dependent CRUD suite. Fix these first.
|
|
222
|
+
- **`undefinedVars`** — suite `{{vars}}` no manifest entry, capture, or param
|
|
223
|
+
produces (`{{bank_code}}`, `{{tax_id}}`). Supply by hand.
|
|
200
224
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
225
|
+
To fill `unseededRoots` autonomously — author create-bodies, POST them, and
|
|
226
|
+
capture the returned ids — hand off to **`zond-seed`** (agent reasons, zond
|
|
227
|
+
executes via `request … --capture`). It seeds only what the API self-serves
|
|
228
|
+
and reports external-input gaps honestly.
|
|
229
|
+
|
|
230
|
+
When a var stays UNSET after `--apply`, read the per-target reason in the
|
|
231
|
+
output, then fill the gap directly:
|
|
232
|
+
|
|
233
|
+
| Input you have | Command |
|
|
234
|
+
|---|---|
|
|
235
|
+
| Concrete id values | `zond fixtures add <var>=<id> [--validate] --apply` |
|
|
236
|
+
| Curl from devtools / dashboard | `pbpaste \| zond fixtures import --from-curl --apply` |
|
|
237
|
+
| Nothing yet (empty list) | Create the resource in the product UI / via API, then harvest its id with `fixtures add` |
|
|
238
|
+
|
|
239
|
+
Common reasons a var stays UNSET (all fixed outside prepare-fixtures):
|
|
240
|
+
- empty list on the target account — no record exists to harvest;
|
|
241
|
+
- the id lives only in a vendor dashboard (Stripe `cus_*`, GitHub PR
|
|
242
|
+
numbers, Sentry issue ids) with no discoverable list endpoint;
|
|
243
|
+
- the value is gated outside the API path (email verify / paid plan /
|
|
244
|
+
SCIM / TOS limits). Document the gap; don't loop.
|
|
204
245
|
|
|
205
246
|
Editing `.env.yaml` by hand is the sanctioned fallback when the CLI
|
|
206
247
|
cannot harvest a value (write-only ingest endpoints, SDK-only ids,
|
|
207
248
|
slugs that live in user's project config). Touch values only — never
|
|
208
249
|
add keys not in `.api-fixtures.yaml`.
|
|
209
250
|
|
|
251
|
+
### Dynamic value functions in `.env.yaml` (ARV-190, m-21)
|
|
252
|
+
|
|
253
|
+
Stale hardcoded UUIDs / dates / idempotency keys pin existing rows or
|
|
254
|
+
expire mid-run. Use `#(funcName)` / `#(funcName(args))` references —
|
|
255
|
+
evaluated at load time, stable within one run:
|
|
256
|
+
|
|
257
|
+
| Token | Resolves to | Use for |
|
|
258
|
+
|---|---|---|
|
|
259
|
+
| `#(uuid)` | fresh UUID v4 (same value across all references in this run) | Idempotency-Key, request id |
|
|
260
|
+
| `#(uuidStable(<seed>))` | deterministic UUID from seed (sha-256 shaped as v4) | reproducible tests |
|
|
261
|
+
| `#(today)` | `YYYY-MM-DD` (UTC) | expiry-from dates |
|
|
262
|
+
| `#(todayPlus(N))` | today + N days (N may be negative) | expires_at, valid_until |
|
|
263
|
+
| `#(now)` | ISO 8601 timestamp | created_at hints |
|
|
264
|
+
| `#(unix)` | seconds since epoch | epoch timestamps |
|
|
265
|
+
| `#(alphanumeric(N))` | N random `[a-z0-9]` chars (default 8) | one-shot tokens |
|
|
266
|
+
| `#(env:VAR)` | `process.env.VAR` (alias for `${VAR}`) | secrets passed through |
|
|
267
|
+
|
|
268
|
+
```yaml
|
|
269
|
+
# apis/foo/.env.yaml
|
|
270
|
+
idempotency_key: "#(uuid)" # same uuid across every step in this run
|
|
271
|
+
trial_expires: "#(todayPlus(30))" # 30 days from now
|
|
272
|
+
request_id: "req-#(uuid)" # embedded; shares uuid with above
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Resolution order: `${ENV}` → `#(...)` → `@secret:` → `@identity:`. Dynamic
|
|
276
|
+
functions run BEFORE secret/identity refs so a secret value that happens to
|
|
277
|
+
contain `#(...)` stays opaque (no double-expansion). Per-run cache means
|
|
278
|
+
`#(uuid)` referenced twice in the same `.env.yaml` returns the same value
|
|
279
|
+
— critical for idempotency-replay flows.
|
|
280
|
+
|
|
210
281
|
## Phase 2 — Annotate (NEW, m-20 / ARV-187)
|
|
211
282
|
|
|
212
283
|
**Critical**: zond itself does NOT call any LLM. The flow is
|
|
@@ -218,7 +289,7 @@ your downstream check needs:
|
|
|
218
289
|
|
|
219
290
|
| Aspect | Dump produces | Feeds into |
|
|
220
291
|
|---|---|---|
|
|
221
|
-
| `--seed-bodies` | request-body schema per resource |
|
|
292
|
+
| `--seed-bodies` | request-body schema per resource | all stateful checks (create-body overlay) |
|
|
222
293
|
| `--readback` | create+read pair (write-shape vs read-shape) | `cross_call_references` ignore_fields, write_to_read_map |
|
|
223
294
|
| `--idempotency` | header candidates | `idempotency_replay` (header, scope, body_hash_fields) |
|
|
224
295
|
| `--pagination` | list-endpoint cursor/page detection | `pagination_invariants` (type, cursor_param) |
|
|
@@ -260,7 +331,36 @@ to defaults and miss API-specific quirks (custom pagination params,
|
|
|
260
331
|
non-standard lifecycle field names, write-only fields in create body).
|
|
261
332
|
|
|
262
333
|
**For Stripe-style form-encoded APIs**: `--seed-bodies` is the single
|
|
263
|
-
biggest win —
|
|
334
|
+
biggest win — the create-body overlay it produces lets stateful checks
|
|
335
|
+
POST valid resources instead of 400-ing on schema-derived random bodies.
|
|
336
|
+
|
|
337
|
+
### Agent-in-the-loop annotate (dump → write → apply, ARV-187 / 277 / 278-282)
|
|
338
|
+
|
|
339
|
+
zond does NOT infer annotations. YOU (the agent) read the spec slice and
|
|
340
|
+
write the overlay; zond only dumps raw material and validates/merges what
|
|
341
|
+
you produce.
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
# 1. Dump the raw material for a resource — spec slice + expected YAML
|
|
345
|
+
# shape + (for seed-bodies) the last N seed-POST attempts so you see
|
|
346
|
+
# the error progression, not just the current 400 (ARV-277/278).
|
|
347
|
+
zond api annotate dump --api <name> --seed-bodies --only <res> \
|
|
348
|
+
--with-last-attempt --history 5
|
|
349
|
+
|
|
350
|
+
# 2. YOU write the overlay: read the dumped slice + attempt history,
|
|
351
|
+
# reason about the required create body / pagination / lifecycle /
|
|
352
|
+
# idempotency, and produce one YAML doc per resource. Edit
|
|
353
|
+
# .api-resources.local.yaml directly OR emit a file for apply.
|
|
354
|
+
|
|
355
|
+
# 3. Apply — zond validates your YAML (zod), renders a diff, and (with
|
|
356
|
+
# --yes) merges. With --gap-fill-only (ARV-281) your response can ONLY
|
|
357
|
+
# add missing fields — already-set blocks are protected from overwrite.
|
|
358
|
+
zond api annotate apply --api <name> --seed-bodies --input agent-out.yaml --gap-fill-only --yes
|
|
359
|
+
|
|
360
|
+
# 4. Refresh fixtures — drop (unset) stale (404) ids so they resurface as
|
|
361
|
+
# gaps; refill them by hand / `fixtures add` (discover never harvests).
|
|
362
|
+
zond prepare-fixtures --api <name> --refresh
|
|
363
|
+
```
|
|
264
364
|
|
|
265
365
|
## Phase 3 — Generate tests
|
|
266
366
|
|
|
@@ -269,9 +369,15 @@ zond generate apis/<name>/spec.json --output apis/<name>/tests [--tag <spec-tag>
|
|
|
269
369
|
zond check tests apis/<name>/tests
|
|
270
370
|
```
|
|
271
371
|
|
|
272
|
-
`generate` fills bodies with `{{$randomString}}
|
|
273
|
-
reject many — that's a **test-fix**, not a backend bug.
|
|
274
|
-
Phase 1 fixtures + Phase 2 annotation.
|
|
372
|
+
`generate` fills bodies with typed generators (`{{$randomString}}` etc.).
|
|
373
|
+
Format-strict APIs reject many — that's a **test-fix**, not a backend bug.
|
|
374
|
+
Pair with Phase 1 fixtures + Phase 2 annotation.
|
|
375
|
+
|
|
376
|
+
Required FK / reference body fields (`*_id`, unresolved `*Code` like
|
|
377
|
+
`sequenceTypeCode`) are wired to `{{fixture}}` refs (ARV-45) — canonicalised
|
|
378
|
+
(`sequence_type_code`) and listed in `.api-fixtures.yaml` as `source: body-fk`.
|
|
379
|
+
Fill their real values once in `.env.yaml`; no more hand-editing generated
|
|
380
|
+
tests. Blank fixture → the var is unbound and dependent tests skip.
|
|
275
381
|
|
|
276
382
|
If a CRUD chain you expected is missing, run
|
|
277
383
|
`zond generate <spec> --explain` — diagnostic table shows every POST
|
|
@@ -318,6 +424,13 @@ zond session end
|
|
|
318
424
|
invisible without it. `schema_violation` failures are real backend bugs;
|
|
319
425
|
treat them like 5xx.
|
|
320
426
|
|
|
427
|
+
**After any `zond run` with failures → delegate to `zond-triage`.** Do
|
|
428
|
+
NOT parse `runs/run-NN.json` with `jq` by hand. The triage skill reads
|
|
429
|
+
`zond db diagnose --json` (which already buckets by `recommended_action`
|
|
430
|
+
in `data.by_recommended_action`, ARV-228) and routes by closed enum —
|
|
431
|
+
re-implementing that logic with prose heuristics drifts. Only fall back
|
|
432
|
+
to manual CLI queries when the failure is outside the example slice.
|
|
433
|
+
|
|
321
434
|
Rate limit: `zond run` defaults to an adaptive limiter (no-op until
|
|
322
435
|
`RateLimit-*` headers appear). Pass `--rate-limit <N>` for a hard cap;
|
|
323
436
|
`--sequential` for old binaries.
|
|
@@ -363,29 +476,47 @@ After Phase 2 annotation is applied, run the cross-call invariants.
|
|
|
363
476
|
See `zond-checks` for per-check semantics.
|
|
364
477
|
|
|
365
478
|
```bash
|
|
366
|
-
zond checks run --api <name> --check stateful --report ndjson
|
|
479
|
+
zond checks run --api <name> --check stateful --report ndjson --live # CRUD create-chains need --live
|
|
367
480
|
```
|
|
368
481
|
|
|
369
|
-
|
|
482
|
+
**Safe by default (ARV-299)**: without `--live`, `checks run` skips the
|
|
483
|
+
stateful CRUD create-chains (`ensure_resource_availability`,
|
|
484
|
+
`use_after_free`) — they'd POST real resources. Read-only stateful checks
|
|
485
|
+
(pagination, observation-mode lifecycle, cross-call GET diff) still run.
|
|
486
|
+
Add `--live` **only against a throwaway/sandbox account** to exercise the
|
|
487
|
+
create-chains.
|
|
488
|
+
|
|
489
|
+
`--check stateful` expands to the state-machine set (ARV-325):
|
|
370
490
|
- `cross_call_references` — POST→GET drift (uses readback_diff overlay)
|
|
371
491
|
- `idempotency_replay` — bit-identical replay on `Idempotency-Key`
|
|
372
492
|
- `pagination_invariants` — `?limit=N` + `?after=last_id` non-overlap
|
|
373
493
|
- `lifecycle_transitions` — declared state-machine validity
|
|
494
|
+
- `use_after_free`, `ensure_resource_availability` — CRUD lifecycle
|
|
495
|
+
- `cursor_boundary_fuzzing` — list-cursor edge values
|
|
374
496
|
- `webhooks` (recipe-based, see `docs/recipes/webhook-receiver.md`)
|
|
375
497
|
|
|
498
|
+
`ignored_auth` / `open_cors_on_sensitive` are **not** part of the alias —
|
|
499
|
+
they're auth/security checks that roughly triple run time on wide APIs.
|
|
500
|
+
Run them by explicit id: `--check ignored_auth,open_cors_on_sensitive`.
|
|
501
|
+
|
|
376
502
|
**Iron rule**: don't run `--check stateful` without prior `api annotate`
|
|
377
503
|
review. Defaults catch the obvious; quirks need declared config.
|
|
378
504
|
|
|
379
505
|
## Phase 7 — Proactive bug hunting (probes)
|
|
380
506
|
|
|
381
507
|
```bash
|
|
382
|
-
zond probe static --api <name> # validation + methods (defaults)
|
|
383
|
-
zond probe mass-assignment --api <name> --emit-tests apis/<name>/probes/mass-assignment
|
|
384
|
-
zond probe security ssrf,crlf,open-redirect
|
|
508
|
+
zond probe static --api <name> # validation + methods (defaults; generator, always safe)
|
|
509
|
+
zond probe mass-assignment --api <name> --live --emit-tests apis/<name>/probes/mass-assignment
|
|
510
|
+
zond probe security ssrf,crlf,open-redirect --api <name> --live \
|
|
385
511
|
--emit-tests apis/<name>/probes/security
|
|
386
512
|
zond run apis/<name>/probes --report json
|
|
387
513
|
```
|
|
388
514
|
|
|
515
|
+
**Safe by default (ARV-299)**: `probe mass-assignment` / `probe security`
|
|
516
|
+
without `--live` plan only (no live attack payloads) — same as `--dry-run`.
|
|
517
|
+
Add `--live` **only against a throwaway/sandbox account** to send probes.
|
|
518
|
+
`probe static` just generates suites, so it's always safe.
|
|
519
|
+
|
|
389
520
|
Flag: 5xx on null/empty/wrong-type body → missing validation. 2xx on
|
|
390
521
|
undeclared method → contract drift. `is_admin: true` echoed in response
|
|
391
522
|
→ HIGH from mass-assignment.
|
|
@@ -416,13 +547,27 @@ round-trip GET → **HIGH**, file via `report bundle --include case-study`.
|
|
|
416
547
|
### 7.2 — Security probes
|
|
417
548
|
|
|
418
549
|
```bash
|
|
419
|
-
zond probe security ssrf,crlf --api <name> --dry-run # first run: which (endpoint, field)
|
|
420
|
-
zond probe security ssrf,crlf,open-redirect
|
|
550
|
+
zond probe security ssrf,crlf --api <name> --dry-run # first run: which (endpoint, field) — same as default --safe
|
|
551
|
+
zond probe security ssrf,crlf,open-redirect --api <name> --live # send, sandbox only
|
|
421
552
|
```
|
|
422
553
|
|
|
554
|
+
**Targeting** (two filters; an endpoint must pass BOTH to be planned):
|
|
555
|
+
|
|
556
|
+
1. **Has a JSON request body** — GET-only routes are skipped with
|
|
557
|
+
`no-body`. The probes work by mutating a body field, so read-only
|
|
558
|
+
endpoints have nothing to attack.
|
|
559
|
+
2. **Has ≥1 field name matching the requested class detectors** — if
|
|
560
|
+
no field matches, the endpoint is skipped with `no-matched-field`.
|
|
561
|
+
|
|
423
562
|
Field autodetection: SSRF (`*_url`/`webhook`/`callback`/`format: uri`),
|
|
424
563
|
CRLF (`subject`/`*_prefix`/`name`), open-redirect (`redirect`/`next`).
|
|
425
564
|
|
|
565
|
+
If `--dry-run` reports `0 planned / 0 skipped / 0 total` with a narrow
|
|
566
|
+
`--include`, the scope likely captured only GET routes — broaden the
|
|
567
|
+
filter or drop `--include` to see the per-endpoint skip reasons. With
|
|
568
|
+
no `--include`, the dry-run shows `no-body` / `no-matched-field` next
|
|
569
|
+
to each skipped endpoint so the gap is obvious.
|
|
570
|
+
|
|
426
571
|
Baseline-OK request first; if baseline ≠ 2xx → `INCONCLUSIVE-BASELINE`,
|
|
427
572
|
attacks skipped (kills 5×404 noise on scope-locked endpoints).
|
|
428
573
|
|
|
@@ -448,16 +593,34 @@ seeded-fixture endpoints).
|
|
|
448
593
|
## Phase 8 — Coverage + spec drift
|
|
449
594
|
|
|
450
595
|
```bash
|
|
451
|
-
zond coverage --api <name> --union session # default
|
|
596
|
+
zond coverage --api <name> --union session # default: dual-metric (test + audit)
|
|
452
597
|
zond coverage --api <name> --union since:1h # last hour
|
|
453
598
|
zond coverage --api <name> --union tag:smoke # tag-filtered
|
|
454
|
-
zond coverage --api <name> --
|
|
599
|
+
zond coverage --api <name> --scope audit --union session # only "did zond touch the API"
|
|
600
|
+
zond coverage --api <name> --scope test --union session # legacy single block
|
|
601
|
+
zond coverage --api <name> --fail-on-coverage 50 # CI gate (gates pass-coverage)
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**Dual-metric output (ARV-265).** `zond coverage` prints two blocks side-by-side by default:
|
|
605
|
+
|
|
606
|
+
- **test-coverage** — runs produced by `zond run` only (`run_kind` ∈ {regular, probe}). Has two sub-metrics:
|
|
607
|
+
- `pass-coverage`: endpoint had a passing 2xx (strict; CI gate via `--fail-on-coverage`)
|
|
608
|
+
- `hit-coverage`: endpoint received any response (loose; breadth proxy)
|
|
609
|
+
Three-bucket JSON: `covered2xx`, `coveredButNon2xx`, `unhit`.
|
|
610
|
+
- **audit-coverage** — any HTTP touch from any producer (`zond run` + `checks run` + `probe` + `request` + `prepare-fixtures`). Use this to answer "did the scan reach the API at all?" — typical safe-mode runs without `zond run` still report >50% on a 1k-endpoint spec from `checks run` alone.
|
|
611
|
+
|
|
612
|
+
Source breakdown (`--scope audit` text or `data.audit_coverage.by_source` JSON):
|
|
613
|
+
|
|
614
|
+
```
|
|
615
|
+
audit-coverage: 619/1184 endpoints (52%, 1500 HTTP touches)
|
|
616
|
+
by source:
|
|
617
|
+
check 619 endpoints, 1500 events
|
|
618
|
+
regular 0 endpoints, 0 events
|
|
619
|
+
probe 0 endpoints, 0 events
|
|
620
|
+
request 3 endpoints, 3 events
|
|
455
621
|
```
|
|
456
622
|
|
|
457
|
-
|
|
458
|
-
`hit-coverage` (any stored result, breadth proxy). Three-bucket JSON:
|
|
459
|
-
`covered2xx`, `coveredButNon2xx` (hit but failed — usually fixture gap,
|
|
460
|
-
not API bug), `unhit`.
|
|
623
|
+
Producers opt out via `ZOND_CHECKS_PERSIST=0`.
|
|
461
624
|
|
|
462
625
|
**Quality signals to gate CI on** (not raw pass-coverage):
|
|
463
626
|
|
|
@@ -474,8 +637,8 @@ not API bug), `unhit`.
|
|
|
474
637
|
|
|
475
638
|
```bash
|
|
476
639
|
zond report export <run-id> # default: triage/<api>/run-<id>/
|
|
477
|
-
zond report bundle 135..142 -o triage/sweep/ # all artefacts (default): case-study +
|
|
478
|
-
zond report bundle <run-id> --include case-study,diagnose # subset only (drop
|
|
640
|
+
zond report bundle 135..142 -o triage/sweep/ # all artefacts (default): case-study + export + diagnose + index.md
|
|
641
|
+
zond report bundle <run-id> --include case-study,diagnose # subset only (drop export)
|
|
479
642
|
zond report bundle --session <id> -o triage/session/ # group by session
|
|
480
643
|
```
|
|
481
644
|
|
|
@@ -489,29 +652,69 @@ Offer this proactively after a run surfaces `definitely_bug`
|
|
|
489
652
|
(5xx / schema violation / mass-assignment 2xx). Skip for `env_issue` /
|
|
490
653
|
`quirk`.
|
|
491
654
|
|
|
492
|
-
## One-shot
|
|
655
|
+
## One-shot smoke audit — `zond audit`
|
|
493
656
|
|
|
494
|
-
|
|
495
|
-
prepare-fixtures → generate → probe static
|
|
496
|
-
tests+probes → coverage → `audit-report.html`.
|
|
497
|
-
|
|
498
|
-
|
|
657
|
+
**`zond audit` is the breadth pass, not "full audit".** It covers the
|
|
658
|
+
*smoke + coverage* slice — prepare-fixtures → generate → probe static
|
|
659
|
+
→ session-wrapped run on tests+probes → coverage → `audit-report.html`.
|
|
660
|
+
Depth (stateful checks, security probes against R18 evidence-gates,
|
|
661
|
+
m-20 annotate, `--learn-apply` iteration) is the **skill's job, not
|
|
662
|
+
audit's**: those stages need decisions audit can't make
|
|
663
|
+
(annotate-before-stateful, scoping after auth-cluster, evidence
|
|
664
|
+
preconditions). Use audit for: CI smoke, demo, "first 5 minutes on a
|
|
665
|
+
new API". For a real depth campaign, walk Phase 0–9 from this skill.
|
|
499
666
|
|
|
500
667
|
```bash
|
|
501
|
-
zond audit --api <name> --dry-run # plan
|
|
502
|
-
zond audit --api <name> #
|
|
503
|
-
zond audit --api <name> --
|
|
504
|
-
zond audit --api <name> --with-mass-assignment --with-security
|
|
668
|
+
zond audit --api <name> --dry-run # print stage plan
|
|
669
|
+
zond audit --api <name> # safe mode (default) — no live probes
|
|
670
|
+
zond audit --api <name> --live --with-mass-assignment --with-security # full live pipeline (throwaway/sandbox only)
|
|
505
671
|
zond audit --api <name> --out reports/audit-<name>.html
|
|
506
672
|
```
|
|
507
673
|
|
|
674
|
+
**ARV-264 safe vs live decision matrix.** Default is `--safe` (implicit;
|
|
675
|
+
no flag needed). `--live` is the opt-in for traffic that mutates the
|
|
676
|
+
target API.
|
|
677
|
+
|
|
678
|
+
| Concern | `--safe` (default) | `--live` |
|
|
679
|
+
|---|---|---|
|
|
680
|
+
| `prepare-fixtures` mode | single-pass path-FK discovery (GET) | same — single-pass, never POST-creates |
|
|
681
|
+
| `probe mass-assignment` | skipped even if `--with-mass-assignment` set, with a warning | runs when `--with-mass-assignment` set |
|
|
682
|
+
| `probe security` (SSRF/CRLF/redirect) | skipped even if `--with-security` set, with a warning | runs when `--with-security` set |
|
|
683
|
+
| `probe static` (validation+methods) | always runs — pure spec walk, no live traffic | same |
|
|
684
|
+
| `run tests / probes` | runs against the live API but only generated GET-shape suites by default | same |
|
|
685
|
+
|
|
686
|
+
**Pre-flight warnings (printed before any subprocess fires):** missing
|
|
687
|
+
`auth_token` when the spec declares securitySchemes (probes will skip
|
|
688
|
+
with 401 and the report will read misleadingly green); no
|
|
689
|
+
securitySchemes declared in the spec (open API or incomplete spec —
|
|
690
|
+
auth-related checks will skip).
|
|
691
|
+
|
|
692
|
+
**Rule of thumb.** Unknown-scope PAT against a production API → `--safe`
|
|
693
|
+
(default). Throwaway account / sandbox tenant → `--live` is fine. Never
|
|
694
|
+
run `--live` against shared/production data on first contact.
|
|
695
|
+
|
|
696
|
+
Each stage prints `==> Stage N/M: <name>` and a completion line
|
|
697
|
+
`└─ OK · Ns` / `FAIL (exit K) · Ns` / `SKIPPED (reason)`. Exit 1 if
|
|
698
|
+
any non-coverage stage failed.
|
|
699
|
+
|
|
508
700
|
`generate` skipped via mtime when `tests/` is fresher than `spec.json`
|
|
509
701
|
(pass `--force` to override).
|
|
510
702
|
|
|
511
|
-
**
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
703
|
+
**Session reuse (ARV-65)**: if `.zond/current-session` is already set
|
|
704
|
+
when audit starts (i.e. user ran `zond session start` first), audit
|
|
705
|
+
reuses that session — `session-start` and `session-end` stages skip
|
|
706
|
+
with reason `reusing active session <id>`. The user's session stays
|
|
707
|
+
active after audit returns; runs inside audit are stamped with the
|
|
708
|
+
outer `session_id` and roll up under the user's `coverage --union
|
|
709
|
+
session` later.
|
|
710
|
+
|
|
711
|
+
ARV-158: when any run inside the audit session has failures, the
|
|
712
|
+
generated `audit-report.html` embeds a "Failures by run" section with
|
|
713
|
+
collapsible `<details>` per run — `by_recommended_action` buckets +
|
|
714
|
+
first example (method, path, status, reason) + concrete drill-down
|
|
715
|
+
commands (`zond db diagnose --run-id N --json`, `zond report export
|
|
716
|
+
N`). Triage starts from the rendered HTML; you only fall back to CLI
|
|
717
|
+
queries when you need fields outside the example slice.
|
|
515
718
|
|
|
516
719
|
When NOT to use audit: narrow asks ("fix run X", "why this endpoint
|
|
517
720
|
500s") — walk phases.
|
|
@@ -582,9 +785,16 @@ verifying a fix** — small focused work. For breadth, use Phase 3
|
|
|
582
785
|
zond db runs --limit 5 --json
|
|
583
786
|
zond db diagnose <run-id> --json # grouped by root_cause
|
|
584
787
|
zond db run <id> --status 500 --json
|
|
585
|
-
zond db compare <idA> <idB> --json # regression diff
|
|
788
|
+
zond db compare <idA> <idB> --json # regression diff + field-level body diff (.data.body_changes[], ARV-339)
|
|
789
|
+
zond db run <id> --report yaml # run snapshot as YAML (ARV-338) — keep/diff as text
|
|
586
790
|
```
|
|
587
791
|
|
|
792
|
+
> **db --json envelope shape** (consistent across siblings): the array
|
|
793
|
+
> always lives under `data.<plural>`, not directly under `data`.
|
|
794
|
+
> `db runs` → `.data.runs[]`, `db collections` → `.data.collections[]`,
|
|
795
|
+
> `db run <id> --status …` → `.data.results[]`. Top-level totals on runs
|
|
796
|
+
> are at `runs[].total/passed/failed`, not under a `.summary` wrapper.
|
|
797
|
+
|
|
588
798
|
`recommended_action` enum (closed):
|
|
589
799
|
- `report_backend_bug` — STOP, surface to user
|
|
590
800
|
- `fix_test_logic` — edit the YAML
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# zond workspace config
|
|
2
2
|
#
|
|
3
3
|
# The presence of this file marks the workspace root for `zond` walk-up
|
|
4
|
-
# resolution (zond.db, apis/<name>/, .zond/current-api are anchored here).
|
|
4
|
+
# resolution (.zond/zond.db, apis/<name>/, .zond/current-api are anchored here).
|
|
5
5
|
|
|
6
6
|
version: 1
|
|
7
7
|
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `zond prepare-fixtures` —
|
|
2
|
+
* `zond prepare-fixtures` — single-pass fixture-pack command.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Deterministically fills `apis/<name>/.env.yaml` with FK ids that can be
|
|
5
|
+
* resolved from list/read endpoints in a single discover pass, and reports
|
|
6
|
+
* which fixtures are still missing so an agent (or the user) can fill the
|
|
7
|
+
* gaps by hand (`fixtures add` / editing `.env.yaml`).
|
|
5
8
|
*
|
|
6
9
|
* - default → single-pass discover (auto-fill FK ids
|
|
7
10
|
* from list endpoints).
|
|
8
|
-
* - --
|
|
9
|
-
* - --seed → cascade + POST-create when discover misses
|
|
10
|
-
* (implies --cascade).
|
|
11
|
-
* - --verify / --refresh → revalidate fixtures via read-by-id
|
|
12
|
-
* (former `discover --verify/--refresh`).
|
|
11
|
+
* - --verify / --refresh → revalidate fixtures via read-by-id.
|
|
13
12
|
*
|
|
14
|
-
* The imperative
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* The imperative core (`discoverCommand`) lives in the original module and
|
|
14
|
+
* is still consumed directly by tests. This module only owns the CLI
|
|
15
|
+
* surface.
|
|
17
16
|
*/
|
|
18
17
|
|
|
19
18
|
import type { Command } from "commander";
|
|
@@ -23,7 +22,6 @@ import { getDb } from "../../db/schema.ts";
|
|
|
23
22
|
import { findCollectionByNameOrId } from "../../db/queries.ts";
|
|
24
23
|
import { printError } from "../output.ts";
|
|
25
24
|
import { discoverCommand } from "./discover.ts";
|
|
26
|
-
import { bootstrapCommand } from "./bootstrap.ts";
|
|
27
25
|
import { loadEnvMeta } from "../../core/parser/variables.ts";
|
|
28
26
|
import { resolveTimeoutMs } from "../../core/workspace/config.ts";
|
|
29
27
|
import { getApi, MISSING_API_MESSAGE } from "../util/api-context.ts";
|
|
@@ -32,9 +30,9 @@ export function registerPrepareFixtures(program: Command): void {
|
|
|
32
30
|
program
|
|
33
31
|
.command("prepare-fixtures")
|
|
34
32
|
.description(
|
|
35
|
-
"Auto-fill apis/<name>/.env.yaml
|
|
36
|
-
"
|
|
37
|
-
"
|
|
33
|
+
"Auto-fill apis/<name>/.env.yaml in a single discover pass: resolve FK " +
|
|
34
|
+
"ids from list/read endpoints and report which fixtures are still " +
|
|
35
|
+
"missing (fill those by hand via `fixtures add` / editing .env.yaml).",
|
|
38
36
|
)
|
|
39
37
|
// Not `requiredOption` — the value can also come from the program-level
|
|
40
38
|
// --api flag (parsed by program.ts and mirrored into ZOND_API_GLOBAL),
|
|
@@ -46,13 +44,9 @@ export function registerPrepareFixtures(program: Command): void {
|
|
|
46
44
|
.option("--api-dir <path>", "Override apis/<name>/ root (defaults to the collection's base_dir)")
|
|
47
45
|
.option("--env <path>", "Override .env.yaml path (defaults to <api-dir>/.env.yaml)")
|
|
48
46
|
.option("--apply", "Write discovered values to .env.yaml (with .env.yaml.bak backup). Default: dry-run.")
|
|
49
|
-
.option("--
|
|
50
|
-
.option("--
|
|
51
|
-
.option("--force", "Re-discover/re-seed even if a fixture is already filled (cascade only)")
|
|
52
|
-
.option("--verify", "GET each fixture's read-by-id endpoint and classify live/stale/unknown (single-pass only). Combine with --apply (or use --refresh) to drop stale fixtures and re-resolve them. (TASK-281)")
|
|
53
|
-
.option("--refresh", "Shortcut for --verify --apply (single-pass only). (TASK-281)")
|
|
47
|
+
.option("--verify", "GET each fixture's read-by-id endpoint and classify live/stale/unknown. Combine with --apply (or use --refresh) to drop stale fixtures and re-resolve them. (TASK-281)")
|
|
48
|
+
.option("--refresh", "Shortcut for --verify --apply. (TASK-281)")
|
|
54
49
|
.option("--timeout <ms>", "Per-request timeout in ms (overrides apis/<name>/.env.yaml `timeoutMs` and zond.config.yml `defaults.timeout_ms`; default 30000)", parsePositiveInt("--timeout"))
|
|
55
|
-
.option("--max-passes <n>", "Cap on cascade passes (default 8; cascade only)", parsePositiveInt("--max-passes"))
|
|
56
50
|
.action(async (opts, cmd: Command) => {
|
|
57
51
|
// ARV-53: --api resolution lives in cli/util/api-context.ts —
|
|
58
52
|
// local opt > ancestor opt > ZOND_API_GLOBAL/ZOND_API/.zond/current-api.
|
|
@@ -64,22 +58,9 @@ export function registerPrepareFixtures(program: Command): void {
|
|
|
64
58
|
}
|
|
65
59
|
opts.api = apiName;
|
|
66
60
|
|
|
67
|
-
const cascade = opts.cascade === true || opts.seed === true;
|
|
68
61
|
const refresh = opts.refresh === true;
|
|
69
62
|
const verify = opts.verify === true || refresh;
|
|
70
63
|
|
|
71
|
-
// Flag combos that don't make sense — fail fast with a clear hint.
|
|
72
|
-
if (cascade && verify) {
|
|
73
|
-
printError("--verify / --refresh are single-pass options; drop --cascade/--seed or drop --verify.");
|
|
74
|
-
process.exitCode = 2;
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
if (!cascade && (opts.force === true || typeof opts.maxPasses === "number")) {
|
|
78
|
-
printError("--force / --max-passes only apply with --cascade (or --seed).");
|
|
79
|
-
process.exitCode = 2;
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
64
|
const resolved = resolveSpecArg(undefined, opts.api, opts.db);
|
|
84
65
|
if ("error" in resolved) { printError(resolved.error); process.exitCode = 2; return; }
|
|
85
66
|
|
|
@@ -100,25 +81,6 @@ export function registerPrepareFixtures(program: Command): void {
|
|
|
100
81
|
} catch { /* meta is best-effort */ }
|
|
101
82
|
const timeoutMs = resolveTimeoutMs(opts.timeout, envTimeout);
|
|
102
83
|
|
|
103
|
-
if (cascade) {
|
|
104
|
-
process.exitCode = await bootstrapCommand({
|
|
105
|
-
specPath: resolved.spec,
|
|
106
|
-
apiDir,
|
|
107
|
-
envPath: opts.env,
|
|
108
|
-
apply: opts.apply === true,
|
|
109
|
-
seed: opts.seed === true,
|
|
110
|
-
force: opts.force === true,
|
|
111
|
-
timeoutMs,
|
|
112
|
-
maxPasses: opts.maxPasses,
|
|
113
|
-
json: globalJson(cmd),
|
|
114
|
-
// ARV-205 (R10/F6, R13/F19): surface the user-facing command name
|
|
115
|
-
// in the JSON envelope. Without this the user sees command="bootstrap"
|
|
116
|
-
// even though they typed `zond prepare-fixtures …`.
|
|
117
|
-
commandName: "prepare-fixtures",
|
|
118
|
-
});
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
84
|
process.exitCode = await discoverCommand({
|
|
123
85
|
specPath: resolved.spec,
|
|
124
86
|
apiDir,
|