@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.
- package/CHANGELOG.md +59 -0
- package/README.md +22 -21
- package/bin/zond.mjs +23 -0
- package/package.json +13 -16
- package/scripts/npm/postinstall.mjs +76 -0
- package/src/CLAUDE.md +0 -112
- package/src/bun-types.d.ts +0 -5
- package/src/cli/argv.ts +0 -122
- package/src/cli/commands/add-api.ts +0 -146
- package/src/cli/commands/api/annotate/idempotency.ts +0 -59
- package/src/cli/commands/api/annotate/index.ts +0 -880
- package/src/cli/commands/api/annotate/lifecycle.ts +0 -74
- package/src/cli/commands/api/annotate/overlay.ts +0 -206
- package/src/cli/commands/api/annotate/pagination.ts +0 -64
- package/src/cli/commands/api/annotate/prompts.ts +0 -220
- package/src/cli/commands/api/annotate/readback.ts +0 -58
- package/src/cli/commands/api/annotate/resources.ts +0 -91
- package/src/cli/commands/api/annotate/seed-bodies.ts +0 -61
- package/src/cli/commands/audit.ts +0 -786
- package/src/cli/commands/catalog.ts +0 -97
- package/src/cli/commands/check.ts +0 -361
- package/src/cli/commands/checks.ts +0 -1072
- package/src/cli/commands/ci-init.ts +0 -223
- package/src/cli/commands/clean.ts +0 -212
- package/src/cli/commands/cleanup.ts +0 -236
- package/src/cli/commands/completions.ts +0 -192
- package/src/cli/commands/coverage.ts +0 -872
- package/src/cli/commands/db.ts +0 -611
- package/src/cli/commands/describe.ts +0 -120
- package/src/cli/commands/discover.ts +0 -1356
- package/src/cli/commands/doctor.ts +0 -661
- package/src/cli/commands/fixtures.ts +0 -402
- package/src/cli/commands/generate.ts +0 -577
- package/src/cli/commands/init/agents-md.ts +0 -61
- package/src/cli/commands/init/bootstrap.ts +0 -111
- package/src/cli/commands/init/index.ts +0 -244
- package/src/cli/commands/init/skills.ts +0 -141
- package/src/cli/commands/init/templates/agents.md +0 -86
- package/src/cli/commands/init/templates/markdown.d.ts +0 -4
- package/src/cli/commands/init/templates/skills/warm-up-target.md +0 -122
- package/src/cli/commands/init/templates/skills/zond-checks.md +0 -621
- package/src/cli/commands/init/templates/skills/zond-seed.md +0 -114
- package/src/cli/commands/init/templates/skills/zond-triage.md +0 -272
- package/src/cli/commands/init/templates/skills/zond.md +0 -861
- package/src/cli/commands/init/templates/zond-config.yml +0 -14
- package/src/cli/commands/prepare-fixtures.ts +0 -97
- package/src/cli/commands/probe/_seed-bodies.ts +0 -52
- package/src/cli/commands/probe/mass-assignment.ts +0 -594
- package/src/cli/commands/probe/security.ts +0 -537
- package/src/cli/commands/probe/static.ts +0 -255
- package/src/cli/commands/probe/webhooks.ts +0 -163
- package/src/cli/commands/probe.ts +0 -535
- package/src/cli/commands/reference.ts +0 -87
- package/src/cli/commands/refresh-api.ts +0 -227
- package/src/cli/commands/remove-api.ts +0 -150
- package/src/cli/commands/report-bundle.ts +0 -310
- package/src/cli/commands/report.ts +0 -241
- package/src/cli/commands/request.ts +0 -548
- package/src/cli/commands/run.ts +0 -1142
- package/src/cli/commands/schema-from-runs.ts +0 -128
- package/src/cli/commands/secrets.ts +0 -133
- package/src/cli/commands/session.ts +0 -244
- package/src/cli/commands/use.ts +0 -74
- package/src/cli/index.ts +0 -55
- package/src/cli/json-envelope.ts +0 -108
- package/src/cli/json-schemas.ts +0 -314
- package/src/cli/output.ts +0 -40
- package/src/cli/program.ts +0 -219
- package/src/cli/resolve.ts +0 -105
- package/src/cli/runtime.ts +0 -7
- package/src/cli/safe-live.ts +0 -24
- package/src/cli/status-filter.ts +0 -114
- package/src/cli/util/api-context.ts +0 -85
- package/src/cli/version.ts +0 -8
- package/src/core/audit/persist.ts +0 -183
- package/src/core/checks/budget.ts +0 -59
- package/src/core/checks/checks/_crud-helpers.ts +0 -133
- package/src/core/checks/checks/_negative_mutator.ts +0 -133
- package/src/core/checks/checks/_readback-helpers.ts +0 -133
- package/src/core/checks/checks/content_type_conformance.ts +0 -39
- package/src/core/checks/checks/cross_call_references.ts +0 -147
- package/src/core/checks/checks/cursor_boundary_fuzzing.ts +0 -219
- package/src/core/checks/checks/ensure_resource_availability.ts +0 -62
- package/src/core/checks/checks/idempotency_replay.ts +0 -242
- package/src/core/checks/checks/ignored_auth.ts +0 -254
- package/src/core/checks/checks/index.ts +0 -68
- package/src/core/checks/checks/lifecycle_transitions.ts +0 -416
- package/src/core/checks/checks/missing_required_header.ts +0 -40
- package/src/core/checks/checks/negative_data_rejection.ts +0 -148
- package/src/core/checks/checks/not_a_server_error.ts +0 -35
- package/src/core/checks/checks/open_cors_on_sensitive.ts +0 -160
- package/src/core/checks/checks/pagination_invariants.ts +0 -419
- package/src/core/checks/checks/positive_data_acceptance.ts +0 -33
- package/src/core/checks/checks/rate_limit_headers_absent.ts +0 -77
- package/src/core/checks/checks/response_headers_conformance.ts +0 -74
- package/src/core/checks/checks/response_schema_conformance.ts +0 -30
- package/src/core/checks/checks/status_code_conformance.ts +0 -132
- package/src/core/checks/checks/unsupported_method.ts +0 -63
- package/src/core/checks/checks/use_after_free.ts +0 -78
- package/src/core/checks/index.ts +0 -30
- package/src/core/checks/mode.ts +0 -82
- package/src/core/checks/recommended-action.ts +0 -68
- package/src/core/checks/registry.ts +0 -78
- package/src/core/checks/runner.ts +0 -1461
- package/src/core/checks/sarif.ts +0 -230
- package/src/core/checks/spec-findings.ts +0 -308
- package/src/core/checks/stateful.ts +0 -121
- package/src/core/checks/types.ts +0 -305
- package/src/core/checks/zond-extensions.ts +0 -73
- package/src/core/classifier/recommended-action.ts +0 -251
- package/src/core/context/current.ts +0 -51
- package/src/core/context/session.ts +0 -78
- package/src/core/coverage/loader.ts +0 -216
- package/src/core/coverage/reasons.ts +0 -300
- package/src/core/diagnostics/db-analysis.ts +0 -666
- package/src/core/diagnostics/failure-class.ts +0 -140
- package/src/core/diagnostics/failure-hints.ts +0 -112
- package/src/core/diagnostics/spec-pointer.ts +0 -99
- package/src/core/diagnostics/suggested-fixes.ts +0 -155
- package/src/core/exporter/case-study/index.ts +0 -270
- package/src/core/exporter/curl.ts +0 -40
- package/src/core/exporter/exporter.ts +0 -48
- package/src/core/exporter/html-report/escape.ts +0 -24
- package/src/core/exporter/html-report/index.ts +0 -479
- package/src/core/exporter/html-report/script.ts +0 -100
- package/src/core/exporter/html-report/styles.ts +0 -408
- package/src/core/generator/catalog-builder.ts +0 -179
- package/src/core/generator/chunker.ts +0 -78
- package/src/core/generator/coverage-phase.ts +0 -0
- package/src/core/generator/coverage-scanner.ts +0 -87
- package/src/core/generator/data-factory.ts +0 -759
- package/src/core/generator/describe.ts +0 -302
- package/src/core/generator/endpoint-warnings.ts +0 -52
- package/src/core/generator/fixtures-builder.ts +0 -332
- package/src/core/generator/index.ts +0 -14
- package/src/core/generator/openapi-reader.ts +0 -297
- package/src/core/generator/path-param-disambig.ts +0 -140
- package/src/core/generator/resources-builder.ts +0 -898
- package/src/core/generator/schema-utils.ts +0 -112
- package/src/core/generator/serializer.ts +0 -343
- package/src/core/generator/suite-generator.ts +0 -1301
- package/src/core/generator/types.ts +0 -63
- package/src/core/identity/identity-file.ts +0 -0
- package/src/core/lint/affects.ts +0 -28
- package/src/core/lint/config.ts +0 -96
- package/src/core/lint/format.ts +0 -42
- package/src/core/lint/index.ts +0 -94
- package/src/core/lint/reporter.ts +0 -128
- package/src/core/lint/rules/consistency.ts +0 -158
- package/src/core/lint/rules/heuristics.ts +0 -97
- package/src/core/lint/rules/strictness.ts +0 -109
- package/src/core/lint/types.ts +0 -96
- package/src/core/lint/walker.ts +0 -248
- package/src/core/meta/meta-store.ts +0 -11
- package/src/core/output/README.md +0 -73
- package/src/core/output/index.ts +0 -13
- package/src/core/output/run.ts +0 -91
- package/src/core/output/types.ts +0 -122
- package/src/core/parser/dynamic-values.ts +0 -160
- package/src/core/parser/env-interpolation.ts +0 -104
- package/src/core/parser/filter.ts +0 -97
- package/src/core/parser/schema.ts +0 -359
- package/src/core/parser/types.ts +0 -117
- package/src/core/parser/variables.ts +0 -0
- package/src/core/parser/yaml-parser.ts +0 -181
- package/src/core/probe/bootstrap.ts +0 -34
- package/src/core/probe/dry-run-envelope.ts +0 -61
- package/src/core/probe/mass-assignment/classify.ts +0 -175
- package/src/core/probe/mass-assignment/cleanup.ts +0 -52
- package/src/core/probe/mass-assignment/digest.ts +0 -114
- package/src/core/probe/mass-assignment/orchestrator.ts +0 -459
- package/src/core/probe/mass-assignment/regression.ts +0 -141
- package/src/core/probe/mass-assignment/suspects.ts +0 -92
- package/src/core/probe/mass-assignment/types.ts +0 -135
- package/src/core/probe/mass-assignment-probe-class.ts +0 -198
- package/src/core/probe/mass-assignment-probe.ts +0 -27
- package/src/core/probe/mass-assignment-template.ts +0 -240
- package/src/core/probe/method-probe.ts +0 -164
- package/src/core/probe/method-shared.ts +0 -69
- package/src/core/probe/negative-probe.ts +0 -691
- package/src/core/probe/orphan-tracker.ts +0 -188
- package/src/core/probe/path-discovery.ts +0 -439
- package/src/core/probe/probe-harness.ts +0 -119
- package/src/core/probe/registry.ts +0 -89
- package/src/core/probe/runner.ts +0 -136
- package/src/core/probe/security/baseline.ts +0 -174
- package/src/core/probe/security/classify.ts +0 -341
- package/src/core/probe/security/cleanup.ts +0 -125
- package/src/core/probe/security/detectors.ts +0 -71
- package/src/core/probe/security/digest.ts +0 -104
- package/src/core/probe/security/orchestrator.ts +0 -398
- package/src/core/probe/security/regression.ts +0 -103
- package/src/core/probe/security/types.ts +0 -151
- package/src/core/probe/security-probe-class.ts +0 -207
- package/src/core/probe/security-probe.ts +0 -32
- package/src/core/probe/shared.ts +0 -531
- package/src/core/probe/static-probe-class.ts +0 -125
- package/src/core/probe/types.ts +0 -165
- package/src/core/probe/verdict-aggregator.ts +0 -33
- package/src/core/probe/webhooks-probe.ts +0 -282
- package/src/core/reporter/console.ts +0 -240
- package/src/core/reporter/index.ts +0 -22
- package/src/core/reporter/json.ts +0 -22
- package/src/core/reporter/junit.ts +0 -93
- package/src/core/reporter/ndjson.ts +0 -37
- package/src/core/reporter/types.ts +0 -15
- package/src/core/runner/assertions.ts +0 -383
- package/src/core/runner/async-pool.ts +0 -108
- package/src/core/runner/auth-path.ts +0 -8
- package/src/core/runner/ci-context.ts +0 -72
- package/src/core/runner/executor.ts +0 -652
- package/src/core/runner/expr-eval.ts +0 -41
- package/src/core/runner/form-encode.ts +0 -41
- package/src/core/runner/http-client.ts +0 -224
- package/src/core/runner/learn-drift.ts +0 -293
- package/src/core/runner/preflight-vars.ts +0 -153
- package/src/core/runner/progress-tracker.ts +0 -73
- package/src/core/runner/rate-limiter.ts +0 -185
- package/src/core/runner/run-kind.ts +0 -45
- package/src/core/runner/schema-validator.ts +0 -308
- package/src/core/runner/send-request.ts +0 -232
- package/src/core/runner/transforms.ts +0 -65
- package/src/core/runner/types.ts +0 -94
- package/src/core/secrets/registry.ts +0 -164
- package/src/core/secrets/secrets-file.ts +0 -115
- package/src/core/selectors/operation-filter.ts +0 -144
- package/src/core/setup-api.ts +0 -590
- package/src/core/severity/category.ts +0 -94
- package/src/core/severity/index.ts +0 -58
- package/src/core/spec/infer-schema.ts +0 -102
- package/src/core/spec/layers.ts +0 -154
- package/src/core/spec/merge-specs.ts +0 -156
- package/src/core/spec/schema-from-runs.ts +0 -117
- package/src/core/spec/schema-overlay.ts +0 -130
- package/src/core/util/ajv.ts +0 -13
- package/src/core/util/format-eta.ts +0 -21
- package/src/core/util/headers.ts +0 -9
- package/src/core/util/url.ts +0 -24
- package/src/core/utils.ts +0 -13
- package/src/core/workspace/config.ts +0 -129
- package/src/core/workspace/fixture-gap-report.ts +0 -84
- package/src/core/workspace/fixture-gaps.ts +0 -71
- package/src/core/workspace/manifest.ts +0 -283
- package/src/core/workspace/output-rotation.ts +0 -62
- package/src/core/workspace/root.ts +0 -96
- package/src/core/workspace/triage-path.ts +0 -87
- package/src/db/lint-runs.ts +0 -47
- package/src/db/migrate.ts +0 -128
- package/src/db/migrations/0001_run_kind.sql +0 -25
- package/src/db/migrations/0002_run_kind_request.sql +0 -59
- package/src/db/migrations/sql.d.ts +0 -4
- package/src/db/queries/collections.ts +0 -133
- package/src/db/queries/coverage.ts +0 -9
- package/src/db/queries/dashboard.ts +0 -59
- package/src/db/queries/results.ts +0 -216
- package/src/db/queries/runs.ts +0 -289
- package/src/db/queries/sessions.ts +0 -42
- package/src/db/queries/settings.ts +0 -28
- package/src/db/queries/types.ts +0 -172
- package/src/db/queries.ts +0 -75
- package/src/db/schema.ts +0 -298
|
@@ -1,621 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: zond-checks
|
|
3
|
-
description: |
|
|
4
|
-
Schemathesis-style depth checks for an API — conformance + security
|
|
5
|
-
probes that go beyond YAML smoke tests. Use when the user asks for:
|
|
6
|
-
"deep audit", "find spec drift", "test edge cases", "boundary value
|
|
7
|
-
coverage", "find security bugs", "broken auth check", "use-after-free",
|
|
8
|
-
"SARIF for code scanning", "GitHub Code Scanning report", "stateful
|
|
9
|
-
invariants", "cross-call drift", "idempotency replay", "pagination
|
|
10
|
-
consistency", "lifecycle state machine". For a full generated test
|
|
11
|
-
pipeline + scenarios + audit chain, hand off to `zond`. For triage
|
|
12
|
-
of a failing run, use `zond-triage`.
|
|
13
|
-
allowed-tools: [Read, Bash(zond *), Bash(bunx zond *), Bash(jq *), Bash(sqlite3 *)]
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
# zond-checks — Depth checks (conformance + security)
|
|
17
|
-
|
|
18
|
-
CLI-only skill. Use *after* a YAML smoke run passes — depth checks
|
|
19
|
-
exercise contract drift, malicious input rejection, broken auth, and
|
|
20
|
-
soft-deleted resource leaks against a live API.
|
|
21
|
-
|
|
22
|
-
The catalog is fixed and self-describing — list it before running so
|
|
23
|
-
the user sees what they'll get:
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
zond checks list # registered checks (id, severity, expected)
|
|
27
|
-
zond checks list --json # same, machine-readable
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## When to use
|
|
31
|
-
|
|
32
|
-
| User asks | Run |
|
|
33
|
-
|---|---|
|
|
34
|
-
| "deep audit", "find edge cases" | `zond checks run --api <name>` |
|
|
35
|
-
| "boundary value coverage" | `... --phase coverage` |
|
|
36
|
-
| "find security bugs", "broken auth" | `... --check ignored_auth,use_after_free,ensure_resource_availability` |
|
|
37
|
-
| "is GET returning what POST accepted?", "cross-call drift" | `... --check cross_call_references` (m-20) |
|
|
38
|
-
| "does the API honor Idempotency-Key?", "two-POST replay" | `... --check idempotency_replay` (m-20) |
|
|
39
|
-
| "are paginated lists consistent?", "duplicates across cursor pages" | `... --check pagination_invariants` (m-20) |
|
|
40
|
-
| "does cancel/archive land the resource in the declared state?", "state-machine" | `... --check lifecycle_transitions` (m-20) |
|
|
41
|
-
| "do captured webhook events match spec.webhooks shape?" | `zond probe webhooks --event-log events.jsonl` (m-20) — recipe: docs/recipes/webhook-receiver.md |
|
|
42
|
-
| "schemathesis-style strict mode" | `... --strict-405 --strict-401` (m-18) |
|
|
43
|
-
| "SARIF for GitHub Code Scanning" | `... --report sarif --output zond.sarif` |
|
|
44
|
-
| "stream findings to a pipeline" | `... --report ndjson \| jq -c '.'` |
|
|
45
|
-
| "scan large API faster" | `... --workers auto` (or `--workers 8`) |
|
|
46
|
-
|
|
47
|
-
### Strict-mode флаги (m-18)
|
|
48
|
-
|
|
49
|
-
- **`--strict-405`** — `unsupported_method` принимает только 405 (вместо
|
|
50
|
-
pragmatic 401/403/404/405). Mirror schemathesis V4 default. Включай
|
|
51
|
-
когда target — backend, где политика «undeclared method → 405»
|
|
52
|
-
обязательна (RFC 9110 compliance).
|
|
53
|
-
- **`--strict-401`** — `ignored_auth` no-auth/bogus_auth требует ровно 401
|
|
54
|
-
(вместо любого 4xx). Включай для проверки точности auth-rejection policy.
|
|
55
|
-
|
|
56
|
-
Pragmatic режим (default) — реалистичный для production API'ев где gateway
|
|
57
|
-
часто возвращает 404 на undeclared method или 403 на missing auth.
|
|
58
|
-
|
|
59
|
-
## Iron rules
|
|
60
|
-
|
|
61
|
-
- **Safe by default (ARV-299).** `checks run` and the mutating probes
|
|
62
|
-
(`probe mass-assignment`, `probe security`) default to `--safe`: no
|
|
63
|
-
destructive traffic. For `checks run` that means the stateful CRUD
|
|
64
|
-
create-chains (`ensure_resource_availability`, `use_after_free`) self-skip
|
|
65
|
-
— read-only checks (conformance, pagination, observation-mode lifecycle)
|
|
66
|
-
still run. For probes it means plan-only (no live attack payloads). Pass
|
|
67
|
-
`--live` to opt into mutating traffic — **only against a throwaway/sandbox
|
|
68
|
-
account.** Same vocabulary as `zond audit --safe/--live`. `probe static`
|
|
69
|
-
only generates suites, so it's always safe (`--live` is a no-op there).
|
|
70
|
-
- **NEVER hand-roll these checks in YAML.** The catalog encodes
|
|
71
|
-
schemathesis V4 semantics 1-to-1 — replicating them in YAML drifts
|
|
72
|
-
silently. `zond checks run` is the single source of truth.
|
|
73
|
-
- **`--check stateful` = state-machine set only (ARV-325):**
|
|
74
|
-
cross_call_references, idempotency_replay, pagination_invariants,
|
|
75
|
-
lifecycle_transitions, use_after_free, ensure_resource_availability,
|
|
76
|
-
cursor_boundary_fuzzing. `ignored_auth` / `open_cors_on_sensitive` are
|
|
77
|
-
NOT included — run them by explicit id when you want the security pair.
|
|
78
|
-
- **NEVER run `--check stateful` without prior `api annotate` review.**
|
|
79
|
-
m-20 stateful checks (cross_call_references, idempotency_replay,
|
|
80
|
-
pagination_invariants, lifecycle_transitions) read `.api-resources.local.yaml`
|
|
81
|
-
for per-API quirks (custom pagination param, non-standard lifecycle
|
|
82
|
-
field, write-only ignore_fields). Defaults catch the obvious; running
|
|
83
|
-
on defaults alone misses API-specific drift. See **Phase pre-0** below.
|
|
84
|
-
- **NEVER ignore `--bootstrap-cleanup-failed`.** Stateful security
|
|
85
|
-
checks (`ignored_auth`, `use_after_free`, `ensure_resource_availability`)
|
|
86
|
-
produce false positives on stale data. If the previous run's
|
|
87
|
-
cleanup failed, pass the flag — they skip with a warning instead.
|
|
88
|
-
- **Auth headers are auto-derived from `--api <name>` `.env.yaml`**
|
|
89
|
-
(`auth_token` → `Authorization: Bearer …`, `api_key` → `X-API-Key`).
|
|
90
|
-
Add explicit ones with `--auth-header 'Name: value'` (repeatable).
|
|
91
|
-
- **Prewarm a fresh workspace before `zond checks run`.** Right after
|
|
92
|
-
`zond add api <name>`, `.env.yaml` only has the auth skeleton — list
|
|
93
|
-
endpoints can return 401 until a `setup:` suite captures session
|
|
94
|
-
vars. Running checks cold (e.g. `--check ignored_auth` or a full
|
|
95
|
-
run) immediately surfaces dozens of
|
|
96
|
-
spurious `ignored_auth` / `report_backend_bug` findings (cold-state
|
|
97
|
-
race, F1/F18 in feedback loop). Fix: run one smoke pass first
|
|
98
|
-
(`zond run apis/<name>/tests --safe --report json` or `zond doctor
|
|
99
|
-
--api <name> --missing-only`), let `prepare-fixtures` warm
|
|
100
|
-
`.env.yaml`, then `zond checks run`.
|
|
101
|
-
- **Treat per-check noise as fixture / generator drift, not a backend
|
|
102
|
-
bug.** Two checks are reliably noisy on production APIs and need a
|
|
103
|
-
pre-filter before triage:
|
|
104
|
-
- `positive_data_acceptance` — emits MEDIUM when a generated body is
|
|
105
|
-
rejected with 422. On APIs with **semantic validation** (Resend
|
|
106
|
-
domain ownership, Stripe currency vs country, …) this is almost
|
|
107
|
-
always `fix_generator` / `fix_fixture`, not `report_backend_bug`.
|
|
108
|
-
Inspect the 422 reason; if it mentions a business rule the
|
|
109
|
-
generator can't satisfy, file under `regenerate_suite` or annotate
|
|
110
|
-
`seed_body`.
|
|
111
|
-
- `response_schema_conformance` — HIGH on format-only mismatches
|
|
112
|
-
(`format: email` rejecting a free-form string the server returned)
|
|
113
|
-
is contract drift; HIGH on a field the server actually doesn't
|
|
114
|
-
persist is a backend bug. Use `cross_call_references` to
|
|
115
|
-
disambiguate before reporting.
|
|
116
|
-
- **No response schema declared** (Sentry-style specs where the
|
|
117
|
-
check skips 200+ endpoints): mine schemas from a real run and
|
|
118
|
-
patch them in — `zond schema-from-runs --api <name>` writes
|
|
119
|
-
`patch.schema.json` from the run's 2xx bodies, then
|
|
120
|
-
`zond refresh-api --api <name> --merge-schema patch.schema.json`
|
|
121
|
-
folds them into `.api-schema.local.yaml` (survives refresh) and
|
|
122
|
-
re-runs conformance with real schemas (ARV-175/176).
|
|
123
|
-
|
|
124
|
-
## In-spec `x-zond-*` extensions (ARV-189, m-21)
|
|
125
|
-
|
|
126
|
-
Low-friction per-endpoint policy via OpenAPI vendor extensions —
|
|
127
|
-
when a single operation needs an exception, avoid the round-trip
|
|
128
|
-
through `.api-resources.local.yaml`. Implemented today:
|
|
129
|
-
|
|
130
|
-
| Extension | Type | Effect |
|
|
131
|
-
|---|---|---|
|
|
132
|
-
| `x-zond-skip: [check_id, …]` (or single string) | string\[] | skip those checks for this op |
|
|
133
|
-
| `x-zond-public: true` | boolean | shortcut: skip auth-class checks (`ignored_auth`, `missing_required_header`) |
|
|
134
|
-
|
|
135
|
-
Place at operation level (preferred) or path-item level — operation
|
|
136
|
-
wins on key collision.
|
|
137
|
-
|
|
138
|
-
```yaml
|
|
139
|
-
paths:
|
|
140
|
-
/v1/public/status:
|
|
141
|
-
get:
|
|
142
|
-
x-zond-public: true # public health route
|
|
143
|
-
responses: { '200': { description: ok } }
|
|
144
|
-
/v1/admin/raw-debug:
|
|
145
|
-
get:
|
|
146
|
-
x-zond-skip: [status_code_conformance] # known 500-on-bad-input
|
|
147
|
-
responses: { '500': { description: 'debug 500' } }
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
Skipped runs surface in `--report` `skipped_outcomes` as
|
|
151
|
-
`<check>: x-zond-skip listed "<id>" at the spec level` or
|
|
152
|
-
`<check>: x-zond-public: true (auth check suppressed …)` so the
|
|
153
|
-
operator can tell spec-level suppression apart from runtime skips.
|
|
154
|
-
|
|
155
|
-
Priority (highest first): `.api-resources.local.yaml` overlay >
|
|
156
|
-
`x-zond-*` extensions > `.api-resources.yaml` baseline > built-in
|
|
157
|
-
defaults. For one-shots use extensions; for resource-wide policy
|
|
158
|
-
(`idempotency.header`, `lifecycle.field`, …) use the overlay yaml.
|
|
159
|
-
|
|
160
|
-
Not yet implemented: `x-zond-resource` / `x-zond-idempotent` /
|
|
161
|
-
`x-zond-lifecycle-field` — these need deeper overlay-config wiring
|
|
162
|
-
and are tracked separately. Use `annotate apply` for those today.
|
|
163
|
-
|
|
164
|
-
## Phase pre-0 — Annotation (mandatory for `--check stateful`)
|
|
165
|
-
|
|
166
|
-
> **Heads-up for `pagination_invariants`:** `cursor` (Stripe-style) and
|
|
167
|
-
> `page` (GitHub/GitLab/Atlassian/Notion-style `?page=N&per_page=M`)
|
|
168
|
-
> are implemented. `offset` / `token` annotations parse but the check
|
|
169
|
-
> short-circuits with "type not implemented" — skip dump+apply for
|
|
170
|
-
> those resources until later milestones.
|
|
171
|
-
|
|
172
|
-
m-20 stateful checks rely on per-resource config in
|
|
173
|
-
`.api-resources.local.yaml` (overlay that survives `refresh-api`).
|
|
174
|
-
`zond api annotate` is the canonical authoring path: zond emits raw
|
|
175
|
-
spec slices, **agent (you) writes the YAML**, zond validates and
|
|
176
|
-
applies. **zond itself does NOT call any LLM** — agent is the LLM, zond
|
|
177
|
-
is the dumb-tool.
|
|
178
|
-
|
|
179
|
-
Six dump aspects (one per check class):
|
|
180
|
-
|
|
181
|
-
```bash
|
|
182
|
-
zond api annotate dump --api <name> --seed-bodies > /tmp/seed.json
|
|
183
|
-
zond api annotate dump --api <name> --readback > /tmp/readback.json
|
|
184
|
-
zond api annotate dump --api <name> --idempotency > /tmp/idem.json
|
|
185
|
-
zond api annotate dump --api <name> --pagination > /tmp/pag.json
|
|
186
|
-
zond api annotate dump --api <name> --lifecycle > /tmp/life.json
|
|
187
|
-
zond api annotate dump --api <name> --resources > /tmp/orphans.json # optional: new CRUD resources from orphans
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
Restrict scope with `--only r1,r2,r3` on any dump.
|
|
191
|
-
|
|
192
|
-
`annotate dump` emits **JSON** (a top-level array of per-resource
|
|
193
|
-
slices). YAML is a JSON superset, so the file parses either way — when
|
|
194
|
-
piping into a YAML-aware reader, treat it as a list of objects with the
|
|
195
|
-
same shape documented below. The corresponding response file the agent
|
|
196
|
-
writes back is **YAML** (more comments-friendly).
|
|
197
|
-
|
|
198
|
-
Agent reads each dump and writes a YAML response file (top-level list
|
|
199
|
-
of entries — see per-check schemas below for the block shape).
|
|
200
|
-
Optional `rationale` and `confidence: high|medium|low` per entry help
|
|
201
|
-
future review.
|
|
202
|
-
|
|
203
|
-
Apply — dry-run first (renders diff + conflicts), then `--yes` to write:
|
|
204
|
-
|
|
205
|
-
```bash
|
|
206
|
-
zond api annotate apply --api <name> --readback --input /tmp/readback.yaml # dry-run
|
|
207
|
-
zond api annotate apply --api <name> --readback --input /tmp/readback.yaml --yes # write
|
|
208
|
-
zond api annotate apply --api <name> --readback --input /tmp/readback.yaml --yes --force # overwrite conflicts
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
Conflicts: when an existing field already has a value, apply keeps
|
|
212
|
-
existing by default (renders `! field: (conflict — kept existing; pass
|
|
213
|
-
--yes to overwrite)`). Pass `--force` to overwrite.
|
|
214
|
-
|
|
215
|
-
**Recommended pre-stateful sweep on a new API:**
|
|
216
|
-
|
|
217
|
-
```bash
|
|
218
|
-
zond api annotate dump --api <name> --seed-bodies > /tmp/seed.json # create-body overlay for stateful checks
|
|
219
|
-
zond api annotate dump --api <name> --readback > /tmp/readback.json # for cross_call_references
|
|
220
|
-
zond api annotate dump --api <name> --pagination > /tmp/pag.json # for pagination_invariants
|
|
221
|
-
zond api annotate dump --api <name> --lifecycle > /tmp/life.json # for lifecycle_transitions
|
|
222
|
-
zond api annotate dump --api <name> --idempotency > /tmp/idem.json # for idempotency_replay
|
|
223
|
-
# … agent generates YAML files for each …
|
|
224
|
-
zond api annotate apply --api <name> --seed-bodies --input /tmp/seed.yaml --yes
|
|
225
|
-
zond api annotate apply --api <name> --readback --input /tmp/readback.yaml --yes
|
|
226
|
-
zond api annotate apply --api <name> --pagination --input /tmp/pag.yaml --yes
|
|
227
|
-
zond api annotate apply --api <name> --lifecycle --input /tmp/life.yaml --yes
|
|
228
|
-
zond api annotate apply --api <name> --idempotency --input /tmp/idem.yaml --yes
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
Each block's YAML format is documented under the per-check section below.
|
|
232
|
-
|
|
233
|
-
**Who writes the annotations:** zond does not infer them. The agent reads
|
|
234
|
-
the `dump` output (spec slice + expected YAML shape) and writes the overlay;
|
|
235
|
-
`apply` validates it against the zod contract and merges into
|
|
236
|
-
`.api-resources.local.yaml`. For large specs, dump per-resource with
|
|
237
|
-
`--only <res>` and (for seed-bodies) `--with-last-attempt --history N` to
|
|
238
|
-
pull the recent seed-POST error progression onto your screen before writing.
|
|
239
|
-
|
|
240
|
-
## Reading findings
|
|
241
|
-
|
|
242
|
-
Every finding carries a closed-enum `recommended_action` so the agent
|
|
243
|
-
can route without parsing free-form messages:
|
|
244
|
-
|
|
245
|
-
| `recommended_action` | What to do |
|
|
246
|
-
|---|---|
|
|
247
|
-
| `report_backend_bug` | Server returned 5xx / accepted invalid auth / leaked deleted resource. File a backend ticket; do not "fix" the test. |
|
|
248
|
-
| `fix_spec` | Server's behaviour is reasonable but spec doesn't predict it. Update `apis/<name>/spec.json` (or upstream OpenAPI) and `zond refresh-api`. |
|
|
249
|
-
| `tighten_validation` | Server accepted a body that violates the schema. Backend should reject earlier (400/422). |
|
|
250
|
-
| `add_required_header` | Spec marked a header `required: true`; server didn't enforce it. Either enforce it or relax the spec. |
|
|
251
|
-
| `fix_auth_config` | Auth-related failure. Check `apis/<name>/.env.yaml` (`auth_token`, `api_key`) — never log the value. |
|
|
252
|
-
| `fix_network_config` | Transport-level error (timeout / DNS / refused). Verify `base_url` and reachability before re-running. |
|
|
253
|
-
| `wontfix_known_limitation` | Known accepted gap. Don't retry, don't file a bug. |
|
|
254
|
-
|
|
255
|
-
**Severity is a coarse deterministic CI-gate default, not a priority signal
|
|
256
|
-
(ARV-346).** zond stamps each check a fixed severity so the exit code
|
|
257
|
-
(`high_or_critical`) and SARIF mapping are reproducible — it is NOT a judgment
|
|
258
|
-
about which finding matters most on *your* API. You (the agent) prioritize
|
|
259
|
-
from `recommended_action` + the raw evidence, not from zond's severity number:
|
|
260
|
-
route by the closed enum first, read the response body, decide impact. The
|
|
261
|
-
severity field only answers "does this trip the CI gate?", nothing more. (This
|
|
262
|
-
is the deterministic per-check default — not the removed ARV-337 calibrator.)
|
|
263
|
-
|
|
264
|
-
HIGH/CRITICAL gates exit-code 1; LOW/MEDIUM is informational. In an
|
|
265
|
-
orchestrator/CI that must tell "found drift" from "command crashed", pass
|
|
266
|
-
`--no-fail-on-findings` (alias `--advisory`, ARV-308): exit 0 even with
|
|
267
|
-
HIGH/CRITICAL findings — the findings are still in the envelope. Mirrors
|
|
268
|
-
`zond run --no-fail-on-failures`. Exit 2 stays reserved for real failures.
|
|
269
|
-
|
|
270
|
-
### Spec-level rollup (ARV-60)
|
|
271
|
-
|
|
272
|
-
When a single spec-level gap manifests on many operations (`401 not
|
|
273
|
-
declared in spec` × 83 sites; `no JSON Schema on this response branch` ×
|
|
274
|
-
all skipped cases; `use_after_free` ran 0 cases because there's no
|
|
275
|
-
DELETE+GET pair in the spec), the runner emits a single `spec_finding`
|
|
276
|
-
instead of N per-op rows. Threshold: ≥80% of the check's applicable
|
|
277
|
-
operations sharing one root cause.
|
|
278
|
-
|
|
279
|
-
Each `spec_finding` has:
|
|
280
|
-
|
|
281
|
-
| Field | Meaning |
|
|
282
|
-
|---|---|
|
|
283
|
-
| `kind` | `status_drift` / `missing_declaration` / `no_detector` / `broken_baseline` / `other` |
|
|
284
|
-
| `reason` | One-line root cause statement |
|
|
285
|
-
| `fix_hint` | Actionable next step (spec edit / tolerate flag / annotate command) |
|
|
286
|
-
| `affected_operations` | Operations covered (empty for skip/no-detector clusters) |
|
|
287
|
-
| `count` / `applicable` | Cluster size + the population it was measured against |
|
|
288
|
-
|
|
289
|
-
`broken_baseline` (ARV-307) is special: when the positive/success probes
|
|
290
|
-
overwhelmingly failed (>90% non-2xx — e.g. a fully auth-rejected scan),
|
|
291
|
-
`status_code_conformance` / `content_type_conformance` findings are baseline
|
|
292
|
-
artifacts, so they're removed from `findings[]` and rolled into this one
|
|
293
|
-
row. Fix the baseline (auth + path-param fixtures) and re-run before trusting
|
|
294
|
-
conformance — undeclared statuses on an all-4xx scan are not real drift.
|
|
295
|
-
|
|
296
|
-
Triage spec_findings first — they collapse 1×N noise into one decision.
|
|
297
|
-
Per-op findings still live in `data.findings`; `--verbose` brings the
|
|
298
|
-
full unaggregated list back to stdout. SARIF + JSON envelope always
|
|
299
|
-
carry both layers.
|
|
300
|
-
|
|
301
|
-
```bash
|
|
302
|
-
zond checks run --api myapi --json | jq '.data.spec_findings'
|
|
303
|
-
zond checks run --api myapi --report ndjson | jq -c 'select(.type == "spec_finding")'
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
## Output formats
|
|
307
|
-
|
|
308
|
-
```bash
|
|
309
|
-
zond checks run --api myapi --json # one envelope: findings[] + spec_findings[] + summary
|
|
310
|
-
zond checks run --api myapi --report sarif --output zond.sarif
|
|
311
|
-
# SARIF v2.1.0 for github/codeql-action/upload-sarif@v3
|
|
312
|
-
zond checks run --api myapi --report ndjson | jq -c '.' # streaming events: check_start | check_result | finding | spec_finding | summary
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
NDJSON event schema is published at `docs/json-schema/ndjson-events.schema.json`
|
|
316
|
-
— pipe through `ajv validate` if you build a downstream consumer.
|
|
317
|
-
|
|
318
|
-
## Scoping a run
|
|
319
|
-
|
|
320
|
-
Long runs feel sluggish on a 200-endpoint spec. Scope down before
|
|
321
|
-
broadening:
|
|
322
|
-
|
|
323
|
-
```bash
|
|
324
|
-
# Filter operations (regex) — same grammar as `zond generate`
|
|
325
|
-
zond checks run --api myapi --include 'tag:billing,users' --exclude 'method:DELETE'
|
|
326
|
-
|
|
327
|
-
# Pick a vector
|
|
328
|
-
zond checks run --api myapi --mode positive # contract verification only
|
|
329
|
-
zond checks run --api myapi --mode negative # malicious-input probes only
|
|
330
|
-
|
|
331
|
-
# Pick a phase
|
|
332
|
-
zond checks run --api myapi --phase coverage # ~×1.75 cases per op; real bug-hunt phase
|
|
333
|
-
zond checks run --api myapi --phase examples # default — 1 positive + 1 negative per op (smoke)
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
**`--phase examples` is smoke, not depth.** It runs one positive + one
|
|
337
|
-
negative example per operation, finishes ~3× faster, but routinely
|
|
338
|
-
returns zero HIGH findings on well-formed APIs because boundary
|
|
339
|
-
mutations never fire. For an actual bug-hunt sweep, pass
|
|
340
|
-
`--phase coverage` — it expands each operation to ~×1.75 cases with
|
|
341
|
-
deterministic boundary values (min/max ints, empty strings, length
|
|
342
|
-
overflows, RFC-3339 edge dates, …). Use `examples` only for fast
|
|
343
|
-
gate-keeping in CI; use `coverage` once before sign-off.
|
|
344
|
-
|
|
345
|
-
`--allow-x00` adds the NUL byte (`\x00`) to string boundaries during
|
|
346
|
-
coverage — off by default (some HTTP/JSON stacks panic on it).
|
|
347
|
-
|
|
348
|
-
## Contribution to audit-coverage (ARV-265)
|
|
349
|
-
|
|
350
|
-
Every case `checks run` dispatches is now written to `runs`/`results`
|
|
351
|
-
with `run_kind = 'check'`. This means `zond coverage` picks it up in
|
|
352
|
-
its **audit-coverage** block — answering "did the scan touch endpoint
|
|
353
|
-
X?" without requiring a separate `zond run`. The test-coverage block
|
|
354
|
-
(pass/hit) is unaffected — `checks run` does not produce passing test
|
|
355
|
-
suites, so it doesn't contribute to `pass-coverage`.
|
|
356
|
-
|
|
357
|
-
```bash
|
|
358
|
-
zond checks run --api myapi # touches every endpoint at least once
|
|
359
|
-
zond coverage --api myapi --scope audit # 100% audit-coverage on the GET surface
|
|
360
|
-
zond coverage --api myapi # default dual output: test + audit blocks
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
Opt-out: `ZOND_CHECKS_PERSIST=0 zond checks run …` skips the
|
|
364
|
-
persistence step (matches pre-ARV-265 behaviour).
|
|
365
|
-
|
|
366
|
-
## Concurrency
|
|
367
|
-
|
|
368
|
-
```bash
|
|
369
|
-
zond checks run --api myapi --workers auto # min(cpus, 8); usually right
|
|
370
|
-
zond checks run --api myapi --workers 16 # explicit; clamped to 64
|
|
371
|
-
zond checks run --api myapi --workers 8 --rate-limit 50
|
|
372
|
-
# 8 workers, global 50 RPS budget
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
Workers parallelize at the *operation* level — cases inside one
|
|
376
|
-
operation always run sequentially (CRUD chain ordering must be
|
|
377
|
-
preserved). `--rate-limit auto` adapts from `RateLimit-*` response
|
|
378
|
-
headers (RFC 9568); use it on rate-limited APIs to avoid bursting.
|
|
379
|
-
|
|
380
|
-
## "0 findings" doesn't always mean "all green"
|
|
381
|
-
|
|
382
|
-
The summary one-liner now ends with `(N check outcome(s) skipped: …)`
|
|
383
|
-
when probes can't validate (e.g. `response_schema_conformance: no JSON
|
|
384
|
-
Schema on this response branch ×2` when probes ran without auth and
|
|
385
|
-
got a 4xx that the spec only declares for 2xx). Treat skipped probes
|
|
386
|
-
as "not yet exercised", not "passed" — re-run with auth (or via
|
|
387
|
-
`zond run --validate-schema`) to actually cover those branches.
|
|
388
|
-
|
|
389
|
-
## Exit codes
|
|
390
|
-
|
|
391
|
-
| Code | Meaning |
|
|
392
|
-
|---|---|
|
|
393
|
-
| 0 | No HIGH/CRITICAL findings (LOW/MEDIUM may be present). |
|
|
394
|
-
| 1 | At least one HIGH/CRITICAL finding — gate CI on this. |
|
|
395
|
-
| 2 | CLI-input error (bad flag value, unreachable spec, etc.). |
|
|
396
|
-
|
|
397
|
-
## Common combos
|
|
398
|
-
|
|
399
|
-
```bash
|
|
400
|
-
# Full conformance pass on staging, output SARIF for code scanning
|
|
401
|
-
zond checks run --api staging --report sarif --output zond.sarif --workers auto
|
|
402
|
-
|
|
403
|
-
# Just the security checks (stateful) with explicit auth
|
|
404
|
-
zond checks run --api prod \
|
|
405
|
-
--check ignored_auth,use_after_free,ensure_resource_availability \
|
|
406
|
-
--auth-header 'Authorization: Bearer $TOKEN'
|
|
407
|
-
|
|
408
|
-
# Coverage-phase boundary sweep, NDJSON pipe into a watcher
|
|
409
|
-
zond checks run --api dev --phase coverage --report ndjson | \
|
|
410
|
-
jq -c 'select(.type == "finding") | {check, op: .finding.operation, action: .finding.recommended_action}'
|
|
411
|
-
|
|
412
|
-
# Cross-call POST→GET drift only (m-20, single CRUD-chain check per resource)
|
|
413
|
-
zond checks run --api stripe --check cross_call_references
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
## Cross-call drift (m-20 ARV-169)
|
|
417
|
-
|
|
418
|
-
`cross_call_references` — POST resource → GET resource, diff write-shape
|
|
419
|
-
vs read-shape. Surfaces fields the server silently dropped:
|
|
420
|
-
|
|
421
|
-
- **state_not_persisted** — POST 2xx echoed the field, GET dropped it.
|
|
422
|
-
HIGH-signal: server lied about persisting state.
|
|
423
|
-
- **write_only** — POST accepted, GET dropped. Spec-declared write-only
|
|
424
|
-
fields (passwords, etc.) are auto-filtered.
|
|
425
|
-
|
|
426
|
-
Tunable per-resource in `apis/<name>/.api-resources.yaml` (или
|
|
427
|
-
`.api-resources.local.yaml` overlay):
|
|
428
|
-
|
|
429
|
-
```yaml
|
|
430
|
-
resources:
|
|
431
|
-
- resource: customer
|
|
432
|
-
# … existing fields …
|
|
433
|
-
readback_diff:
|
|
434
|
-
ignore_fields: [metadata, livemode] # API-quirks, suppress
|
|
435
|
-
write_to_read_map:
|
|
436
|
-
tax_id_data: tax_ids # write-shape → read-shape
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
Defaults already filter timestamps (`created_at`, `updated_at`), envelope
|
|
440
|
-
fields (`object`, `_links`), and ETag. Per-API quirks need a yaml line.
|
|
441
|
-
Authored either by hand or via the `zond api annotate dump --readback` →
|
|
442
|
-
agent → `apply` flow (see **Phase pre-0** above). zond emits the
|
|
443
|
-
write+read endpoint slice; agent decides `ignore_fields` /
|
|
444
|
-
`write_to_read_map` and writes the YAML; zond validates + writes to
|
|
445
|
-
`.api-resources.local.yaml`.
|
|
446
|
-
|
|
447
|
-
## Idempotency replay (m-20 ARV-170)
|
|
448
|
-
|
|
449
|
-
`idempotency_replay` — two POSTs with the same `Idempotency-Key` header.
|
|
450
|
-
Server must (a) return the same resource id and (b) bit-identical response
|
|
451
|
-
bodies (modulo timestamps / request-id / etag).
|
|
452
|
-
|
|
453
|
-
- **duplicate_resource** — ids differ → server ignored the key. HIGH.
|
|
454
|
-
- **non_bit_identical** — same id but bodies drift on non-ignored fields
|
|
455
|
-
→ replay isn't truly idempotent. Surfaced in the same HIGH finding via
|
|
456
|
-
`evidence.kind`.
|
|
457
|
-
|
|
458
|
-
Two ways to opt-in per resource:
|
|
459
|
-
|
|
460
|
-
1. Spec declares `Idempotency-Key` as a header parameter on the create
|
|
461
|
-
endpoint → auto-detected, runs with defaults.
|
|
462
|
-
2. `.api-resources.local.yaml` block (preferred — documents intent +
|
|
463
|
-
lets you tune the ignore list). Author via Phase pre-0
|
|
464
|
-
`annotate dump --idempotency` → agent → `apply`:
|
|
465
|
-
|
|
466
|
-
```yaml
|
|
467
|
-
resources:
|
|
468
|
-
- resource: charge
|
|
469
|
-
# … existing fields …
|
|
470
|
-
idempotency:
|
|
471
|
-
header: Idempotency-Key # default; override for non-standard names
|
|
472
|
-
scope: endpoint # informational; `endpoint` | `global`
|
|
473
|
-
ignore_response_fields: # added on top of timestamp/request_id baseline
|
|
474
|
-
- retry_after
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
Anti-FP: 429/409 on the 2nd POST → skip with cleanup. No DELETE on the
|
|
478
|
-
group → finding still fires, evidence carries `cleanup_warning`.
|
|
479
|
-
|
|
480
|
-
## Pagination invariants (m-20 ARV-171, m-21 ARV-220)
|
|
481
|
-
|
|
482
|
-
`pagination_invariants` — fetch two consecutive pages and assert the
|
|
483
|
-
contract holds. Two styles implemented:
|
|
484
|
-
|
|
485
|
-
**Cursor style** (Stripe / Notion / Linear, default when
|
|
486
|
-
`starting_after` / `cursor` / `after` / `page_token` query param is
|
|
487
|
-
detected):
|
|
488
|
-
|
|
489
|
-
- **duplicate_items** — an item id appears on both page A and page B
|
|
490
|
-
(off-by-one / cursor stops one short). HIGH-signal.
|
|
491
|
-
- **has_more_inconsistent** — page A said `has_more=true`, but page B is
|
|
492
|
-
empty and doesn't flip to `has_more=false`. Broken end-of-list signal.
|
|
493
|
-
- **partial_page_with_has_more** — page A returns fewer items than the
|
|
494
|
-
requested limit *yet* advertises `has_more=true`. Cursor truncates.
|
|
495
|
-
|
|
496
|
-
**Page-number style** (GitHub / GitLab / Atlassian / Notion, default
|
|
497
|
-
when `page` / `page_number` query param is detected):
|
|
498
|
-
|
|
499
|
-
- **duplicate_items** — items overlap across pages N and N+1.
|
|
500
|
-
- **per_page_exceeded** — server returned more items than `per_page=N`
|
|
501
|
-
(e.g. asked for 2, got 5). `has_more` is NOT enforced — most APIs in
|
|
502
|
-
this family signal end-of-list via Link headers / `total_pages`.
|
|
503
|
-
|
|
504
|
-
`offset` / `token` declarations parse but the check short-circuits with
|
|
505
|
-
a "type not implemented" reason so the yaml block stays a stable schema.
|
|
506
|
-
|
|
507
|
-
Defaults: `cursor_field=id`, `items_field=data|items|results|value`,
|
|
508
|
-
`has_more_field=has_more`, `limit=2`. Page-style adds `page_param=page`,
|
|
509
|
-
`start_page=1`, `limit_param=per_page`.
|
|
510
|
-
|
|
511
|
-
Per-resource yaml override (author via Phase pre-0
|
|
512
|
-
`annotate dump --pagination` → agent → `apply`):
|
|
513
|
-
|
|
514
|
-
```yaml
|
|
515
|
-
resources:
|
|
516
|
-
- resource: customer # Stripe-style cursor
|
|
517
|
-
pagination:
|
|
518
|
-
type: cursor
|
|
519
|
-
cursor_param: starting_after # Stripe; "after" / "cursor" / "page_token" also work
|
|
520
|
-
cursor_field: id # field on each item that feeds the next cursor
|
|
521
|
-
has_more_field: has_more # response field that flips on end-of-list
|
|
522
|
-
limit_param: limit
|
|
523
|
-
default_limit: 2
|
|
524
|
-
items_field: data # array container (falls back to items / results / value)
|
|
525
|
-
|
|
526
|
-
- resource: issue # GitHub-style page
|
|
527
|
-
pagination:
|
|
528
|
-
type: page
|
|
529
|
-
page_param: page # query param carrying page number (default `page`)
|
|
530
|
-
start_page: 1 # 1-based on GitHub/GitLab; 0 for some APIs
|
|
531
|
-
limit_param: per_page # default `per_page` for page-style
|
|
532
|
-
default_limit: 2
|
|
533
|
-
cursor_field: id # dedupe key across pages
|
|
534
|
-
items_field: data # omit if response is a bare array
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
## Lifecycle transitions (m-20 ARV-172, m-21 ARV-219)
|
|
538
|
-
|
|
539
|
-
`lifecycle_transitions` runs in one of two modes based on the
|
|
540
|
-
`actions:` field of the yaml manifest:
|
|
541
|
-
|
|
542
|
-
**Action-driven mode** (preferred — needs create + read endpoints):
|
|
543
|
-
The check creates a resource and walks the named actions, asserting:
|
|
544
|
-
|
|
545
|
-
- **undeclared_state** — observed state isn't in declared `states[]`.
|
|
546
|
-
- **wrong_expected_state** — action landed the resource in a state other
|
|
547
|
-
than its declared `expected_state`.
|
|
548
|
-
- **forbidden_transition** — observed (from, to) isn't in declared
|
|
549
|
-
transitions graph.
|
|
550
|
-
- **state_regression_on_replay** — invoking the action a second time
|
|
551
|
-
drifted state instead of staying idempotent.
|
|
552
|
-
- **double_action_5xx** — replay 5xx'd. Idempotent actions should 4xx
|
|
553
|
-
or 2xx, never crash.
|
|
554
|
-
- **action_rejected** — first-call non-2xx (server-side gating). Not a
|
|
555
|
-
contract bug per se, surfaced as INCONCLUSIVE-class info.
|
|
556
|
-
|
|
557
|
-
**Observation mode** (ARV-219 — read-only state machines): when
|
|
558
|
-
`actions: {}` (or omitted) AND the resource has a list endpoint, the
|
|
559
|
-
check GETs the list once and asserts every observed `field` value is
|
|
560
|
-
in `states[]`. Reports `undeclared_state` with sample item ids for
|
|
561
|
-
each unexpected value.
|
|
562
|
-
|
|
563
|
-
When to use observation mode:
|
|
564
|
-
- The API exposes a status enum the agent enumerated from the spec,
|
|
565
|
-
but the auth scope is read-only and cannot drive mutations (GitHub
|
|
566
|
-
Issues with a read PAT, public-read endpoints).
|
|
567
|
-
- You want to detect contract-doc drift (spec says `enum: [open,
|
|
568
|
-
closed]`, prod returns `archived`).
|
|
569
|
-
|
|
570
|
-
Observation mode cannot verify the `transitions` graph (no time
|
|
571
|
-
series in a single list call) — the transition arrows stay purely
|
|
572
|
-
documentation. If write scope is available, prefer action mode for
|
|
573
|
-
the full guarantee surface.
|
|
574
|
-
|
|
575
|
-
Skip reasons unique to observation mode: `list returned <code>` (broken
|
|
576
|
-
baseline), `list empty — no data to observe`, `state field "<x>"
|
|
577
|
-
missing on all N observed items — yaml mismatch or nested field`.
|
|
578
|
-
|
|
579
|
-
Manifest validation runs at yaml load (catches cycles, unreachable
|
|
580
|
-
states, missing terminal, actions referencing undeclared states)
|
|
581
|
-
before any HTTP call goes out.
|
|
582
|
-
|
|
583
|
-
Author via Phase pre-0 `annotate dump --lifecycle` → agent → `apply`.
|
|
584
|
-
The dump emits action-endpoint candidates (POST `/{resource}/{id}/cancel`,
|
|
585
|
-
PATCH `/{resource}/{id}/status` etc.); agent decides the state machine
|
|
586
|
-
graph. For observation-only resources, return `actions: {}` and the
|
|
587
|
-
check picks the observation path automatically.
|
|
588
|
-
|
|
589
|
-
```yaml
|
|
590
|
-
resources:
|
|
591
|
-
- resource: subscription # action-driven mode
|
|
592
|
-
lifecycle:
|
|
593
|
-
field: status
|
|
594
|
-
states: [pending, active, cancelled]
|
|
595
|
-
transitions:
|
|
596
|
-
- from: pending
|
|
597
|
-
to: [active, cancelled]
|
|
598
|
-
- from: active
|
|
599
|
-
to: [cancelled]
|
|
600
|
-
- from: cancelled
|
|
601
|
-
to: [] # terminal
|
|
602
|
-
actions:
|
|
603
|
-
cancel:
|
|
604
|
-
endpoint: POST /v1/subscriptions/{id}/cancel
|
|
605
|
-
expected_state: cancelled
|
|
606
|
-
|
|
607
|
-
- resource: issue # observation mode
|
|
608
|
-
lifecycle:
|
|
609
|
-
field: state
|
|
610
|
-
states: [open, closed]
|
|
611
|
-
transitions:
|
|
612
|
-
- from: open
|
|
613
|
-
to: [closed]
|
|
614
|
-
- from: closed
|
|
615
|
-
to: []
|
|
616
|
-
actions: {} # no actions → check observes via GET /issues
|
|
617
|
-
```
|
|
618
|
-
|
|
619
|
-
Action endpoints accept the `{id}` placeholder (replaced with the
|
|
620
|
-
captured create-id) or `{<idParam>}`. Body-less actions are the common
|
|
621
|
-
case; provide `body:` only for actions that demand a request payload.
|