automated-qa 0.11.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 (187) hide show
  1. package/LICENSE +22 -0
  2. package/NOTICE +9 -0
  3. package/README.md +100 -0
  4. package/SKILL.md +65 -0
  5. package/agent/prebuilt/darwin-universal/qa-agent +0 -0
  6. package/agent/prebuilt/linux-arm64/qa-agent +0 -0
  7. package/agent/prebuilt/linux-x64/qa-agent +0 -0
  8. package/agent/release-manifest.json +31 -0
  9. package/bin/autonomous-cache.mjs +2 -0
  10. package/bin/bench-generate.mjs +232 -0
  11. package/bin/bench-graph-query.mjs +2 -0
  12. package/bin/bench-score.mjs +10 -0
  13. package/bin/classify.mjs +15 -0
  14. package/bin/coherence-playwright.mjs +1 -0
  15. package/bin/coherence-runner.mjs +6 -0
  16. package/bin/crud-runner.mjs +12 -0
  17. package/bin/experiment-executor.mjs +10 -0
  18. package/bin/experiment-runner.mjs +3 -0
  19. package/bin/inventory.mjs +17 -0
  20. package/bin/probe.js +879 -0
  21. package/bin/qa.mjs +16 -0
  22. package/bin/rules.mjs +5 -0
  23. package/bin/run-benchmarks.mjs +2 -0
  24. package/bin/runtime-packs.mjs +3 -0
  25. package/bin/semantic-explorer.mjs +3 -0
  26. package/bin/shadow-collector.mjs +3 -0
  27. package/bin/shadow-replay.mjs +6 -0
  28. package/bin/soak-marathon.mjs +15 -0
  29. package/bin/sweep.mjs +43 -0
  30. package/bin/validate-priors.mjs +3 -0
  31. package/bin/verify-artifacts.mjs +3 -0
  32. package/bin/verify-manifest.mjs +2 -0
  33. package/install-skill.sh +45 -0
  34. package/lib/qa/agent-availability.mjs +1 -0
  35. package/lib/qa/agent-result.mjs +1 -0
  36. package/lib/qa/analysis.mjs +5 -0
  37. package/lib/qa/atlas-coverage.mjs +11 -0
  38. package/lib/qa/atlas-v1.mjs +1 -0
  39. package/lib/qa/atlas.mjs +2 -0
  40. package/lib/qa/auth/coordinator.mjs +1 -0
  41. package/lib/qa/auth/launcher.mjs +3 -0
  42. package/lib/qa/auth/lease.mjs +1 -0
  43. package/lib/qa/auth/oracle.mjs +1 -0
  44. package/lib/qa/auth/personas.mjs +3 -0
  45. package/lib/qa/autonomous-cache.mjs +3 -0
  46. package/lib/qa/block-ledger.mjs +1 -0
  47. package/lib/qa/capture-report.mjs +63 -0
  48. package/lib/qa/ci.mjs +11 -0
  49. package/lib/qa/cli.mjs +538 -0
  50. package/lib/qa/coherence/analyze.mjs +1 -0
  51. package/lib/qa/coherence/cache-key.mjs +6 -0
  52. package/lib/qa/coherence/causes.mjs +1 -0
  53. package/lib/qa/coherence/concurrency.mjs +1 -0
  54. package/lib/qa/coherence/contracts.mjs +1 -0
  55. package/lib/qa/coherence/entity-aliases.mjs +1 -0
  56. package/lib/qa/coherence/enums.mjs +1 -0
  57. package/lib/qa/coherence/family-report.mjs +1 -0
  58. package/lib/qa/coherence/flow-pool.mjs +1 -0
  59. package/lib/qa/coherence/graph.mjs +1 -0
  60. package/lib/qa/coherence/identity.mjs +1 -0
  61. package/lib/qa/coherence/ir.mjs +4 -0
  62. package/lib/qa/coherence/navigation.mjs +1 -0
  63. package/lib/qa/coherence/observe.mjs +2 -0
  64. package/lib/qa/coherence/offline.mjs +1 -0
  65. package/lib/qa/coherence/plan-join.mjs +1 -0
  66. package/lib/qa/coherence/provenance.mjs +2 -0
  67. package/lib/qa/coherence/report.mjs +1 -0
  68. package/lib/qa/coherence/representations.mjs +1 -0
  69. package/lib/qa/coherence/risk.mjs +2 -0
  70. package/lib/qa/coherence/structural-js.mjs +11 -0
  71. package/lib/qa/coherence/symbols.mjs +6 -0
  72. package/lib/qa/coherence/tanstack.mjs +4 -0
  73. package/lib/qa/coherence/views.mjs +1 -0
  74. package/lib/qa/coherence-planner.mjs +1 -0
  75. package/lib/qa/command-catalog.mjs +2 -0
  76. package/lib/qa/commands/dev.mjs +1 -0
  77. package/lib/qa/commands/fix.mjs +1 -0
  78. package/lib/qa/commands/impl/analysis.mjs +1 -0
  79. package/lib/qa/commands/impl/atlas.mjs +1 -0
  80. package/lib/qa/commands/impl/coherence.mjs +1 -0
  81. package/lib/qa/commands/impl/crud.mjs +2 -0
  82. package/lib/qa/commands/impl/doctor.mjs +1 -0
  83. package/lib/qa/commands/impl/experiments.mjs +1 -0
  84. package/lib/qa/commands/impl/fp.mjs +1 -0
  85. package/lib/qa/commands/impl/graph.mjs +1 -0
  86. package/lib/qa/commands/impl/invariants.mjs +1 -0
  87. package/lib/qa/commands/impl/lanes.mjs +7 -0
  88. package/lib/qa/commands/impl/ledger.mjs +11 -0
  89. package/lib/qa/commands/impl/loop.mjs +1 -0
  90. package/lib/qa/commands/impl/marathon.mjs +1 -0
  91. package/lib/qa/commands/impl/next.mjs +38 -0
  92. package/lib/qa/commands/impl/report.mjs +4 -0
  93. package/lib/qa/commands/impl/research.mjs +4 -0
  94. package/lib/qa/commands/impl/selftest.mjs +1 -0
  95. package/lib/qa/commands/impl/serve.mjs +1 -0
  96. package/lib/qa/commands/impl/shadow.mjs +1 -0
  97. package/lib/qa/commands/impl/stability.mjs +1 -0
  98. package/lib/qa/commands/impl/triage.mjs +1 -0
  99. package/lib/qa/commands/impl/validate.mjs +4 -0
  100. package/lib/qa/commands/impl/version.mjs +1 -0
  101. package/lib/qa/commands/impl/watch.mjs +1 -0
  102. package/lib/qa/commands/init.mjs +6 -0
  103. package/lib/qa/commands/measure.mjs +1 -0
  104. package/lib/qa/commands/run.mjs +1 -0
  105. package/lib/qa/commands/show.mjs +1 -0
  106. package/lib/qa/commands/verify.mjs +6 -0
  107. package/lib/qa/deepening.mjs +1 -0
  108. package/lib/qa/denominators.mjs +1 -0
  109. package/lib/qa/depth.mjs +4 -0
  110. package/lib/qa/doctor.mjs +2 -0
  111. package/lib/qa/experiments-default.mjs +1 -0
  112. package/lib/qa/fix-lanes.mjs +3 -0
  113. package/lib/qa/forced-states.mjs +1 -0
  114. package/lib/qa/fp.mjs +2 -0
  115. package/lib/qa/fs.mjs +6 -0
  116. package/lib/qa/graph-schema.mjs +2 -0
  117. package/lib/qa/graph.mjs +3 -0
  118. package/lib/qa/harness-plan.mjs +3 -0
  119. package/lib/qa/init.mjs +5 -0
  120. package/lib/qa/integrity.mjs +1 -0
  121. package/lib/qa/invariants.mjs +84 -0
  122. package/lib/qa/jcs.mjs +1 -0
  123. package/lib/qa/lanes.mjs +1 -0
  124. package/lib/qa/loop.mjs +7 -0
  125. package/lib/qa/marathon-coherence.mjs +1 -0
  126. package/lib/qa/marathon-phases.mjs +1 -0
  127. package/lib/qa/marathon-wait.mjs +3 -0
  128. package/lib/qa/marathon.mjs +5 -0
  129. package/lib/qa/mode-coverage.mjs +1 -0
  130. package/lib/qa/mutation-policy.mjs +1 -0
  131. package/lib/qa/pattern-ledger.mjs +3 -0
  132. package/lib/qa/pending.mjs +1 -0
  133. package/lib/qa/proc.mjs +4 -0
  134. package/lib/qa/recommend.mjs +1 -0
  135. package/lib/qa/release-manifest.mjs +1 -0
  136. package/lib/qa/report-html.mjs +178 -0
  137. package/lib/qa/reports.mjs +2 -0
  138. package/lib/qa/request-evidence.mjs +1 -0
  139. package/lib/qa/research.mjs +20 -0
  140. package/lib/qa/routing.mjs +1 -0
  141. package/lib/qa/runs.mjs +1 -0
  142. package/lib/qa/schema.mjs +1 -0
  143. package/lib/qa/serve.mjs +4 -0
  144. package/lib/qa/server.mjs +1 -0
  145. package/lib/qa/source-snapshot.mjs +3 -0
  146. package/lib/qa/stability.mjs +3 -0
  147. package/lib/qa/state-evidence.mjs +1 -0
  148. package/lib/qa/sweep-incremental.mjs +1 -0
  149. package/lib/qa/triage.mjs +2 -0
  150. package/lib/qa/verdict.mjs +1 -0
  151. package/lib/qa/watch.mjs +2 -0
  152. package/package.json +62 -0
  153. package/references/ATLAS-COVERAGE.md +231 -0
  154. package/references/atlas-v1.md +318 -0
  155. package/references/atlas.md +338 -0
  156. package/references/verify.crud.example.json +1 -0
  157. package/references/verify.impact.example.json +1 -0
  158. package/references/verify.shadow.example.json +1 -0
  159. package/schemas/agent-result.schema.json +135 -0
  160. package/schemas/analysis.schema.json +15 -0
  161. package/schemas/classify.schema.json +61 -0
  162. package/schemas/coherence-ir.schema.json +1585 -0
  163. package/schemas/coherence-observed.schema.json +1171 -0
  164. package/schemas/coherence-plan.schema.json +77 -0
  165. package/schemas/coherence-risk.schema.json +46 -0
  166. package/schemas/crud-observed.schema.json +118 -0
  167. package/schemas/experiment-execution.schema.json +82 -0
  168. package/schemas/graph-edge.schema.json +22 -0
  169. package/schemas/graph-node.schema.json +81 -0
  170. package/schemas/graph.schema.json +2493 -0
  171. package/schemas/identity-oracle.schema.json +19 -0
  172. package/schemas/inventory.schema.json +2167 -0
  173. package/schemas/lanes-plan.schema.json +52 -0
  174. package/schemas/next.schema.json +54 -0
  175. package/schemas/personas.schema.json +52 -0
  176. package/schemas/research.schema.json +192 -0
  177. package/schemas/run.schema.json +160 -0
  178. package/schemas/shadow-replay.schema.json +68 -0
  179. package/schemas/stability.schema.json +92 -0
  180. package/schemas/sweep.schema.json +239 -0
  181. package/schemas/v1/agent-result.schema.json +106 -0
  182. package/schemas/v1/run.schema.json +96 -0
  183. package/schemas/verify-crud.schema.json +37 -0
  184. package/schemas/verify-read-only.schema.json +37 -0
  185. package/templates/github-workflow.yml +80 -0
  186. package/templates/pre-commit.sample +22 -0
  187. package/verify.sh +385 -0
