@azure-id/orc 1.4.2 → 1.5.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 (36) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/README.md +46 -46
  3. package/bin/cli.js +5918 -400
  4. package/bin/mockrun-catalog.js +4 -0
  5. package/bin/verify-contracts.js +4475 -4410
  6. package/bin/verify-package.js +21 -2
  7. package/bin/webui/api.js +108 -8
  8. package/bin/webui/app.html +9 -0
  9. package/bin/webui/css/06-responsive.css +14 -0
  10. package/bin/webui/css/panels/test.css +132 -0
  11. package/bin/webui/fixtures/index.js +23 -1
  12. package/bin/webui/fixtures/test.js +547 -0
  13. package/bin/webui/i18n/en/common.json +1 -0
  14. package/bin/webui/i18n/en/nav.json +1 -0
  15. package/bin/webui/i18n/en/overview.json +4 -0
  16. package/bin/webui/i18n/en/test.json +141 -0
  17. package/bin/webui/i18n/id/common.json +1 -0
  18. package/bin/webui/i18n/id/nav.json +1 -0
  19. package/bin/webui/i18n/id/overview.json +4 -0
  20. package/bin/webui/i18n/id/test.json +141 -0
  21. package/bin/webui/js/01-i18n.js +1 -0
  22. package/bin/webui/js/panels/challenge.js +8 -3
  23. package/bin/webui/js/panels/overview.js +10 -0
  24. package/bin/webui/js/panels/test.js +733 -0
  25. package/bin/webui/serve.js +572 -557
  26. package/mock-run/orc-test.md +327 -0
  27. package/package.json +1 -1
  28. package/templates/agents/MODEL-MAPPING.md +2 -0
  29. package/templates/agents/orc-test-designer-opus-5-high.md +158 -0
  30. package/templates/agents/orc-test-interpreter-opus-5-low.md +130 -0
  31. package/templates/commands/orc-test.md +66 -0
  32. package/templates/skills/_shared/extra-dispatch.md +1346 -1337
  33. package/templates/skills/_shared/live-target.md +188 -0
  34. package/templates/skills/_shared/phases/trace.md +2 -2
  35. package/templates/skills/_shared/wait.md +241 -240
  36. package/templates/skills/orc-test/SKILL.md +233 -0
