@lovrozagar/oat 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +103 -32
  2. package/dist/cli.js +56 -14
  3. package/dist/config/define-config.d.ts +7 -1
  4. package/dist/config/define-config.d.ts.map +1 -1
  5. package/dist/config/define-config.js.map +1 -1
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +1 -0
  8. package/dist/report/console.d.ts +2 -1
  9. package/dist/report/console.d.ts.map +1 -1
  10. package/dist/report/console.js +33 -1
  11. package/dist/report/console.js.map +1 -1
  12. package/dist/report/matrix.d.ts +8 -13
  13. package/dist/report/matrix.d.ts.map +1 -1
  14. package/dist/report/matrix.js +21 -10
  15. package/dist/report/matrix.js.map +1 -1
  16. package/dist/report/render.d.ts +8 -0
  17. package/dist/report/render.d.ts.map +1 -1
  18. package/dist/report/render.js +94 -1
  19. package/dist/report/render.js.map +1 -1
  20. package/dist/runtime/checks.d.ts +42 -1
  21. package/dist/runtime/checks.d.ts.map +1 -1
  22. package/dist/runtime/checks.js +190 -24
  23. package/dist/runtime/checks.js.map +1 -1
  24. package/dist/runtime/finding.d.ts +14 -1
  25. package/dist/runtime/finding.d.ts.map +1 -1
  26. package/dist/runtime/finding.js +25 -6
  27. package/dist/runtime/finding.js.map +1 -1
  28. package/dist/runtime/run.d.ts +17 -0
  29. package/dist/runtime/run.d.ts.map +1 -1
  30. package/dist/runtime/run.js +126 -66
  31. package/dist/runtime/run.js.map +1 -1
  32. package/dist/runtime/scope.d.ts +163 -0
  33. package/dist/runtime/scope.d.ts.map +1 -0
  34. package/dist/runtime/scope.js +423 -0
  35. package/dist/runtime/scope.js.map +1 -0
  36. package/dist/runtime/world.d.ts +7 -2
  37. package/dist/runtime/world.d.ts.map +1 -1
  38. package/dist/runtime/world.js +12 -6
  39. package/dist/runtime/world.js.map +1 -1
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -40,6 +40,7 @@ This file is the operator manual. An agent that has read it can install oat, wri
40
40
  - [Complete configs](#complete-configs)
41
41
  - [Commands](#commands)
42
42
  - [oat run](#oat-run)
43
+ - [Targeted runs (`--ops`)](#targeted-runs---ops)
43
44
  - [oat doctor](#oat-doctor)
44
45
  - [oat plan](#oat-plan)
45
46
  - [oat serve](#oat-serve)
@@ -255,19 +256,20 @@ oat help
255
256
 
256
257
  Requires `--config`. CLI flags override the same field in the config when both are set.
257
258
 
258
- | flag | default | meaning |
259
- | ------------------------------------------ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
260
- | `--config` | required | module or JSON file, default export |
261
- | `--base-url` | `config.baseUrl` | backend origin |
262
- | `--only` | `config.only` or all entities | comma-separated entity names as `oat plan` prints them (singularised) |
263
- | `--seed` | `config.seed` or `1` | fixture generation seed (reproducible) |
264
- | `--out` | `config.outDir` or `./.oat/runs` | history root; each run writes `<out>/<datetime>/` and updates `latest` |
265
- | `--max-in-flight` | `config.maxInFlight` or `4` | HTTP requests allowed at once |
266
- | `--keep-fixtures` | `config.keepFixtures` or false | do not DELETE what the run created |
267
- | `--quiet` | false | no stderr progress; files under `--out` still update |
268
- | `--save-exchanges` / `--no-save-exchanges` | on unless `--profile cheap` | persist every HTTP exchange under the run dir (`exchanges.jsonl`, `exchanges/`, `blobs/`). Does **not** change in-memory transcript RAM |
269
-
270
- **Exit codes:** `0` no defects, `1` at least one root-cause finding (`BACKEND_BUG`, `SPEC_BUG`, `SECURITY`, `AMBIGUITY`) **or** the run stopped because the network never came back, `2` usage error (missing `--config`, no principals, unknown flag). `COVERAGE_GAP` and `BLOCKED` do not fail the process.
259
+ | flag | default | meaning |
260
+ | ------------------------------------------ | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
261
+ | `--config` | required | module or JSON file, default export |
262
+ | `--base-url` | `config.baseUrl` | backend origin |
263
+ | `--ops` | `config.ops` or every operation | comma-separated operationIds to grade; everything else is support. `*` globs, `<originId>:` prefixes. See [Targeted runs](#targeted-runs---ops) |
264
+ | `--only` | `config.only` or all entities | comma-separated entity names as `oat plan` prints them (singularised); each grades every operation it owns. Joins `--ops` as a union |
265
+ | `--seed` | `config.seed` or `1` | fixture generation seed (reproducible) |
266
+ | `--out` | `config.outDir` or `./.oat/runs` | history root; each run writes `<out>/<datetime>/` and updates `latest` |
267
+ | `--max-in-flight` | `config.maxInFlight` or `4` | HTTP requests allowed at once |
268
+ | `--keep-fixtures` | `config.keepFixtures` or false | do not DELETE what the run created |
269
+ | `--quiet` | false | no stderr progress; files under `--out` still update |
270
+ | `--save-exchanges` / `--no-save-exchanges` | on unless `--profile cheap` | persist every HTTP exchange under the run dir (`exchanges.jsonl`, `exchanges/`, `blobs/`). Does **not** change in-memory transcript RAM |
271
+
272
+ **Exit codes:** `0` no defects, `1` at least one root-cause finding (`BACKEND_BUG`, `SPEC_BUG`, `SECURITY`, `AMBIGUITY`) **or** the run stopped because the network never came back **or**, on a targeted run, a target that did not end `held`, `2` usage error (missing `--config`, no principals, unknown flag, an `--ops` / `--only` name that matches nothing, a target the profile excludes). `COVERAGE_GAP` and `BLOCKED` do not fail a full run.
271
273
 
272
274
  Example:
273
275
 
@@ -275,10 +277,47 @@ Example:
275
277
  oat run --config oat.config.ts --only store,product --out .oat/runs/prod
276
278
  ```
277
279
 
278
- `--only store,product` matches the **entity names** from `oat plan`, not path segments. `/v1/stores` is usually the entity `store`. If a name is unknown, that entity is simply not tested (the others still run).
280
+ `--only store,product` matches the **entity names** from `oat plan`, not path segments. `/v1/stores` is usually the entity `store`. An unknown name exits `2` with the nearest names.
279
281
 
280
282
  A run with no principals exits `2`. Isolation checks then need a second principal; they are skipped, not failed, when only one is present.
281
283
 
284
+ ### Targeted runs (`--ops`)
285
+
286
+ A full run grades every operation. After changing two endpoints you usually want those two, not the whole API:
287
+
288
+ ```bash
289
+ oat run --config oat.config.ts --ops table.create,row.list
290
+ oat run --config oat.config.ts --ops 'row.*' # glob within an operationId
291
+ oat run --config oat.config.ts --ops cdn:asset.get # an operation on the origin with id "cdn"
292
+ oat plan --config oat.config.ts --ops table.create # dry run: what would be graded, queued, called
293
+ ```
294
+
295
+ **Targets** are graded: every check whose subjects include a target runs the full matrix against it. **Support** operations are called and never graded: principal sign-up, parent creates, the cohort create behind a list, reads used as an oracle, teardown deletes. A list route cannot be tested without rows in it, so its create still runs, but a defect in that create is out of this run's scope.
296
+
297
+ How the scope is decided:
298
+
299
+ - Every check declares its **subjects**, the operations whose contract it judges (`filter.*` judges the list route, `patch.minimality` the update, `effects.declared-effect-occurs` each `x-effects` operation). A check runs when one of its subjects is a target. Others are **out of scope**: not run, not skipped.
300
+ - An entity is seeded only when one of its checks grades a target. Declared edges pull in the other direction too: targeting a read route that another entity's create names in `x-invalidate` queues that entity, so its invalidation check grades the route.
301
+ - Checks that judge several operations (`effects.*`, `async.*`, `response.status-is-documented`, `schema.error-response-matches-document`, `invalidation.declared-route-changes`) invoke and judge only the targeted ones. An untargeted `x-effects` upload is never POSTed.
302
+ - Every finding names the operations it judges (`operations` in `oat-report.json`).
303
+ - An operation counts as graded only when a check exercised it. A check that judges after the fact (`response.status-is-documented` reads the transcript) grades only the operations it saw. A check that reports a coverage gap for an operation did not grade it. `--only <entity>` therefore surfaces every action no check can reach as `untested`.
304
+
305
+ Each target ends with one status:
306
+
307
+ | status | meaning |
308
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
309
+ | `held` | at least one check graded it and none failed |
310
+ | `failed` | a defect finding is attributed to it |
311
+ | `inconclusive` | checks ran and none could decide |
312
+ | `blocked` | every check was suppressed, or a support operation it needed failed (named, with evidence) |
313
+ | `untested` | nothing exercised it. The reason says why (`unmodeled`, `entity-not-testable`, `coverage gap: …`, `no check applied: needs …`, `no check exercised it`) |
314
+
315
+ A targeted run exits `0` only when every target is `held`. A target nothing could grade must not read as a pass.
316
+
317
+ Validation happens before any principal signs up: a name that matches nothing exits `2` with the nearest operationIds; an exact target the active `--profile` excludes exits `2`; a glob or `--only` match the profile excludes is dropped and listed. An origin no target names is not run.
318
+
319
+ A targeted run is for iterating. It grades what you named and what those operations observably affect through declared edges. It does not catch a regression in an untargeted operation caused by shared code, so run the full suite before a release.
320
+
282
321
  ### `oat doctor`
283
322
 
284
323
  Offline. Loads the document, builds the model, prints coverage.
@@ -337,6 +376,8 @@ store CLRU· id 2 route(s) (inferred)
337
376
 
338
377
  `--json` is `{ entities, operations, roots }` — the full `SpecModel` maps, including conventions, query capability, async, invite, and path params. Use this when you need to know what oat will call something.
339
378
 
379
+ With `--ops` / `--only` (flag or config), `plan` prints the targeted scope instead: each target with its entity and the checks that would grade it, untestable targets with the reason, the entities that would be queued, and the support operations predicted from the graph. `--json` adds a `scope` object with the same fields. An unknown name exits `2`.
380
+
340
381
  ### `oat serve`
341
382
 
342
383
  In-process demo API. Same fixture as conformance.
@@ -433,7 +474,8 @@ export default defineConfig({
433
474
  seed: 42, // fixture generation; a failing run with the same seed is identical
434
475
  cohortSize: 12, // records created per entity (default 7)
435
476
  maxInFlight: 4, // HTTP in flight
436
- only: ["store", "product"], // restrict entities
477
+ ops: ["store.create", "product.*"], // grade only these operationIds; the rest is support
478
+ only: ["store", "product"], // grade every operation these entities own (union with ops)
437
479
  keepFixtures: false,
438
480
  outDir: "./.oat/runs",
439
481
  saveExchanges: true, // default on unless --profile cheap; --quiet does not turn this off
@@ -483,7 +525,7 @@ export default defineConfig({
483
525
 
484
526
  `spec` may be a path relative to `baseUrl` (`/v1/openapi/spec`) or an absolute URL or a file.
485
527
 
486
- CLI `--base-url`, `--only`, `--seed`, `--out`, `--max-in-flight`, `--keep-fixtures`, `--save-exchanges` / `--no-save-exchanges` override these when passed.
528
+ CLI `--base-url`, `--ops`, `--only`, `--seed`, `--out`, `--max-in-flight`, `--keep-fixtures`, `--save-exchanges` / `--no-save-exchanges` override these when passed.
487
529
 
488
530
  ### Spec loading
489
531
 
@@ -1686,7 +1728,7 @@ Coverage is split **never** (zero entities could run it) vs **partial** (ran on
1686
1728
 
1687
1729
  Cascade suppression is transitive: one root cause is one finding, not a page of consequences. A blocked check has **not** been verified; re-run after the cause is fixed.
1688
1730
 
1689
- `oat run` exit `1` if any finding has a failing verdict. Gaps and blocked entries do not fail CI.
1731
+ `oat run` exit `1` if any finding has a failing verdict. Gaps and blocked entries do not fail a full run. A targeted run (`--ops`) also exits `1` when any target did not end `held`.
1690
1732
 
1691
1733
  Console (stdout) after a run:
1692
1734
 
@@ -1715,20 +1757,20 @@ Written under `--out` (default `./.oat/runs`). Each invocation creates a UTC tim
1715
1757
 
1716
1758
  `--out` / `outDir` replace the root, not the leaf — `--out .oat/runs/prod` writes `.oat/runs/prod/<datetime>/`.
1717
1759
 
1718
- | file | |
1719
- | ---------------------------- | ------------------------------------------------------------------------------------------------------ |
1720
- | `oat-report.md` | human report: summary, findings with request/response excerpts, coverage, latency p50/p95/max |
1721
- | `oat-report.json` | same data for CI. Summary only — full exchanges are not inlined here |
1722
- | `matrix.html` | visual matrix of entities × checks |
1723
- | `matrix.json` | the same graph (AI-friendly), including a mermaid string |
1724
- | `issue-repro/*.sh` | one executable `curl` script per finding that has evidence. Directory is omitted when the run is clean |
1725
- | `progress.log` | logfmt, one event per line, never truncated |
1726
- | `progress.jsonl` | same events as JSON |
1727
- | `progress.tsv` | same columns, tab-separated. `req_id` is the join key to the exchange journal |
1728
- | `progress.json` | latest snapshot only (overwritten ~1s) |
1729
- | `exchanges.jsonl` | one line per request (`seq`, `requestId`, method, url, status, bytes, …). Greppable |
1730
- | `exchanges/<requestId>.json` | full exchange: status, headers, described bodies. Missing id → `seq-<n>.json`. Duplicate id → `-<seq>` |
1731
- | `blobs/<sha256>` | content-addressed file parts and oversized / binary bodies |
1760
+ | file | |
1761
+ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
1762
+ | `oat-report.md` | human report: scope, per-operation status, summary, findings with request/response excerpts, coverage, latency p50/p95/max |
1763
+ | `oat-report.json` | same data for CI. Summary only — full exchanges are not inlined here |
1764
+ | `matrix.html` | visual matrix of entities × checks |
1765
+ | `matrix.json` | the same graph (AI-friendly), including a mermaid string |
1766
+ | `issue-repro/*.sh` | one executable `curl` script per finding that has evidence. Directory is omitted when the run is clean |
1767
+ | `progress.log` | logfmt, one event per line, never truncated |
1768
+ | `progress.jsonl` | same events as JSON |
1769
+ | `progress.tsv` | same columns, tab-separated. `req_id` is the join key to the exchange journal |
1770
+ | `progress.json` | latest snapshot only (overwritten ~1s) |
1771
+ | `exchanges.jsonl` | one line per request (`seq`, `requestId`, method, url, status, bytes, …). Greppable |
1772
+ | `exchanges/<requestId>.json` | full exchange: status, headers, described bodies. Missing id → `seq-<n>.json`. Duplicate id → `-<seq>` |
1773
+ | `blobs/<sha256>` | content-addressed file parts and oversized / binary bodies |
1732
1774
 
1733
1775
  The journal is oat `Exchange` JSON, not HAR (HAR export is out of scope). Default **on** unless `--profile cheap`. `--no-save-exchanges` skips `exchanges/` entirely. `--quiet` does not. **`--no-save-exchanges` does not reduce transcript RAM** — it only skips the disk journal. The in-memory transcript always drops live `FormData` / `Blob` / `ArrayBuffer` / multi-MiB strings after each hop, keeping citeable metadata and content-addressed body descriptors. Reconstruct bytes from `blobs/<sha256>` when a check genuinely needs them.
1734
1776
 
@@ -1739,6 +1781,8 @@ Redaction is on by default (not opt-in), applied on write:
1739
1781
  - Headers: `authorization`, `cookie`, `set-cookie`, `proxy-authorization`, and any `x-*-key` / `x-*-secret` / `x-ia-tester-key` (case-insensitive).
1740
1782
  - JSON bodies, any depth: `access_token`, `refresh_token`, `id_token`, `password`, `token`, `secret`, `api_key` → `"<redacted>"`.
1741
1783
 
1784
+ Every report names its scope. The console prints `scope: full · 141 of 212 operations graded` or `scope: targeted · 2 operations (--ops …)` followed by one line per target. `oat-report.md` has a **Scope** line and an **Operations** table: on a full run, every operation in the document with its status (so operations no check ever grades are visible); on a targeted run, the targets only, followed by **Support operations** with call and non-2xx counts. In `matrix.html` / `matrix.json` a check a targeted run did not ask for is `out-of-scope`, distinct from `skipped` (not applicable).
1785
+
1742
1786
  Join `progress.tsv` `req_id` to `exchanges.jsonl` `requestId` (response `x-request-id` / `request-id` / `x-correlation-id` / `correlation-id` wins; else what oat sent). `oat-report.md` includes one line such as `1841 exchanges → exchanges/`.
1743
1787
 
1744
1788
  ### `oat-report.json`
@@ -1750,6 +1794,30 @@ Join `progress.tsv` `req_id` to `exchanges.jsonl` `requestId` (response `x-reque
1750
1794
  "durationMs": 41200,
1751
1795
  "requests": 842,
1752
1796
  "entitiesTested": ["store", "product"],
1797
+ "scope": {
1798
+ "mode": "targeted",
1799
+ "requested": { "ops": ["product.update"], "only": [] },
1800
+ "operations": [
1801
+ {
1802
+ "operationId": "product.update",
1803
+ "entity": "product",
1804
+ "status": "failed",
1805
+ "checks": {
1806
+ "held": ["concurrency.no-lost-update"],
1807
+ "failed": ["patch.minimality"],
1808
+ "suppressed": [],
1809
+ "inconclusive": [],
1810
+ "gaps": []
1811
+ },
1812
+ "findings": 1,
1813
+ "reason": null
1814
+ }
1815
+ ],
1816
+ "support": [{ "operationId": "product.create", "calls": 9, "non2xx": 0 }],
1817
+ "excluded": [],
1818
+ "originsSkipped": []
1819
+ },
1820
+ "checksOutOfScope": [{ "check": "filter.in-is-union-of-eq", "entity": "product" }],
1753
1821
  "checksRun": ["list.read-after-write", "patch.minimality"],
1754
1822
  "checksSkipped": [{ "check": "async.reaches-terminal-state", "entity": "store", "needs": "…" }],
1755
1823
  "checksSuppressed": [{ "check": "query.axes-compose", "entity": "store", "because": "list.read-after-write" }],
@@ -1918,7 +1986,9 @@ const result = await run({
1918
1986
  })
1919
1987
 
1920
1988
  // result.findings, result.checksSkipped, result.checksSuppressed,
1921
- // result.inconclusive, result.entitiesTested, result.teardown, result.created
1989
+ // result.inconclusive, result.entitiesTested, result.teardown, result.created,
1990
+ // result.scope (per-operation status), result.checksOutOfScope
1991
+ // pass `ops: ["product.update"]` to run() for a targeted run; ScopeError on an unknown name
1922
1992
  ```
1923
1993
 
1924
1994
  `loadConfig(path)` loads `.ts` / `.js` / `.mjs` / `.json` the same way the CLI does. The CLI then expands `${NAME}` in every string. `defineConfig` is an identity function for typing; it does not interpolate. If you call `run()` with an in-process object, resolve secrets yourself (template literals, `process.env`) before passing it.
@@ -2058,6 +2128,7 @@ These are deliberate. An agent should not invent a flag for them.
2058
2128
  - **`or()` is postgrest-only.**
2059
2129
  - **Leftover rows on a shared DB** poison numeric and filter checks. Wipe between runs.
2060
2130
  - **`--only` uses plan names** (`store`, not `stores` or `/v1/stores`).
2131
+ - **`--ops` trusts support operations.** A targeted green means the targets held, given that what they needed worked. A defect in an untargeted operation is out of scope, even when a target called it.
2061
2132
  - **Default cohort is 7.** `pagination.limit-respects-documented-max` needs `cohortSize > maxLimit`.
2062
2133
  - **First principal is the writer.** Extra principals are peers / lattice, not a pool of writers.
2063
2134
 
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ import { ISSUE_REPRO_DIR, renderConsole, renderJson, renderMarkdown, renderRepro
10
10
  import { createProgressPump, createStderrProgress, formatProgressJsonl, formatProgressLine, formatProgressTsv, PROGRESS_GLOSSARY, PROGRESS_TSV_HEADER, } from "./runtime/progress.js";
11
11
  import { resolveSaveExchanges } from "./runtime/exchanges.js";
12
12
  import { allocateRunDir, DEFAULT_RUNS_ROOT } from "./runtime/runs.js";
13
+ import { authStepOperationIds, planScope, resolveTargetScope, ScopeError } from "./runtime/scope.js";
13
14
  import { renderTeardown } from "./runtime/teardown.js";
14
15
  import { buildModel } from "./spec/graph.js";
15
16
  import { dereference, loadSpec } from "./spec/load.js";
@@ -19,6 +20,7 @@ export const KNOWN_FLAGS = new Set([
19
20
  "spec",
20
21
  "base-url",
21
22
  "only",
23
+ "ops",
22
24
  "profile",
23
25
  "seed",
24
26
  "out",
@@ -74,7 +76,8 @@ Flags
74
76
  --config oat config module (.ts/.js/.mjs/.json) with a default export
75
77
  --spec OpenAPI document, http(s) URL or filesystem path
76
78
  --base-url backend under test, overriding the config
77
- --only comma-separated entity names to restrict the run to
79
+ --ops comma-separated operationIds to grade (row.*, cdn:asset.get); the rest is support
80
+ --only comma-separated entity names; each grades every operation it owns
78
81
  --profile named profile gating which operations run (built-in: full, cheap)
79
82
  --seed integer seed for fixture generation (default 1)
80
83
  --out history root; each run is <out>/<datetime>/ (default ./.oat/runs)
@@ -97,6 +100,26 @@ function str(flags, key) {
97
100
  const value = flags[key];
98
101
  return typeof value === "string" ? value : undefined;
99
102
  }
103
+ function list(flags, key) {
104
+ return str(flags, key)
105
+ ?.split(",")
106
+ .map((s) => s.trim())
107
+ .filter(Boolean);
108
+ }
109
+ /**
110
+ * Exit code for a finished run. Root causes fail it, not raw findings: gaps and blocked entries
111
+ * are information. A targeted run also fails unless every target held — under `--ops` a target
112
+ * nothing graded must not read as a pass.
113
+ */
114
+ export function exitCode(result) {
115
+ if (result.network?.incomplete === true)
116
+ return 1;
117
+ if (result.findings.some((f) => f.verdict !== "COVERAGE_GAP" && f.verdict !== "BLOCKED"))
118
+ return 1;
119
+ if (result.scope.mode === "targeted" && result.scope.operations.some((op) => op.status !== "held"))
120
+ return 1;
121
+ return 0;
122
+ }
100
123
  async function commandRun(flags) {
101
124
  const configPath = str(flags, "config");
102
125
  if (configPath === undefined) {
@@ -107,11 +130,8 @@ async function commandRun(flags) {
107
130
  const config = interpolate(await loadConfig(configPath));
108
131
  const baseUrl = str(flags, "base-url") ?? config.baseUrl;
109
132
  const seedFlag = str(flags, "seed");
110
- const onlyFromFlag = str(flags, "only")
111
- ?.split(",")
112
- .map((s) => s.trim())
113
- .filter(Boolean);
114
- const only = onlyFromFlag ?? config.only?.filter((name) => name.trim() !== "");
133
+ const only = list(flags, "only") ?? config.only?.filter((name) => name.trim() !== "");
134
+ const ops = list(flags, "ops") ?? config.ops?.filter((name) => name.trim() !== "");
115
135
  const profile = str(flags, "profile") ?? config.profile;
116
136
  const saveExchanges = resolveSaveExchanges({
117
137
  ...(flags["no-save-exchanges"] === true ? { flag: false } : flags["save-exchanges"] === true ? { flag: true } : {}),
@@ -169,6 +189,7 @@ async function commandRun(flags) {
169
189
  ...(config.roots === undefined ? {} : { roots: config.roots }),
170
190
  ...(config.cohortSize === undefined ? {} : { cohortSize: config.cohortSize }),
171
191
  ...(only === undefined || only.length === 0 ? {} : { only }),
192
+ ...(ops === undefined || ops.length === 0 ? {} : { ops }),
172
193
  ...(profile === undefined ? {} : { profile }),
173
194
  ...(config.profiles === undefined ? {} : { profiles: config.profiles }),
174
195
  ...(config.rateLimits === undefined ? {} : { rateLimits: config.rateLimits }),
@@ -184,6 +205,12 @@ async function commandRun(flags) {
184
205
  ...(config.network === undefined ? {} : { network: config.network }),
185
206
  });
186
207
  }
208
+ catch (error) {
209
+ if (!(error instanceof ScopeError))
210
+ throw error;
211
+ process.stderr.write(`${error.message}\n`);
212
+ return 2;
213
+ }
187
214
  finally {
188
215
  stderrProgress?.stop();
189
216
  fileProgress.stop();
@@ -205,6 +232,7 @@ async function commandRun(flags) {
205
232
  model: result.model,
206
233
  profile: result.profile,
207
234
  profileExclusions: result.profileExclusions,
235
+ scope: result.scope,
208
236
  startedAt,
209
237
  ...(result.exchanges === undefined ? {} : { exchanges: result.exchanges }),
210
238
  ...(result.network === undefined ? {} : { network: result.network }),
@@ -237,11 +265,7 @@ async function commandRun(flags) {
237
265
  process.stdout.write(` network: ${result.network.kind} — run incomplete\n`);
238
266
  }
239
267
  process.stdout.write(` latest: ${allocated.latest}\n\n`);
240
- /* Exit code counts root causes, not raw findings: gaps and blocked entries are information,
241
- * not failures, and a CI gate should react to defects only. */
242
- if (result.network?.incomplete === true)
243
- return 1;
244
- return result.findings.filter((f) => f.verdict !== "COVERAGE_GAP" && f.verdict !== "BLOCKED").length > 0 ? 1 : 0;
268
+ return exitCode(result);
245
269
  }
246
270
  export async function main() {
247
271
  const { command, flags } = parseArgs(process.argv.slice(2));
@@ -436,7 +460,8 @@ export async function main() {
436
460
  return commandRun(flags);
437
461
  const specFlag = str(flags, "spec");
438
462
  const configPath = str(flags, "config");
439
- const specSource = specFlag ?? (configPath === undefined ? undefined : (await loadConfig(configPath)).spec);
463
+ const loaded = configPath === undefined ? undefined : await loadConfig(configPath);
464
+ const specSource = specFlag ?? loaded?.spec;
440
465
  if (specSource === undefined) {
441
466
  process.stderr.write("oat: --spec (or --config) is required\n\n" + USAGE);
442
467
  return 2;
@@ -454,9 +479,26 @@ export async function main() {
454
479
  throw error;
455
480
  }
456
481
  switch (command) {
457
- case "plan":
458
- process.stdout.write(report.plan(model, flags.json === true));
482
+ case "plan": {
483
+ const ops = list(flags, "ops") ?? loaded?.ops;
484
+ const only = list(flags, "only") ?? loaded?.only;
485
+ let scope;
486
+ try {
487
+ scope = planScope(model, resolveTargetScope(model, {
488
+ ...(ops === undefined ? {} : { ops }),
489
+ ...(only === undefined ? {} : { only }),
490
+ authCreates: authStepOperationIds(loaded?.principals ?? []),
491
+ }));
492
+ }
493
+ catch (error) {
494
+ if (!(error instanceof ScopeError))
495
+ throw error;
496
+ process.stderr.write(`${error.message}\n`);
497
+ return 2;
498
+ }
499
+ process.stdout.write(report.plan(model, flags.json === true, scope.mode === "targeted" ? scope : undefined));
459
500
  return 0;
501
+ }
460
502
  case "doctor": {
461
503
  const config = configPath === undefined ? undefined : interpolate(await loadConfig(configPath));
462
504
  const output = report.doctor(model, externalRefs, flags.json === true, config === undefined
@@ -395,8 +395,14 @@ export interface OatConfig {
395
395
  cohortSize?: number;
396
396
  /** Requests allowed in flight at once. Past a server's comfort this makes runs slower. */
397
397
  maxInFlight?: number;
398
- /** Restrict the run to these entity names. */
398
+ /** Grade every operation these entities own, and nothing else. Joins `ops` as a union. */
399
399
  only?: string[];
400
+ /**
401
+ * Grade only these operationIds; every other operation is support — called to reach or observe
402
+ * a target, never graded. `*` globs within an id (`row.*`); `<originId>:<operationId>` targets a
403
+ * secondary origin. `--ops` on the CLI replaces this list.
404
+ */
405
+ ops?: string[];
400
406
  /**
401
407
  * Named profiles this run can select between, keyed by name. `"full"` and `"cheap"` exist
402
408
  * implicitly and need no entry here; add one only for a profile a cost band can't express.
@@ -1 +1 @@
1
- {"version":3,"file":"define-config.d.ts","sourceRoot":"","sources":["../../src/config/define-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"define-config.d.ts","sourceRoot":"","sources":["../../src/config/define-config.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"define-config.js","sourceRoot":"","sources":["../../src/config/define-config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAwIH,MAAM,UAAU,UAAU,CAAC,IAAmB;IAC7C,OAAO,UAAU,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC,CAAA;AACrF,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAmB;IAC7C,OAAO,OAAO,IAAI,IAAI,CAAA;AACvB,CAAC;AA4VD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAC,MAAiB;IAC7C,OAAO,MAAM,CAAA;AACd,CAAC"}
1
+ {"version":3,"file":"define-config.js","sourceRoot":"","sources":["../../src/config/define-config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAwIH,MAAM,UAAU,UAAU,CAAC,IAAmB;IAC7C,OAAO,UAAU,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC,CAAA;AACrF,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAmB;IAC7C,OAAO,OAAO,IAAI,IAAI,CAAA;AACvB,CAAC;AAkWD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAC,MAAiB;IAC7C,OAAO,MAAM,CAAA;AACd,CAAC"}
package/dist/index.d.ts CHANGED
@@ -17,6 +17,8 @@ export { NetworkError, classifyNetworkError, isNetworkError } from "./runtime/ne
17
17
  export type { NetworkKind } from "./runtime/network.ts";
18
18
  export { run } from "./runtime/run.ts";
19
19
  export type { RunOptions, RunResult } from "./runtime/run.ts";
20
+ export { planScope, resolveTargetScope, ScopeError } from "./runtime/scope.ts";
21
+ export type { OperationCoverage, OperationStatus, ScopePlan, ScopeReport, SupportUse, TargetScope, } from "./runtime/scope.ts";
20
22
  export type { Actor } from "./runtime/checks.ts";
21
23
  export type { Finding, Verdict } from "./runtime/finding.ts";
22
24
  export { buildMatrixGraph, matrixViewFromReport, renderMatrixGraph, renderMatrixHtml } from "./report/matrix.ts";
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ export { dereference, loadSpec, normalisePath, parseRouteRef } from "./spec/load
9
9
  export { AuthRefreshRequiredError } from "./runtime/auth.js";
10
10
  export { NetworkError, classifyNetworkError, isNetworkError } from "./runtime/network.js";
11
11
  export { run } from "./runtime/run.js";
12
+ export { planScope, resolveTargetScope, ScopeError } from "./runtime/scope.js";
12
13
  export { buildMatrixGraph, matrixViewFromReport, renderMatrixGraph, renderMatrixHtml } from "./report/matrix.js";
13
14
  export { coverageByCheck, renderConsole, renderJson, renderMarkdown, renderRepros } from "./report/render.js";
14
15
  //# sourceMappingURL=index.js.map
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import type { EntityConfig, OatConfig, QueryCapabilities } from "../config/define-config.ts";
9
9
  import type { SpecModel } from "../spec/graph.ts";
10
+ import { type ScopePlan } from "../runtime/scope.ts";
10
11
  export interface DoctorConfig {
11
12
  query?: QueryCapabilities;
12
13
  entities?: Record<string, EntityConfig>;
@@ -27,7 +28,7 @@ export interface DoctorConfig {
27
28
  * not apply.
28
29
  */
29
30
  export declare const TAG_UNLOCKS: Record<string, readonly string[]>;
30
- declare function plan(model: SpecModel, asJson: boolean): string;
31
+ declare function plan(model: SpecModel, asJson: boolean, scope?: ScopePlan): string;
31
32
  declare function doctor(model: SpecModel, externalRefs: string[], asJson: boolean, config?: DoctorConfig): {
32
33
  text: string;
33
34
  blocking: number;
@@ -1 +1 @@
1
- {"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../src/report/console.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAE5F,OAAO,KAAK,EAAe,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAU9D,MAAM,WAAW,YAAY;IAC5B,KAAK,CAAC,EAAE,iBAAiB,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACvC,KAAK,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,0BAA0B,CAAC,CAAA;CACzE;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CA+BzD,CAAA;AA0CD,iBAAS,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAoEvD;AA+KD,iBAAS,MAAM,CACd,KAAK,EAAE,SAAS,EAChB,YAAY,EAAE,MAAM,EAAE,EACtB,MAAM,EAAE,OAAO,EACf,MAAM,CAAC,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CA8OpC;AAED,eAAO,MAAM,MAAM;;;CAAmB,CAAA"}
1
+ {"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../src/report/console.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAE5F,OAAO,KAAK,EAAe,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAUvE,MAAM,WAAW,YAAY;IAC5B,KAAK,CAAC,EAAE,iBAAiB,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACvC,KAAK,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,0BAA0B,CAAC,CAAA;CACzE;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CA+BzD,CAAA;AA0CD,iBAAS,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,CAuE1E;AA2MD,iBAAS,MAAM,CACd,KAAK,EAAE,SAAS,EAChB,YAAY,EAAE,MAAM,EAAE,EACtB,MAAM,EAAE,OAAO,EACf,MAAM,CAAC,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CA8OpC;AAED,eAAO,MAAM,MAAM;;;CAAmB,CAAA"}
@@ -6,6 +6,7 @@
6
6
  * and work the list down.
7
7
  */
8
8
  import { formatUniqueSets } from "../spec/extensions.js";
9
+ import { describeRequested } from "../runtime/scope.js";
9
10
  import { canWriteFilterOp } from "../spec/conventions.js";
10
11
  import { anyFieldAllows, fieldAllowsNulls, mergeQueryCapabilities, opsAreClosed, } from "../spec/query-capabilities.js";
11
12
  /**
@@ -89,17 +90,20 @@ function lifecycleGlyphs(entity) {
89
90
  ];
90
91
  return slots.map(([letter, opId]) => (opId === undefined ? "·" : letter)).join("");
91
92
  }
92
- function plan(model, asJson) {
93
+ function plan(model, asJson, scope) {
93
94
  if (asJson) {
94
95
  return `${JSON.stringify({
95
96
  entities: [...model.entities.values()],
96
97
  operations: model.operations,
97
98
  roots: model.roots,
99
+ ...(scope === undefined ? {} : { scope }),
98
100
  unique: [...model.entities.values()]
99
101
  .filter((entity) => entity.unique !== null)
100
102
  .map((entity) => ({ entity: entity.name, unique: entity.unique })),
101
103
  }, null, 2)}\n`;
102
104
  }
105
+ if (scope !== undefined)
106
+ return planScopeText(scope);
103
107
  const lines = [];
104
108
  const entities = [...model.entities.values()].sort((a, b) => a.name.localeCompare(b.name));
105
109
  lines.push("");
@@ -147,6 +151,34 @@ function plan(model, asJson) {
147
151
  lines.push("");
148
152
  return lines.join("\n");
149
153
  }
154
+ /** What a targeted run would grade, queue and call — the dry run behind `oat plan --ops`. */
155
+ function planScopeText(scope) {
156
+ const lines = ["", ` scope: targeted (${describeRequested(scope.requested)})`, ""];
157
+ const width = Math.max(12, ...[...scope.targets, ...scope.untestable].map((t) => t.operationId.length)) + 2;
158
+ lines.push(" targets");
159
+ if (scope.targets.length === 0)
160
+ lines.push(" — none gradable");
161
+ for (const target of scope.targets) {
162
+ lines.push(` ${target.operationId.padEnd(width)}entity ${(target.entity ?? "—").padEnd(14)}${target.checks.length} check(s)`);
163
+ }
164
+ if (scope.untestable.length > 0) {
165
+ lines.push(" untestable — a run reports these untested and exits 1");
166
+ for (const item of scope.untestable)
167
+ lines.push(` ${item.operationId.padEnd(width)}${item.reason}`);
168
+ }
169
+ if (scope.excluded.length > 0) {
170
+ lines.push(" excluded by the profile");
171
+ for (const item of scope.excluded)
172
+ lines.push(` ${item.operationId.padEnd(width)}${item.reason}`);
173
+ }
174
+ for (const [origin, ops] of Object.entries(scope.origins)) {
175
+ lines.push(` origin ${origin}: ${ops.join(", ")}`);
176
+ }
177
+ lines.push(` entities queued: ${scope.entities.length === 0 ? "none" : scope.entities.join(", ")}`);
178
+ lines.push(` support (called, never graded): ${scope.support.length === 0 ? "none" : scope.support.join(", ")}`);
179
+ lines.push("");
180
+ return lines.join("\n");
181
+ }
150
182
  function inviteAcceptMode(spec) {
151
183
  if (spec === null)
152
184
  return "—";