clinkx 1.1.0 → 1.1.2
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 +39 -1
- package/README.md +210 -177
- package/clinkx-workflows/dist/architecture-comparison/admission.d.ts +5 -0
- package/clinkx-workflows/dist/architecture-comparison/admission.js +50 -0
- package/clinkx-workflows/dist/architecture-comparison/admission.js.map +1 -0
- package/clinkx-workflows/dist/architecture-comparison/contract.d.ts +995 -0
- package/clinkx-workflows/dist/architecture-comparison/contract.js +149 -0
- package/clinkx-workflows/dist/architecture-comparison/contract.js.map +1 -0
- package/clinkx-workflows/dist/architecture-comparison/runtime.d.ts +29 -0
- package/clinkx-workflows/dist/architecture-comparison/runtime.js +387 -0
- package/clinkx-workflows/dist/architecture-comparison/runtime.js.map +1 -0
- package/clinkx-workflows/dist/contained-file.js +1 -1
- package/clinkx-workflows/dist/contained-file.js.map +1 -1
- package/clinkx-workflows/dist/council-execution-policy.d.ts +4 -2
- package/clinkx-workflows/dist/council-execution-policy.js +5 -1
- package/clinkx-workflows/dist/council-execution-policy.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +72 -72
- package/clinkx-workflows/dist/dashboard/contracts.d.ts +1564 -122
- package/clinkx-workflows/dist/dashboard/contracts.js +4 -0
- package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +12 -12
- package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.d.ts +8 -8
- package/clinkx-workflows/dist/dashboard/scanner.d.ts +3 -0
- package/clinkx-workflows/dist/dashboard/scanner.js +27 -0
- package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +48 -48
- package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +2 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js +4 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/verified-repair-contract.d.ts +191 -0
- package/clinkx-workflows/dist/dashboard/verified-repair-contract.js +28 -0
- package/clinkx-workflows/dist/dashboard/verified-repair-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/index-BdpmbPps.js +115 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/style-Im5i1iAs.css +1 -0
- package/clinkx-workflows/dist/dashboard-ui/index.html +2 -2
- package/clinkx-workflows/dist/engine.d.ts +6 -0
- package/clinkx-workflows/dist/engine.js +78 -7
- package/clinkx-workflows/dist/engine.js.map +1 -1
- package/clinkx-workflows/dist/index.d.ts +3 -0
- package/clinkx-workflows/dist/index.js +3 -2
- package/clinkx-workflows/dist/index.js.map +1 -1
- package/clinkx-workflows/dist/reducers/index.d.ts +9 -1
- package/clinkx-workflows/dist/reducers/index.js +22 -0
- package/clinkx-workflows/dist/reducers/index.js.map +1 -1
- package/clinkx-workflows/dist/state.js +17 -7
- package/clinkx-workflows/dist/state.js.map +1 -1
- package/clinkx-workflows/dist/validators/architecture-comparison.d.ts +2 -0
- package/clinkx-workflows/dist/validators/architecture-comparison.js +12 -0
- package/clinkx-workflows/dist/validators/architecture-comparison.js.map +1 -0
- package/clinkx-workflows/dist/validators/index.js +4 -0
- package/clinkx-workflows/dist/validators/index.js.map +1 -1
- package/clinkx-workflows/dist/validators/verified-repair.d.ts +2 -0
- package/clinkx-workflows/dist/validators/verified-repair.js +12 -0
- package/clinkx-workflows/dist/validators/verified-repair.js.map +1 -0
- package/clinkx-workflows/dist/verification-proof.d.ts +254 -0
- package/clinkx-workflows/dist/verification-proof.js +195 -0
- package/clinkx-workflows/dist/verification-proof.js.map +1 -0
- package/clinkx-workflows/dist/verified-repair/admission.d.ts +72 -0
- package/clinkx-workflows/dist/verified-repair/admission.js +63 -0
- package/clinkx-workflows/dist/verified-repair/admission.js.map +1 -0
- package/clinkx-workflows/dist/verified-repair/contract.d.ts +1910 -0
- package/clinkx-workflows/dist/verified-repair/contract.js +127 -0
- package/clinkx-workflows/dist/verified-repair/contract.js.map +1 -0
- package/clinkx-workflows/dist/verified-repair/host.d.ts +32 -0
- package/clinkx-workflows/dist/verified-repair/host.js +2 -0
- package/clinkx-workflows/dist/verified-repair/host.js.map +1 -0
- package/clinkx-workflows/dist/verified-repair/prompts.d.ts +12 -0
- package/clinkx-workflows/dist/verified-repair/prompts.js +58 -0
- package/clinkx-workflows/dist/verified-repair/prompts.js.map +1 -0
- package/clinkx-workflows/dist/verified-repair/result.d.ts +16 -0
- package/clinkx-workflows/dist/verified-repair/result.js +102 -0
- package/clinkx-workflows/dist/verified-repair/result.js.map +1 -0
- package/clinkx-workflows/dist/verified-repair/runtime.d.ts +28 -0
- package/clinkx-workflows/dist/verified-repair/runtime.js +186 -0
- package/clinkx-workflows/dist/verified-repair/runtime.js.map +1 -0
- package/clinkx-workflows/dist/workflow-tools.d.ts +3 -0
- package/clinkx-workflows/dist/workflow-tools.js +148 -14
- package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
- package/clinkx-workflows/dist/workflow-verification-contract.d.ts +539 -0
- package/clinkx-workflows/dist/workflow-verification-contract.js +52 -0
- package/clinkx-workflows/dist/workflow-verification-contract.js.map +1 -0
- package/clinkx-workflows/dist/workflow-verification.d.ts +15 -0
- package/clinkx-workflows/dist/workflow-verification.js +218 -0
- package/clinkx-workflows/dist/workflow-verification.js.map +1 -0
- package/clinkx-workflows/templates/architecture-comparison/candidate.txt +29 -0
- package/clinkx-workflows/templates/architecture-comparison/judge.txt +31 -0
- package/clinkx-workflows/templates/verified-repair/investigate.txt +20 -0
- package/clinkx-workflows/templates/verified-repair/repair.txt +11 -0
- package/clinkx-workflows/templates/verified-repair/review.txt +18 -0
- package/clinkx-workflows/workflows/architecture-comparison.yaml +133 -0
- package/clinkx-workflows/workflows/verified-repair.yaml +104 -0
- package/dist/allowed-roots.js +4 -1
- package/dist/allowed-roots.js.map +1 -1
- package/dist/codex/app-server-client.d.ts +1 -0
- package/dist/codex/app-server-client.js +3 -2
- package/dist/codex/app-server-client.js.map +1 -1
- package/dist/context-builder/diagnostics.d.ts +1 -0
- package/dist/context-builder/diagnostics.js +1 -1
- package/dist/context-builder/diagnostics.js.map +1 -1
- package/dist/context-builder/discovery.d.ts +18 -1
- package/dist/context-builder/discovery.js +69 -1
- package/dist/context-builder/discovery.js.map +1 -1
- package/dist/context-builder/handler.js +8 -1
- package/dist/context-builder/handler.js.map +1 -1
- package/dist/context-builder/lifecycle-contract.d.ts +24 -24
- package/dist/context-builder/refine-handler.js +16 -1
- package/dist/context-builder/refine-handler.js.map +1 -1
- package/dist/context-builder/secrets.d.ts +11 -0
- package/dist/context-builder/secrets.js +81 -1
- package/dist/context-builder/secrets.js.map +1 -1
- package/dist/context-builder/versions.d.ts +5 -2
- package/dist/context-builder/versions.js +5 -2
- package/dist/context-builder/versions.js.map +1 -1
- package/dist/dashboard-cli.js +10 -3
- package/dist/dashboard-cli.js.map +1 -1
- package/dist/dashboard-gateway/main.js +8 -3
- package/dist/dashboard-gateway/main.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/project-verification/contract.d.ts +537 -0
- package/dist/project-verification/contract.js +40 -0
- package/dist/project-verification/contract.js.map +1 -0
- package/dist/project-verification/files.d.ts +23 -0
- package/dist/project-verification/files.js +80 -0
- package/dist/project-verification/files.js.map +1 -0
- package/dist/project-verification/process.d.ts +7 -0
- package/dist/project-verification/process.js +115 -0
- package/dist/project-verification/process.js.map +1 -0
- package/dist/project-verification/runner.d.ts +35 -0
- package/dist/project-verification/runner.js +185 -0
- package/dist/project-verification/runner.js.map +1 -0
- package/dist/project-verification/service.d.ts +37 -0
- package/dist/project-verification/service.js +151 -0
- package/dist/project-verification/service.js.map +1 -0
- package/dist/provider-doctor.d.ts +22 -0
- package/dist/provider-doctor.js +22 -0
- package/dist/provider-doctor.js.map +1 -0
- package/dist/server.js +29 -1
- package/dist/server.js.map +1 -1
- package/dist/verification-tools.d.ts +90 -0
- package/dist/verification-tools.js +120 -0
- package/dist/verification-tools.js.map +1 -0
- package/dist/verified-repair/files.d.ts +22 -0
- package/dist/verified-repair/files.js +114 -0
- package/dist/verified-repair/files.js.map +1 -0
- package/dist/verified-repair/host.d.ts +14 -0
- package/dist/verified-repair/host.js +612 -0
- package/dist/verified-repair/host.js.map +1 -0
- package/docs/configuration.md +493 -0
- package/docs/engineering-recipes.md +415 -0
- package/docs/security.md +293 -0
- package/docs/verification/README.md +59 -0
- package/docs/verification/dashboard-real-disk.md +436 -0
- package/docs/verification/project-checks.example.json +14 -0
- package/docs/verification/project-checks.md +444 -0
- package/docs/verification/workflow-handoff.md +113 -0
- package/package.json +13 -6
- package/scripts/provider-doctor-core.mjs +364 -0
- package/scripts/provider-doctor-probe.mjs +34 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/index-JPHQVtn9.js +0 -106
- package/clinkx-workflows/dist/dashboard-ui/assets/style-DOMHk-j6.css +0 -1
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
# Dashboard Real-Disk Check
|
|
2
|
+
|
|
3
|
+
## Feature
|
|
4
|
+
|
|
5
|
+
The built dashboard CLI serves persisted workflow history from a real state
|
|
6
|
+
directory. When a run's `state.json` is atomically replaced, the run detail
|
|
7
|
+
and call inspector refresh: the run generation stays the same and the state
|
|
8
|
+
revision changes.
|
|
9
|
+
|
|
10
|
+
Required test: `actual CLI serves persisted history and refreshes same-generation raw content`
|
|
11
|
+
in `clinkx-workflows/dashboard-ui/e2e/dashboard.real-disk.spec.ts`. The
|
|
12
|
+
Playwright project name is empty.
|
|
13
|
+
|
|
14
|
+
Claim boundary: this exercises the real dashboard CLI, HTTP API, Chromium, and
|
|
15
|
+
files on disk. The workflow records are fixtures written by
|
|
16
|
+
`e2e/real-disk-server.mjs`. It does not prove provider or workflow-engine
|
|
17
|
+
execution.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- An owned checkout with `npm ci` done and Node `>=24`. Acceptance evidence
|
|
22
|
+
should come from a clean checkout; see [Clean Checkout](#clean-checkout).
|
|
23
|
+
- Chromium for the installed `@playwright/test`: `npx playwright install chromium`.
|
|
24
|
+
- A private evidence root outside the checkout, for example
|
|
25
|
+
`export EVIDENCE_ROOT="${XDG_STATE_HOME:-$HOME/.local/state}/clinkx-verification"`
|
|
26
|
+
created with `mkdir -p` and `chmod 700`.
|
|
27
|
+
- `CLINKX_DASHBOARD_REAL_DISK_TEST_CHILD` unset. It is a test-only seam that
|
|
28
|
+
replaces the dashboard CLI with a stub from `tests/fixtures/`. When it is
|
|
29
|
+
honored, the fixture writes `real-disk dashboard fixture: TEST CHILD <name>
|
|
30
|
+
is replacing the real dashboard CLI` to stderr; the check below rejects any
|
|
31
|
+
`stderr.log` containing `TEST CHILD`.
|
|
32
|
+
|
|
33
|
+
The preflight below checks each of these, so an unavailable prerequisite is
|
|
34
|
+
reported as `blocked` instead of surfacing as a failed run.
|
|
35
|
+
|
|
36
|
+
Playwright prefixes every fixture line in `stderr.log` with `[WebServer] `.
|
|
37
|
+
The lines quoted below omit that prefix; the checks match substrings.
|
|
38
|
+
|
|
39
|
+
## Commands
|
|
40
|
+
|
|
41
|
+
Run every block from the checkout root. Build once. Invocations below do not
|
|
42
|
+
build.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm run clean && npm run build:all
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Create one private attempt directory per attempt and run the preflight into
|
|
49
|
+
it:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
: "${EVIDENCE_ROOT:?set EVIDENCE_ROOT}"
|
|
53
|
+
RUN="$(mktemp -d "$EVIDENCE_ROOT/real-disk.XXXXXX")"
|
|
54
|
+
chmod 700 "$RUN"
|
|
55
|
+
EVIDENCE_ROOT="$EVIDENCE_ROOT" node clinkx-workflows/dashboard-ui/e2e/real-disk-doctor.mjs preflight \
|
|
56
|
+
> "$RUN/doctor.jsonl"
|
|
57
|
+
echo "preflight exit=$?"; cat "$RUN/doctor.jsonl"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The preflight prints one JSON line per check, `{check, status, detail}`, then
|
|
61
|
+
`{"verdict":"ready"}` (exit 0) or `{"verdict":"blocked"}` (exit 2). It checks
|
|
62
|
+
the Node major; that `dist` is present and that no sampled source is newer
|
|
63
|
+
than its output: the pairs `scripts/check-dist-fresh.mjs` samples,
|
|
64
|
+
`src/dashboard-cli.ts` against `dist/dashboard-cli.js`, and the newest file
|
|
65
|
+
under `clinkx-workflows/dashboard-ui/src` against
|
|
66
|
+
`clinkx-workflows/dist/dashboard-ui/index.html` (mtime samples, not a full
|
|
67
|
+
freshness proof); that Chromium launches; that the test child is unset; and that `EVIDENCE_ROOT` is a writable `700` directory outside
|
|
68
|
+
the checkout. Only when all of those pass does it check app health: it starts
|
|
69
|
+
`real-disk-server.mjs` with a temporary bootstrap file, waits for readiness,
|
|
70
|
+
fetches the dashboard page and the authenticated `/api/env` over the
|
|
71
|
+
bootstrapped origin (10 s timeout each), then sends `SIGTERM` and requires
|
|
72
|
+
`fixture stopped (ok)` with no bootstrap file or state left behind. A skipped
|
|
73
|
+
app-health check is `not_run`. The token is never printed. Continue only on
|
|
74
|
+
`ready`; the result check below requires it in `$RUN/doctor.jsonl`.
|
|
75
|
+
|
|
76
|
+
Run one invocation in the attempt directory. The first command records what
|
|
77
|
+
the attempt executes in `$RUN/identity.json`; Playwright runs only if it
|
|
78
|
+
succeeds, so a failed or refused identity step cannot pair an old
|
|
79
|
+
`identity.json` with a new report. `exit.txt` then holds the identity step's
|
|
80
|
+
status, and `classify` reports `not_run`:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
: "${RUN:?set RUN to an attempt directory}"
|
|
84
|
+
node clinkx-workflows/dashboard-ui/e2e/real-disk-doctor.mjs identity "$RUN" \
|
|
85
|
+
&& CLINKX_DASHBOARD_REAL_DISK_BOOTSTRAP_FILE="$RUN/bootstrap.json" \
|
|
86
|
+
PLAYWRIGHT_JSON_OUTPUT_FILE="$RUN/report.json" \
|
|
87
|
+
PLAYWRIGHT_LAST_RUN_OUTPUT_FILE="$RUN/last-run.json" \
|
|
88
|
+
npm run test:dashboard:e2e:real-disk -- \
|
|
89
|
+
--output "$RUN/artifacts" \
|
|
90
|
+
--reporter=line,json \
|
|
91
|
+
--retries=0 \
|
|
92
|
+
--forbid-only \
|
|
93
|
+
> "$RUN/stdout.log" 2> "$RUN/stderr.log"
|
|
94
|
+
echo "exit=$?" > "$RUN/exit.txt"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`identity.json` records Git `HEAD`; whether the worktree is dirty
|
|
98
|
+
(`git status --porcelain=v1 -uall`); the SHA-256 of
|
|
99
|
+
`git diff --binary --no-ext-diff --no-textconv --no-color --src-prefix=a/
|
|
100
|
+
--dst-prefix=b/ --diff-algorithm=myers --no-relative HEAD` and of each
|
|
101
|
+
untracked, non-ignored file (`git ls-files --others --exclude-standard`).
|
|
102
|
+
Every Git command in identity mode runs with `GIT_CONFIG_GLOBAL=/dev/null`
|
|
103
|
+
and `GIT_CONFIG_NOSYSTEM=1`, so global and system Git configuration
|
|
104
|
+
(including a global `core.excludesFile`) is ignored, and the flags pin the
|
|
105
|
+
remaining output.
|
|
106
|
+
Repository-local diff settings in `.git/config` (for example `diff.context`
|
|
107
|
+
or `diff.renames`) can still change the digest. The record also holds
|
|
108
|
+
SHA-256 digests of `dist/dashboard-cli.js`, the served
|
|
109
|
+
`clinkx-workflows/dist/dashboard-ui` tree, and the whole `dist` and
|
|
110
|
+
`clinkx-workflows/dist` trees, plus the Node, `@playwright/test`, platform,
|
|
111
|
+
and architecture versions. It refuses to replace an existing `identity.json`.
|
|
112
|
+
A Git `HEAD` alone does not identify what ran: ignored build output can be
|
|
113
|
+
stale, which is why the built trees are hashed.
|
|
114
|
+
|
|
115
|
+
Keep `report.json`, `last-run.json`, and `bootstrap.json` beside
|
|
116
|
+
`artifacts/`, never inside it: Playwright empties the `--output` directory at
|
|
117
|
+
startup.
|
|
118
|
+
|
|
119
|
+
## Checking The Result
|
|
120
|
+
|
|
121
|
+
Exit status zero is not proof. Require all of the following from the
|
|
122
|
+
attempt directory. The command below checks them and exits nonzero if any
|
|
123
|
+
fails:
|
|
124
|
+
|
|
125
|
+
- `report.json` exists and parses, with no top-level `errors`.
|
|
126
|
+
- The report's `config.rootDir` is this checkout's
|
|
127
|
+
`clinkx-workflows/dashboard-ui/e2e`, and its only project is the unnamed
|
|
128
|
+
one (`name: ""`) that `playwright.real-disk.config.ts` produces.
|
|
129
|
+
- Exactly one test has the required title, spec file exactly
|
|
130
|
+
`dashboard.real-disk.spec.ts` (relative to `rootDir`), and project name `""`.
|
|
131
|
+
- That test has `expectedStatus: "passed"` and exactly one result with
|
|
132
|
+
`status: "passed"` and `retry: 0`.
|
|
133
|
+
- The result carries exactly one `real-disk-observation` attachment of type
|
|
134
|
+
`application/json` whose decoded body has `observation_version: 1`, a
|
|
135
|
+
loopback `origin`, absolute `state_dir` and `state_dir_realpath`,
|
|
136
|
+
`run_id: "real_disk_run"`, an unchanged non-empty generation, a changed
|
|
137
|
+
non-empty revision, and no `#token=`.
|
|
138
|
+
- `identity.json` exists with `identity_version: 1`, a commit `head`, a
|
|
139
|
+
boolean `dirty`, and a `checkout` equal to this checkout. The command
|
|
140
|
+
prints `head` and `dirty`. A dirty attempt is not rejected, but it is not
|
|
141
|
+
acceptance evidence: its source is only identified by digests, not
|
|
142
|
+
recoverable from a commit.
|
|
143
|
+
- `exit.txt` is `exit=0`.
|
|
144
|
+
- `doctor.jsonl` records `"verdict":"ready"`.
|
|
145
|
+
- Cleanup ran: `$RUN` holds no `bootstrap.json` and no
|
|
146
|
+
`.bootstrap.json.<pid>.tmp`, and the state root named in `stderr.log`
|
|
147
|
+
(see Cleanup) no longer exists.
|
|
148
|
+
- `stderr.log` contains `real-disk dashboard fixture stopped (ok)` and does
|
|
149
|
+
not contain `TEST CHILD`. `(ok)` means the dashboard CLI exited with code 0
|
|
150
|
+
after the fixture requested the stop; a signal death or nonzero code is
|
|
151
|
+
`stopped (failed: ...)`. Playwright ignores the web server's exit status at
|
|
152
|
+
teardown, so a fixture shutdown failure shows up only in this line, not in
|
|
153
|
+
the exit status or the report.
|
|
154
|
+
|
|
155
|
+
Run the command from the checkout root; it compares `rootDir` with the
|
|
156
|
+
physical working directory (`pwd -P`), so a symlinked path to the checkout,
|
|
157
|
+
such as `/tmp` on macOS, does not fail the check.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
node -e '
|
|
161
|
+
const { readFileSync, realpathSync } = require("node:fs");
|
|
162
|
+
const { isAbsolute, join } = require("node:path");
|
|
163
|
+
const [reportPath, checkout, identityPath] = process.argv.slice(1);
|
|
164
|
+
const r = JSON.parse(readFileSync(reportPath, "utf8"));
|
|
165
|
+
const title = "actual CLI serves persisted history and refreshes same-generation raw content";
|
|
166
|
+
const tests = [];
|
|
167
|
+
const walk = (s) => {
|
|
168
|
+
for (const sp of s.specs ?? []) if (sp.title === title && sp.file === "dashboard.real-disk.spec.ts") tests.push(...sp.tests);
|
|
169
|
+
for (const c of s.suites ?? []) walk(c);
|
|
170
|
+
};
|
|
171
|
+
(r.suites ?? []).forEach(walk);
|
|
172
|
+
const res = tests[0]?.results ?? [];
|
|
173
|
+
const att = (res[0]?.attachments ?? []).filter((a) => a.name === "real-disk-observation");
|
|
174
|
+
let obs;
|
|
175
|
+
try { obs = JSON.parse(Buffer.from(att[0]?.body ?? "", "base64").toString("utf8")); } catch {}
|
|
176
|
+
let id;
|
|
177
|
+
try { id = JSON.parse(readFileSync(identityPath, "utf8")); } catch {}
|
|
178
|
+
console.log(`identity head=${id?.git?.head} dirty=${id?.git?.dirty}`);
|
|
179
|
+
const text = (v) => typeof v === "string" && v !== "";
|
|
180
|
+
const t = obs?.transition;
|
|
181
|
+
const ok = (r.errors ?? []).length === 0
|
|
182
|
+
&& r.config?.rootDir === join(checkout, "clinkx-workflows", "dashboard-ui", "e2e")
|
|
183
|
+
&& r.config.projects?.length === 1 && r.config.projects[0].name === ""
|
|
184
|
+
&& tests.length === 1 && tests[0].projectName === "" && tests[0].expectedStatus === "passed"
|
|
185
|
+
&& res.length === 1 && res[0].status === "passed" && res[0].retry === 0
|
|
186
|
+
&& att.length === 1 && att[0].contentType === "application/json"
|
|
187
|
+
&& obs?.observation_version === 1
|
|
188
|
+
&& /^http:\/\/127\.0\.0\.1:[0-9]{1,5}$/.test(obs.origin)
|
|
189
|
+
&& text(obs.state_dir) && isAbsolute(obs.state_dir)
|
|
190
|
+
&& text(obs.state_dir_realpath) && isAbsolute(obs.state_dir_realpath)
|
|
191
|
+
&& obs.run_id === "real_disk_run"
|
|
192
|
+
&& text(t?.generation_before) && t.generation_after === t.generation_before
|
|
193
|
+
&& text(t.revision_before) && text(t.revision_after) && t.revision_after !== t.revision_before
|
|
194
|
+
&& !JSON.stringify(obs).includes("#token=")
|
|
195
|
+
&& id?.identity_version === 1 && /^[0-9a-f]{40}([0-9a-f]{24})?$/.test(id.git?.head ?? "")
|
|
196
|
+
&& typeof id.git.dirty === "boolean" && id.checkout === realpathSync(checkout);
|
|
197
|
+
console.log(ok ? "report check passed" : "report check NOT proved");
|
|
198
|
+
process.exit(ok ? 0 : 1);
|
|
199
|
+
' "$RUN/report.json" "$(pwd -P)" "$RUN/identity.json" \
|
|
200
|
+
&& grep -q "real-disk dashboard fixture stopped (ok)" "$RUN/stderr.log" \
|
|
201
|
+
&& ! grep -q "TEST CHILD" "$RUN/stderr.log" \
|
|
202
|
+
&& grep -qx "exit=0" "$RUN/exit.txt" \
|
|
203
|
+
&& grep -q '"verdict":"ready"' "$RUN/doctor.jsonl" \
|
|
204
|
+
&& [ -z "$(find "$RUN" -maxdepth 1 \( -name bootstrap.json -o -name '.bootstrap.json.*.tmp' \) -print)" ] \
|
|
205
|
+
&& STATE_DIR="$(sed -n 's/^.*real-disk dashboard fixture state: //p' "$RUN/stderr.log")" \
|
|
206
|
+
&& [ -n "$STATE_DIR" ] && [ ! -e "$(dirname "$STATE_DIR")" ]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Classifying A Failed Attempt
|
|
210
|
+
|
|
211
|
+
When the check above does not pass, classify the attempt before repairing
|
|
212
|
+
anything:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
node clinkx-workflows/dashboard-ui/e2e/real-disk-doctor.mjs classify "$RUN"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
It prints exactly one outcome on stdout and one token-free detail line on
|
|
219
|
+
stderr. The first matching rule wins:
|
|
220
|
+
|
|
221
|
+
| Outcome | Rule | Exit |
|
|
222
|
+
| --- | --- | --- |
|
|
223
|
+
| `not_run` | `doctor.jsonl` is missing. | 2 |
|
|
224
|
+
| `blocked` | `doctor.jsonl` has no `"verdict":"ready"` line (blocked, or the preflight did not finish). | 2 |
|
|
225
|
+
| `not_run` | `identity.json` is missing. | 2 |
|
|
226
|
+
| `blocked` | `stderr.log` contains `TEST CHILD`. | 2 |
|
|
227
|
+
| `failed_fixture` | `stderr.log` contains `fixture stopped (failed: ...)`. | 1 |
|
|
228
|
+
| `not_run` | `exit.txt` or `report.json` is missing, or `report.json` does not parse. | 2 |
|
|
229
|
+
| `harness_drift` | The required test failed and an error matches `strict mode violation`, `element(s) not found`, or a `locator.<action>: ... timeout ... exceeded`. | 1 |
|
|
230
|
+
| `failed_assertion` | The required test failed with any other error, such as an `expect` mismatch on a resolved element. | 1 |
|
|
231
|
+
| `not_run` | The required test was `skipped` or `interrupted`, or `exit.txt` is `exit=130`. | 2 |
|
|
232
|
+
| `failed_fixture` | The report has top-level errors. | 1 |
|
|
233
|
+
| `not_run` | The report holds no result for the required test. | 2 |
|
|
234
|
+
| `failed_fixture` | Nonzero exit, or no `fixture stopped (ok)` line, without a test failure. | 1 |
|
|
235
|
+
| `passed` | Otherwise: one passing result, `exit=0`, `fixture stopped (ok)`. | 0 |
|
|
236
|
+
|
|
237
|
+
These heuristics match Playwright 1.61 message text and only triage.
|
|
238
|
+
`harness_drift` corresponds to the *Documentation drift* class in
|
|
239
|
+
[README.md](README.md#maintaining-the-feature-map): the procedure or its
|
|
240
|
+
selectors did not follow an intended product change. A missing element is
|
|
241
|
+
`harness_drift` even when the product stopped rendering it, which is a
|
|
242
|
+
*Product regression*, so confirm against the trace before choosing the repair
|
|
243
|
+
class. `passed` does not replace the result check above.
|
|
244
|
+
|
|
245
|
+
## Assertions
|
|
246
|
+
|
|
247
|
+
The test keeps the intent of every assertion it had before the observation
|
|
248
|
+
was added. Selectors and labels follow the redesigned run detail and history
|
|
249
|
+
lane (`e8e9116`). One check could no longer be observed and was dropped: the
|
|
250
|
+
history lane used to show the page number (`[ all 2 · page 2 ]`, then
|
|
251
|
+
`[ all 100 · page 1 ]`). The redesign renders only the count of loaded rows,
|
|
252
|
+
`[ history N ]`, and no component renders a page number, so the spec now
|
|
253
|
+
asserts `[ history 2 ]` and `[ history 100 ]`. Paging is still covered by
|
|
254
|
+
which rows each page shows: the older page has 2 rows including
|
|
255
|
+
`real_disk_history_100`, and the newer page has 100 rows including the
|
|
256
|
+
selected run.
|
|
257
|
+
|
|
258
|
+
- The bootstrap `#token=` fragment is removed from the URL and the
|
|
259
|
+
`Run monitor` heading renders.
|
|
260
|
+
- The selected run `real-disk-qa` shows its persisted prompt and final output
|
|
261
|
+
in the call inspector, in both the formatted and `Raw` tabs.
|
|
262
|
+
- After an atomic `state.json` replacement, the title becomes
|
|
263
|
+
`real-disk-qa-revised`, the generation is unchanged, the revision changes,
|
|
264
|
+
and the inspector shows only the replaced output.
|
|
265
|
+
- `[ older ]` shows `[ history 2 ]` with `real_disk_history_100`; `[ newer ]`
|
|
266
|
+
returns to `[ history 100 ]` with the selected run.
|
|
267
|
+
- Authenticated `GET /api/env` reports the fixture's state directory.
|
|
268
|
+
- No console or page errors occur.
|
|
269
|
+
|
|
270
|
+
Only after all of these pass does the test attach `real-disk-observation`:
|
|
271
|
+
origin, state directory (and its realpath), run id, and the
|
|
272
|
+
generation/revision values before and after. It never contains the bearer
|
|
273
|
+
token or the bootstrap URL.
|
|
274
|
+
|
|
275
|
+
## Evidence
|
|
276
|
+
|
|
277
|
+
Retain per attempt: `doctor.jsonl`, `identity.json`, `exit.txt`,
|
|
278
|
+
`stdout.log`, `stderr.log`, `report.json`, `last-run.json`, and `artifacts/`
|
|
279
|
+
(traces on failure).
|
|
280
|
+
|
|
281
|
+
`EVIDENCE_ROOT` is owned by whoever runs the procedure. Retain an attempt
|
|
282
|
+
directory until the exact state it proves has been accepted and integrated,
|
|
283
|
+
or rejected; the procedure never prunes, so removing it is that owner's
|
|
284
|
+
decision. Attempt directories can hold Playwright traces with page content
|
|
285
|
+
and request data: keep them private (`700`) and share only checked,
|
|
286
|
+
token-free summaries.
|
|
287
|
+
|
|
288
|
+
## Clean Checkout
|
|
289
|
+
|
|
290
|
+
Optional but recommended for acceptance evidence: run the procedure from a
|
|
291
|
+
detached worktree of the exact revision under review, so uncommitted files
|
|
292
|
+
and stale build output in your working checkout cannot leak into the result.
|
|
293
|
+
Run from your normal checkout root:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
: "${EVIDENCE_ROOT:?set EVIDENCE_ROOT}"
|
|
297
|
+
REV="$(git rev-parse HEAD)"
|
|
298
|
+
git worktree add --detach "$EVIDENCE_ROOT/checkout" "$REV"
|
|
299
|
+
cd "$EVIDENCE_ROOT/checkout"
|
|
300
|
+
npm ci && npm --prefix clinkx-workflows ci && npm run build:all
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Then follow [Commands](#commands) from the attempt-directory step and
|
|
304
|
+
[Checking The Result](#checking-the-result) with `$PWD` still at
|
|
305
|
+
`$EVIDENCE_ROOT/checkout`. The checker must print `dirty=false`. Afterwards
|
|
306
|
+
return to your normal checkout and remove the worktree; the attempt
|
|
307
|
+
directories stay in `EVIDENCE_ROOT`:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
cd - && git worktree remove "$EVIDENCE_ROOT/checkout"
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Ignored `node_modules` and build output do not block the removal. If it
|
|
314
|
+
refuses, something left untracked or modified files in the worktree; inspect
|
|
315
|
+
them before forcing.
|
|
316
|
+
|
|
317
|
+
## Checking Overlap
|
|
318
|
+
|
|
319
|
+
To show two checks ran concurrently, save the invocation block above as
|
|
320
|
+
`$EVIDENCE_ROOT/real-disk-run.sh` and run it in two attempt directories at
|
|
321
|
+
once. Run the preflight into each directory first, as above:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
: "${EVIDENCE_ROOT:?set EVIDENCE_ROOT}"
|
|
325
|
+
RUN_A="$(mktemp -d "$EVIDENCE_ROOT/real-disk.XXXXXX")"
|
|
326
|
+
RUN_B="$(mktemp -d "$EVIDENCE_ROOT/real-disk.XXXXXX")"
|
|
327
|
+
chmod 700 "$RUN_A" "$RUN_B"
|
|
328
|
+
if ! EVIDENCE_ROOT="$EVIDENCE_ROOT" node clinkx-workflows/dashboard-ui/e2e/real-disk-doctor.mjs preflight \
|
|
329
|
+
> "$RUN_A/doctor.jsonl"; then
|
|
330
|
+
echo "preflight blocked; see $RUN_A/doctor.jsonl"
|
|
331
|
+
elif ! EVIDENCE_ROOT="$EVIDENCE_ROOT" node clinkx-workflows/dashboard-ui/e2e/real-disk-doctor.mjs preflight \
|
|
332
|
+
> "$RUN_B/doctor.jsonl"; then
|
|
333
|
+
echo "preflight blocked; see $RUN_B/doctor.jsonl"
|
|
334
|
+
else
|
|
335
|
+
RUN="$RUN_A" bash "$EVIDENCE_ROOT/real-disk-run.sh" &
|
|
336
|
+
RUN="$RUN_B" bash "$EVIDENCE_ROOT/real-disk-run.sh" &
|
|
337
|
+
wait
|
|
338
|
+
fi
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Each `RUN` must hold its own intact results: run the result check above once
|
|
342
|
+
with `RUN="$RUN_A"` and once with `RUN="$RUN_B"`. Then, from each
|
|
343
|
+
`report.json`, take the required result's `startTime` and `duration`, compute
|
|
344
|
+
`end = start + duration`, and require `max(startA, startB) < min(endA, endB)`.
|
|
345
|
+
Launching both processes together is not enough. Also require distinct
|
|
346
|
+
`origin` and `state_dir` values in the two observations. The command below
|
|
347
|
+
checks both:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
node -e '
|
|
351
|
+
const { readFileSync } = require("node:fs");
|
|
352
|
+
const title = "actual CLI serves persisted history and refreshes same-generation raw content";
|
|
353
|
+
const read = (path) => {
|
|
354
|
+
const found = [];
|
|
355
|
+
const walk = (s) => {
|
|
356
|
+
for (const sp of s.specs ?? []) if (sp.title === title) found.push(...sp.tests.flatMap((t) => t.results));
|
|
357
|
+
for (const c of s.suites ?? []) walk(c);
|
|
358
|
+
};
|
|
359
|
+
(JSON.parse(readFileSync(path, "utf8")).suites ?? []).forEach(walk);
|
|
360
|
+
if (found.length !== 1) throw new Error(`${path}: expected one result, got ${found.length}`);
|
|
361
|
+
const att = (found[0].attachments ?? []).filter((a) => a.name === "real-disk-observation");
|
|
362
|
+
if (att.length !== 1) throw new Error(`${path}: expected one observation, got ${att.length}`);
|
|
363
|
+
const obs = JSON.parse(Buffer.from(att[0].body, "base64").toString("utf8"));
|
|
364
|
+
const start = Date.parse(found[0].startTime);
|
|
365
|
+
return { start, end: start + found[0].duration, origin: obs.origin, stateDir: obs.state_dir, stateDirReal: obs.state_dir_realpath };
|
|
366
|
+
};
|
|
367
|
+
const [a, b] = process.argv.slice(1).map(read);
|
|
368
|
+
const overlap = Math.min(a.end, b.end) - Math.max(a.start, b.start);
|
|
369
|
+
const distinct = a.origin !== b.origin && a.stateDir !== b.stateDir && a.stateDirReal !== b.stateDirReal;
|
|
370
|
+
console.log(`overlap_ms=${overlap} distinct_origin_and_state_dir=${distinct}`);
|
|
371
|
+
process.exit(overlap > 0 && distinct ? 0 : 1);
|
|
372
|
+
' "$RUN_A/report.json" "$RUN_B/report.json"
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Cleanup
|
|
376
|
+
|
|
377
|
+
Cleanup is automatic. The Playwright config sets
|
|
378
|
+
`webServer.gracefulShutdown: { signal: "SIGTERM", timeout: 10_000 }`, so at
|
|
379
|
+
teardown the fixture stops the dashboard CLI, waits for it to exit, then
|
|
380
|
+
deletes its temporary state and `$RUN/bootstrap.json`. A clean run logs
|
|
381
|
+
`real-disk dashboard fixture stopped (ok)` in `stderr.log` and leaves no
|
|
382
|
+
`bootstrap.json` in `$RUN`. Any other `stopped (failed: ...)` line names the
|
|
383
|
+
first failure. The fixture also cleans up when it fails on its own (spawn
|
|
384
|
+
error, bootstrap publication failure, premature dashboard exit). Its lifecycle
|
|
385
|
+
is covered by `npx vitest run tests/dashboard-real-disk-fixture.test.ts`, and
|
|
386
|
+
the doctor by `npx vitest run tests/dashboard-real-disk-doctor.test.ts`. Chromium
|
|
387
|
+
preflight creates an owned root for its profile and artifacts and removes only
|
|
388
|
+
that root, including after a failed launch. Other Playwright runs' temporary
|
|
389
|
+
directories are preserved and the caller's `TMPDIR` is unchanged. The app-health
|
|
390
|
+
preflight removes its own temporary bootstrap directory and fixture state,
|
|
391
|
+
and always sends `SIGKILL` to the fixture's process group afterwards. If the
|
|
392
|
+
preflight itself receives `SIGINT` or `SIGTERM` during app health, it sends
|
|
393
|
+
`SIGTERM` to that group, escalates to `SIGKILL` after 5 s, removes the same
|
|
394
|
+
files, and exits 130 or 143 without a verdict line, so `classify` reports
|
|
395
|
+
`blocked`. A repeated `SIGINT` or `SIGTERM` during that cleanup is ignored,
|
|
396
|
+
so the cleanup still finishes.
|
|
397
|
+
|
|
398
|
+
If the runner itself is killed (for example `SIGKILL`, or the 10 s grace
|
|
399
|
+
expires), cleanup may not run. Then delete `$RUN/bootstrap.json`, which holds
|
|
400
|
+
the bearer token for the stopped dashboard, and the state root printed in
|
|
401
|
+
`stderr.log` as `[WebServer] real-disk dashboard fixture state: <dir>/state`
|
|
402
|
+
(drop the `[WebServer] ` prefix and remove `<dir>`, a
|
|
403
|
+
`clinkx-dashboard-real-disk-*` directory under the OS temp directory).
|
|
404
|
+
|
|
405
|
+
## Limitations
|
|
406
|
+
|
|
407
|
+
- A bare `npm run test:dashboard:e2e:real-disk` still writes artifacts to the
|
|
408
|
+
fixed `/tmp/clinkx-dashboard-real-disk-playwright-results` directory and
|
|
409
|
+
emits no JSON report. Concurrent bare runs share and wipe that directory.
|
|
410
|
+
Callers must pass the options above and check that `report.json` exists.
|
|
411
|
+
- Playwright sends the shutdown `SIGTERM` to the whole process group, so the
|
|
412
|
+
dashboard CLI receives it directly and again from the fixture a fraction of
|
|
413
|
+
a millisecond later. That is safe because the CLI's `SIGTERM` handling is
|
|
414
|
+
idempotent: a repeat during or just after shutdown is ignored, and the CLI
|
|
415
|
+
exits explicitly once its listeners close (exit 1 if closing fails). Here a
|
|
416
|
+
hung close surfaces as the fixture's failed stop (5 s grace, then
|
|
417
|
+
`SIGKILL`); the CLI's own 8 s forced exit applies only when it runs
|
|
418
|
+
standalone, for example under Docker. The fixture counts a stop as clean
|
|
419
|
+
only when the CLI exits with code 0 after the fixture requested it. It also
|
|
420
|
+
treats the child's exit as requested only after its own `SIGTERM` handler
|
|
421
|
+
has run and it has signaled the child. If the CLI exited before the
|
|
422
|
+
fixture's own `SIGTERM` handler had signaled it, the fixture would log
|
|
423
|
+
`dashboard CLI exited unexpectedly` and `stopped (failed: ...)`; that
|
|
424
|
+
ordering can only cause a false failure, not a false pass. After the CLI's
|
|
425
|
+
explicit `process.exit`, a microsecond window remains in which a late
|
|
426
|
+
`SIGTERM` kills it by signal; the fixture reports that as a failed stop,
|
|
427
|
+
never `(ok)`. Because Playwright ignores the fixture's exit status, the
|
|
428
|
+
`stopped (ok)` check above is what turns any of these cases into a failed
|
|
429
|
+
check. `npx vitest run tests/dashboard-real-disk-fixture.test.ts` includes
|
|
430
|
+
a process-group `SIGTERM` test against the real built CLI.
|
|
431
|
+
- `identity.json` is written before the run and records dependencies only by
|
|
432
|
+
the Node and `@playwright/test` versions, not a `node_modules` digest. It
|
|
433
|
+
does not detect a rebuild or edit made while the attempt runs; build once
|
|
434
|
+
and do not touch the checkout until the attempt finishes.
|
|
435
|
+
- Retained evidence is identified by hashes only. This is a trusted local
|
|
436
|
+
execution model, not an attestation.
|