@azure-id/orc 1.4.2 → 1.6.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 +186 -0
- package/README.md +63 -50
- package/bin/cli.js +5732 -133
- package/bin/mockrun-catalog.js +4 -0
- package/bin/verify-contracts.js +4509 -4426
- package/bin/verify-package.js +24 -2
- package/bin/webui/api.js +108 -8
- package/bin/webui/app.html +9 -0
- package/bin/webui/css/06-responsive.css +14 -0
- package/bin/webui/css/panels/test.css +132 -0
- package/bin/webui/fixtures/index.js +23 -1
- package/bin/webui/fixtures/test.js +547 -0
- package/bin/webui/i18n/en/common.json +1 -0
- package/bin/webui/i18n/en/nav.json +1 -0
- package/bin/webui/i18n/en/overview.json +5 -0
- package/bin/webui/i18n/en/test.json +141 -0
- package/bin/webui/i18n/id/common.json +1 -0
- package/bin/webui/i18n/id/nav.json +1 -0
- package/bin/webui/i18n/id/overview.json +5 -0
- package/bin/webui/i18n/id/test.json +141 -0
- package/bin/webui/js/01-i18n.js +1 -0
- package/bin/webui/js/panels/challenge.js +8 -3
- package/bin/webui/js/panels/overview.js +18 -0
- package/bin/webui/js/panels/test.js +733 -0
- package/bin/webui/serve.js +572 -557
- package/mock-run/orc-test.md +327 -0
- package/package.json +1 -1
- package/templates/agents/MODEL-MAPPING.md +2 -0
- package/templates/agents/orc-test-designer-opus-5-high.md +158 -0
- package/templates/agents/orc-test-interpreter-opus-5-low.md +130 -0
- package/templates/commands/orc-test.md +66 -0
- package/templates/hooks/README.md +110 -1
- package/templates/hooks/orc-read-gate.js +279 -0
- package/templates/skills/_shared/extra-dispatch.md +1346 -1337
- package/templates/skills/_shared/live-target.md +188 -0
- package/templates/skills/_shared/phases/trace.md +2 -2
- package/templates/skills/_shared/wait.md +241 -240
- package/templates/skills/orc-test/SKILL.md +233 -0
package/bin/verify-package.js
CHANGED
|
@@ -52,6 +52,7 @@ const required = [
|
|
|
52
52
|
"bin/webui/css/panels/challenge.css",
|
|
53
53
|
"bin/webui/css/panels/docs.css",
|
|
54
54
|
"bin/webui/css/panels/extra.css",
|
|
55
|
+
"bin/webui/css/panels/test.css",
|
|
55
56
|
"bin/webui/css/panels/experiment.css",
|
|
56
57
|
// The client modules (v0.48.1). The <script> order in app.html is the load
|
|
57
58
|
// order and the numeric prefix is that order; 99-boot.js must be last.
|
|
@@ -82,6 +83,7 @@ const required = [
|
|
|
82
83
|
"bin/webui/js/panels/challenge.js",
|
|
83
84
|
"bin/webui/js/panels/docs.js",
|
|
84
85
|
"bin/webui/js/panels/extra.js",
|
|
86
|
+
"bin/webui/js/panels/test.js",
|
|
85
87
|
"bin/webui/js/90-tour.js",
|
|
86
88
|
"bin/webui/js/91-shortcuts.js",
|
|
87
89
|
"bin/webui/js/99-boot.js",
|
|
@@ -106,6 +108,7 @@ const required = [
|
|
|
106
108
|
"bin/webui/fixtures/crosslink.js",
|
|
107
109
|
"bin/webui/fixtures/mockrun.js",
|
|
108
110
|
"bin/webui/fixtures/extra.js",
|
|
111
|
+
"bin/webui/fixtures/test.js",
|
|
109
112
|
// The string tables (v0.43.6). Named here for the same reason: English is the
|
|
110
113
|
// FALLBACK table every other language falls back to, so a publish that drops
|
|
111
114
|
// it renders raw dotted keys on every panel, in every language.
|
|
@@ -135,6 +138,7 @@ const required = [
|
|
|
135
138
|
"bin/webui/i18n/en/challenge.json",
|
|
136
139
|
"bin/webui/i18n/en/docs.json",
|
|
137
140
|
"bin/webui/i18n/en/extra.json",
|
|
141
|
+
"bin/webui/i18n/en/test.json",
|
|
138
142
|
"bin/webui/i18n/en/experiment.json",
|
|
139
143
|
"bin/webui/i18n/en/tour.json",
|
|
140
144
|
"bin/webui/i18n/id/common.json",
|
|
@@ -159,6 +163,7 @@ const required = [
|
|
|
159
163
|
"bin/webui/i18n/id/challenge.json",
|
|
160
164
|
"bin/webui/i18n/id/docs.json",
|
|
161
165
|
"bin/webui/i18n/id/extra.json",
|
|
166
|
+
"bin/webui/i18n/id/test.json",
|
|
162
167
|
"bin/webui/i18n/id/experiment.json",
|
|
163
168
|
"bin/webui/i18n/id/tour.json",
|
|
164
169
|
// The mocked runs (v0.46.x): the catalogue module plus the folder it reads.
|
|
@@ -449,6 +454,15 @@ const required = [
|
|
|
449
454
|
// lint. Both are already Opus 5, so `opus5_only` adds no twin for either.
|
|
450
455
|
"templates/agents/orc-doc-writer-opus-5-med.md",
|
|
451
456
|
"templates/agents/orc-doc-checker-opus-5-low.md",
|
|
457
|
+
// v1.5.0 - /orc-test's two agents, and they are two INSTRUMENTS rather than
|
|
458
|
+
// two tiers of one. The designer is `high` because the cheap answer to
|
|
459
|
+
// "design some test cases" is the list the CLI already derived for free; the
|
|
460
|
+
// interpreter is `low` because a harder-thinking interpreter reasons its way
|
|
461
|
+
// to why a leaked stack trace is probably fine in staging, which is exactly
|
|
462
|
+
// the gap it exists to find. Nothing may ever upgrade the interpreter. Both
|
|
463
|
+
// are claude-opus-5, so `opus5_only` adds NO pair and the floor moves by two.
|
|
464
|
+
"templates/agents/orc-test-designer-opus-5-high.md",
|
|
465
|
+
"templates/agents/orc-test-interpreter-opus-5-low.md",
|
|
452
466
|
// v1.2.1 — the status line explained for the person reading it, in
|
|
453
467
|
// Simplified Technical English. It ships INTO .claude/hooks/ next to the hook
|
|
454
468
|
// it describes, because that is where somebody looking at a segment they do
|
|
@@ -466,6 +480,9 @@ const required = [
|
|
|
466
480
|
// it. Wired even with the board off, because the measurement is handed over
|
|
467
481
|
// either way.
|
|
468
482
|
"templates/hooks/orc-subagent-line.js",
|
|
483
|
+
// v1.6.0 — the read gate. The one layer that can refuse an oversized read by
|
|
484
|
+
// the ORCHESTRATOR. Ships wired but OFF; `read_gate` arms it.
|
|
485
|
+
"templates/hooks/orc-read-gate.js",
|
|
469
486
|
"templates/hooks/orc-trace.js",
|
|
470
487
|
];
|
|
471
488
|
|
|
@@ -538,8 +555,13 @@ const agentCount = walkCount(path.join(ROOT, "templates/agents"), ".md");
|
|
|
538
555
|
// capability from a lane. Fable 5 survives as a SESSION model (the effort guard
|
|
539
556
|
// and the statusline still clear it at medium) — that is a different question,
|
|
540
557
|
// and this wave does not touch it.
|
|
541
|
-
|
|
542
|
-
|
|
558
|
+
// v1.5.0: +1 skill (orc-test) and +2 agents - the designer and the interpreter.
|
|
559
|
+
// The lane's recon (T2's `unresolved[]`, T4's flow trace) is dispatched AD-HOC
|
|
560
|
+
// by model+effort and earns no agent, the v0.38.0/v0.45.0 precedent, and that
|
|
561
|
+
// limit is stated in the skill: no `SPAWN` line, so `/orc-retro` cannot
|
|
562
|
+
// aggregate it.
|
|
563
|
+
if (skillCount < 39) missing.push(`templates/skills (expected >=39 SKILL.md, found ${skillCount})`);
|
|
564
|
+
if (agentCount < 48) missing.push(`templates/agents (expected >=48 .md, found ${agentCount})`);
|
|
543
565
|
|
|
544
566
|
// B4 — encoding/mojibake guard. The OneDrive corruption rule becomes a gate:
|
|
545
567
|
// scan every shipped text file for the U+FFFD replacement char (invalid UTF-8
|
package/bin/webui/api.js
CHANGED
|
@@ -24,6 +24,7 @@ const { spawn, spawnSync } = require("child_process");
|
|
|
24
24
|
const fs = require("fs");
|
|
25
25
|
const os = require("os");
|
|
26
26
|
const path = require("path");
|
|
27
|
+
const zlib = require("zlib");
|
|
27
28
|
const fixtures = require("./fixtures/index.js");
|
|
28
29
|
|
|
29
30
|
const CLI = path.join(__dirname, "..", "cli.js");
|
|
@@ -411,6 +412,21 @@ const READS = {
|
|
|
411
412
|
"/api/extra/demotion": (q) => ["extra", "demotion", ...(q.run ? [String(q.run)] : [])],
|
|
412
413
|
"/api/extra/reconcile": (q) => ["extra", "reconcile", String(q.task || "")],
|
|
413
414
|
"/api/extra/journal/prune/preview": () => ["extra", "journal", "prune", "--dry-run"],
|
|
415
|
+
// v1.5.0 — /orc-test, the lane that RUNS the test. THREE reads, and every one
|
|
416
|
+
// of them is a READ in the strict sense this lane needs: none re-scans the
|
|
417
|
+
// repository, none probes the target, and none writes the ledger. Opening a
|
|
418
|
+
// page must never be a measurement — a measurement nobody asked for is
|
|
419
|
+
// traffic nobody authorized.
|
|
420
|
+
//
|
|
421
|
+
// `orc test show` is the whole computed view (`--json is not a summary`), so
|
|
422
|
+
// the surface, the case matrix, the closed OWASP set, the runs and the
|
|
423
|
+
// findings all arrive in ONE object the panel renders and derives nothing
|
|
424
|
+
// from: not a state word, not a severity, not an OWASP id, not the tier.
|
|
425
|
+
// `status` 0 always, `show` 0 / 2 no run, `ui tools` 0 ready / 1 not ready /
|
|
426
|
+
// 2 the driver is `none` — and 2 there is a SETTING, not a missing tool.
|
|
427
|
+
"/api/test": () => ["test", "status"],
|
|
428
|
+
"/api/test/one": (q) => ["test", "show", String(q.slug || "")],
|
|
429
|
+
"/api/test/ui": () => ["test", "ui", "tools"],
|
|
414
430
|
"/api/patterns": () => ["pattern", "status"],
|
|
415
431
|
"/api/gotchas": () => ["gotcha", "list"],
|
|
416
432
|
"/api/stats": (q) => (q.since ? ["stats", "--since", String(q.since)] : ["stats"]),
|
|
@@ -671,6 +687,24 @@ const WRITES = {
|
|
|
671
687
|
// interactive prompt collects, and every rejection the user sees is the
|
|
672
688
|
// CLI's own validator speaking — there is no second idea of a valid slug,
|
|
673
689
|
// a valid kind or a valid edge target anywhere in this panel.
|
|
690
|
+
// v1.5.0 — the THREE /orc-test mutations this panel may make, and NONE of
|
|
691
|
+
// them sends a request to the system under test. `orc test run` is where the
|
|
692
|
+
// traffic is, and it is a COPY-ABLE COMMAND here and never a button: a page
|
|
693
|
+
// that can start a scan against a live host is a page that can start one by
|
|
694
|
+
// accident.
|
|
695
|
+
//
|
|
696
|
+
// · `surface` is the FREE PASS — it reads the repository and the target's
|
|
697
|
+
// own spec document, and it is what every later step is derived from.
|
|
698
|
+
// · `env` is a HEALTH PROBE, the /orc-test equivalent of `orc extra ping`:
|
|
699
|
+
// it asks the target whether it is up. It never starts, stops or fixes
|
|
700
|
+
// anything, and on a REMOTE target it refuses BY NAME.
|
|
701
|
+
// · `report` RENDERS the ledger into REPORT.md and derives nothing.
|
|
702
|
+
//
|
|
703
|
+
// Every one costs zero model tokens, which is the line this panel does not
|
|
704
|
+
// cross. Nothing here dispatches an agent.
|
|
705
|
+
"/api/test/surface": (b) => ["test", "surface", String(b.slug)],
|
|
706
|
+
"/api/test/env": (b) => ["test", "env", String(b.slug)],
|
|
707
|
+
"/api/test/report": (b) => ["test", "report", String(b.slug)],
|
|
674
708
|
"/api/crosslink/add": (b) => {
|
|
675
709
|
const argv = ["crosslink", "add", String(b.name), String(b.repo_path), "--kinds", String(b.kinds)];
|
|
676
710
|
if (b.direction) argv.push("--direction", String(b.direction));
|
|
@@ -875,14 +909,80 @@ function launchClaude(ctx) {
|
|
|
875
909
|
|
|
876
910
|
// ── request handling ────────────────────────────────────────────────────────
|
|
877
911
|
|
|
912
|
+
// A RESPONSE OVER 64 KiB IS A HAZARD ON WINDOWS LOOPBACK (v1.5.0).
|
|
913
|
+
//
|
|
914
|
+
// Measured, and NOT an ORC bug: a twenty-line plain `http.createServer` on this
|
|
915
|
+
// platform loses the tail of a response larger than the 64 KiB socket buffer
|
|
916
|
+
// roughly one time in six, after a handful of short-lived connections. The
|
|
917
|
+
// client receives ~65,077 of 69,177 bytes, the server's `finish` fires and
|
|
918
|
+
// `writableFinished` is true, and nineteen seconds later the client gets
|
|
919
|
+
// ECONNRESET with nothing in any log to say why. Under 60,000 bytes it never
|
|
920
|
+
// happened in any run.
|
|
921
|
+
//
|
|
922
|
+
// `/api/config` was 64,934 bytes — six hundred short of that line — so the
|
|
923
|
+
// Settings tab was one config key away from failing with no message, and it
|
|
924
|
+
// crossed on the release that added five. Two things answer it, and neither is
|
|
925
|
+
// a size limit on what this API may say (`--json is not a summary`):
|
|
926
|
+
//
|
|
927
|
+
// 1. DECLARE THE LENGTH. Without `content-length` Node falls back to chunked
|
|
928
|
+
// encoding, which is a worse shape for a body of known size and gives the
|
|
929
|
+
// client no way to tell a truncated read from a complete one.
|
|
930
|
+
// 2. COMPRESS IT WHEN THE CLIENT ASKS. Every browser sends
|
|
931
|
+
// `accept-encoding: gzip`, and the panel IS a browser — gzip takes that
|
|
932
|
+
// 69 KB answer to about 8 KB, far below the cliff. It is skipped for a
|
|
933
|
+
// small body, where the CPU is not worth it, and skipped entirely for a
|
|
934
|
+
// client that did not ask.
|
|
935
|
+
const GZIP_MIN_BYTES = 32 * 1024;
|
|
936
|
+
// `gzip` as a WHOLE token in the header's comma list, so `x-gzip-not-really`
|
|
937
|
+
// never matches. Written out rather than with a regex word boundary, which is
|
|
938
|
+
// not one beside a hyphen.
|
|
939
|
+
const GZIP_ACCEPTED = /(^|,)\s*gzip\s*(;|,|$)/;
|
|
940
|
+
|
|
941
|
+
// ONE implementation, and serve.js uses it for STATIC files too (v1.5.0). The
|
|
942
|
+
// 64 KiB cliff is a property of the SOCKET, not of the content type: it does not
|
|
943
|
+
// care whether the bytes are an API answer or a panel script. Leaving the static
|
|
944
|
+
// path uncompressed left the two largest files in the app — `extra.js` at 138 KB
|
|
945
|
+
// and `hookui.js` at 76 KB — riding the exact path this was written to fix. It
|
|
946
|
+
// showed up as an intermittent ECONNRESET in the asset-walk test, and it would
|
|
947
|
+
// show up in a real browser as a panel script that stops mid-function.
|
|
948
|
+
//
|
|
949
|
+
// Returns the bytes to send plus the headers that describe them. A caller that
|
|
950
|
+
// forgets to declare the length is the other half of the same bug, so the length
|
|
951
|
+
// is set HERE rather than left to each call site.
|
|
952
|
+
function encodeBody(raw, acceptEncoding) {
|
|
953
|
+
const headers = {};
|
|
954
|
+
let body = raw;
|
|
955
|
+
if (raw.length >= GZIP_MIN_BYTES && GZIP_ACCEPTED.test(String(acceptEncoding || ""))) {
|
|
956
|
+
try {
|
|
957
|
+
body = zlib.gzipSync(raw);
|
|
958
|
+
headers["content-encoding"] = "gzip";
|
|
959
|
+
// A cache keyed on the URL alone would hand a gzip body to a client that
|
|
960
|
+
// cannot read one. Nothing caches on loopback today; saying so costs one
|
|
961
|
+
// header and removes the whole class.
|
|
962
|
+
headers["vary"] = "accept-encoding";
|
|
963
|
+
} catch (_) {
|
|
964
|
+
body = raw;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
headers["content-length"] = String(body.length);
|
|
968
|
+
return { body, headers };
|
|
969
|
+
}
|
|
970
|
+
|
|
878
971
|
function json(res, status, obj) {
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
972
|
+
const raw = Buffer.from(JSON.stringify(obj), "utf8");
|
|
973
|
+
const { body, headers } = encodeBody(raw, res.req && res.req.headers && res.req.headers["accept-encoding"]);
|
|
974
|
+
res.writeHead(
|
|
975
|
+
status,
|
|
976
|
+
Object.assign(
|
|
977
|
+
{
|
|
978
|
+
"content-type": "application/json; charset=utf-8",
|
|
979
|
+
"cache-control": "no-store",
|
|
980
|
+
// Belt and braces on top of the loopback + token checks in serve.js.
|
|
981
|
+
"x-content-type-options": "nosniff",
|
|
982
|
+
},
|
|
983
|
+
headers
|
|
984
|
+
)
|
|
985
|
+
);
|
|
886
986
|
res.end(body);
|
|
887
987
|
}
|
|
888
988
|
|
|
@@ -1285,4 +1385,4 @@ function isDirtyTree(ctx) {
|
|
|
1285
1385
|
}
|
|
1286
1386
|
}
|
|
1287
1387
|
|
|
1288
|
-
module.exports = { handleApi, clearCache, READS, WRITES, MAINTENANCE };
|
|
1388
|
+
module.exports = { handleApi, clearCache, encodeBody, READS, WRITES, MAINTENANCE };
|
package/bin/webui/app.html
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
<link rel="stylesheet" href="css/panels/challenge.css">
|
|
43
43
|
<link rel="stylesheet" href="css/panels/docs.css">
|
|
44
44
|
<link rel="stylesheet" href="css/panels/extra.css">
|
|
45
|
+
<link rel="stylesheet" href="css/panels/test.css">
|
|
45
46
|
<link rel="stylesheet" href="css/06-responsive.css">
|
|
46
47
|
<link rel="stylesheet" href="css/04-motion.css">
|
|
47
48
|
</head>
|
|
@@ -97,6 +98,13 @@
|
|
|
97
98
|
-->
|
|
98
99
|
<li><a href="#/docs" data-panel="docs" data-idx="d" data-i18n="nav.docs">Docs</a></li>
|
|
99
100
|
<li><a href="#/challenge" data-panel="challenge" data-idx="c" data-i18n="nav.challenge">Challenge</a></li>
|
|
101
|
+
<!--
|
|
102
|
+
v1.5.0. Test sits directly under Challenge because it is the same kind
|
|
103
|
+
of thing one step further on: Challenge grades what you WROTE, this
|
|
104
|
+
reports what a running system actually DID. Both stop and hand back
|
|
105
|
+
rather than fixing what they measured.
|
|
106
|
+
-->
|
|
107
|
+
<li><a href="#/test" data-panel="test" data-idx="t" data-i18n="nav.test">Test</a></li>
|
|
100
108
|
<li><a href="#/pact" data-panel="pact" data-idx="p" data-i18n="nav.pact">Promises</a></li>
|
|
101
109
|
<li><a href="#/boundary" data-panel="boundary" data-idx="b" data-i18n="nav.boundary">Boundary</a></li>
|
|
102
110
|
<li><a href="#/wait" data-panel="wait" data-idx="w" data-i18n="nav.wait">Wait</a></li>
|
|
@@ -206,6 +214,7 @@
|
|
|
206
214
|
<script src="js/panels/extra.js"></script>
|
|
207
215
|
<script src="js/panels/wait.js"></script>
|
|
208
216
|
<script src="js/panels/hookui.js"></script>
|
|
217
|
+
<script src="js/panels/test.js"></script>
|
|
209
218
|
<script src="js/90-tour.js"></script>
|
|
210
219
|
<script src="js/91-shortcuts.js"></script>
|
|
211
220
|
<script src="js/99-boot.js"></script>
|
|
@@ -76,6 +76,20 @@
|
|
|
76
76
|
band template here would put the target under the caret. */
|
|
77
77
|
.ex-slot-head { grid-template-columns: minmax(0, 40%) minmax(0, 1fr) 16px; }
|
|
78
78
|
.ex-slot-when { grid-column: 1 / -1; }
|
|
79
|
+
/* v1.5.0 — /orc-test. Every grid here DECLARES its columns, so every one of
|
|
80
|
+
them collapses explicitly rather than inheriting a template it no longer
|
|
81
|
+
has the width for. The route, the case row and the OWASP row all keep the
|
|
82
|
+
SAME child count in every state (that is the `.ex-tool` lesson), so the
|
|
83
|
+
collapse is a template change and never a hidden child. */
|
|
84
|
+
.ts-route { grid-template-columns: 56px minmax(0, 1fr); }
|
|
85
|
+
.ts-route > .chip { grid-column: 2; justify-self: start; }
|
|
86
|
+
.ts-where { grid-column: 1 / -1; }
|
|
87
|
+
.ts-case { grid-template-columns: 56px minmax(0, 1fr); }
|
|
88
|
+
.ts-case > .chip { grid-column: 2; justify-self: start; }
|
|
89
|
+
.ts-case-mid { grid-column: 1 / -1; }
|
|
90
|
+
.ts-case-tail { grid-column: 1 / -1; justify-content: flex-start; }
|
|
91
|
+
.ts-owasp { grid-template-columns: 56px minmax(0, 1fr); }
|
|
92
|
+
.ts-owasp-mid { grid-column: 1 / -1; }
|
|
79
93
|
.ex-band-chips { grid-column: 1 / -1; justify-content: flex-start; }
|
|
80
94
|
.ex-route-controls { align-items: stretch; }
|
|
81
95
|
.ex-route-controls .btn { width: 100%; }
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* panels/test.css — the Test panel (v1.5.0).
|
|
2
|
+
|
|
3
|
+
/orc-test: five tabs' worth of surface. Loaded by app.html in the order its
|
|
4
|
+
<link> sits in that manifest, and for CSS the load order IS the cascade
|
|
5
|
+
order — which is why 06-responsive and 04-motion come after every panel
|
|
6
|
+
sheet.
|
|
7
|
+
|
|
8
|
+
NO COLOUR TOKEN IS DEFINED HERE. Every one of them is declared once, on bare
|
|
9
|
+
:root, in 00-tokens.css.
|
|
10
|
+
|
|
11
|
+
NO MOTION IS DEFINED HERE either. The run rows reuse `.run-row` / `.run-card`
|
|
12
|
+
from panels/runs.css, so the fold and its reduced-motion behaviour are
|
|
13
|
+
already one implementation rather than two. */
|
|
14
|
+
|
|
15
|
+
/* --- the header strip ---------------------------------------------------- */
|
|
16
|
+
/* Above the tabs, on every one of them: what this run measured, in one line.
|
|
17
|
+
Every value is the CLI's. */
|
|
18
|
+
.ts-strip {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-wrap: wrap;
|
|
21
|
+
gap: var(--sp-4);
|
|
22
|
+
padding: var(--sp-3);
|
|
23
|
+
border: 1px solid var(--line-soft);
|
|
24
|
+
border-radius: var(--radius-sm);
|
|
25
|
+
margin-bottom: var(--sp-3);
|
|
26
|
+
}
|
|
27
|
+
.ts-strip-item { display: grid; gap: 2px; }
|
|
28
|
+
.ts-strip-value { font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
29
|
+
.ts-strip-label { font-size: 11px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .04em; }
|
|
30
|
+
.ts-strip-item.ts-ok .ts-strip-value { color: var(--ok); }
|
|
31
|
+
.ts-strip-item.ts-warn .ts-strip-value { color: var(--warn); }
|
|
32
|
+
.ts-strip-item.ts-bad .ts-strip-value { color: var(--bad); }
|
|
33
|
+
|
|
34
|
+
/* --- the caution --------------------------------------------------------- */
|
|
35
|
+
/* OUTSIDE the tabs, because a caution you must hunt for is a caution nobody
|
|
36
|
+
reads. It fires only on something the run actually MEASURED. */
|
|
37
|
+
.ts-caution {
|
|
38
|
+
padding: var(--sp-3);
|
|
39
|
+
border: 1px solid var(--bad);
|
|
40
|
+
border-radius: var(--radius-sm);
|
|
41
|
+
background: var(--surface-2);
|
|
42
|
+
margin-bottom: var(--sp-3);
|
|
43
|
+
}
|
|
44
|
+
.ts-caution-line { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
|
|
45
|
+
|
|
46
|
+
/* --- the authorization block --------------------------------------------- */
|
|
47
|
+
/* It renders ALWAYS and verbatim. A statement nobody filled in is a STATE, and
|
|
48
|
+
it is drawn as one — never as an empty row. */
|
|
49
|
+
.ts-auth {
|
|
50
|
+
display: grid;
|
|
51
|
+
gap: 2px;
|
|
52
|
+
padding: var(--sp-3);
|
|
53
|
+
border-left: 3px solid var(--ok);
|
|
54
|
+
background: var(--surface-2);
|
|
55
|
+
border-radius: var(--radius-sm);
|
|
56
|
+
margin: var(--sp-3) 0;
|
|
57
|
+
}
|
|
58
|
+
.ts-auth-empty { border-left-color: var(--warn); }
|
|
59
|
+
.ts-auth-label { font-size: 11px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .04em; }
|
|
60
|
+
.ts-auth-value { font-size: 14px; }
|
|
61
|
+
.ts-auth-empty .ts-auth-value { color: var(--warn); }
|
|
62
|
+
|
|
63
|
+
/* The hand-back. ORC does not repair the system it is testing, and the sentence
|
|
64
|
+
that says so is not decoration. */
|
|
65
|
+
.ts-handback {
|
|
66
|
+
padding: var(--sp-2) var(--sp-3);
|
|
67
|
+
border-left: 3px solid var(--warn);
|
|
68
|
+
background: var(--surface-2);
|
|
69
|
+
border-radius: var(--radius-sm);
|
|
70
|
+
font-size: 13px;
|
|
71
|
+
margin: var(--sp-2) 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.ts-label { font-size: 11px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .04em; }
|
|
75
|
+
.ts-where { color: var(--text-dim); font-family: var(--mono); font-size: 12px; }
|
|
76
|
+
|
|
77
|
+
/* --- the surface --------------------------------------------------------- */
|
|
78
|
+
/* A ROUTE ROW DECLARES ITS COLUMNS. Every route carries the same five children
|
|
79
|
+
in every state — method, path, two chips, and where it came from — so the
|
|
80
|
+
template is fixed and a chip can never land in a slack row. */
|
|
81
|
+
.ts-route {
|
|
82
|
+
display: grid;
|
|
83
|
+
grid-template-columns: 60px minmax(0, 1fr) auto auto minmax(0, 1fr);
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: var(--sp-2);
|
|
86
|
+
padding: var(--sp-2) 0;
|
|
87
|
+
border-top: 1px solid var(--line-soft);
|
|
88
|
+
}
|
|
89
|
+
.ts-route:first-of-type { border-top: 0; }
|
|
90
|
+
.ts-method { font-family: var(--mono); font-size: 12px; font-weight: 600; color: var(--accent); }
|
|
91
|
+
.ts-path { font-family: var(--mono); font-size: 13px; overflow-wrap: anywhere; }
|
|
92
|
+
.ts-route-min { font-family: var(--mono); font-size: 12px; color: var(--text-dim); padding: 2px 0; overflow-wrap: anywhere; }
|
|
93
|
+
.ts-bad { color: var(--bad); }
|
|
94
|
+
|
|
95
|
+
/* --- the case matrix ----------------------------------------------------- */
|
|
96
|
+
/* Same rule as the route row: FIXED columns, and the tail is a container that
|
|
97
|
+
is always present even when it holds nothing. A card whose child count
|
|
98
|
+
changes with its state must not declare its rows (`.ex-tool`'s 250px
|
|
99
|
+
ellipse), so the count here does not change. */
|
|
100
|
+
.ts-case {
|
|
101
|
+
display: grid;
|
|
102
|
+
grid-template-columns: 64px auto auto minmax(0, 1fr) auto;
|
|
103
|
+
align-items: start;
|
|
104
|
+
gap: var(--sp-2);
|
|
105
|
+
padding: var(--sp-2) 0;
|
|
106
|
+
border-top: 1px solid var(--line-soft);
|
|
107
|
+
}
|
|
108
|
+
.ts-case:first-of-type { border-top: 0; }
|
|
109
|
+
.ts-case-id { font-family: var(--mono); font-size: 12px; font-weight: 600; }
|
|
110
|
+
.ts-case-mid { display: grid; gap: 2px; min-width: 0; }
|
|
111
|
+
.ts-case-target { font-family: var(--mono); font-size: 13px; overflow-wrap: anywhere; }
|
|
112
|
+
.ts-case-why { font-size: 12px; color: var(--text-dim); overflow-wrap: anywhere; }
|
|
113
|
+
.ts-case-gap { font-size: 12px; color: var(--warn); overflow-wrap: anywhere; }
|
|
114
|
+
.ts-case-ev { font-family: var(--mono); font-size: 11px; color: var(--text-faint); overflow-wrap: anywhere; }
|
|
115
|
+
.ts-case-tail { display: flex; flex-wrap: wrap; gap: var(--sp-1); justify-content: flex-end; }
|
|
116
|
+
|
|
117
|
+
/* --- the closed OWASP set ------------------------------------------------ */
|
|
118
|
+
/* TEN ROWS, ALWAYS. An `unchecked` row keeps its slot and carries its reason;
|
|
119
|
+
it is drawn quieter than a FOUND row and it is never drawn as absent. */
|
|
120
|
+
.ts-owasp {
|
|
121
|
+
display: grid;
|
|
122
|
+
grid-template-columns: 64px auto minmax(0, 1fr);
|
|
123
|
+
align-items: start;
|
|
124
|
+
gap: var(--sp-2);
|
|
125
|
+
padding: var(--sp-2) 0;
|
|
126
|
+
border-top: 1px solid var(--line-soft);
|
|
127
|
+
}
|
|
128
|
+
.ts-owasp:first-of-type { border-top: 0; }
|
|
129
|
+
.ts-owasp-id { font-family: var(--mono); font-size: 12px; font-weight: 600; }
|
|
130
|
+
.ts-owasp-mid { display: grid; gap: 3px; min-width: 0; }
|
|
131
|
+
.ts-owasp-name { font-size: 13px; }
|
|
132
|
+
.ts-owasp-unchecked { font-size: 12px; color: var(--text-dim); overflow-wrap: anywhere; }
|
|
@@ -36,6 +36,7 @@ const { laneList, lanePhases, laneCalls } = require("./lanes.js");
|
|
|
36
36
|
const { diy } = require("./flow.js");
|
|
37
37
|
const { crosslink } = require("./crosslink.js");
|
|
38
38
|
const { mockDetail } = require("./mockrun.js");
|
|
39
|
+
const { testList, testShows, testShowToy, testUiTools, testEnvUnhealthy, testEnvAbsent, testEnvRemote } = require("./test.js");
|
|
39
40
|
const { extraProviders, extraList, extraListNoConnection, extraListNeverTested, extraTools, extraKeyhelp, extraModels, extraDoctor, extraRoute, extraRole, extraLanes, extraStats, extraRates, extraPingOk, extraPingBad, extraPingSaveOffer, extraJournal, extraReconcile, extraJournalPrune, extraPingLive, extraPingDeadModel, extraPingNotInstalled, extraInstall, extraDemotion } = require("./extra.js");
|
|
40
41
|
|
|
41
42
|
module.exports.get = function get(route, q) {
|
|
@@ -50,6 +51,15 @@ module.exports.get = function get(route, q) {
|
|
|
50
51
|
// a required reason is collected) could never be designed.
|
|
51
52
|
case "/api/extra/demotion":
|
|
52
53
|
return extraDemotion;
|
|
54
|
+
// v1.5.0 — /orc-test. An unknown slug falls to the RED run rather than
|
|
55
|
+
// returning nothing: a fixture that 404s teaches the panel a state the CLI
|
|
56
|
+
// does not have (`orc test show` exits 2 and says so in words).
|
|
57
|
+
case "/api/test":
|
|
58
|
+
return testList;
|
|
59
|
+
case "/api/test/one":
|
|
60
|
+
return testShows[(q && q.slug) || ""] || testShowToy;
|
|
61
|
+
case "/api/test/ui":
|
|
62
|
+
return testUiTools;
|
|
53
63
|
case "/api/lanes":
|
|
54
64
|
return laneList;
|
|
55
65
|
case "/api/lane/phases":
|
|
@@ -496,11 +506,23 @@ module.exports.get = function get(route, q) {
|
|
|
496
506
|
// from the profile so they agree with what the list already claims.
|
|
497
507
|
//
|
|
498
508
|
// A route with no entry here still gets the ordinary "nothing ran" reply.
|
|
509
|
+
// v1.5.0 — the /orc-test environment probe. Its OUTCOME is the state worth
|
|
510
|
+
// designing — `unhealthy` is the one where ORC stops and hands the machine back
|
|
511
|
+
// — so it answers with canned data rather than "nothing ran", on the
|
|
512
|
+
// `orc extra ping` precedent. Keyed by slug so `absent` and the REMOTE refusal
|
|
513
|
+
// are reachable too. `surface` and `report` still answer "nothing ran": their
|
|
514
|
+
// outcome is a file on disk, not a state.
|
|
515
|
+
const TEST_ENV = { "api-users": [1, testEnvUnhealthy], "orders-api": [1, testEnvAbsent], "staging-checkout": [2, testEnvRemote] };
|
|
516
|
+
|
|
499
517
|
module.exports.post = function post(route, body) {
|
|
500
518
|
// `add` answers OK so the CONNECT FLOW can be walked end to end in fixture
|
|
501
519
|
// mode — the add is only the step before the test, and the test's outcome is
|
|
502
520
|
// the state worth designing. Every other write still answers "nothing ran".
|
|
503
|
-
if (route === "/api/
|
|
521
|
+
if (route === "/api/test/env") {
|
|
522
|
+
const [exit_code, data] = TEST_ENV[String((body && body.slug) || "")] || TEST_ENV["api-users"];
|
|
523
|
+
return { exit_code, data };
|
|
524
|
+
}
|
|
525
|
+
if (route === "/api/extra/add") return { exit_code: 0, data: { ok: true, next: "orc extra ping" } };
|
|
504
526
|
// v0.51.0 — a launch that could NOT happen and one that did. Both are exit 0,
|
|
505
527
|
// because "no terminal here" is an ANSWER carrying the command to paste.
|
|
506
528
|
if (route === "/api/extra/install")
|