clinkx 1.1.1 → 1.2.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 (199) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +97 -13
  3. package/clinkx-workflows/dist/allowed-roots.js +2 -0
  4. package/clinkx-workflows/dist/allowed-roots.js.map +1 -1
  5. package/clinkx-workflows/dist/architecture-comparison/admission.d.ts +5 -0
  6. package/clinkx-workflows/dist/architecture-comparison/admission.js +50 -0
  7. package/clinkx-workflows/dist/architecture-comparison/admission.js.map +1 -0
  8. package/clinkx-workflows/dist/architecture-comparison/contract.d.ts +995 -0
  9. package/clinkx-workflows/dist/architecture-comparison/contract.js +149 -0
  10. package/clinkx-workflows/dist/architecture-comparison/contract.js.map +1 -0
  11. package/clinkx-workflows/dist/architecture-comparison/runtime.d.ts +29 -0
  12. package/clinkx-workflows/dist/architecture-comparison/runtime.js +387 -0
  13. package/clinkx-workflows/dist/architecture-comparison/runtime.js.map +1 -0
  14. package/clinkx-workflows/dist/child-env.d.ts +3 -1
  15. package/clinkx-workflows/dist/child-env.js +6 -2
  16. package/clinkx-workflows/dist/child-env.js.map +1 -1
  17. package/clinkx-workflows/dist/config.d.ts +28 -5
  18. package/clinkx-workflows/dist/config.js +137 -20
  19. package/clinkx-workflows/dist/config.js.map +1 -1
  20. package/clinkx-workflows/dist/contained-file.js +10 -1
  21. package/clinkx-workflows/dist/contained-file.js.map +1 -1
  22. package/clinkx-workflows/dist/council-execution-policy.d.ts +4 -2
  23. package/clinkx-workflows/dist/council-execution-policy.js +5 -1
  24. package/clinkx-workflows/dist/council-execution-policy.js.map +1 -1
  25. package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +72 -72
  26. package/clinkx-workflows/dist/dashboard/contracts.d.ts +1564 -122
  27. package/clinkx-workflows/dist/dashboard/contracts.js +4 -0
  28. package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -1
  29. package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +12 -12
  30. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.d.ts +8 -8
  31. package/clinkx-workflows/dist/dashboard/scanner.d.ts +3 -0
  32. package/clinkx-workflows/dist/dashboard/scanner.js +27 -0
  33. package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -1
  34. package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +48 -48
  35. package/clinkx-workflows/dist/dashboard/session-log-identity.js +2 -1
  36. package/clinkx-workflows/dist/dashboard/session-log-identity.js.map +1 -1
  37. package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +2 -0
  38. package/clinkx-workflows/dist/dashboard/session-log-redaction.js +4 -0
  39. package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -1
  40. package/clinkx-workflows/dist/dashboard/verified-repair-contract.d.ts +191 -0
  41. package/clinkx-workflows/dist/dashboard/verified-repair-contract.js +28 -0
  42. package/clinkx-workflows/dist/dashboard/verified-repair-contract.js.map +1 -0
  43. package/clinkx-workflows/dist/dashboard-ui/assets/index-BdpmbPps.js +115 -0
  44. package/clinkx-workflows/dist/dashboard-ui/assets/style-Im5i1iAs.css +1 -0
  45. package/clinkx-workflows/dist/dashboard-ui/index.html +2 -2
  46. package/clinkx-workflows/dist/engine.d.ts +6 -0
  47. package/clinkx-workflows/dist/engine.js +78 -7
  48. package/clinkx-workflows/dist/engine.js.map +1 -1
  49. package/clinkx-workflows/dist/index.d.ts +3 -0
  50. package/clinkx-workflows/dist/index.js +3 -2
  51. package/clinkx-workflows/dist/index.js.map +1 -1
  52. package/clinkx-workflows/dist/reducers/index.d.ts +9 -1
  53. package/clinkx-workflows/dist/reducers/index.js +22 -0
  54. package/clinkx-workflows/dist/reducers/index.js.map +1 -1
  55. package/clinkx-workflows/dist/root-path-validation.d.ts +12 -0
  56. package/clinkx-workflows/dist/root-path-validation.js +63 -0
  57. package/clinkx-workflows/dist/root-path-validation.js.map +1 -0
  58. package/clinkx-workflows/dist/rosters.d.ts +1 -0
  59. package/clinkx-workflows/dist/rosters.js +16 -6
  60. package/clinkx-workflows/dist/rosters.js.map +1 -1
  61. package/clinkx-workflows/dist/server.js +5 -2
  62. package/clinkx-workflows/dist/server.js.map +1 -1
  63. package/clinkx-workflows/dist/state-schema.d.ts +13 -0
  64. package/clinkx-workflows/dist/state.d.ts +7 -1
  65. package/clinkx-workflows/dist/state.js +90 -19
  66. package/clinkx-workflows/dist/state.js.map +1 -1
  67. package/clinkx-workflows/dist/validators/architecture-comparison.d.ts +2 -0
  68. package/clinkx-workflows/dist/validators/architecture-comparison.js +12 -0
  69. package/clinkx-workflows/dist/validators/architecture-comparison.js.map +1 -0
  70. package/clinkx-workflows/dist/validators/index.js +4 -0
  71. package/clinkx-workflows/dist/validators/index.js.map +1 -1
  72. package/clinkx-workflows/dist/validators/verified-repair.d.ts +2 -0
  73. package/clinkx-workflows/dist/validators/verified-repair.js +12 -0
  74. package/clinkx-workflows/dist/validators/verified-repair.js.map +1 -0
  75. package/clinkx-workflows/dist/verification-proof.d.ts +254 -0
  76. package/clinkx-workflows/dist/verification-proof.js +195 -0
  77. package/clinkx-workflows/dist/verification-proof.js.map +1 -0
  78. package/clinkx-workflows/dist/verified-repair/admission.d.ts +72 -0
  79. package/clinkx-workflows/dist/verified-repair/admission.js +63 -0
  80. package/clinkx-workflows/dist/verified-repair/admission.js.map +1 -0
  81. package/clinkx-workflows/dist/verified-repair/contract.d.ts +1910 -0
  82. package/clinkx-workflows/dist/verified-repair/contract.js +127 -0
  83. package/clinkx-workflows/dist/verified-repair/contract.js.map +1 -0
  84. package/clinkx-workflows/dist/verified-repair/host.d.ts +32 -0
  85. package/clinkx-workflows/dist/verified-repair/host.js +2 -0
  86. package/clinkx-workflows/dist/verified-repair/host.js.map +1 -0
  87. package/clinkx-workflows/dist/verified-repair/prompts.d.ts +12 -0
  88. package/clinkx-workflows/dist/verified-repair/prompts.js +58 -0
  89. package/clinkx-workflows/dist/verified-repair/prompts.js.map +1 -0
  90. package/clinkx-workflows/dist/verified-repair/result.d.ts +16 -0
  91. package/clinkx-workflows/dist/verified-repair/result.js +102 -0
  92. package/clinkx-workflows/dist/verified-repair/result.js.map +1 -0
  93. package/clinkx-workflows/dist/verified-repair/runtime.d.ts +28 -0
  94. package/clinkx-workflows/dist/verified-repair/runtime.js +186 -0
  95. package/clinkx-workflows/dist/verified-repair/runtime.js.map +1 -0
  96. package/clinkx-workflows/dist/workflow-completion.d.ts +83 -0
  97. package/clinkx-workflows/dist/workflow-completion.js +250 -0
  98. package/clinkx-workflows/dist/workflow-completion.js.map +1 -0
  99. package/clinkx-workflows/dist/workflow-tools.d.ts +7 -3
  100. package/clinkx-workflows/dist/workflow-tools.js +487 -46
  101. package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
  102. package/clinkx-workflows/dist/workflow-verification-contract.d.ts +539 -0
  103. package/clinkx-workflows/dist/workflow-verification-contract.js +52 -0
  104. package/clinkx-workflows/dist/workflow-verification-contract.js.map +1 -0
  105. package/clinkx-workflows/dist/workflow-verification.d.ts +15 -0
  106. package/clinkx-workflows/dist/workflow-verification.js +218 -0
  107. package/clinkx-workflows/dist/workflow-verification.js.map +1 -0
  108. package/clinkx-workflows/templates/architecture-comparison/candidate.txt +29 -0
  109. package/clinkx-workflows/templates/architecture-comparison/judge.txt +31 -0
  110. package/clinkx-workflows/templates/verified-repair/investigate.txt +20 -0
  111. package/clinkx-workflows/templates/verified-repair/repair.txt +11 -0
  112. package/clinkx-workflows/templates/verified-repair/review.txt +18 -0
  113. package/clinkx-workflows/workflows/architecture-comparison.yaml +133 -0
  114. package/clinkx-workflows/workflows/verified-repair.yaml +104 -0
  115. package/dist/allowed-roots.d.ts +1 -1
  116. package/dist/allowed-roots.js +128 -15
  117. package/dist/allowed-roots.js.map +1 -1
  118. package/dist/codex/app-server-client.d.ts +1 -0
  119. package/dist/codex/app-server-client.js +3 -2
  120. package/dist/codex/app-server-client.js.map +1 -1
  121. package/dist/context-builder/config.d.ts +1 -0
  122. package/dist/context-builder/config.js +21 -9
  123. package/dist/context-builder/config.js.map +1 -1
  124. package/dist/context-builder/diagnostics.d.ts +1 -0
  125. package/dist/context-builder/diagnostics.js +1 -1
  126. package/dist/context-builder/diagnostics.js.map +1 -1
  127. package/dist/context-builder/discovery.d.ts +18 -1
  128. package/dist/context-builder/discovery.js +69 -1
  129. package/dist/context-builder/discovery.js.map +1 -1
  130. package/dist/context-builder/handler.js +8 -1
  131. package/dist/context-builder/handler.js.map +1 -1
  132. package/dist/context-builder/lifecycle-contract.d.ts +24 -24
  133. package/dist/context-builder/mutation-lock.d.ts +4 -0
  134. package/dist/context-builder/mutation-lock.js +7 -1
  135. package/dist/context-builder/mutation-lock.js.map +1 -1
  136. package/dist/context-builder/refine-handler.js +16 -1
  137. package/dist/context-builder/refine-handler.js.map +1 -1
  138. package/dist/context-builder/secrets.d.ts +11 -0
  139. package/dist/context-builder/secrets.js +75 -0
  140. package/dist/context-builder/secrets.js.map +1 -1
  141. package/dist/context-builder/storage.d.ts +10 -2
  142. package/dist/context-builder/storage.js +213 -16
  143. package/dist/context-builder/storage.js.map +1 -1
  144. package/dist/context-builder/versions.d.ts +5 -2
  145. package/dist/context-builder/versions.js +5 -2
  146. package/dist/context-builder/versions.js.map +1 -1
  147. package/dist/dashboard-cli.d.ts +6 -0
  148. package/dist/dashboard-cli.js +18 -32
  149. package/dist/dashboard-cli.js.map +1 -1
  150. package/dist/dashboard-gateway/main.js +8 -3
  151. package/dist/dashboard-gateway/main.js.map +1 -1
  152. package/dist/index.d.ts +2 -0
  153. package/dist/index.js +8 -2
  154. package/dist/index.js.map +1 -1
  155. package/dist/project-verification/contract.d.ts +537 -0
  156. package/dist/project-verification/contract.js +40 -0
  157. package/dist/project-verification/contract.js.map +1 -0
  158. package/dist/project-verification/files.d.ts +23 -0
  159. package/dist/project-verification/files.js +80 -0
  160. package/dist/project-verification/files.js.map +1 -0
  161. package/dist/project-verification/process.d.ts +7 -0
  162. package/dist/project-verification/process.js +115 -0
  163. package/dist/project-verification/process.js.map +1 -0
  164. package/dist/project-verification/runner.d.ts +35 -0
  165. package/dist/project-verification/runner.js +185 -0
  166. package/dist/project-verification/runner.js.map +1 -0
  167. package/dist/project-verification/service.d.ts +37 -0
  168. package/dist/project-verification/service.js +151 -0
  169. package/dist/project-verification/service.js.map +1 -0
  170. package/dist/provider-doctor.d.ts +22 -0
  171. package/dist/provider-doctor.js +22 -0
  172. package/dist/provider-doctor.js.map +1 -0
  173. package/dist/registry.d.ts +1 -1
  174. package/dist/registry.js +5 -1
  175. package/dist/registry.js.map +1 -1
  176. package/dist/server.js +41 -6
  177. package/dist/server.js.map +1 -1
  178. package/dist/verification-tools.d.ts +90 -0
  179. package/dist/verification-tools.js +120 -0
  180. package/dist/verification-tools.js.map +1 -0
  181. package/dist/verified-repair/files.d.ts +22 -0
  182. package/dist/verified-repair/files.js +114 -0
  183. package/dist/verified-repair/files.js.map +1 -0
  184. package/dist/verified-repair/host.d.ts +14 -0
  185. package/dist/verified-repair/host.js +612 -0
  186. package/dist/verified-repair/host.js.map +1 -0
  187. package/docs/configuration.md +224 -12
  188. package/docs/engineering-recipes.md +415 -0
  189. package/docs/security.md +3 -0
  190. package/docs/verification/README.md +59 -0
  191. package/docs/verification/dashboard-real-disk.md +436 -0
  192. package/docs/verification/project-checks.example.json +14 -0
  193. package/docs/verification/project-checks.md +444 -0
  194. package/docs/verification/workflow-handoff.md +113 -0
  195. package/package.json +10 -2
  196. package/scripts/provider-doctor-core.mjs +380 -0
  197. package/scripts/provider-doctor-probe.mjs +34 -0
  198. package/clinkx-workflows/dist/dashboard-ui/assets/index-JPHQVtn9.js +0 -106
  199. 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.
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 1,
3
+ "inputs": ["tests/unit.test.mjs"],
4
+ "checks": [
5
+ {
6
+ "id": "unit-tests",
7
+ "command": "node",
8
+ "args": ["--test", "tests/unit.test.mjs"],
9
+ "cwd": ".",
10
+ "timeout_seconds": 300,
11
+ "env_allowlist": []
12
+ }
13
+ ]
14
+ }