@@ -0,0 +1,547 @@
1
+ "use strict";
2
+ /* fixtures/test.js — canned data for `orc ui --fixtures`.
3
+ /orc-test: the lane that RUNS the test.
4
+
5
+ THE RULE FOR EVERY FILE IN HERE: carry ONE OF EVERY STATE, including the
6
+ ugly ones. You cannot DESIGN a state you cannot reach, and on a healthy
7
+ machine with one green run most of the states below are unreachable — so
8
+ they get built once, blind, and never looked at again.
9
+
10
+ What this file therefore carries, deliberately:
11
+ · every list state the CLI computes, INCLUDING `UNREADABLE`
12
+ · `env: unhealthy` — the state where ORC stops and hands back
13
+ · a RED happy path that STOPPED the ladder, so the edge and security
14
+ tiers are `unknown` rather than absent
15
+ · `unchecked` API1 and API5, each with the reason it could not be measured
16
+ · a REMOTE target with a build mismatch and an authorization statement
17
+ · a target where NOBODY filled the authorization in
18
+ · a code-vs-live diff that was NOT MEASURED, with its reason
19
+ · a Playwright driver that is `absent`
20
+ · a finding that was NOT observed, and a run whose REPORT.md is missing
21
+ · a recorded flake
22
+
23
+ Shapes MUST match what `bin/cli.js --json` really emits — a drifted fixture
24
+ is worse than no fixture. Every shape below was taken from a real run of
25
+ `orc test show --json`, `orc test status --json`, `orc test env --json` and
26
+ `orc test ui tools --json`. */
27
+
28
+ const { PROJECT } = require("./shell.js");
29
+
30
+ const DIR = "orc/orc-test";
31
+
32
+ /* ------------------------------------------------------------- the routes -- */
33
+
34
+ const routesToy = [
35
+ { key: "GET /users", method: "GET", path: "/users", source: "code", file: "src/routes/users.js", line: 12, framework: "express", also: [], params: null, body: null, response: null, in_code: true, live: true },
36
+ { key: "POST /users", method: "POST", path: "/users", source: "code", file: "src/routes/users.js", line: 31, framework: "express", also: [], params: null, body: null, response: null, in_code: true, live: true },
37
+ { key: "GET /users/{}", method: "GET", path: "/users/{}", source: "spec", file: "openapi.yaml", line: null, framework: null, also: ["src/routes/users.js:44"], params: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], body: null, response: { status: 200, schema: { type: "object", properties: { id: { type: "string" }, email: { type: "string" } } } }, in_code: true, live: true },
38
+ { key: "GET /orders", method: "GET", path: "/orders", source: "code", file: "src/routes/orders.js", line: 9, framework: "express", also: [], params: null, body: null, response: null, in_code: true, live: false },
39
+ // THE SHADOW API. It answers at the target and exists nowhere in the
40
+ // repository — OWASP API9. It keeps every field the live spec declared, so
41
+ // the free case matrix derives from it exactly as it would from a route in
42
+ // the code.
43
+ { key: "GET /internal/debug", method: "GET", path: "/internal/debug", source: "spec-live", file: null, line: null, framework: null, also: [], params: null, body: null, response: null, in_code: false, live: true },
44
+ ];
45
+
46
+ /* --------------------------------------------------------------- the cases -- */
47
+
48
+ const casesToy = [
49
+ { id: "C-001", target: "GET /users", tier: "happy", kind: null, source: "derived", why: "the declared happy path", owasp: null, identity: "reader", mutates: false, gaps: [], verdict: "pass", verdict_why: "200, and the body carried no leak marker", last_status: 200, evidence: DIR + "/api-users/runs/03/evidence/C-001" },
50
+ { id: "C-002", target: "GET /users/{}", tier: "happy", kind: null, source: "derived", why: "the declared happy path", owasp: null, identity: "reader", mutates: false, gaps: [], verdict: "pass", verdict_why: "200, and the body matched the declared response schema", last_status: 200, evidence: DIR + "/api-users/runs/03/evidence/C-002" },
51
+ { id: "C-003", target: "GET /users/{}", tier: "edge", kind: null, source: "derived", why: "the path parameter at its declared boundary", owasp: null, identity: "reader", mutates: false, gaps: [], verdict: "fail", verdict_why: "expected one of 400, 404 — observed 500", last_status: 500, evidence: DIR + "/api-users/runs/03/evidence/C-003" },
52
+ { id: "C-004", target: "GET /orders", tier: "edge", kind: null, source: "derived", why: "no page size was declared, so the default was pushed", owasp: null, identity: "reader", mutates: false, gaps: ["no schema was declared for this route — the designer fills what a schema cannot know"], verdict: "unknown", verdict_why: "not observed — the ladder stopped at the edge tier", last_status: null, evidence: null },
53
+ { id: "C-005", target: "POST /users", tier: "abuse", kind: null, source: "designed", why: "the same email twice — the second write must be refused, not accepted", owasp: null, identity: "writer", mutates: true, gaps: [], verdict: "unknown", verdict_why: "not observed — the ladder stopped at the edge tier", last_status: null, evidence: null },
54
+ { id: "C-006", target: "GET /users/{}", tier: "security", kind: null, source: "derived", why: "replay a successful request as a SECOND identity against an object it does not own", owasp: "API1", identity: "reader", mutates: false, gaps: [], verdict: "fail", verdict_why: "200 — the second identity read an object it does not own", last_status: 200, evidence: DIR + "/api-users/runs/03/evidence/C-006" },
55
+ { id: "C-007", target: "GET /internal/debug", tier: "security", kind: null, source: "derived", why: "a route that answers at the target and exists nowhere in the repository", owasp: "API9", identity: null, mutates: false, gaps: [], verdict: "fail", verdict_why: "200 — this route is live and is in no file in this repository", last_status: 200, evidence: DIR + "/api-users/runs/03/evidence/C-007" },
56
+ { id: "C-008", target: "GET /users", tier: "security", kind: null, source: "derived", why: "misconfiguration — the response security headers", owasp: "API8", identity: null, mutates: false, gaps: [], verdict: "pass", verdict_why: "every header the closed set names was present", last_status: 200, evidence: DIR + "/api-users/runs/03/evidence/C-008" },
57
+ ];
58
+
59
+ /* ------------------------------------------------------ the OWASP row set -- */
60
+ // The CLOSED set — ten rows, every one of them present in every state,
61
+ // because a category nobody could measure and a category that came back clean
62
+ // must never look the same. `unchecked` keeps its slot, never becomes a pass
63
+ // and never raises the exit code.
64
+
65
+ const sev = (severity, why, cvss) => ({ severity, derived: !!cvss, why, cvss: cvss || null, cvss_note: cvss ? null : "No CVSS vector: this finding was not OBSERVED, and a candidate pending verification does not get a score." });
66
+
67
+ const secRowsToy = [
68
+ {
69
+ owasp: "API1",
70
+ name: "Broken object level authorization (BOLA)",
71
+ needs: "two identities",
72
+ probe: "replay a successful request as a SECOND identity against an object it does not own",
73
+ cases: [{ id: "C-006", target: "GET /users/{}", why: "replay a successful request as a SECOND identity against an object it does not own", verdict: "fail", verdict_why: "200 — the second identity read an object it does not own", evidence: DIR + "/api-users/runs/03/evidence/C-006", identity: "reader", mutates: false }],
74
+ found: 1,
75
+ unknown: 0,
76
+ pass: 0,
77
+ unchecked: null,
78
+ state: "FOUND",
79
+ severity: sev("critical", "OBSERVED, and the request crossed an identity boundary: a second caller read an object it does not own.", "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"),
80
+ },
81
+ {
82
+ owasp: "API2",
83
+ name: "Broken authentication",
84
+ needs: "a traced flow that shows the route is authenticated",
85
+ probe: "repeat an authenticated request with the credential removed",
86
+ cases: [{ id: "C-009", target: "GET /users", why: "repeat an authenticated request with the credential removed", verdict: "pass", verdict_why: "401 — the credential is required", evidence: DIR + "/api-users/runs/03/evidence/C-009", identity: null, mutates: false }],
87
+ found: 0,
88
+ unknown: 0,
89
+ pass: 1,
90
+ unchecked: null,
91
+ state: "observed-clean",
92
+ severity: sev("high", "OWASP API2 carries a base severity of high. NOT OBSERVED — that base is the CATEGORY's severity, not this finding's."),
93
+ },
94
+ {
95
+ owasp: "API3",
96
+ name: "Broken object property level authorization (BOPLA)",
97
+ needs: "a declared success response schema",
98
+ probe: "compare the returned properties against the declared schema, and offer a privileged property on a write",
99
+ cases: [{ id: "C-011", target: "POST /users", why: "offer a privileged property the schema does not declare", verdict: "unknown", verdict_why: "not observed — the ladder stopped at the edge tier", evidence: null, identity: "writer", mutates: true }],
100
+ found: 0,
101
+ unknown: 1,
102
+ pass: 0,
103
+ unchecked: null,
104
+ state: "not-observed",
105
+ severity: sev("high", "OWASP API3 carries a base severity of high. NOT OBSERVED — that base is the CATEGORY's severity, not this finding's."),
106
+ },
107
+ {
108
+ owasp: "API4",
109
+ name: "Unrestricted resource consumption",
110
+ needs: "a declared request body or a page-size parameter",
111
+ probe: "push the declared size against ORC's own caps and record what the target does",
112
+ cases: [
113
+ { id: "C-012", target: "GET /orders", why: "push the page size against ORC's own cap of 1,000,000", verdict: "pass", verdict_why: "400 — the page size was refused", evidence: DIR + "/api-users/runs/03/evidence/C-012", identity: "reader", mutates: false },
114
+ { id: "C-013", target: "POST /users", why: "push the body against ORC's own 256KB cap", verdict: "unknown", verdict_why: "not observed — the ladder stopped at the edge tier", evidence: null, identity: "writer", mutates: true },
115
+ ],
116
+ found: 0,
117
+ unknown: 1,
118
+ pass: 1,
119
+ unchecked: null,
120
+ state: "partly-observed",
121
+ severity: sev("medium", "OWASP API4 carries a base severity of medium. NOT OBSERVED — that base is the CATEGORY's severity, not this finding's."),
122
+ },
123
+ {
124
+ owasp: "API5",
125
+ name: "Broken function level authorization (BFLA)",
126
+ needs: "two distinguishable roles",
127
+ probe: "call a privileged function as the role that should be refused",
128
+ cases: [],
129
+ found: 0,
130
+ unknown: 0,
131
+ pass: 0,
132
+ unchecked: [{ scope: "API5", why: "only one ROLE is declared. BFLA asks whether a caller who should be REFUSED is refused; with one role there is nobody to refuse. Declare a second role with `orc test identity add`.", measured: false }],
133
+ state: "unchecked",
134
+ severity: sev("high", "OWASP API5 carries a base severity of high. NOT OBSERVED — that base is the CATEGORY's severity, not this finding's."),
135
+ },
136
+ {
137
+ owasp: "API6",
138
+ name: "Unrestricted access to sensitive business flows",
139
+ needs: "`full` tier and --destructive allow",
140
+ probe: "repeat a state-changing request and stop the moment something refuses",
141
+ cases: [],
142
+ found: 0,
143
+ unknown: 0,
144
+ pass: 0,
145
+ unchecked: [{ scope: "API6", why: "a business-flow probe REPEATS a state-changing request, so it is `full` only. It needs `test_security_tier: full` AND `--destructive allow` with a recorded reason.", measured: false }],
146
+ state: "unchecked",
147
+ severity: sev("medium", "OWASP API6 carries a base severity of medium. NOT OBSERVED — that base is the CATEGORY's severity, not this finding's."),
148
+ },
149
+ {
150
+ owasp: "API7",
151
+ name: "Server side request forgery (SSRF)",
152
+ needs: "a URL-shaped input",
153
+ probe: "supply an address ORC's own loopback listener owns, and record whether the target fetched it",
154
+ cases: [],
155
+ found: 0,
156
+ unknown: 0,
157
+ pass: 0,
158
+ unchecked: [{ scope: "API7", why: "no selected target declares a URL-shaped input (a `url`/`uri`/`callback`/`webhook`-named parameter, or one with `format: uri`). Nothing here takes an address for the server to fetch.", measured: false }],
159
+ state: "unchecked",
160
+ severity: sev("high", "OWASP API7 carries a base severity of high. NOT OBSERVED — that base is the CATEGORY's severity, not this finding's."),
161
+ },
162
+ {
163
+ owasp: "API8",
164
+ name: "Security misconfiguration",
165
+ needs: "nothing — it reads the response the happy path already produced",
166
+ probe: "the response security headers, the error body, and the methods the route answers",
167
+ cases: [{ id: "C-008", target: "GET /users", why: "misconfiguration — the response security headers", verdict: "pass", verdict_why: "every header the closed set names was present", evidence: DIR + "/api-users/runs/03/evidence/C-008", identity: null, mutates: false }],
168
+ found: 0,
169
+ unknown: 0,
170
+ pass: 1,
171
+ unchecked: null,
172
+ state: "observed-clean",
173
+ severity: sev("medium", "OWASP API8 carries a base severity of medium. NOT OBSERVED — that base is the CATEGORY's severity, not this finding's."),
174
+ },
175
+ {
176
+ owasp: "API9",
177
+ name: "Improper inventory management",
178
+ needs: "a spec at the target to compare the code against",
179
+ probe: "the code-vs-live diff — a route that answers and is in no file here",
180
+ cases: [{ id: "C-007", target: "GET /internal/debug", why: "a route that answers at the target and exists nowhere in the repository", verdict: "fail", verdict_why: "200 — this route is live and is in no file in this repository", evidence: DIR + "/api-users/runs/03/evidence/C-007", identity: null, mutates: false }],
181
+ found: 1,
182
+ unknown: 0,
183
+ pass: 0,
184
+ unchecked: null,
185
+ state: "FOUND",
186
+ severity: sev("medium", "OBSERVED. A live route that is in no file here is undocumented surface: nobody reviews it and nobody patches it.", "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"),
187
+ },
188
+ {
189
+ owasp: "API10",
190
+ name: "Unsafe consumption of APIs",
191
+ needs: "a traced flow that names an outbound call",
192
+ probe: "STATIC ONLY — nothing is probed",
193
+ cases: [],
194
+ found: 0,
195
+ unknown: 0,
196
+ pass: 0,
197
+ unchecked: [{ scope: "API10", why: "this row is STATIC ONLY — nothing is probed, because what this application calls out to belongs to somebody else and this run's authorization covers the target alone.", measured: true }],
198
+ state: "unchecked",
199
+ severity: sev("medium", "OWASP API10 carries a base severity of medium. NOT OBSERVED — that base is the CATEGORY's severity, not this finding's."),
200
+ },
201
+ ];
202
+
203
+ /* ------------------------------------------------------------ the findings -- */
204
+
205
+ const findingsToy = [
206
+ {
207
+ id: "F-001",
208
+ title: "A second identity can read another account's user record",
209
+ what: "C-006 replayed the reader's successful GET /users/{id} as the second identity against an object it does not own, and the target answered 200 with that object's body.",
210
+ evidence: DIR + "/api-users/runs/03/evidence/C-006",
211
+ case: "C-006",
212
+ target: "GET /users/{}",
213
+ owasp: "API1",
214
+ observed: true,
215
+ claimed_severity: "high",
216
+ severity: "critical",
217
+ severity_why: "OBSERVED, and the request crossed an identity boundary: a second caller read an object it does not own.",
218
+ cvss: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
219
+ cvss_note: null,
220
+ confidence: "observed",
221
+ impact: "Any signed-in caller can read any other account's record by changing one path segment.",
222
+ fix: "Check the object's owner against the caller in the handler, not in the route table.",
223
+ at: "2026-09-04T11:22:41.000Z",
224
+ },
225
+ {
226
+ id: "F-002",
227
+ title: "GET /internal/debug answers at the target and is in no file here",
228
+ what: "The code-vs-live diff names it live-only. C-007 confirmed it answers 200.",
229
+ evidence: DIR + "/api-users/runs/03/evidence/C-007",
230
+ case: "C-007",
231
+ target: "GET /internal/debug",
232
+ owasp: "API9",
233
+ observed: true,
234
+ claimed_severity: null,
235
+ severity: "medium",
236
+ severity_why: "OBSERVED. A live route that is in no file here is undocumented surface: nobody reviews it and nobody patches it.",
237
+ cvss: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
238
+ cvss_note: null,
239
+ confidence: "observed",
240
+ impact: "Undocumented surface on a host this repository does not describe.",
241
+ fix: "Either delete the route or bring it into the repository and the spec.",
242
+ at: "2026-09-04T11:22:44.000Z",
243
+ },
244
+ {
245
+ // NOT OBSERVED. The interpreter proposed it; the case behind it never ran,
246
+ // because the ladder stopped. It carries no CVSS vector and it is not a
247
+ // pass — the two things a report must never blur.
248
+ id: "F-003",
249
+ title: "The duplicate-write path may accept the same email twice",
250
+ what: "C-005 was derived and never reached: the ladder stopped at the edge tier when C-003 came back 500.",
251
+ evidence: DIR + "/api-users/runs/03/evidence/C-003",
252
+ case: "C-005",
253
+ target: "POST /users",
254
+ owasp: null,
255
+ observed: false,
256
+ claimed_severity: "medium",
257
+ severity: "medium",
258
+ severity_why: "NOT OBSERVED. This is a candidate pending verification, not a finding.",
259
+ cvss: null,
260
+ cvss_note: "No CVSS vector: this finding was not OBSERVED, and a candidate pending verification does not get a score.",
261
+ confidence: "suspected",
262
+ impact: null,
263
+ fix: null,
264
+ at: "2026-09-04T11:22:46.000Z",
265
+ },
266
+ ];
267
+
268
+ /* --------------------------------------------------------------- the runs -- */
269
+
270
+ const testShowToy = {
271
+ ok: true,
272
+ slug: "api-users",
273
+ state: "RED",
274
+ target: { kind: "be", env: "local", base_url: "http://127.0.0.1:4000", origin: "http://127.0.0.1:4000", authorized: "me, on my own laptop, against my own dev database", destructive: { mode: "deny", reason: "this box shares a database with the seed script" } },
275
+ authorized: "me, on my own laptop, against my own dev database",
276
+ destructive: { mode: "deny", reason: "this box shares a database with the seed script" },
277
+ identities: [
278
+ { name: "reader", role: "user", source: "env", verified_at: "2026-09-04T10:58:02.000Z" },
279
+ { name: "writer", role: "user", source: "vault", verified_at: "2026-09-04T10:58:40.000Z" },
280
+ ],
281
+ two_identities: true,
282
+ selected: ["GET /users", "GET /users/{}", "POST /users", "GET /orders", "GET /internal/debug"],
283
+ flows: ["GET /users/{}"],
284
+ counts: { pass: 4, fail: 3, unknown: 4, unrun: 2 },
285
+ cases: casesToy,
286
+ case_budget: { budget: 200, reached: false, unfinished: [] },
287
+ surface: {
288
+ at: "2026-09-04T10:41:09.000Z",
289
+ spec_on_disk: { kind: "openapi", file: "openapi.yaml", parsed: true, why: null, routes: 4 },
290
+ spec_at_target: { probed: true, reachable: true, tried: ["/openapi.json", "/swagger.json"], found: "http://127.0.0.1:4000/openapi.json" },
291
+ frameworks: ["express"],
292
+ files_scanned: 34,
293
+ routes: routesToy,
294
+ mounts: [{ prefix: "/api/v1", file: "src/server.js", line: 22 }],
295
+ unresolved: [{ file: "src/routes/index.js", line: 18, text: "router.use(mountFromConfig(cfg))" }],
296
+ diff: {
297
+ source: "spec-vs-spec",
298
+ code_only: [{ key: "GET /orders", file: "src/routes/orders.js", line: 9 }],
299
+ live_only: [{ key: "GET /internal/debug" }],
300
+ note: "derived from the two specs — no route was probed to produce it",
301
+ },
302
+ },
303
+ env: { state: null, stored: false, note: "not stored — a health state is computed fresh on every read. `orc test env <slug>` takes a reading." },
304
+ security: {
305
+ tier: "safe",
306
+ tier_source: { value: "safe", source: "the shipped default" },
307
+ notes: ["`full` was requested and is CLIPPED to `safe`: it adds the MUTATING probes, and this run was frozen `--destructive deny`."],
308
+ rows: secRowsToy,
309
+ statics: [{ owasp: "API10", what: "outbound calls named by a traced flow — STATIC ONLY, nothing was probed", rows: [{ at: "src/clients/billing.js:41", what: "POST https://billing.example.com/charges", target: "GET /users/{}" }] }],
310
+ found: 2,
311
+ },
312
+ runs: [
313
+ { n: 1, at: "2026-09-04T11:02:10.000Z", counts: { pass: 2, fail: 0, unknown: 6, unrun: 0 }, stopped: null, dir: "runs/01" },
314
+ { n: 2, at: "2026-09-04T11:14:55.000Z", counts: { pass: 5, fail: 1, unknown: 2, unrun: 0 }, stopped: null, dir: "runs/02" },
315
+ // THE LADDER STOPPED. A red happy path makes the edge cases meaningless and
316
+ // the security tier noise, so the run stops, reports, and hands back.
317
+ { n: 3, at: "2026-09-04T11:22:03.000Z", counts: { pass: 4, fail: 3, unknown: 4, unrun: 0 }, stopped: "edge", dir: "runs/03" },
318
+ ],
319
+ findings: findingsToy,
320
+ // A FLAKE IS RECORDED, NEVER RETRIED AWAY. The instability IS the finding.
321
+ flakes: [{ run: 3, id: "C-004", was: "pass", now: "unknown" }],
322
+ events: [
323
+ { at: "2026-09-04T10:38:00.000Z", what: "init", detail: "target frozen: be/local http://127.0.0.1:4000" },
324
+ { at: "2026-09-04T10:41:09.000Z", what: "surface", detail: "5 routes, 1 unresolved" },
325
+ { at: "2026-09-04T11:22:03.000Z", what: "run", detail: "run 3: 4 pass, 3 fail, 4 unknown" },
326
+ { at: "2026-09-04T11:22:46.000Z", what: "record", detail: "3 recorded, 1 dropped" },
327
+ ],
328
+ paths: { run: DIR + "/api-users", report: DIR + "/api-users/REPORT.md", findings: DIR + "/api-users/findings.md", surface: DIR + "/api-users/surface.md", changes: DIR + "/api-users/changes.md" },
329
+ report_exists: true,
330
+ where: "Where it stands: /orc-test · be/local · 12 cases · 3 runs · 3 red",
331
+ never_staged: "This folder holds real response bodies from a real system. It is never staged, and ORC does not edit your .gitignore.",
332
+ };
333
+
334
+ /* A REMOTE target. Everything about it is different: ORC will not bring an
335
+ environment up on a host it does not own, the authorization statement is
336
+ REQUIRED, and a failing selector here is a BUILD MISMATCH before it is a
337
+ defect. This one also carries a diff that was NOT MEASURED, which must never
338
+ render as "no shadow APIs". */
339
+ const testShowRemote = {
340
+ ok: true,
341
+ slug: "staging-checkout",
342
+ state: "PARTIAL",
343
+ target: { kind: "both", env: "remote", base_url: "https://staging.example.com", origin: "https://staging.example.com", authorized: "the platform team, in ticket OPS-4471, for the window 04-09 09:00-12:00", destructive: { mode: "allow", reason: "OPS-4471 names the seeded tenant and says it is disposable" } },
344
+ authorized: "the platform team, in ticket OPS-4471, for the window 04-09 09:00-12:00",
345
+ destructive: { mode: "allow", reason: "OPS-4471 names the seeded tenant and says it is disposable" },
346
+ identities: [{ name: "shopper", role: "user", source: "login", verified_at: "2026-09-04T09:12:00.000Z" }],
347
+ two_identities: false,
348
+ selected: ["POST /checkout"],
349
+ flows: [],
350
+ counts: { pass: 1, fail: 0, unknown: 2, unrun: 0 },
351
+ cases: [
352
+ { id: "C-001", target: "POST /checkout", tier: "happy", kind: null, source: "derived", why: "the declared happy path", owasp: null, identity: "shopper", mutates: true, gaps: [], verdict: "pass", verdict_why: "201", last_status: 201, evidence: DIR + "/staging-checkout/runs/01/evidence/C-001" },
353
+ { id: "C-002", target: "POST /checkout", tier: "edge", kind: null, source: "designed", why: "an empty basket must be refused", owasp: null, identity: "shopper", mutates: true, gaps: [], verdict: "unknown", verdict_why: "429 — the target rate-limited this run, and ORC backed off rather than pushing through", last_status: 429, evidence: DIR + "/staging-checkout/runs/01/evidence/C-002" },
354
+ // A UI case. It is an ordinary case row and the BE runner never touches it.
355
+ { id: "C-003", target: "checkout-happy", tier: "happy", kind: "ui", source: "designed", why: "the checkout journey, end to end", owasp: null, identity: "shopper", mutates: true, gaps: [], verdict: "unknown", verdict_why: "the selector `[data-testid=\"pay\"]` was not found. On a REMOTE target that is a BUILD MISMATCH before it is a defect: the deployed bundle may predate the attribute.", last_status: null, evidence: DIR + "/staging-checkout/ui/runs/01/checkout-happy" },
356
+ ],
357
+ case_budget: { budget: 200, reached: false, unfinished: [] },
358
+ surface: {
359
+ at: "2026-09-04T09:20:00.000Z",
360
+ spec_on_disk: { kind: "openapi", file: "openapi.yaml", parsed: false, why: "the file is present and did not parse as YAML at line 212", routes: 0 },
361
+ spec_at_target: { probed: true, reachable: true, tried: ["/openapi.json", "/swagger.json", "/v3/api-docs"], found: null },
362
+ frameworks: ["nestjs"],
363
+ files_scanned: 61,
364
+ routes: [{ key: "POST /checkout", method: "POST", path: "/checkout", source: "code", file: "src/checkout/checkout.controller.ts", line: 28, framework: "nestjs", also: [], params: null, body: null, response: null, in_code: true, live: null }],
365
+ mounts: [],
366
+ unresolved: [],
367
+ diff: {
368
+ source: null,
369
+ code_only: null,
370
+ live_only: null,
371
+ why: "no spec answered at the target, so there is nothing to compare the code against. This is NOT 'no shadow APIs' — it is 'not measured'.",
372
+ },
373
+ },
374
+ env: { state: null, stored: false, note: "not stored — a health state is computed fresh on every read. `orc test env <slug>` takes a reading." },
375
+ security: { tier: "off", tier_source: { value: "off", source: "test_security_tier in .claude/orc.config.yaml" }, notes: [], rows: secRowsToy.map((r) => ({ ...r, cases: [], found: 0, unknown: 0, pass: 0, state: "unchecked", unchecked: [{ scope: r.owasp, why: "the security tier is `off` for this run. Nothing was probed, and nothing here is a pass.", measured: false }] })), statics: [], found: 0 },
376
+ runs: [{ n: 1, at: "2026-09-04T09:41:00.000Z", counts: { pass: 1, fail: 0, unknown: 2, unrun: 0 }, stopped: null, dir: "runs/01" }],
377
+ findings: [],
378
+ flakes: [],
379
+ events: [{ at: "2026-09-04T09:05:00.000Z", what: "init", detail: "target frozen: both/remote https://staging.example.com" }],
380
+ paths: { run: DIR + "/staging-checkout", report: DIR + "/staging-checkout/REPORT.md", findings: DIR + "/staging-checkout/findings.md", surface: DIR + "/staging-checkout/surface.md", changes: DIR + "/staging-checkout/changes.md" },
381
+ // NO REPORT ON DISK YET. The panel must offer to render one rather than link
382
+ // at a file that is not there.
383
+ report_exists: false,
384
+ where: "Where it stands: /orc-test · both/remote · 3 cases · 1 run",
385
+ never_staged: "This folder holds real response bodies from a real system. It is never staged, and ORC does not edit your .gitignore.",
386
+ };
387
+
388
+ /* A run that got as far as a frozen target and no further, and whose
389
+ AUTHORIZATION STATEMENT NOBODY FILLED IN. That is a state, not a blank: the
390
+ panel says so on the target card rather than rendering an empty row. */
391
+ const testShowEarly = {
392
+ ok: true,
393
+ slug: "orders-api",
394
+ state: "AWAITING-IDENTITY",
395
+ target: { kind: "be", env: "local", base_url: "http://127.0.0.1:3999", origin: "http://127.0.0.1:3999", authorized: null, destructive: { mode: "deny", reason: "read only" } },
396
+ authorized: null,
397
+ destructive: { mode: "deny", reason: "read only" },
398
+ identities: [],
399
+ two_identities: false,
400
+ selected: [],
401
+ flows: [],
402
+ counts: { pass: 0, fail: 0, unknown: 0, unrun: 0 },
403
+ cases: [],
404
+ case_budget: null,
405
+ surface: null,
406
+ env: { state: null, stored: false, note: "not stored — a health state is computed fresh on every read. `orc test env <slug>` takes a reading." },
407
+ security: { tier: "safe", tier_source: { value: "safe", source: "the shipped default" }, notes: [], rows: secRowsToy.map((r) => ({ ...r, cases: [], found: 0, unknown: 0, pass: 0, state: "not-run", unchecked: null })), statics: [], found: 0 },
408
+ runs: [],
409
+ findings: [],
410
+ flakes: [],
411
+ events: [{ at: "2026-09-05T08:00:00.000Z", what: "init", detail: "target frozen: be/local http://127.0.0.1:3999" }],
412
+ paths: { run: DIR + "/orders-api", report: DIR + "/orders-api/REPORT.md", findings: DIR + "/orders-api/findings.md", surface: DIR + "/orders-api/surface.md", changes: DIR + "/orders-api/changes.md" },
413
+ report_exists: false,
414
+ where: "Where it stands: /orc-test · be/local · 0 cases · 0 runs",
415
+ never_staged: "This folder holds real response bodies from a real system. It is never staged, and ORC does not edit your .gitignore.",
416
+ };
417
+
418
+ /* An UNREADABLE ledger. It is a ROW, never a crash and never a silent gap —
419
+ a listing that drops it is a listing that hides the one run that needs
420
+ attention. */
421
+ const testShowUnreadable = { ok: true, slug: "half-written", state: "UNREADABLE", error: "orc/orc-test/half-written/test.json: Unexpected end of JSON input" };
422
+
423
+ const testShows = {
424
+ "api-users": testShowToy,
425
+ "staging-checkout": testShowRemote,
426
+ "orders-api": testShowEarly,
427
+ "half-written": testShowUnreadable,
428
+ };
429
+
430
+ /* --------------------------------------------------------------- the list -- */
431
+
432
+ const rowOf = (s, cases, runs, findings, updated) => ({
433
+ slug: s.slug,
434
+ state: s.state,
435
+ target: s.target,
436
+ identities: s.identities,
437
+ counts: s.counts,
438
+ cases,
439
+ runs,
440
+ last_run: (s.runs && s.runs.length && s.runs[s.runs.length - 1].at) || null,
441
+ findings,
442
+ updated_at: updated,
443
+ where: s.where,
444
+ });
445
+
446
+ const testList = {
447
+ ok: true,
448
+ dir: DIR,
449
+ count: 4,
450
+ open: 3,
451
+ runs: [
452
+ rowOf(testShowToy, 12, 3, 3, "2026-09-04T11:22:46.000Z"),
453
+ rowOf(testShowRemote, 3, 1, 0, "2026-09-04T09:41:00.000Z"),
454
+ rowOf(testShowEarly, 0, 0, 0, "2026-09-05T08:00:00.000Z"),
455
+ { slug: "half-written", state: "UNREADABLE", error: "orc/orc-test/half-written/test.json: Unexpected end of JSON input", target: null },
456
+ ],
457
+ };
458
+
459
+ /* -------------------------------------------------- the front-end driver -- */
460
+ // `absent`, because that is the state a first-time reader is in and the one
461
+ // the panel has the most to say about. `no_install_alternative: null` MEANS
462
+ // there is none — never that ORC forgot to look — and ORC NAMES the install
463
+ // command and never runs it.
464
+
465
+ const testUiTools = {
466
+ ok: true,
467
+ ui: {
468
+ driver: "playwright",
469
+ tool: "playwright",
470
+ states: ["absent", "outdated", "unauthenticated", "ready"],
471
+ state: "absent",
472
+ installed: false,
473
+ bin: null,
474
+ from: null,
475
+ version: null,
476
+ min_version: "1.40.0",
477
+ outdated: false,
478
+ browsers_dir: PROJECT + "/.cache/ms-playwright",
479
+ browsers: [],
480
+ no_install_alternative: null,
481
+ install: {
482
+ docs_url: "https://playwright.dev/docs/intro",
483
+ cmds: [
484
+ { manager: "npm", cmd: "npm i -D @playwright/test" },
485
+ { manager: "browsers", cmd: "npx playwright install chromium" },
486
+ ],
487
+ },
488
+ next: "npm i -D @playwright/test && npx playwright install chromium",
489
+ why: "no `playwright` is resolvable — not in this project's node_modules and not on PATH.",
490
+ },
491
+ };
492
+
493
+ /* ------------------------------------------------------- the environment -- */
494
+ // The env probe is the /orc-test equivalent of `orc extra ping`: a button, and
495
+ // the outcome is the state worth designing. Keyed by slug so `unhealthy` — the
496
+ // state where ORC STOPS AND HANDS BACK — is reachable in the panel, alongside
497
+ // `absent` and the remote refusal.
498
+
499
+ const testEnvUnhealthy = {
500
+ ok: true,
501
+ slug: "api-users",
502
+ state: "unhealthy",
503
+ next: { action: "STOP — hand back", cmd: null, why: "the process is up and answering wrong. ORC does not fix the system it is testing." },
504
+ detected: { rung: "package.json", file: "package.json", command: "npm run dev", healthcheck: false },
505
+ looked_for: ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml", "package.json", "Procfile", "Makefile", "makefile"],
506
+ health: {
507
+ rung: "/health",
508
+ ok: false,
509
+ tried: [
510
+ { path: "/health", status: 500, reason: null },
511
+ { path: "/healthz", status: 404, reason: null },
512
+ { path: "/", status: 500, reason: null },
513
+ ],
514
+ why: "the base URL answered 500 on every health path",
515
+ measures: "the process is answering, and answering wrong",
516
+ },
517
+ process: { pid: 48211, at: "2026-09-04T10:55:12.000Z", cmd: "npm run dev", alive: true },
518
+ env_vars: { file: ".env.example", missing: ["STRIPE_SECRET_KEY", "SESSION_SECRET"], note: "ORC NAMES the absent keys and invents no value for one — a placeholder written into a real environment is a credential-shaped lie." },
519
+ log_tail: [
520
+ " at Object.connect (src/db/pool.js:41:11)",
521
+ "Error: password authentication failed for user \"app\"",
522
+ "[dev] listening on http://127.0.0.1:4000",
523
+ ],
524
+ };
525
+
526
+ const testEnvAbsent = {
527
+ ok: true,
528
+ slug: "orders-api",
529
+ state: "absent",
530
+ next: { action: "tell ORC the command", cmd: null, why: "no start command could be detected — see `looked_for`" },
531
+ detected: { rung: null, file: null, command: null, healthcheck: false },
532
+ looked_for: ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml", "package.json", "Procfile", "Makefile", "makefile"],
533
+ health: { rung: null, ok: false, tried: [{ path: "/health", status: null, reason: "unreachable" }, { path: "/", status: null, reason: "unreachable" }], why: "connect ECONNREFUSED 127.0.0.1:3999", measures: "nothing answered on the base URL" },
534
+ process: null,
535
+ env_vars: null,
536
+ log_tail: [],
537
+ };
538
+
539
+ // A REMOTE target has no environment here for ORC to bring up, and it will not
540
+ // try. Exit 2, refused by name.
541
+ const testEnvRemote = {
542
+ ok: false,
543
+ reason: "remote-target",
544
+ hint: "staging-checkout is a REMOTE target. There is no environment here for ORC to bring up, and it will not try: starting or stopping something on a host you do not own is not this lane's business. Check the target yourself, then `orc test run`.",
545
+ };
546
+
547
+ module.exports = { testList, testShows, testShowToy, testUiTools, testEnvUnhealthy, testEnvAbsent, testEnvRemote };
@@ -63,6 +63,7 @@
63
63
  "setting.resetTo": "reset to {value}",