package/lib/qa/cli.mjs ADDED
@@ -0,0 +1,538 @@
1
+ import x from"node:path";import{fileURLToPath as k}from"node:url";import{commandCatalog as q,groupedCatalog as j,nearest as p}from"./command-catalog.mjs";const f=Object.freeze({OK:0,FINDINGS:1,INVALID:2}),m=new Set(["base","auth","width","widths","parallel","routes","run","from","to","runs","fixer","fixer-timeout","max","nav-timeout","warm-timeout","settle","text-floor","integrity-share","label","keep","inventory","json-out","timeout","note","evidence","expires","by","approve","revoke","out","format","kind","limit","budget-cells","wall-ms","families","flow","max-mutations","update-selector","delete-selector","trace","policy","ids","port","build-cmd","start-cmd","debounce","seed","count","corpus","oracle","oracle-out","min-precision","min-recall","capture-dir","discard-first","layer","queue","search","flush-every","delay","height","launcher","launcher-timeout","oracle-path","auth-lease-fd","theme-storage-key","repo","around","depth","link-adapters","across","hours","stability-runs","checkpoint-minutes","shadow-trace","lane","lanes","runid","flow-workers","plan","evidence-envelope"]),h=new Set(["states","exercise"]),E=new Set(["static","cli","coherence","runtime","atlas","agent","tournament","e2e"]),A=new Set(["install","leak-check","no-install","no-journeys","no-warm","help","json","prod","autonomous","no-archive","mutate","ratchet","resume","fresh","strict","write","all","isolated","roles","ignore","quiet","once","verbose","fast","capture","capture-full","views","workflow","hook","crud","shadow","experiments","force","list","md","open","fix","clean","commit","serve","dry-run","keep-serving","stop","status","warmup","version","no-states","no-dark","dark","stdout","strict-json","no-color","phases","measure-only","fixer-protocol","no-autonomous","i-know-this-is-not-production","study","analysis","preflight"]);function U(e){const a=new Set([...m,...h,...A,...E,...Object.keys(u)]),t={command:null,positional:[],flags:{},passthru:[],json:!1};for(let r=0;r<e.length;r++){const n=e[r];if(n==="--"){t.passthru.push(...e.slice(r+1));break}if(!n.startsWith("--")){if(n==="-h"){t.flags.help=!0;continue}if(n.length>1&&n[0]==="-"){const i=p(n.slice(1),a);throw new d(`unknown flag ${n}`+(i?`; flags take two dashes -- did you mean --${i}?`:"; flags take two dashes")+". Use `qa <command> --help` for the flags this command takes, or `--` to pass arguments through verbatim.")}t.command===null?t.command=n:t.positional.push(n);continue}const s=n.indexOf("="),o=s>0?n.slice(2,s):n.slice(2);if(!o)throw new d(`${n} has no flag name before the "=". Use \`qa <command> --help\` for the flags this command takes.`);if(!a.has(o)){const i=p(o,a);throw new d(`unknown flag --${o}`+(i?`; did you mean --${i}?`:"")+". Use `qa <command> --help` for the flags this command takes, or `--` to pass arguments through verbatim.")}const c=m.has(o)||o==="version"&&t.command==="dev"&&t.positional[0]==="atlas";if(s>0){if(!c&&!h.has(o)&&!Object.hasOwn(u,o))throw new d(`--${o} does not take a value; use --${o} or omit it`);if(!n.slice(s+1))throw new d(`--${o} needs a value`);t.flags[o]=n.slice(s+1);continue}if(o==="json"){t.json=!0;continue}if(c){const i=e[r+1];if(i===void 0||/^-(?!\d)/.test(i))throw new d(`--${o} needs a value`);t.flags[o]=i,r++;continue}if(h.has(o)){const i=e[r+1];i!==void 0&&!/^-(?!\d)/.test(i)?(t.flags[o]=i,r++):t.flags[o]=!0;continue}t.flags[o]=!0}return t}class d extends Error{constructor(a){super(a),this.name="UsageError",this.exit=f.INVALID}}class R extends Error{constructor(a,t={}){super(a),this.name="InvalidError",this.exit=f.INVALID,Object.assign(this,t)}}function P(e,{required:a=!0}={}){const t=e.positional[0];if(!t){if(!a)return null;throw new d("missing <repo> \u2014 pass the repository path (absolute paths are safest)")}return x.resolve(process.cwd(),t)}const v=new Set(["login","login-user","login-pass"]),l="<redacted>",u=Object.freeze({login:"use the host-harness launcher, invoked by persona name","login-user":"use the host-harness launcher, invoked by persona name","login-pass":"use the host-harness launcher, invoked by persona name","login-path":"handled by the target-owned launcher itself",auth:"use the host-harness launcher and the per-run lease -- there is no shared auth-state file any more",fixer:"the host agent owns source fixes; run with --once to write a prioritized brief, then apply and verify the change from the host","fixer-protocol":"the CLI never launches an agent; run with --once to write a prioritized brief for the host agent","fixer-timeout":"there is no CLI fixer process to time; run with --once to hand the brief to the host agent","measure-only":"the no-fixer marathon is already measurement-only; run with --once for a bounded host handoff"});function C(e,{command:a,flags:t=Object.keys(u)}){for(const r of t)if(e.flags[r]!==void 0)throw new R(`qa ${a}: --${r} was removed -- ${u[r]}`)}const g=new Set(["base"]),w=new Set(["fixer","start-cmd","build-cmd"]),y=/pass|secret|token|key|credential|auth/i;function S(e){let a;try{a=new URL(e)}catch{return e}let t=!1;(a.username||a.password)&&(a.username=l,a.password="",t=!0);for(const r of[...a.searchParams.keys()])y.test(r)&&(a.searchParams.set(r,l),t=!0);return t?a.toString().replaceAll(encodeURIComponent(l),l):e}function I(e){return e.replace(/(^|\s)(--?[A-Za-z][\w-]*|[A-Za-z_]\w*)=(\S+)/g,(a,t,r,n)=>y.test(r)?`${t}${r}=${l}`:a)}function b(e,a){return v.has(e)?l:g.has(e)?S(a):w.has(e)?I(a):a}const N=new Set([...v,...g,...w]);function _(e){const a=[];for(let t=0;t<e.length;t++){const r=e[t],n=r.startsWith("--")?r.indexOf("="):-1,s=r.startsWith("--")?n>0?r.slice(2,n):r.slice(2):null;if(s===null||!N.has(s)){a.push(r);continue}if(n>0){a.push(`--${s}=${b(s,r.slice(n+1))}`);continue}a.push(r),t+1<e.length&&(a.push(b(s,e[t+1])),t++)}return a}function M(e,a){const t=[];for(const[r,n]of Object.entries(e.flags))if(!a.has(r))if(n===!0)t.push("--"+r);else{if(n===!1||n==null)continue;t.push("--"+r,String(n))}return t.push(...e.passthru),t}function F(e){return{say:(...n)=>{const s=n.join(`
2
+ `);(e?process.stderr:process.stdout).write(s+`
3
+ `)},warn:(...n)=>process.stderr.write(n.join(`
4
+ `)+`
5
+ `),emit:n=>{e&&process.stdout.write(JSON.stringify(n,null,2)+`
6
+ `)},json:e}}function L(e,a){return e=String(e),e.length>=a?e:e+" ".repeat(a-e.length)}function V(e,a){return e=String(e),e.length>=a?e:" ".repeat(a-e.length)+e}function W(e,{indent:a=" "}={}){if(!e.length)return"";const t=[];for(const r of e)r.forEach((n,s)=>{t[s]=Math.max(t[s]??0,String(n??"").length)});return e.map(r=>a+r.map((n,s)=>s===r.length-1?String(n??""):L(n??"",t[s])).join(" ")).join(`
7
+ `)}const G={"":`qa \u2014 evidence-first frontend verification
8
+
9
+ Seven commands. The one long-running "do everything" command:
10
+ qa run <repo> --base URL --once
11
+ measures, prioritizes, and hands one brief to the host agent
12
+ qa run <repo> --base URL --hours 10
13
+ resumable measurement; waits for host-owned source changes
14
+
15
+ COPY-PASTE FOR YOUR CURRENT CODEX/CLAUDE AGENT:
16
+ Use frontend-verify in this host worktree. Run \`qa run <repo> --base URL --once\`, read
17
+ the generated \`.verify/lanes/<class>/brief.md\`, own the source fixes and commits, then rerun
18
+ the same command to recheck.
19
+
20
+ Then, for one check at a time:
21
+ qa verify <repo> static analysis, archived and triaged
22
+ qa verify <repo> --base URL also measure the running app
23
+ qa verify <repo> --preflight check readiness and explain blockers
24
+
25
+ Read the evidence:
26
+ qa show <repo> ranked findings and coverage (triage)
27
+ qa show next <repo> the next finding to fix
28
+ qa show report <repo> self-contained HTML report
29
+
30
+ qa help --phases all commands, grouped by task
31
+ qa help --search TERM find a command by task, artifact, or flag
32
+ qa help <command> usage and options
33
+ qa help [command] --json machine-readable help
34
+ qa dev version installed version
35
+
36
+ Every pre-consolidation top-level name is retired (see RETIRED_COMMANDS in
37
+ lib/qa/command-catalog.mjs for the full list): typing one exits 2 and names
38
+ its replacement (qa help <old-name> does the same).
39
+
40
+ Generic guessed mutation replay is disabled.
41
+ Service-worker or cache-dependent protected journeys are unmeasured.
42
+
43
+ Exit codes: 0 clean/done \xB7 1 findings or regression \xB7 2 could not run (never a pass).
44
+ Relative <repo> paths resolve against your shell's working directory.`,get phases(){const e=q({commandsDir:k(new URL("./commands",import.meta.url)),help:this});return`qa commands \u2014 grouped by task
45
+
46
+ `+j(e).map(({group:a,commands:t})=>a+`
47
+ `+t.map(r=>` ${this[r]?.split(`
48
+ `)[0]??`qa ${r}`}`).join(`
49
+ `)).join(`
50
+
51
+ `)+`
52
+
53
+ Use qa help <command> for options and examples.`},"verify preflight":`qa verify <repo> --preflight [--base URL] [--prod] [--strict]
54
+
55
+ Preflight. Hard failures exit 2 and name what to fix: unreadable repo, node < 18,
56
+ zero routes, unreachable --base, --prod against a detectable dev server, a lock
57
+ held by another sweep, a malformed verify.roles.json. Warnings exit 0 (or 1 with
58
+ --strict): Playwright/chromium missing, a build process running, stale auth
59
+ state, dirty git tree, journeys absent while mutation routes exist, roles file
60
+ absent while more than one principal was detected.`,"init study":`qa init <repo> --study [--json]
61
+
62
+ Studies the repo and writes, under <repo>/.verify/:
63
+ research.json everything below, with a confidence on every heuristic
64
+ research.md the study a marathon's Phase 1 asks for
65
+ ledger.md exactly inventory.counts.routes rows; both numbers printed
66
+ journeys.draft.mjs one draft journey per mutation route, fields from source
67
+ research.json's harness field and research.md's handoff group source mutations
68
+ by exact entity identity, retain shared-hook importing routes, and rank affected
69
+ routes/surfaces. Each item names source-derived assertion candidates and missing
70
+ persona, declaration, fixture/cleanup, readback, driver, and provenance work.
71
+ Validated declarations and present drivers are not execution: every handoff is
72
+ source-derived/non-executed. No selectors, payloads, or runtime proof are guessed.
73
+ The draft is never loaded by the sweep. Promote it to <repo>/verify.journeys.mjs
74
+ after replacing every TODO selector.`,verify:`qa verify <repo> [any verify.sh flag] [--lane LANE] [--autonomous] [--json]
75
+ qa verify <repo> [--no-archive] [--keep N] [--link-adapters crud,shadow,experiments]
76
+
77
+ Runs verify.sh with the flags given, unchanged. Then opens a private pending
78
+ run directory and atomically writes immutable report snapshots plus normalized
79
+ roles and the exact validated CRUD snapshots, with their sha256 digests. The
80
+ agent receives only those explicit paths and never reads the live role or CRUD files.
81
+ It always attempts a compatible qa-agent
82
+ (a verified prebuilt binary, or a \`cargo build\` fallback) with those explicit
83
+ paths, the run ID, lane and mode via --emit-result -- optional evidence by
84
+ default (unmeasured when no compatible binary exists), never gating unless
85
+ --autonomous makes it required. Only after the returned digests and schema
86
+ are verified does the pending directory publish to .verify/runs/<id>/: a
87
+ reader sees no run or a complete run, never a partial one. run.json carries
88
+ the embedded result under \`agent\`, plus \`lane\` and \`runId\`; the archive
89
+ also gets agent-result.json (its canonical bytes). Adapter evidence (qa measure crud,
90
+ qa measure shadow replay, qa measure experiments) is NEVER folded in automatically: name the
91
+ adapters explicitly with --link-adapters, and their findings travel with their
92
+ own denominator rather than entering this run's counts. Appends to
93
+ .verify/trajectory.jsonl and prints the triage. Exit is derived: 2 dominates
94
+ (the gate itself invalid, OR --autonomous with an absent/invalid agent), else
95
+ 1 if the gate found gating evidence, else 0. --json output separates \`gate\`
96
+ (verify.sh's own exit) from \`agent\` (the embedded result) so a consumer
97
+ never conflates the two verdicts. --no-archive runs the same snapshot and
98
+ agent sequence into a private pending directory and still emits gate/agent
99
+ on stdout (--no-archive --autonomous still enforces require-agent) but
100
+ publishes nothing: the pending directory is discarded, only after the
101
+ result is validated, so a failure still leaves a diagnosis on stdout.
102
+
103
+ --autonomous also produces the canonical three-file graph contract
104
+ (nodes.jsonl, edges.jsonl, run-manifest.json) via the cargo-built qa-agent
105
+ into .verify/autonomous/ and validates its manifest. This is reported under
106
+ the JSON output's \`autonomous\` field, distinct from \`agent\`. Needs cargo;
107
+ absent cargo is reported unmeasured. A required kernel or manifest failure
108
+ is a hard exit 2. The earlier 42-file surface is retired.`,"show triage":`qa show <repo> [--run ID] [--json]
109
+
110
+ Reads an archived run (latest by default). Groups runtime findings by (kind,
111
+ selector) across routes, static by rule; ranks by severity, leverage, and
112
+ determinism; prints every denominator; flags structural tells that mean the run
113
+ measured something other than the app.`,"show next":`qa show next <repo> [--run ID] [--json]
114
+
115
+ Picks the top finding from triage (skipping fids blocked by qa run loop), writes a
116
+ brief to .verify/next.md and .verify/next.json: where, what, the atlas entry,
117
+ affected routes, how to reproduce, how to waive with proof. Exit 1 when a finding
118
+ was selected, 0 when there is nothing to fix, 2 when the run is invalid.`,"verify runs":`qa verify <repo> --base URL --runs 3 [--routes /a,/b] [--warmup] [sweep flags] [--json]
119
+
120
+ Runs sweep.mjs N times serially (--parallel 1 forced) into .verify/stability/<id>/,
121
+ plus .verify/stability.json (the latest). --warmup runs one extra, UNCOUNTED sweep
122
+ first to prime the dev server compile; it is not one of the N. Any run that exits
123
+ 2, or whose own integrity gate is not ok, makes the WHOLE measurement INVALID
124
+ (exit 2) -- partial results are still written, labelled invalid. Per finding:
125
+ stable (N of N, gates), intermittent (>= ceil(2N/3), gates), ghost (< ceil(2N/3);
126
+ listed under "needs reproduction", never gating). Exit 1 only when a gating
127
+ stable/intermittent P0/P1 finding exists. Also reported: routes whose surface
128
+ fingerprint differs between runs (informational), and per-metric min/median/max
129
+ with env-sensitive marked when max/min > 3. Runs, routes, and cells are printed
130
+ as denominators beside every count.`,"show diff":`qa show diff <repo> [--from ID] [--to ID] [--json]
131
+
132
+ Fixed / new / persisting findings between two archived runs (default: the two
133
+ newest). Identity is the fid, so a line shift does not read as fixed-and-new.`,"show ledger":`qa show ledger <repo> [--json]
134
+
135
+ Prints the ledger: one row per inventory route with entities, mutations, whether
136
+ a journey covers it, whether the latest run swept it, and its status. The row
137
+ count is asserted equal to inventory.counts.routes.`,"show runs":`qa show runs <repo> [--json]
138
+
139
+ Lists archived runs with mode, exit, counts and integrity.`,"run loop":`qa run loop <repo> [verify flags]
140
+
141
+ Runs one ratcheted gate, ranks the next actionable finding, and writes
142
+ .verify/next.md plus .verify/next.json for the host agent. Exit 0 means the
143
+ measured scope is clean; exit 1 means the brief was handed off; exit 2 means
144
+ the measurement was invalid. The CLI never launches an agent, executes a
145
+ fixer command, or commits source. Use qa run <repo> --once for the normal
146
+ bounded host-agent handoff.`,run:`qa run <repo> [--base URL | --serve] [--hours N]
147
+ [--lane LANE] [--lanes N] [--widths 390,1440] [--no-states] [--no-dark]
148
+ [--stability-runs 3] [--checkpoint-minutes 45] [--resume] [--fresh] [--keep-serving] [--no-autonomous]
149
+ [--once] [--dry-run] [--json] [passthrough verify flags]
150
+ qa run [kind] <repo> ... kind: loop, watch, serve (default: below)
151
+
152
+ The one long-running "do everything" command. With no kind, every phase below is
153
+ a child \`qa <cmd>\` process; this command reads what each one wrote to .verify/
154
+ and decides whether the next one runs:
155
+ 0 --base is used as given. Otherwise this command builds and starts a
156
+ production server itself (qa run serve --prod; its base becomes --base,
157
+ stopped at the end unless --keep-serving). If that fails without an
158
+ explicit --serve, the run is static-only and the report says so; with
159
+ --serve the failure is INVALID.
160
+ 1 qa verify --preflight -- exit 2 stops everything and prints its fix lines.
161
+ When a --base is known, its probe of it decides --prod automatically for
162
+ every phase below: prod -> add --prod, dev -> continue without it and
163
+ say timing findings will be P3, unknown -> continue without it.
164
+ 2 qa init --study; qa init --roles --ignore -- init never overwrites. With
165
+ more than one principal and no roles file, init scaffolds one role per
166
+ principal (each owning "/") and the run continues, saying so.
167
+ 2b Writes turn on when verify.crud.json declares every flow
168
+ "production": false and the base is loopback: the crud and coherence
169
+ phases pass --mutate themselves. The sweep gets --mutate only when an
170
+ app-owned journey declares mutates: true -- generic guessed replay is
171
+ disabled, so without such a journey the flag could only invalidate the
172
+ run, and the decision log says it was withheld. An explicit
173
+ --lane wins; otherwise real-integration is selected when
174
+ verify.auth-launcher.mjs exists and every persona in verify.roles.json
175
+ declares "expect". Draft journeys whose every field
176
+ and submit control resolved from source are promoted to
177
+ .verify/journeys.mjs under that same mutation opt-in. Each decision and
178
+ its reason is printed and lands in the exit report.
179
+ 3 qa verify --ratchet (the baseline) -- exit 2 stops. No --base means a
180
+ static-only run, said plainly in the report.
181
+ 3b deep measurement, once, in two groups: Group A (concurrent -- read-only
182
+ against the app, or writing only to an isolated target) is static
183
+ coherence, qa measure invariants mine + status (review/enforce stay a human
184
+ call), and qa measure shadow replay (when verify.shadow-trace.json or
185
+ .verify/shadow-trace.json exists -- a trace of real traffic cannot be
186
+ manufactured by the run). Group B (strictly serial -- these can all
187
+ write to the SAME dev database) is qa measure crud (under the mutation opt-in),
188
+ qa measure experiments (qa run generates the Node-owned default queue from
189
+ the baseline's Rust route priority when none exists, so it needs cargo;
190
+ pass --queue <path> for a supplied plan; wall time is a
191
+ quarter of what is left, at most 30 minutes), then runtime coherence
192
+ (under its own repo-declared opt-in). Whatever could not run is a named
193
+ line under "what was not measured" with how to enable it. Whichever
194
+ adapter left measured evidence is folded into the very next qa verify
195
+ via --link-adapters automatically.
196
+ 4 qa verify --runs N on the baseline's runtime P0/P1 routes (skipped
197
+ when there are none) -- ghost findings are written into .verify/loop.json's
198
+ blocked list so the fix loop never chases them.
199
+ 5 qa fix plan --lanes N (one worktree + brief per class, disjoint batches),
200
+ then waits for the host agent to execute the briefs. --once hands off
201
+ immediately with exit 1.
202
+ 6 every chunk: qa show ledger, qa show report, one line appended to
203
+ .verify/trajectory.jsonl. If something was fixed and time remains,
204
+ qa verify runs again and qa show diff compares it -- new findings send it
205
+ back to step 4 with whatever time is left. After the depth ladder is
206
+ exhausted, runtime measurements repeat at --checkpoint-minutes cadence
207
+ without --resume; source changes wake the coordinator sooner. Static-only
208
+ runs wait for source changes. --once finishes after the first full pass.
209
+ 7 exit report: .verify/marathon-report.md (routes total/clean/fixed with
210
+ commit hashes/open fids/needs-reproduction ghost fids, the ratchet
211
+ trajectory, every phase's command and exit code verbatim, what stayed
212
+ unmeasured, which adapters got linked, the depth ladder's rungs with
213
+ their --resume reused/rejected counts) plus a qa show report HTML. Its first
214
+ word is PASS, FAIL, or INVALID, matching this command's own exit code.
215
+ --status reads .verify/marathon.json and prints phase, running/stopped/finished,
216
+ minutes left, base, lane, writes on/off, depth rung, last command and decision,
217
+ the last checkpoint counts, wait state, and the report path -- for an agent polling a
218
+ background run. Exit 0 running or finished, 1 stopped mid-run (resume it), 2
219
+ nothing has run here.
220
+ A bare \`qa run <repo>\` on a repo whose .verify/marathon.json is not
221
+ "done" resumes it -- this covers both a killed process and a --once handoff to
222
+ open lanes; --fresh discards that state and starts over. A separate marathon.lock
223
+ refuses a second live coordinator, including --fresh. --resume is still
224
+ accepted and is a no-op. A recorded server that died is restarted only if
225
+ --serve was given, otherwise that is INVALID. --dry-run prints the plan
226
+ above with this run's actual flags and runs nothing. --mutate is added only
227
+ by the declaration rule in step 2b, never by --hours or any other flag.
228
+ Default --hours is 10, default --lanes is 4. Exit: 0 the final gate is
229
+ clean, 1 it has findings (or lanes are ready/open, or time ran out with
230
+ findings open), 2 a phase could not run.
231
+
232
+ --fixer, --fixer-protocol, --fixer-timeout, --measure-only, and --commit are
233
+ retired for qa run: the host agent owns source changes and commits. Use --once
234
+ for a copy-paste-ready brief.
235
+ loop/watch/serve are reachable by name (\`qa run loop ...\`, \`qa run watch ...\`,
236
+ \`qa run serve ...\`) -- see qa help run loop / run watch / run serve for their
237
+ own flags and exit codes.`,"fix fp":`qa fix fp add <repo> <fid> --note "why" [--evidence REF --expires YYYY-MM-DD] | qa fix fp list | qa fix fp rm <id>
238
+
239
+ Source entries retain their content anchor. Runtime entries require evidence
240
+ and expiry, and match only the same repository, rule, route, persona, selector
241
+ and measurement identity -- lane, phase, width and rule version, the identity of
242
+ the observation. The run's measurement modes are recorded on the entry, not part
243
+ of that key, so climbing a depth-ladder rung does not retire the adjudication.
244
+ Raw observations are never removed. Stored in
245
+ ~/.config/qa/known-false-positives.json (QA_HOME overrides).`,"dev selftest":`qa dev selftest [--parallel N] [--static] [--cli] [--coherence] [--runtime] [--atlas] [--e2e] [--agent] [--tournament] [--fast] [--all]
246
+
247
+ Runs the selected suites. Node test files use four workers by default;
248
+ --parallel N controls that limit. Child processes can spawn their own children.
249
+ Choose --all, --fast, or individual suites, not a mixture. Default: static + cli, the two that need nothing but
250
+ node. --fast runs every suite that does not drive a browser (static, cli,
251
+ coherence, agent, tournament) -- the per-change gate, minutes not a quarter of
252
+ an hour. --all runs every suite, and is what a release is graded on: --fast
253
+ cannot see a probe rule that stopped matching, because every rule that needs a
254
+ browser to fire lives in runtime/atlas/e2e.
255
+
256
+ static selftest-static.sh -- every classifier rule, planted and asserted
257
+ cli the unit suites plus selftest-cli.sh against fixture repos
258
+ coherence selftest-coherence.sh -- the entity chain end to end: families,
259
+ the sparse obligation universe, a real mutation and authoritative
260
+ readback through a fake driver, the per-obligation join, the
261
+ confirmed root cause and its affected surfaces
262
+ runtime selftest.sh -- the probes, differentially (needs Playwright)
263
+ atlas selftest-atlas.sh -- every runtime finding kind still fires (needs Playwright)
264
+ e2e selftest-e2e.sh -- one real run of the whole chain, inventory
265
+ through classify, sweep, the rust kernel, the artifact contract,
266
+ triage, report and SARIF, asserting the JOINS between them and
267
+ that two identical runs are byte-identical (needs Playwright and cargo)
268
+ agent selftest-agent.sh -- the artifact contract through a symlinked install (needs cargo)
269
+ tournament cargo test plus the adapter, privacy and benchmark gates (needs cargo)
270
+
271
+ A suite that cannot run (no Playwright, no cargo) is reported as skipped, never
272
+ as passed. Exit 1 if a suite failed, 2 if one could not run.`,"dev atlas":`qa dev atlas [--json] [--write] [--layer N] [--strict]
273
+
274
+ Cross-references references/atlas.md (the 11-layer failure taxonomy) against
275
+ the real detectors: bin/classify.mjs's static rules, bin/probe.js's and
276
+ bin/sweep.mjs's runtime finding kinds, and lib/qa/atlas.mjs's KIND_ATLAS map.
277
+ Classifies every S/R class as implemented (lists the rule/kind ids),
278
+ deliberately-not-implemented (the reason from the "Evaluated and deliberately
279
+ not implemented" table), or gap (nothing implements it); every G/H/A class as
280
+ not-automatable (says which: a generated journey, a human question, or an
281
+ architecture choice) -- and separately notes when a detector exists anyway for
282
+ a class atlas.md itself marks not-automatable, since that usually means the
283
+ detector is a partial proxy or the atlas's own Det column is stale. Also
284
+ reports the reverse drift: every finding kind with no atlas id, and every
285
+ atlas id a detector references that is not a row in atlas.md -- both are bugs.
286
+ --layer N narrows the printed table to one layer (accepts "--layer N" or
287
+ "--layer=N"; "--layer" alone is not a value flag in this CLI's shared parser).
288
+ --write regenerates references/ATLAS-COVERAGE.md from the same data. Current
289
+ producer-derived coverage:
290
+ v2: 77 of 105 S/R classes; 79 of 108 mode obligations
291
+ v1: 72 of 96 S/R classes; 74 of 99 mode obligations
292
+ Both have 30 not-automatable classes. Producers are discovered recursively from
293
+ every JavaScript finding producer under bin/ and lib/. Exit 1 under --strict when
294
+ any drift was found, 0 otherwise -- gaps alone never fail the command.`,"show report":`qa show report <repo> [--run ID] [--out FILE] [--format html|sarif|github|junit]
295
+ qa show runs <repo> [--json]
296
+ qa show diff <repo> [--from ID] [--to ID] [--json]
297
+
298
+ HTML is the default. Export formats preserve the archived gate exit.
299
+ diff/runs/ci are also reachable directly under show (qa show diff/runs/ci <repo>),
300
+ not only nested under report.
301
+
302
+ Renders one self-contained HTML file (inline CSS/SVG/JS, no external URLs) from
303
+ an archived run: verdict banner with exit code and denominators, integrity
304
+ gauges, the trajectory as a line chart, the cell matrix heatmap, ranked
305
+ findings, the mutation -> entity -> route sync graph, structural tells, and
306
+ stability classes / the ledger when those have been written. Default output:
307
+ <repo>/.verify/report.html.`,"show graph":`qa show graph <repo> --format dot|mermaid|json [--kind sync|transition]
308
+ qa show graph <repo> --run ID [--around RECORD_ID] [--depth N] [--limit N] [--json]
309
+
310
+ Prints the mutation -> entity -> route sync graph from .verify/inventory.json
311
+ (--kind sync, default), or the route -> route transition graph projected from
312
+ .verify/autonomous/edges.jsonl (--kind transition) -- unmeasured and empty,
313
+ exit 0, if that file has not been written yet.
314
+
315
+ With --run, verifies nodes.jsonl/edges.jsonl against the archived run.json graph
316
+ SHA-256 digests, VALIDATES graph integrity, and returns a bounded neighbourhood.
317
+ --around must name an existing record id; --depth is a nonnegative integer
318
+ (default 1), --limit a positive integer (default 200). --kind must name a finite
319
+ node kind: finding, route, component, query, mutation, entity, endpoint, role,
320
+ transition, or evidence. Unknown kinds/centres and invalid numbers exit 2.
321
+ Collections carry returned/total/truncated. A requested graph that is absent or
322
+ fails integrity validation exits 2 -- never an empty successful neighbourhood.`,"show ci":`qa show ci <repo> [--run ID] --format sarif|github|junit [--out file]
323
+
324
+ Renders an archived run (latest by default) for a CI system:
325
+ sarif SARIF 2.1.0, one result per finding, rules from distinct kinds,
326
+ partialFingerprints.fid, atlas ids as helpUri fragments
327
+ github GitHub Actions workflow commands (::error/::warning/::notice), one per line
328
+ junit JUnit XML: one testsuite per phase, one testcase per group
329
+ Without --out the rendered content is written straight to stdout (redirect it);
330
+ with --out it is written to that file (resolved against your shell's cwd) and a
331
+ short summary is printed instead. Exit code mirrors the archived run's own exit.`,init:`qa init <repo> [--roles] [--workflow] [--hook] [--ignore] [--all] [--crud]
332
+
333
+ Scaffolds the per-repo wiring. With no flags, does everything below (same as
334
+ --all); a specific flag narrows it to just that one:
335
+ --roles verify.roles.json \u2014 from .verify/research.json principals if
336
+ present, else one default role owning "/"
337
+ --ignore .verifyignore (design-lab defaults noted, commented) and appends
338
+ .verify/ to .gitignore if it is not already ignored
339
+ --workflow .github/workflows/frontend-verify.yml \u2014 static gate on every PR;
340
+ runtime gate + SARIF upload only when FRONTEND_VERIFY_BASE is set
341
+ --hook prints (does not write) the Stop-hook JSON for ~/.claude/settings.json
342
+ and a pre-commit sample
343
+ --crud verify.crud.json \u2014 one flow per entity qa init --study saw written,
344
+ every flow "production": false. Never implied by --all or by no
345
+ flags: this file is the opt-in that lets qa run write to the
346
+ app (on a loopback base), so write it only against a disposable DB
347
+ Never overwrites an existing file; prints created / skipped-exists / appended
348
+ for each action, so a second run is visibly a no-op.`,"run serve":`qa run serve <repo> [--prod] [--port N] [--build-cmd CMD] [--start-cmd CMD] [--timeout S]
349
+ qa run serve <repo> --stop
350
+ qa run serve <repo> --status
351
+
352
+ Builds (with --prod, using the repo's own build script) then starts the app in
353
+ the background -- start script for --prod, dev script otherwise -- detached in
354
+ its own process group so this command returns while the app keeps running.
355
+ Output goes to .verify/serve.log; state to .verify/serve.json. Waits for
356
+ liveness (any HTTP response at --port, default 3000) up to --timeout seconds
357
+ (default 180), then prints the exact "qa verify ... --base ..." line to run
358
+ next. Refuses a second start while a recorded pid is still alive, and refuses
359
+ if the port is already in use. A build failure or a liveness timeout exits 2
360
+ with the log path. --stop kills the process group and clears the state file
361
+ (always exit 0, idempotent). --status probes the recorded server and reports
362
+ liveness plus dev/prod detection: exit 0 live, 1 not running.`,"run watch":`qa run watch <repo> [--debounce MS] [--once]
363
+
364
+ Watches source files -- recursive fs.watch where the platform supports it
365
+ (darwin, win32, linux on node >= 20), mtime polling every 2s otherwise --
366
+ ignoring .verify, node_modules, .git, .next, dist, build, out. On change,
367
+ re-runs the static gate (\`qa verify <repo>\`, a real child process) and
368
+ prints fixed / new / persisting findings by fid since the previous archived
369
+ run. --debounce sets the quiet period before a cycle runs (default 400ms);
370
+ changes that arrive while a cycle is already running coalesce into exactly
371
+ one follow-up cycle, never one per file. --once runs a single cycle after
372
+ the first change and exits with that cycle's own gate exit code (for
373
+ scripting and tests). Ctrl-C stops cleanly: the watcher stops taking new
374
+ changes immediately, and an in-flight cycle is allowed to finish archiving
375
+ before the process exits.`,"measure shadow":`qa measure shadow replay <repo> --trace FILE --base URL --isolated [--policy verify.shadow.json] [--ids /route/:id=123,...] [--auth state.json] [--json]
376
+
377
+ Read-only replay of a collected shadow trace (bin/shadow-collector.mjs output)
378
+ against an isolated target. Refuses (exit 2) without --isolated, against a
379
+ target that looks production-like, without a policy declaring
380
+ replay.isolated === true and replay.production === false, when the trace uses
381
+ a route/endpoint template the policy's allow_routes/allow_endpoints does not
382
+ cover, or when there is nothing replayable. Only replays GET navigations, in
383
+ transition_index order; a non-GET trace row is always skipped, never issued.
384
+ Compares same-origin traffic observed during each navigation to the trace's
385
+ expectation for the same endpoint. Writes
386
+ .verify/autonomous/shadow-replay.json. Exit 1 when a measured endpoint moved
387
+ from 2xx to 4xx/5xx (finding shadow.drift), 0 when replayed clean.`,"measure invariants":`qa measure invariants mine <repo>
388
+ qa measure invariants review <repo> --approve ID [--by NAME]
389
+ qa measure invariants review <repo> --revoke ID
390
+ qa measure invariants enforce <repo>
391
+ qa measure invariants status <repo>
392
+
393
+ The independent invariant authority, separate from the Rust output directory
394
+ (agent/src/invariants/*.rs fail-closes Reviewed -> Enforced on purpose; this
395
+ is the authority that can actually promote one). Proofs are sha256 digests of
396
+ archived run files under .verify/runs/<id>/, recomputed at approve time and
397
+ again at pack-load time -- a tampered archive makes approve refuse and makes
398
+ the generated pack throw (infrastructure exit 2), never a finding.
399
+
400
+ mine mines route-level candidates over archived runtime runs, writes
401
+ .verify/invariants/candidates.json
402
+ review --approve ID promotes a candidate into the committed policy file
403
+ <repo>/verify.invariants.json (refused below support 2, above 0
404
+ counterexamples, or on any proof mismatch); --revoke ID removes one
405
+ enforce regenerates .verify/rules/invariants.mjs, a runtime rule pack that
406
+ re-verifies every enforced proof on load and files
407
+ invariant.violated P1 findings for route-reachable / renders-text
408
+ (the only kinds a pack can independently re-check per cell; other
409
+ approved kinds still get proof-tamper protection, see \`status\`)
410
+ status lifecycle table: candidate / approved / enforced, with support,
411
+ counterexamples, proof validity, and whether a kind is runtime-checked`,"fix plan":`qa fix plan <repo> [--run ID] [--lanes N]
412
+ qa fix gate <class> <repo>
413
+ qa fix close <class> <repo>
414
+ qa fix rebase <repo>
415
+ qa fix board <repo>
416
+
417
+ Parallel class-level fixes. plan groups the latest archived run's findings
418
+ into classes ((rule) or (kind, selector)), resolves the files each would
419
+ touch, packs classes into batches whose files are disjoint (--lanes N wide,
420
+ default 4), opens one git worktree + branch qa/<class> per lane of the first
421
+ batch under .verify/lanes/<class>/tree, writes .verify/lanes/<class>/brief.md
422
+ and .verify/board.md. Exit 1 lanes ready, 0 nothing to plan, 2 invalid.
423
+ gate runs qa verify in that worktree with the plan's base and flags under one
424
+ lock; exit 0 every finding of the class gone and no regression, 1 red (the
425
+ worktree is reverted and the attempt recorded), 2 could not gate. close needs
426
+ a passing gate: commits, pushes qa/<class>, opens the PR with the numbers,
427
+ records it; never merges. rebase rebases every open lane onto main and
428
+ re-gates; a conflict marks the lane conflict. board regenerates
429
+ .verify/board.md. qa run plans the lanes and leaves source fixes and commits to
430
+ the host agent; it never executes a fixer.`,"measure crud":`qa measure crud <repo> --base URL --mutate --max-mutations N [--flow NAME] [--auth state.json]
431
+
432
+ Executable CRUD reconciliation: performs and observes create/read/update/delete
433
+ plus reload and navigation reconciliation through a real browser, per flow x
434
+ persona declared in <repo>/verify.crud.json (see references/verify.crud.example.json).
435
+ Every browser context is intercepted before transmission: same-origin writes
436
+ must match the selected declared flow and shared budget, including popups and
437
+ workers; service workers are blocked. An authorization gap invalidates the run.
438
+ Each persona's storage state is minted through <repo>/verify.auth-launcher.mjs
439
+ (the same launcher and lease the sweep uses; verify.roles.json carries no
440
+ credentials) and checked against the identity oracle where the app serves
441
+ one; a persona that cannot be provisioned refuses the run rather than
442
+ measuring the login page. --auth <state.json> overrides with one file for
443
+ every persona; --launcher <path> and --oracle-path <path> override the launcher
444
+ and the oracle endpoint exactly as they do for the sweep (defaults
445
+ <repo>/verify.auth-launcher.mjs and /__qa/identity).
446
+ Refuses without --mutate, refuses if verify.crud.json is absent or any flow's
447
+ "production" is not literally false, without a positive --max-mutations,
448
+ or against a non-loopback base (unless the explicit non-production override is
449
+ used; production-looking names are always refused). A persona listed under a flow's "denied" is exercised for
450
+ authorization only, never the full cycle. Mutations are capped at
451
+ --max-mutations; a create this run performs is always affordable
452
+ to clean up too, and cleanup is verified, never assumed. Writes
453
+ .verify/crud-observed.json. Exit 0 measured clean, 1 on any P0/P1 finding
454
+ (crud.create-failed, crud.write-lost, crud.update-lost, crud.delete-ineffective,
455
+ crud.unauthorized-allowed, crud.cleanup-failed), 2 invalid (refused, or the run
456
+ measured nothing).
457
+
458
+ The create surface is found as \`form:visible\` on the flow's route, waited for
459
+ (--form-timeout, default 10000ms). A surface that is not there at load -- a
460
+ dialog behind a quick action, a form gated on a filter -- needs the flow to
461
+ name an entry in <repo>/verify.journeys.mjs via "journey": its run() is
462
+ called after landing on the route and must leave the surface mounted (the
463
+ sweep drives the same entry, so write nothing in run()). "form_selector"
464
+ names the surface when it is not a <form> (a dialog), "submit_selector" the
465
+ control inside it that performs the write (clicked instead of
466
+ requestSubmit()). A journey entry may carry create(page, { sentinel }) to
467
+ perform the create itself where the generic filler cannot (a <select>-only
468
+ form), returning { sentinel } when the text that proves the write is not
469
+ what was typed. "update_selector" / "delete_selector" set to false declare
470
+ the surface has no such control (step stays unmeasured, with that reason);
471
+ "cleanup_required": false skips the direct DELETE for a record type the API
472
+ cannot delete. Templates interpolate only {fixtureId} and {sentinel}; unresolved
473
+ tokens are unmeasured, never guessed. Schema-v2 declarations may also name
474
+ bounded canonical entity aliases for coherence.`,"measure coherence":`qa measure coherence <repo> [--views] [--base URL --mutate --flow NAME --max-mutations N]
475
+ [--plan agent-result.json] [--flow-workers N] [--runid ID]
476
+
477
+ Without --mutate, prints the coherence-IR denominators (writes, reads,
478
+ surfaces per route component, candidates with their affected surfaces) and the
479
+ fan-out view for every candidate mutation; --views adds the full fan-out
480
+ (mutation -> surfaces) and fan-in (surface -> writers) tables and the
481
+ obligation universe (mutation x surface x principal x context). No browser. Runtime mode is fail-closed: every
482
+ selected flow must be schema-v2 with a crosscheck and the repository must
483
+ provide a provenance adapter (verify.crud.json schema_version 2 with a
484
+ crosscheck, verify.coherence.mjs, verify.coherence-provenance.mjs).
485
+ --plan <agent-result.json> executes the obligations the Rust kernel selected
486
+ (auto-detected from the latest archived run when its coherenceObligations
487
+ dimension is measured) and reports planned/reached/compared/agreed;
488
+ --flow-workers N runs isolated flows in parallel (distinct entities, or both
489
+ flows declaring isolation: per-flow). Schema-v2 canonical entity aliases join
490
+ declared endpoint families while retaining the original endpoint evidence.
491
+ Incomplete evidence exits 2, never clean.`,"measure experiments":`qa measure experiments <repo> --base URL [--budget-cells N] [--wall-ms MS] [--families a,b] [--mutate] [sweep flags] [--json]
492
+
493
+ Executes the accepted candidates from .verify/autonomous/experiment-queue.json,
494
+ or --queue <path> naming one explicitly (a ranked plan -- never auto-executed
495
+ elsewhere), as targeted sweeps, one control/treatment pair per (route, family):
496
+ latency-baseline-vs-injection plain sweep vs the same route with --states slow
497
+ role-pair the two owning roles' cells from one sweep
498
+ (needs two roles in verify.roles.json)
499
+ mutation-vs-no-mutation unmeasured: generic guessed replay is disabled and
500
+ fails closed; use declared CRUD or coherence instead
501
+ navigation-vs-reload and cache-enabled-vs-disabled are also not executable by
502
+ this tool and are always reported unmeasured with the reason. A trial whose sweep
503
+ exits 2 invalidates that family for that route -- never averaged. Writes
504
+ .verify/experiment.json, .verify/experiment-observations.json,
505
+ .verify/autonomous/experiment-observation.json (via bin/experiment-runner.mjs)
506
+ and .verify/autonomous/experiment-execution.json. Exit 0 measured clean, 1 a
507
+ measured treatment introduced a P0/P1 the control lacked, 2 nothing was
508
+ measured (missing queue, budget exhausted before anything ran, or every
509
+ combination was ineligible/invalid).`,init:`qa init <repo> [--study|--analysis] [--roles|--crud|--ignore|--workflow|--hook|--all]
510
+
511
+ Scaffolds declarations without overwriting them. --study writes source-derived research; --analysis additionally writes .verify/analysis.json and analysis.md with an evidence ledger, feature/context/risk/story/mutation/proof/fix view. It never invents business requirements or executes mutations.`,show:`qa show [kind] <repo> ... kind: triage (default), next, report, ledger, graph, analysis
512
+
513
+ Reads results. \u2018show analysis\u2019 renders the generated analysis and \u2018--format json\u2019 emits its structured artifact.`,"dev validate":`qa dev validate <repo> [--run ID]
514
+
515
+ Validates every present artifact under <repo>/.verify/ (inventory.json,
516
+ classify.json, sweep.json, research.json, stability.json, next.json,
517
+ crud-observed.json, autonomous/shadow-replay.json,
518
+ autonomous/experiment-execution.json) against the JSON Schemas in schemas/,
519
+ using a dependency-free draft 2020-12 subset validator. Also validates the
520
+ archived copies (inventory.json, classify.json, sweep.json, run.json) under
521
+ .verify/runs/<id>/ -- <id> is --run when given, else the latest archived run
522
+ if one exists; an explicit --run that does not resolve is an error. Prints a
523
+ table of artifact -> ok/absent/VIOLATIONS/ERROR with the path and message of
524
+ each violation. Absent is not a failure -- most artifacts are produced by
525
+ commands nobody has run yet in a given repo. Exit 0 when every present
526
+ artifact validates, 2 on any schema violation or unreadable/malformed file.`,measure:`qa measure <kind> <repo> ... kind: coherence, crud, experiments, shadow, invariants
527
+
528
+ Dispatches to one of the five deep-measurement adapters. Each kind keeps its
529
+ own flags and subwords -- \`qa measure shadow replay <repo> ...\`, \`qa measure
530
+ invariants mine|review|enforce|status <repo>\`. crud and coherence --mutate
531
+ perform real, declared mutation writes under the repo's own opt-in
532
+ (verify.crud.json); every other kind is read-only. See qa help measure
533
+ coherence / measure crud / measure experiments / measure shadow / measure
534
+ invariants for what each one measures and its exit codes.`,show:"qa show [kind] <repo> ... kind: triage (default), next, report, ledger, graph, analysis\n\nReads results. With no kind, prints ranked findings and coverage (triage).\nreport's own sub-verbs stay reachable both nested (`qa show report\ndiff|runs|ci <repo>`) and directly (`qa show diff|runs|ci <repo>`). See qa\nhelp show triage / show next / show report / show ledger / show graph for\neach one's flags, output, and exit codes. `qa show analysis <repo>` renders the generated analysis; `--format json` emits its structured artifact.",fix:"qa fix <sub> ... sub: plan, gate, close, rebase, board (lanes) \xB7 fp add|list|rm\n\nParallel class-level remediation (`qa fix plan <repo>`, `qa fix gate <class>\n<repo>`, ...) and the cross-repo false-positive memory (`qa fix fp\nadd|list|rm ...`). See qa help fix plan / fix fp for flags, output, and exit\ncodes.",dev:"qa dev <kind> ... kind: atlas, selftest, validate, version\n\nMaintaining the harness itself, not the target repo under test: `qa dev atlas\n--strict`, `qa dev selftest --fast`, `qa dev validate <repo>`, `qa dev\nversion --json`. See qa help dev atlas / dev selftest / dev validate / dev\nversion for flags and exit codes.","dev version":`qa dev version
535
+
536
+ Prints the installed name, semver, short commit, node version, and install
537
+ root -- both as one line and, with --json, as {name, version, commit, node,
538
+ root}.`};export{A as BOOLEAN_FLAGS,w as COMMAND_VALUE_FLAGS,f as EXIT,G as HELP,R as InvalidError,h as OPTIONAL_VALUE_FLAGS,l as REDACTED,u as REMOVED_FLAGS,v as SECRET_VALUE_FLAGS,E as SELFTEST_SUITES,g as URL_VALUE_FLAGS,d as UsageError,m as VALUE_FLAGS,M as forwardFlags,V as lpad,F as makeOutput,L as pad,U as parseArgs,_ as redactArgs,b as redactValue,C as rejectRemovedFlags,P as resolveRepo,W as table};
@@ -0,0 +1 @@
1
+ import{createHash as k}from"node:crypto";import{canonicalize as b}from"../jcs.mjs";import{deriveOperationReadPairs as x,keyCoverage as P,reduceCoverage as E}from"./cache-key.mjs";function K(t,n){const o=P(t,n);return o==="covered"?!0:o==="not-covered"?!1:"unresolved"}const M=E;function N(t,n){return x(new Map((t??[]).map(o=>[o.id,o])),new Map((n??[]).map(o=>[o.id,o])))}function z(t){const n={kind:t.kind,mutationId:t.mutationId??t.mutation?.id??null,family:t.family??null,readIds:[...t.readIds??[]].sort()};return"crc:"+k("sha256").update(b(n)).digest("hex")}function C(t){return(t.writeCandidates??0)===0||(t.writesResolved??0)===0?"unmeasured":t.writesUnresolved||t.readsUnresolved||t.keysUnresolved||t.operationsUnresolved||t.operationReadPairsUnresolved||t.invalidationsUnresolved||t.ownershipEdgesUnresolved||t.writersWithNoReaderSurface||t.coverageUndecidable?"partial":"measured"}function R(t,n){return n!=="unmeasured"?null:(t.writeCandidates??0)===0?"no-write-candidates":"no-writes-resolved"}const _=Object.freeze(["no-write-candidates","no-writes-resolved"]);function $(t,{waivers:n=[]}={}){const o={...t?.coverage?.counts??{}},v=C(o),i={status:v,reason:R(o,v),counts:o,unresolved:[...t?.coverage?.unresolved??[]]},l=new Map;for(const e of t?.surfaces??[])for(const s of e.readIds??[])l.has(s)||l.set(s,[]),l.get(s).push(e);const u=[],S=new Map((t?.cacheKeys??[]).map(e=>[e.id,e])),U=new Map((t?.cacheOperations??[]).map(e=>[e.id,{...e,material:{...e.material,key:S.get(e.material.keyId)?.material??null}}]));for(const e of t?.mutations??[]){if(!e.family||!(e.writes??(e.pattern?[{pattern:e.pattern}]:[])).length)continue;const f=(t.reads??[]).filter(r=>r.family&&r.family===e.family);if(!f.length)continue;const g=(e.operationIds??[]).map(r=>U.get(r)).filter(Boolean),O=N(g,f),m=[],c=[];for(const r of f){const a=O.filter(p=>p.readId===r.id).map(p=>p.effectCoverage),I=M(a);I!=="covered"&&(I==="unresolved"?c.push(r):m.push(r))}if(!m.length){c.length&&(o.coverageUndecidable=(o.coverageUndecidable??0)+1,i.unresolved.push({kind:"coherence.coverage-undecidable",mutationId:e.id,family:e.family??null,source:{file:e.file,line:e.line},readIds:c.map(r=>r.id).sort()}));continue}const h=new Set,w=new Set,y=new Set;for(const r of m)for(const a of l.get(r.id)??[])h.add(a.id),a.route&&w.add(a.route),a.route&&a.component&&y.add(`${a.route}:${a.component}`);const d={kind:g.length===0?"coherence.no-invalidation-candidate":"coherence.partial-invalidation-candidate",severity:"P3",confirmed:!1,mutationId:e.id,source:{file:e.file,line:e.line},family:e.family??null,readIds:m.map(r=>r.id).sort(),unresolvedReadIds:c.map(r=>r.id).sort(),surfaceIds:[...h].sort(),routes:[...w].sort(),components:[...y].sort(),coverage:c.length?"partial":i.status};(e.familySource==="declaration"||f.some(r=>r.familySource==="declaration"))&&(d.confidence="uncertain",d.basis="declaration-backed"),d.rootCauseId=z(d),n.includes(d.rootCauseId)&&(d.waived=!0),u.push(d)}return u.sort((e,s)=>e.rootCauseId.localeCompare(s.rootCauseId)),i.status=C(o),i.reason=R(o,i.status),i.unresolved.sort((e,s)=>JSON.stringify(e).localeCompare(JSON.stringify(s))),{coverage:i,candidates:u,waived:u.filter(e=>e.waived)}}export{_ as UNSUPPORTED_COVERAGE_REASONS,$ as analyzeCoherence,z as coherenceRootCauseId,N as operationReadPairs,K as patternCovers,M as reduceOperationCoverage};
@@ -0,0 +1,6 @@
1
+ import{createCoherenceIdentity as Q}from"./identity.mjs";import{canonicalize as E}from"../jcs.mjs";import{deriveOperationReadPairs as ce,effectCoverage as fe,keyCoverage as pe,reduceCoverage as me}from"./contracts.mjs";const $=/^[A-Za-z_$][\w$]*$/,U=new Set(["dynamic-key","unknown-helper","unsupported-client","unsupported-expression","unsafe-number","non-json-value","contract-conflict","dynamic-import","namespace-spread","export-star","computed-member","ambiguous-alias","unresolved-module","unsupported-syntax"]);function y(n,e=0,t=n.length){for(;e<t&&/\s/.test(n[e]);)e++;for(;t>e&&/\s/.test(n[t-1]);)t--;return{start:e,end:t,text:n.slice(e,t)}}function S(n,e=","){const t=[];let s=0,r=null,l=!1;const o=[],a={"(":")","[":"]","{":"}"};for(let d=0;d<n.length;d++){const c=n[d];if(r){!l&&c===r&&(r=null),l=!l&&c==="\\",c!=="\\"&&(l=!1);continue}if(c==="'"||c==='"'||c==="`"){r=c;continue}if(a[c])o.push(a[c]);else if(c===o.at(-1))o.pop();else if(c===e&&o.length===0){const k=y(n,s,d);k.text&&t.push(k),s=d+1}}const h=y(n,s);return h.text&&t.push(h),t}function T(n){let e=null,t=!1;const s=[],r={"(":")","[":"]","{":"}"};for(let l=0;l<n.length;l++){const o=n[l];if(e){!t&&o===e&&(e=null),t=!t&&o==="\\",o!=="\\"&&(t=!1);continue}if(o==="'"||o==='"'||o==="`"){e=o;continue}if(r[o])s.push(r[o]);else if(o===s.at(-1))s.pop();else if(o===":"&&s.length===0)return l}return-1}function q(n,e,t=0){let s=null,r=!1;const l=[],o={"(":")","[":"]","{":"}"};for(let a=t;a<=n.length-e.length;a++){const h=n[a];if(s){!r&&h===s&&(s=null),r=!r&&h==="\\",h!=="\\"&&(r=!1);continue}if(h==="'"||h==='"'||h==="`"){s=h;continue}if(o[h]){l.push(o[h]);continue}if(h===l.at(-1)){l.pop();continue}if(l.length===0&&n.startsWith(e,a))return a}return-1}function _(n){if(n[0]==="`"&&/\$\{/.test(n))return null;if(n[0]==='"')try{return JSON.parse(n)}catch{return null}const e=n[0];if(e!=="'"&&e!=="`"||n.at(-1)!==e)return null;let t="";const s={n:`
2
+ `,r:"\r",t:" ",b:"\b",f:"\f",v:"\v",0:"\0"};for(let r=1;r<n.length-1;r++){const l=n[r];if(l!=="\\"){t+=l;continue}const o=n[++r];if(o==null)return null;if(o==="x"&&/^[0-9a-fA-F]{2}$/.test(n.slice(r+1,r+3))){t+=String.fromCodePoint(Number.parseInt(n.slice(r+1,r+3),16)),r+=2;continue}if(o==="u"&&/^[0-9a-fA-F]{4}$/.test(n.slice(r+1,r+5))){t+=String.fromCodePoint(Number.parseInt(n.slice(r+1,r+5),16)),r+=4;continue}t+=s[o]??o}return t}function N(n){const e=n.trim();if(/^(?:true|false|null)$/.test(e))return{ok:!0,value:e==="null"?null:e==="true"};if(/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(e)){const t=Number(e);return Number.isFinite(t)&&Math.abs(t)<=Number.MAX_SAFE_INTEGER?{ok:!0,value:t}:{ok:!1,reason:"unsafe-number"}}if(/^['"`]/.test(e)){const t=_(e);return t===null?{ok:!1,reason:"dynamic-key"}:{ok:!0,value:t}}if(e.startsWith("[")&&e.endsWith("]")){const t=[];for(const s of S(e.slice(1,-1))){if(s.text.startsWith("..."))return{ok:!1,reason:"unsupported-expression"};const r=N(s.text);if(!r.ok)return r;t.push(r.value)}return{ok:!0,value:t}}if(e.startsWith("{")&&e.endsWith("}")){const t={};for(const s of S(e.slice(1,-1))){const r=T(s.text);if(r<0||s.text.startsWith("...")||s.text.startsWith("["))return{ok:!1,reason:"unsupported-expression"};const l=s.text.slice(0,r).trim(),o=$.test(l)?l:_(l);if(o===null)return{ok:!1,reason:"unsupported-expression"};const a=N(s.text.slice(r+1));if(!a.ok)return a;t[o]=a.value}return{ok:!0,value:t}}return{ok:!1,reason:"unsupported-expression"}}function O(n,e,t){if(!n.file)return null;const s=n.source??"",r=(n.start??0)+e,l=(n.start??0)+t,o=s.slice(0,r);return{file:n.file,start:r,end:l,line:o.split(`
3
+ `).length,column:r-o.lastIndexOf(`
4
+ `)}}function I(n,e,t,s,r,l,o={}){return U.has(t)||(t="unsupported-expression"),{kind:n,expression:e,ownerSymbol:s.ownerSymbol??null,targetSymbol:o.targetSymbol??null,origin:O(s,r,l)??{file:s.file??"<unknown>",start:0,end:Math.max(1,e.length),line:1,column:1},resolutionPath:o.resolutionPath??[],resolution:"unresolved",reason:t}}function V(n,e,t,s){const r=N(n);if(r.ok)return{kind:"literal",value:r.value,...O(e,t,s)?{origin:O(e,t,s)}:{}};const l=n.trim(),o=$.test(l)||/^`[\s\S]*\$\{/.test(l)?"parameter":/\(/.test(l)?"call":"unknown",a=r.reason==="unsafe-number"?r.reason:o==="unknown"?"unsupported-expression":"dynamic-key";return I(o,l,a,e,t,s)}function Y(n,e={}){const t=String(n??""),s=y(t);if(!s.text)return{segments:[I("unknown","","unsupported-expression",e,s.start,Math.max(s.end,s.start+1))]};if(s.text.startsWith("[")&&s.text.endsWith("]")){const l=s.start+1;return{segments:S(s.text.slice(1,-1)).map(o=>V(o.text,e,l+o.start,l+o.end))}}const r=$.test(s.text)?e.bindings?.get?.(s.text)??e.bindings?.[s.text]:null;return Array.isArray(r)&&r.length===1?Y(r[0].expression,e):{segments:[V(s.text,e,s.start,s.end)]}}function j(n){const e=(n?.segments??[]).map(t=>t.kind==="literal"?{kind:"literal",value:t.value}:Object.fromEntries(["kind","expression","ownerSymbol","targetSymbol","origin","resolutionPath","resolution","reason"].filter(s=>Object.hasOwn(t,s)).map(s=>[s,t[s]])));return Q("cache-key",{segments:e})}function Z(n){return(n?.segments??[]).every(e=>e.kind==="literal"||e.kind==="wildcard"||e.resolution==="resolved")?"resolved":"unresolved"}function ee(n,e){return(n?.declarations??[]).find(t=>t.key===e)??null}function te(n,e,t){const s=(n?.declarations??[]).filter(l=>l.file===e&&l.name===t);if(s.length===1)return s[0].key;const r=(n?.imports??[]).filter(l=>l.file===e&&l.local===t&&l.resolution==="resolved");return r.length===1?r[0].target:null}function L(n){const e=f=>{let i=f;for(;i<n.length;){if(/\s/.test(n[i])){i++;continue}if(n[i]==="/"&&n[i+1]==="/"){for(i+=2;i<n.length&&n[i]!==`
5
+ `;)i++;continue}if(n[i]==="/"&&n[i+1]==="*"){const x=n.indexOf("*/",i+2);if(x<0)return-1;i=x+2;continue}break}return i},t=e(0);if(n.slice(t,t+6)!=="return"||/[\w$]/.test(n[t+6]??""))return null;let s=t+6;for(;n[s]===" "||n[s]===" ";)s++;if(s>=n.length||/[\r\n\u2028\u2029]/.test(n[s]))return null;const r={"(":")","[":"]","{":"}"},l=new Set(Object.values(r)),o=[];let a=null,h=!1,d=null,c=s,k=-1;for(let f=s;f<n.length;f++){const i=n[f],x=n[f+1];if(d==="line"){i===`
6
+ `&&(d=null);continue}if(d==="block"){i==="*"&&x==="/"&&(d=null,f++);continue}if(a){c=f+1,h?h=!1:i==="\\"?h=!0:i===a&&(a=null);continue}if(i==="/"&&x==="/"){d="line",f++;continue}if(i==="/"&&x==="*"){d="block",f++;continue}if(i==="'"||i==='"'||i==="`"){a=i,c=f+1;continue}if(r[i])o.push(r[i]);else if(l.has(i)){if(o.pop()!==i)return null}else if(i===";"&&o.length===0){k=f;break}/\s/.test(i)||(c=f+1)}if(a||d==="block"||o.length||c<=s||k>=0&&e(k+1)!==n.length||k<0&&e(c)!==n.length)return null;const u=y(n,s,c);return u.text?{text:u.text,start:u.start}:null}function ne(n,e){const t=e.find(l=>l.file===n.file)?.source??"";if(n.kind==="const"&&t[n.bodyStart-1]!=="{"){const l=t.slice(n.bodyStart,n.end);let o=y(l);return o.text.endsWith(";")&&(o=y(l,o.start,o.end-1)),{source:t,expression:o.text,start:n.bodyStart+o.start}}const s=t.slice(n.bodyStart,Math.max(n.bodyStart,n.end-1)),r=L(s);return r?{source:t,expression:r.text,start:n.bodyStart+r.start}:{source:t,expression:"",start:n.bodyStart}}function re(n,e){const t=y(n);if(!t.text.startsWith("{")||!t.text.endsWith("}"))return null;for(const s of S(t.text.slice(1,-1))){const r=T(s.text);if(r<0)continue;const l=s.text.slice(0,r).trim();if(($.test(l)?l:_(l))===e){const a=y(s.text,r+1);return{text:a.text,start:t.start+1+s.start+a.start}}}return null}function se(n){const e=q(n,"=>");if(e>=0){let t=n.slice(0,e).trim().replace(/^async\s+/,"");t.startsWith("(")&&t.endsWith(")")&&(t=t.slice(1,-1));const s=S(t).map(l=>l.text.split(/[:=]/)[0].trim()).filter(l=>$.test(l));let r=y(n,e+2);if(r.text.startsWith("{")&&r.text.endsWith("}")){const l=L(r.text.slice(1,-1));if(!l)return{params:s,body:"",start:r.start+1};r={text:l.text,start:r.start+1+l.start}}return{params:s,body:r.text,start:r.start}}return null}function D(n,e,t){const s=y(String(n??"")),r={text:s.text,options:{...e,start:(e.start??0)+s.start}};return t.get(r.text)??r}function b(n,e,t="dynamic-key",s=null,r=[]){return{patterns:[{segments:[I(/\(/.test(n)?"call":"parameter",n,t,e,0,Math.max(1,n.length),{targetSymbol:s,resolutionPath:r})]}],provenance:s?[{method:"symbol-evaluation",targetSymbol:s,resolutionPath:r}]:[],resolution:"unresolved",reason:t}}function B(n){const e=new Map;for(const t of n)e.set(E(j(t).material),t);return[...e.entries()].sort(([t],[s])=>t.localeCompare(s)).map(([,t])=>t)}function W(n,e,t){const s=D(n,e,t.env),r=s.text.replace(/\s+as\s+const\s*$/,"");e=s.options;const l=q(r,"?");if(l>=0){const p=q(r,":",l+1);if(p>l){const m=W(r.slice(l+1,p),{...e,start:(e.start??0)+l+1},t),g=W(r.slice(p+1),{...e,start:(e.start??0)+p+1},t),v=B([...m.patterns,...g.patterns]);return{patterns:v,provenance:[...m.provenance,...g.provenance],resolution:v.every(K=>Z(K)==="resolved")?"resolved":"unresolved",reason:m.reason??g.reason??null}}}if(r.startsWith("[")&&r.endsWith("]")){let p=[{segments:[]}];for(const m of S(r.slice(1,-1))){const g=m.text.match(/^\.\.\.([\s\S]+)$/),v={...e,start:(e.start??0)+1+m.start+(g?3:0)},X=W(g?g[1]:m.text,v,t).patterns.map(A=>g||A.segments.length===1?A.segments:[I("unknown",m.text,"unsupported-expression",v,0,m.text.length)]);p=p.flatMap(A=>X.map(H=>({segments:[...A.segments,...H]})))}return p=B(p),{patterns:p,provenance:[],resolution:p.every(m=>Z(m)==="resolved")?"resolved":"unresolved",reason:null}}const o=N(r);if(o.ok)return{patterns:[{segments:[{kind:"literal",value:o.value,...O(e,0,r.length)?{origin:O(e,0,r.length)}:{}}]}],provenance:[],resolution:"resolved",reason:null};const a=r.match(/^([A-Za-z_$][\w$]*)(?:\.([A-Za-z_$][\w$]*))?\s*\(([\s\S]*)\)$/),h=r.match(/^([A-Za-z_$][\w$]*)$/);if(!a&&!h)return b(r,e,o.reason??"unsupported-expression");const d=(a??h)[1],c=a?.[2]??null,k=e.bindings?.get?.(d);if(!a&&k?.length===1&&!(e.units??[]).some(p=>p.file===e.file))return W(k[0].expression,e,t);const u=te(e.symbolIndex,e.file,d);if(!u)return b(r,e,"dynamic-key");if(t.seen.has(u)||t.depth>=24)return b(r,e,"dynamic-key",u,[...t.path,u]);const f=ee(e.symbolIndex,u);if(!f)return b(r,e,"dynamic-key",u,[...t.path,u]);const i=ne(f,e.units??[]),x=c?re(i.expression,c):{text:i.expression,start:0};if(x==null||x.text==="")return b(r,e,"unsupported-expression",u,[...t.path,u]);let R=x.text,F=i.start+x.start;const P=a?se(R):null,G=a?r.indexOf("(")+1:0,C=a?S(a[3]).map(p=>D(p.text,{...e,start:(e.start??0)+G+p.start},t.env)):[],z=new Map(t.env);if(P)P.params.forEach((p,m)=>{C[m]&&z.set(p,C[m])}),R=P.body,F+=P.start;else if(a&&!c&&(f.kind==="function"||f.kind==="const"&&f.supported))i.source.slice(f.paramsStart,f.paramsEnd).split(",").map(m=>m.split(/[:=]/)[0].trim()).forEach((m,g)=>{$.test(m)&&C[g]&&z.set(m,C[g])});else if(a)return b(r,e,"unsupported-expression",u,[...t.path,u]);const J={...e,file:f.file,source:i.source,start:F,ownerSymbol:u},w=W(R,J,{depth:t.depth+1,seen:new Set([...t.seen,u]),path:[...t.path,u],env:z});w.provenance.push({method:"symbol-evaluation",targetSymbol:u,resolutionPath:[...t.path,u]});const M=e.contracts?.factories?.[u];if(M!=null){if(!Array.isArray(M)||M.some(v=>!Array.isArray(v?.segments)))return b(r,e,"contract-conflict",u,[...t.path,u]);const p=B(M),m=p.map(v=>E(j(v).material)),g=w.patterns.map(v=>E(j(v).material));return w.resolution==="resolved"&&E(g)!==E(m)?{...b(r,e,"contract-conflict",u,[...t.path,u]),provenance:[...w.provenance,{method:"explicit-contract",targetSymbol:u,resolutionPath:[...t.path,u]}]}:{patterns:p,provenance:[...w.provenance,{method:"explicit-contract",targetSymbol:u,resolutionPath:[...t.path,u]}],resolution:"resolved",reason:null}}return w}function ie(n,e={}){return W(String(n??""),e,{depth:0,seen:new Set,path:[e.ownerSymbol].filter(Boolean),env:new Map})}export{j as cacheKeyIdentity,ce as deriveOperationReadPairs,fe as effectCoverage,ie as evaluateCacheKeyExpression,pe as keyCoverage,Z as keyResolution,Y as parseTypedKeyExpression,me as reduceCoverage};
@@ -0,0 +1 @@
1
+ import{parseObligationId as b}from"./plan-join.mjs";const y={P0:0,P1:1,P2:2,P3:3},j=(n,r)=>(y[n]??9)<=(y[r]??9)?n:r;function V(n){const r=[3,4].includes(n?.schemaVersion),u=new Map;for(const c of n?.plan?.journeys??[])for(const s of c.targets??[])for(const i of s.obligations??[]){const d=r?i.mutationId&&i.surfaceId?i:null:b(i.id);if(!d)continue;const f=`${d.mutationId}|${d.surfaceId}`;u.has(f)||u.set(f,[]),u.get(f).push({...i,...d,route:s.route??null,journeyId:c.journeyId,flow:c.flow??null})}return u}function v(n,r=[]){return r.length?{runtime:"disagreed",reason:null}:n.length?n.filter(s=>s.status==="disagreed").length?{runtime:"disagreed",reason:null}:n.filter(s=>s.status==="agreed").length?{runtime:"agreed",reason:null}:{runtime:"unmeasured",reason:[...new Set(n.map(i=>i.reason??i.status))].sort().join(", ")}:{runtime:"unmeasured",reason:"no-planned-obligation-for-this-cause"}}function A(n,r){return n.waived?"waived":r==="disagreed"?"confirmed":r==="agreed"?"not-reproduced":"unmeasured"}function L({candidates:n=[],observed:r=null,surfaces:u=[],mutations:c=[]}){const s=V(r),i=new Map(u.map(e=>[e.id,e])),d=new Map(c.map(e=>[e.id,e])),f=r?.status==="measured"||r?.status==="partial",w=new Map;for(const e of r?.findings??[])e.rootCauseId&&(w.has(e.rootCauseId)||w.set(e.rootCauseId,[]),w.get(e.rootCauseId).push(e));const m=n.map(e=>{const o=(e.surfaceIds??[]).flatMap(t=>s.get(`${e.mutationId}|${t}`)??[]),a=w.get(e.rootCauseId)??[],l=r?v(o,a):{runtime:"unmeasured",reason:"no-runtime-evidence"},M=new Set(a.map(t=>t.route).filter(Boolean)),R=new Set(a.map(t=>t.surfaceId).filter(Boolean)),P=(e.surfaceIds??[]).map(t=>{const g=i.get(t),C=o.filter(k=>k.surfaceId===t);let p;return C.length?p=v(C):[3,4].includes(r?.schemaVersion)&&R.has(t)?p={runtime:"disagreed",reason:"finding-on-this-surface"}:![3,4].includes(r?.schemaVersion)&&g?.route&&M.has(g.route)?p={runtime:"disagreed",reason:"finding-on-this-route"}:p={runtime:"unmeasured",reason:r?"no-planned-obligation-for-this-surface":"no-runtime-evidence"},{surfaceId:t,route:g?.route??C[0]?.route??null,component:g?.component??null,runtime:p.runtime,reason:p.reason}}).sort((t,g)=>t.surfaceId.localeCompare(g.surfaceId)),h=d.get(e.mutationId)??null;return{rootCauseId:e.rootCauseId,kind:e.kind,mutationId:e.mutationId,mutation:h?.hook?`${h.hook}()`:null,source:e.source??(h?{file:h.file,line:h.line}:null),family:e.family??null,label:A(e,l.runtime),runtime:l.runtime,reason:l.reason,affectedSurfaces:P,routes:[...new Set(P.map(t=>t.route).filter(Boolean))].sort(),obligations:{planned:o.length,agreed:o.filter(t=>t.status==="agreed").length,disagreed:o.filter(t=>t.status==="disagreed").length,unmeasured:o.filter(t=>t.status==="unmeasured"||t.status==="reached").length},severity:"P3"}}),B=new Set(m.map(e=>e.rootCauseId)),$=new Map;if(f)for(const e of r?.findings??[]){const o=e.rootCauseId;if(!o||B.has(o))continue;const a=$.get(o)??{rootCauseId:o,kind:"coherence.unpredicted-runtime-cause",label:"confirmed",runtime:"disagreed",reason:null,mutationId:null,mutation:null,source:null,family:null,affectedSurfaces:[],routes:new Set,obligations:{planned:0,agreed:0,disagreed:0,unmeasured:0},severity:e.severity??"P3",surfaceIds:new Set};a.severity=j(e.severity??"P3",a.severity);const l=e.surfaceId??e.route??null;l&&!a.surfaceIds.has(l)&&(a.surfaceIds.add(l),a.affectedSurfaces.push({surfaceId:e.surfaceId??null,route:e.route??null,component:null,runtime:"disagreed",reason:null})),e.route&&a.routes.add(e.route),$.set(o,a)}for(const e of $.values())e.routes=[...e.routes].sort(),e.affectedSurfaces.sort((o,a)=>String(o.surfaceId).localeCompare(String(a.surfaceId))),delete e.surfaceIds,m.push(e);const S={confirmed:0,unmeasured:1,"not-reproduced":2,waived:3};m.sort((e,o)=>(S[e.label]??9)-(S[o.label]??9)||(y[e.severity]??9)-(y[o.severity]??9)||String(e.rootCauseId).localeCompare(String(o.rootCauseId)));const I={causes:m.length,confirmed:0,notReproduced:0,unmeasured:0,waived:0};for(const e of m)e.label==="confirmed"?I.confirmed++:e.label==="not-reproduced"?I.notReproduced++:e.label==="waived"?I.waived++:I.unmeasured++;return{status:r&&f?"measured":"unmeasured",reason:r?null:"no-runtime-evidence",causes:m,counts:I}}const E=n=>n.component?`${n.route}:${n.component}`:n.route??n.surfaceId;function N(n,{max:r=10}={}){const u=[],c=n?.causes??[];if(!c.length)return u;u.push(`causes: ${n.counts.confirmed} confirmed / ${n.counts.notReproduced} not reproduced / ${n.counts.unmeasured} unmeasured of ${n.counts.causes}`);for(const s of c.slice(0,r)){const i=s.source?.file?` (${s.source.file}:${s.source.line})`:"";u.push(` [${s.label}] ${s.mutation??s.mutationId??s.rootCauseId}${i} writes ${s.family??"(unknown)"}`);for(const d of s.affectedSurfaces)u.push(` ${E(d)} -- ${d.runtime}${d.reason?` (${d.reason})`:""}`)}return c.length>r&&u.push(` ... ${c.length-r} more`),u}export{L as linkCauses,N as renderCauses};
@@ -0,0 +1 @@
1
+ function d(t=[]){const u=new Set(t),a=new Map([...u].map(e=>[e,{released:!1,waiters:[]}])),s=[],n=(e,r,o)=>s.push({sequence:s.length,type:e,actor:r??null,barrier:o});function c(e){if(!a.has(e))throw new Error(`unknown barrier: ${e}`);return a.get(e)}return{async arrive(e,r){c(r),n("arrive",e,r)},release(e){const r=c(e);if(!r.released){r.released=!0,n("release",null,e);for(const o of r.waiters.splice(0))o()}},wait(e,r,o){const i=c(r);return n("wait",e,r),i.released?(n("continue",e,r),Promise.resolve()):new Promise((l,f)=>{const w=setTimeout(()=>f(new Error(`barrier timeout: ${r}`)),o);i.waiters.push(()=>{clearTimeout(w),n("continue",e,r),l()})})},events:()=>s.map(e=>({...e}))}}function m(t){return!Array.isArray(t?.actors)||t.actors.length<2||new Set(t.actors).size!==t.actors.length?{ok:!1,reason:"concurrency requires distinct actors"}:Array.isArray(t.schedule)?!t.accept||typeof t.accept.kind!="string"?{ok:!1,reason:"correctness case requires an accepted outcome"}:["conflict","merge","last-writer-wins","observe-only"].includes(t.accept.kind)?{ok:!0}:{ok:!1,reason:"unsupported accepted outcome"}:{ok:!1,reason:"concurrency requires a schedule"}}export{d as createBarrierController,m as validateConcurrencySpec};