64
64
  "common.open": "Open",
65
65
  "common.writeFail": "That did not work",
66
+ "common.runInTerminal": "Run this in your terminal",
66
67
  "common.runInClaude": "Run this in Claude Code",
67
68
  "shortcuts.panelsLetters": "Promises, Boundary, Self-serve, Extra, Mocked Skill Use",
68
69
  "common.confirm": "Confirm"
@@ -17,6 +17,7 @@
17
17
  "nav.challenge": "Challenge",
18
18
  "nav.extra": "Extra",
19
19
  "nav.wait": "Wait",
20
+ "nav.test": "Test",
20
21
  "nav.docs": "Docs",
21
22
  "nav.hookui": "CLI Hook Interface"
22
23
  }
@@ -78,6 +78,10 @@
78
78
  "overview.item.pactBroken.cta": "Open Promises",
79
79
  "overview.item.boundaryRefuse.cta": "Open Boundary",
80
80
  "overview.item.docDrifted.cta": "Open Docs",
81
+ "overview.item.testEnvUnhealthy.cta": "Open Test",
82
+ "overview.item.testRunRed.cta": "Open Test",
83
+ "overview.item.testUncheckedOwasp.cta": "Open Test",
84
+ "overview.item.testEvidenceUnstaged.cta": "Open Test",
81
85
  "overview.item.wikiDebt.cta": "Open Knowledge",
82
86
  "overview.item.extraFindings.cta": "Open Extra",
83
87
  "overview.item.legacyGlobalPackage.cta": "Open Maintenance",