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
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import p from"node:fs";import b from"node:path";import{createHash as ue}from"node:crypto";import{pathToFileURL as ot}from"node:url";import{emptyCoherenceObserved as st}from"../lib/qa/coherence/contracts.mjs";import{COHERENCE_OBSERVED_SCHEMA_VERSION as rt,compareObservationEvidence as He,runObservationCase as it}from"../lib/qa/coherence/observe.mjs";import{buildFlowExecutionIdentity as ct,createCoherenceIdentity as de,validateCoherenceObservedIdentities as lt,validateCoherencePlanIdentities as ut,validateTypedCrudDeclaration as dt}from"../lib/qa/coherence/identity.mjs";import{representationFinding as ft}from"../lib/qa/coherence/representations.mjs";import{compareProvenance as pt,expectedSourceIdentity as mt,loadServedProvenance as ht}from"../lib/qa/coherence/provenance.mjs";import{evaluateMutationPolicy as vt}from"../lib/qa/mutation-policy.mjs";import{selectCoherenceJourneys as gt}from"../lib/qa/coherence-planner.mjs";import{coherenceRootCauseId as Je}from"../lib/qa/coherence/analyze.mjs";import{runBounded as yt,checkFlowIsolation as bt,fixtureNamespaceFor as Be}from"../lib/qa/coherence/flow-pool.mjs";import{classifyCoherencePlan as wt,joinPlan as jt,matchFlow as Le}from"../lib/qa/coherence/plan-join.mjs";import{writeJson as It}from"../lib/qa/fs.mjs";import{canonicalize as m,jcsDigest as E}from"../lib/qa/jcs.mjs";import{buildCoherenceEvidenceEnvelope as xt,coherenceEnvelopeMismatch as we}from"../lib/qa/marathon-coherence.mjs";import{loadSourceSnapshot as kt}from"../lib/qa/source-snapshot.mjs";import{validate as je}from"../lib/qa/schema.mjs";const Y=process.argv.slice(2),F=(e,t=null)=>{const o=Y.indexOf("--"+e);return o>=0&&Y[o+1]!=null?Y[o+1]:t},Z=e=>Y.includes("--"+e),k=b.resolve(F("repo",".")),J=String(F("base","")).replace(/\/$/,""),U=F("lane","public-runtime"),fe=Number(F("max-mutations","20")),pe=F("json",b.join(k,".verify","coherence-observed.json")),Ie=F("flow"),P=Y.includes("--runid"),$=F("runid",`coh-${Date.now().toString(36)}`),ee=F("plan",null),te=Math.max(1,Math.min(4,Math.trunc(Number(F("flow-workers","1")))||1)),xe=F("evidence-envelope",null),B=/^sha256:[a-f0-9]{64}$/,A=b.join(k,".verify","coherence-journals",`${Ie?E({runId:$,flow:Ie}):E($)}.json`);function G(e){p.mkdirSync(b.dirname(pe),{recursive:!0});const t=pe+`.pending-${process.pid}`;p.writeFileSync(t,JSON.stringify(e,null,2)+`
3
+ `,{mode:384}),p.renameSync(t,pe)}function St(e){const t={...e};return delete t.documentDigest,{...t,documentDigest:E(t)}}function Et(){const e=p.realpathSync(k),t=b.join(e,".verify"),o=b.join(t,"coherence-journals");try{const n=p.lstatSync(t);if(!n.isDirectory()||n.isSymbolicLink()||p.realpathSync(t)!==t)return"verify-root-symlink";p.existsSync(o)||p.mkdirSync(o,{mode:448});const a=p.lstatSync(o);return a.isSymbolicLink()?"coherence-journals-symlink":!a.isDirectory()||p.realpathSync(o)!==o?"coherence-journals-not-contained":null}catch(n){return`coherence-journals-unavailable:${n?.code??"unknown"}`}}let ke={discovered:0,resolved:0,unresolved:0,planned:0},Se={declared:0};function Ve(e){e.counts.cases={planned:e.cases.length,completed:e.cases.filter(n=>n.status==="measured").length,unmeasured:e.cases.filter(n=>n.status!=="measured").length},e.counts.targets=e.cases.reduce((n,a)=>{for(const u of Object.keys(n))n[u]+=a.counts.targets[u]??0;return n},{planned:0,reached:0,compared:0,agreed:0,unmeasured:0});const t=n=>e.cases.reduce((a,u)=>{const g=u.counts?.[n]??{};for(const c of Object.keys(a))a[c]+=g[c]??0;return a},{planned:0,completed:0,unmeasured:0});e.counts.cleanups=t("cleanups"),e.counts.personas=t("personas"),e.counts.contexts=t("contexts"),e.counts.obligations={...ke,reached:e.counts.targets.reached,compared:e.counts.targets.compared,agreed:e.counts.targets.agreed};const o=e.cases.filter(n=>n.mutationAccepted===!0).length;e.counts.mutations={declared:Se.declared??0,executed:o}}function Te(e){const t=[e.agreed,e.compared,e.reached,e.planned,e.resolved,e.discovered];return!t.every((o,n)=>n===0||t[n-1]<=o)||e.agreed<0?!1:e.disagreed==null||e.agreed+e.disagreed===e.compared}function _e(e){if(!Te(e.counts.obligations)){e.status="invalid",e.reason="coverage-not-monotone";return}e.plan?.status==="measured"&&!Te(e.plan.coverage)&&(e.status="invalid",e.reason="plan-coverage-not-monotone")}function w(e,t){e.status="invalid",e.reason=t,Ve(e),_e(e),G(e),console.error("qa measure coherence: INVALID -- "+e.reason),console.error(" full evidence: "+pe),process.exit(2)}const s=st({runId:$,base:J,lane:U});s.counts.obligations={...ke},s.counts.mutations={declared:Se.declared,executed:0},P&&!xe&&w(s,"evidence-envelope-required");let W;try{W=JSON.parse(p.readFileSync(b.join(k,"verify.crud.json"),"utf8"))}catch{w(s,"invalid-or-missing-verify-crud")}W.schema_version===3&&(s.schemaVersion=rt);try{const e=JSON.parse(p.readFileSync(new URL("../schemas/verify-crud.schema.json",import.meta.url),"utf8")),t=je(e,W);if(t.length)throw new TypeError(`verify-crud-schema:${t[0].path||"(root)"}`);dt(W)}catch(e){w(s,`coherence-identity-invalid:${e.message}`)}const L=vt({mutate:Z("mutate"),base:J,lane:U,declaration:W,flowName:Ie,maxMutations:fe,cleanupCapable:!0,fixtureNamespace:`coherence-${$}`,overrideNonLocal:Z("i-know-this-is-not-production")});L.ok||w(s,L.reason);const q=L.flows.filter(e=>[2,3].includes(W.schema_version)&&e.crosscheck);q.length!==L.flows.length&&w(s,"selected-flow-missing-crosscheck"),Se={declared:q.length};const V=gt({obligations:q.flatMap(e=>e.crosscheck.targets.map(t=>({id:`${e.name}:${t.identity?.id??t.id}`,weight:1,mandatory:!0}))),candidates:q.map(e=>({id:e.name,obligationIds:e.crosscheck.targets.map(t=>`${e.name}:${t.identity?.id??t.id}`),cost:1})),budget:fe});ke={discovered:V.coverage.discovered,resolved:V.coverage.resolved,unresolved:V.coverage.unresolved,planned:V.coverage.planned},V.ok||w(s,`mandatory-obligation-budget: ${V.reason}`);let O=V.selected.map(e=>q.find(t=>t.name===e.id)),R=null,T=null;if(ee){try{T=JSON.parse(p.readFileSync(ee,"utf8"))}catch(o){w(s,`coherence-plan-invalid:${String(o?.message??o).split(`
4
+ `)[0]}`)}R=T?.dimensions?.coherenceObligations??null,R?.status!=="measured"&&w(s,`coherence-plan-${T==null?"unreadable":R==null?"absent":R.status}`);try{if(wt(R.value)!=="current")throw new TypeError("legacy-coherence-plan-non-proof");const o=JSON.parse(p.readFileSync(new URL("../schemas/coherence-plan.schema.json",import.meta.url),"utf8")),n=je(o,R.value);if(n.length)throw new TypeError(`coherence-plan-schema:${n[0].path||"(root)"}`);ut(R.value)}catch(o){w(s,`coherence-identity-invalid:${o.message}`)}const e=R.value?.plan?.selected??[],t=new Map;e.forEach((o,n)=>{const a=Le(o,q);a&&!t.has(a.name)&&t.set(a.name,n)}),O=O.map((o,n)=>({flow:o,index:n})).sort((o,n)=>(t.has(o.flow.name)?t.get(o.flow.name):1/0)-(t.has(n.flow.name)?t.get(n.flow.name):1/0)||o.index-n.index).map(({flow:o})=>o)}const Ct=b.join(k,"verify.coherence-provenance.mjs");let Ke;try{Ke=mt(k)}catch{w(s,"source-identity-unmeasured")}const ne=await ht({repo:k,base:J,adapterPath:Ct});ne.status!=="measured"&&w(s,`served-provenance-${ne.reason??ne.status}`);const Ee=pt(Ke,ne.provenance);Ee.status!=="measured"&&w(s,`served-provenance-${Ee.problems.join(",")}`),s.provenance=[{...ne.provenance,status:Ee.status}];let I=null;if(P){const e=b.join(p.realpathSync(k),".verify","marathon")+b.sep;try{const c=p.lstatSync(xe),h=p.realpathSync(xe);if(!c.isFile()||c.isSymbolicLink()||!h.startsWith(e))throw new Error("path-outside-invocation-root");I=JSON.parse(p.readFileSync(h,"utf8"))}catch(c){w(s,`evidence-envelope-invalid:${String(c?.message??c).split(`
5
+ `)[0]}`)}let t;const o=I?.outerEvidenceIdentity?.material,n={marathonRunId:$,repoRealPath:p.realpathSync(k),sourceDigest:`sha256:${kt(k,{skipRules:/(^|\/)(?:\.git|\.verify|node_modules)(?:\/|$)/}).identity.sourceDigest}`,base:J,lane:U,flowWorkers:te,planDigest:T?`sha256:${E(T.dimensions??T)}`:null,flowDeclarationDigest:`sha256:${ue("sha256").update(p.readFileSync(b.join(k,"verify.crud.json"))).digest("hex")}`,principalDigest:null,configurationDigest:`sha256:${I?.coordinates?.configuration?.digest??""}`,toolProtocol:"marathon-adapter/v1"},a=[b.join(k,"verify.roles.json"),b.join(k,".verify","roles.json")].find(c=>p.existsSync(c))??null;a&&(n.principalDigest=`sha256:${ue("sha256").update(p.readFileSync(a)).digest("hex")}`);const u=Object.entries(n).find(([c,h])=>o?.[c]!==h);u&&w(s,`evidence-envelope-mismatch:outer-${u[0]}`);try{t=await xt({repo:k,runId:$,base:J,lane:U,flowWorkers:te,planPath:ee,evidenceIdentity:I?.outerEvidenceIdentity?.material,configurationMaterial:I?.coordinates?.configuration?.material,maxMutations:fe,overrideNonLocal:Z("i-know-this-is-not-production")})}catch(c){w(s,`evidence-envelope-local-recompute-failed:${String(c?.message??c).split(`
6
+ `)[0]}`)}const g=we(t,I);g&&w(s,`evidence-envelope-mismatch:${g}`),s.envelope=I}if(P){const e=Et();e&&w(s,`journal-root-invalid:${e}`)}function Ce(e){Ve(e),e.status=e.cases.length>0&&e.cases.every(t=>t.status==="measured")?"measured":"invalid",_e(e)}let me=null,ae=1;if(te>1)if(O.length<2)me="fewer than two flows to parallelise";else{const e=bt(O);e.ok?ae=Math.min(te,O.length):me=e.reason}ae>1&&(ae=1,me="mutating coherence flows serialize until resource coloring is available"),s.parallelism={requested:te,used:ae,reason:me};const he=new Map,Ue=new Map;if(R?.status==="measured")for(const e of R.value?.plan?.selected??[]){const t=Le(e,q);t&&!he.has(t.name)&&(he.set(t.name,e.mutationId),Ue.set(t.name,e))}const ve=new Map,Ge=new Map,We=e=>String(e??"").split("/").filter(Boolean).pop()?.toLowerCase().replace(/s$/,"")??"",ze=new Map;try{const e=JSON.parse(p.readFileSync(b.join(k,".verify","inventory.json"),"utf8")),t=new Map,o=new Map;for(const n of e?.coherence?.candidates??[])if(n?.rootCauseId&&(n.mutationId&&!ve.has(n.mutationId)&&ve.set(n.mutationId,n.rootCauseId),n.family)){t.set(n.family,[...t.get(n.family)??[],n.rootCauseId]);const a=We(n.family);o.set(a,[...new Set([...o.get(a)??[],n.rootCauseId])])}for(const[n,a]of t)a.length===1&&Ge.set(n,a[0]);for(const[n,a]of o)a.length===1&&ze.set(n,a[0])}catch{}function De(e){const t=he.get(e.name)??e.crosscheck?.mutationIdentity?.id;if(t&&ve.has(t))return ve.get(t);const o=e.crosscheck?.entity,n=e.identity?null:Ge.get(o)??ze.get(We(o));return n||(t?Je({kind:"coherence.no-invalidation-candidate",mutationId:t,family:e.crosscheck.entity,readIds:[]}):Je({kind:"runtime",mutationId:e.name,family:e.crosscheck.entity,readIds:[]}))}const Dt={namespace:"coherence/mutation-permit",version:1,allowed:!0,mutateRequested:Z("mutate"),overrideNonLocalRequested:Z("i-know-this-is-not-production"),base:J,lane:U,maxMutations:fe,cleanupCapable:!0,fixtureNamespaceRule:"fixtureNamespaceFor(runId,flowName)/v1",selectedFlows:L.flows.map(e=>e.name),cleanup:L.cleanup};function Mt(e){return ct({flow:e,planMutation:he.get(e.name)??null,planJourney:Ue.get(e.name)??null,mutationPermitMaterial:Dt})}const z=new Map(O.map(e=>[e.name,Mt(e)]));function Qe(e,{mutationAccepted:t=!1,cleanupOk:o=null,verificationOk:n=null,causalContract:a=!1}={}){return{namespace:"coherence/flow-observation",version:1,mutationAccepted:t,targets:[],personasCompleted:0,contextsCompleted:0,causal:{status:"unmeasured",reason:a?"causal-proof-unavailable":"causal-contract-absent",accepted:t,fixture:null,effect:null,convergence:null,observers:[]},cleanup:{attempted:t,cleanupOk:o,verificationOk:n},failureReason:e}}function At(e,t,o,n=null){const a=e?.causal;if(!a)return t.crosscheck?.causal?"causal-proof-unavailable":null;const u=(f,C)=>f&&typeof f=="object"&&!Array.isArray(f)&&Object.keys(f).sort().join("\0")===[...C].sort().join("\0"),g=o.flatMap(f=>[...(f.observations??[]).map(C=>({targetId:f.targetId??null,surfaceId:f.surfaceId??null,route:f.route??null,persona:C.persona,context:C.context,status:C.status,agreed:C.agreed,projection:C.observed})),f.fresh?{targetId:f.targetId??null,surfaceId:f.surfaceId??null,route:f.route??null,persona:f.fresh.persona,context:f.fresh.context,status:f.fresh.status,agreed:f.fresh.agreed,projection:f.fresh.observed}:null].filter(Boolean));if(!u(a,["status","reason","accepted","fixture","effect","convergence","observers"])||m(a.observers)!==m(g))return"causal-evidence-shape";if(!t.crosscheck?.causal)return a.status==="unmeasured"&&a.reason==="causal-contract-absent"?null:"causal-contract-unexpected";if(a.accepted!==e.mutationAccepted)return"causal-mutation-acceptance-conflict";if(a.status==="unmeasured"&&a.reason==="causal-capability-unavailable")return"causal-capability-unavailable";if(a.status!=="measured")return a.reason??"causal-proof-unmeasured";if(a.accepted!==!0||a.reason!==null)return"causal-measured-status-conflict";const c=t.crosscheck.causal,h=Be($,t.name);if(!a.fixture||!u(a.fixture,["namespace","entity","entityId"])||a.fixture.namespace!==h||a.fixture.entity!==c.entity||!B.test(a.fixture.entityId??""))return"causal-fixture-identity-mismatch";const d=a.effect;if(!d||!u(d,["kind","field","verified","beforeHash","expectedHash","afterHash","settledHash","beforeVersionHash","afterVersionHash","versionAdvanced"])||d.kind!==c.effect.kind||d.field!==c.effect.field||d.verified!==!0||!B.test(d.beforeHash??"")||!B.test(d.expectedHash??"")||!B.test(d.afterHash??"")||!B.test(d.settledHash??"")||d.expectedHash!==d.afterHash||d.afterHash!==d.settledHash||d.kind==="field-change"&&(d.beforeHash===d.afterHash||d.beforeVersionHash!==null||d.afterVersionHash!==null||d.versionAdvanced!==null)||d.kind==="version-advance"&&(!B.test(d.beforeVersionHash??"")||!B.test(d.afterVersionHash??"")||d.beforeVersionHash===d.afterVersionHash||d.versionAdvanced!==!0))return"causal-effect-invalid";const j=a.convergence;if(!j||!u(j,["deadlineMs","elapsedMs","converged"])||j.deadlineMs!==c.convergence.deadlineMs||j.converged!==!0||!Number.isFinite(j.elapsedMs)||j.elapsedMs<0||j.elapsedMs>j.deadlineMs)return"causal-convergence-invalid";if(n&&(!Number.isFinite(n.durationMs)||j.elapsedMs>n.durationMs+1))return"causal-timing-conflict";const S=g.filter(f=>f.context==="fresh-context"||f.persona===t.crosscheck.writer);return!S.length||!S.every(f=>f.status==="measured"&&f.agreed===!0)?"causal-observer-projection-not-converged":null}function Xe(e,t,o,n=null){const a=i=>{throw new Error(i)},u=(i,Q)=>i&&typeof i=="object"&&!Array.isArray(i)&&Object.keys(i).sort().join("\0")===[...Q].sort().join("\0");(!u(t,[...["namespace","version","mutationAccepted","targets","personasCompleted","contextsCompleted","cleanup","failureReason"],...Object.hasOwn(t,"causal")?["causal"]:[]])||t.namespace!=="coherence/flow-observation"||t.version!==1||typeof t.mutationAccepted!="boolean"||!Array.isArray(t.targets)||!Number.isInteger(t.personasCompleted)||t.personasCompleted<0||!Number.isInteger(t.contextsCompleted)||t.contextsCompleted<0||!(t.failureReason===null||typeof t.failureReason=="string"))&&a("journal-observation-evidence-shape"),(!u(t.cleanup,["attempted","cleanupOk","verificationOk"])||typeof t.cleanup.attempted!="boolean"||![!0,!1,null].includes(t.cleanup.cleanupOk)||![!0,!1,null].includes(t.cleanup.verificationOk))&&a("journal-observation-cleanup-shape"),(t.cleanup.attempted!==t.mutationAccepted||!t.cleanup.attempted&&(t.cleanup.cleanupOk!==null||t.cleanup.verificationOk!==null))&&a("journal-observation-cleanup-conflict");const c=e.crosscheck,h=!!e.identity,d=i=>i.identity?.id??i.id,j=i=>i.surfaceMatch?.surface?.id??i.id,S={planned:c.targets.length,reached:0,compared:0,agreed:0,unmeasured:0},f=[],C=[],D=new Set;let x=t.failureReason;for(const i of t.targets){(!i||typeof i!="object"||!c.targets.some(r=>d(r)===i.targetId&&j(r)===(i.surfaceId??i.targetId)&&(r.route??null)===(i.route??null))||D.has(i.targetId))&&a("journal-observation-target-conflict"),D.add(i.targetId);const Q=c.targets.find(r=>d(r)===i.targetId),Fe=[...(c.observers??[]).map(r=>({observer:r,context:"warm-cache",transition:Q.transition??null})),...(c.observers??[]).filter(r=>r.persona!==c.writer).map(r=>({observer:r,context:"fresh-context",transition:"fresh-context"}))];(!Array.isArray(i.observations)||i.observations.length!==Fe.length||!i.fresh)&&a("journal-observation-target-shape");const re=r=>u(r,["status","state","hash","reason"])&&["measured","unmeasured"].includes(r.status)&&(r.state===null||["value","empty","absent","failed","withheld","redacted","forbidden","loading"].includes(r.state))&&(r.hash===null||/^sha256:[a-f0-9]{64}$/.test(r.hash))&&(r.reason===null||typeof r.reason=="string")&&(r.status==="measured"?r.state!==null&&r.reason===null:typeof r.reason=="string")&&(r.state==="value"?/^sha256:[a-f0-9]{64}$/.test(r.hash??""):r.hash===null);let N=null;for(let r=0;r<i.observations.length;r++){const v=i.observations[r],{observer:H,context:le,transition:Ne}=Fe[r],at=["persona","context","transition","invariant","status","agreed","reason","comparisonReason","refetched","typed","authoritative","observed",...h?["principalId","contextId","invariantId","expectation"]:[]];(!u(v,at)||v.persona!==H.persona||v.context!==le||v.transition!==Ne||v.invariant!==(H.expect==="visible"?"field-parity":"visibility-policy")||typeof v.refetched!="boolean"||v.typed!==!0||!re(v.authoritative)||!re(v.observed))&&a("journal-observation-principal-conflict"),h&&(v.principalId!==H.identity.id||v.expectation!==H.expect||v.contextId!==de("context",{kind:le,transition:Ne}).id||v.invariantId!==de("invariant",{name:v.invariant}).id)&&a("journal-observation-identity-conflict");const K=He(v.authoritative,v.observed,H,Q);(v.status!==K.status||v.agreed!==(K.status==="measured"?K.agreed:null)||v.reason!==(K.status==="measured"?null:K.reason)||v.comparisonReason!==(K.reason??null))&&a("journal-observation-agreement-conflict"),N&&m(N)!==m(v.authoritative)&&a("journal-observation-authoritative-conflict"),N=v.authoritative}const y=i.fresh,tt=["persona","context","invariants","status","agreed","reason","authoritative","observed",...h?["principalId","contextId","invariantIds","expectation"]:[]];(!u(y,tt)||y.persona!==c.writer||y.context!=="fresh-context"||m(y.invariants)!==m(["field-parity","durability"])||!re(y.authoritative)||!re(y.observed))&&a("journal-observation-fresh-conflict"),h&&(y.principalId!==c.writerIdentity.id||y.expectation!=="visible"||y.contextId!==de("context",{kind:"fresh-context",transition:"fresh-context"}).id||m(y.invariantIds)!==m(["field-parity","durability"].map(r=>de("invariant",{name:r}).id)))&&a("journal-observation-identity-conflict");const X=He(y.authoritative,y.observed,{expect:"visible"},Q);(y.status!==X.status||y.agreed!==(X.status==="measured"?X.agreed:null)||y.reason!==(X.status==="measured"?null:X.reason))&&a("journal-observation-agreement-conflict"),N&&m(N)!==m(y.authoritative)&&a("journal-observation-authoritative-conflict"),N=y.authoritative;const ie=i.observations.filter(r=>!(r.context==="warm-cache"&&r.persona!==c.writer)),ce=y.status==="measured"&&ie.every(r=>r.status==="measured"),Pe=ce&&y.agreed&&ie.every(r=>r.agreed),qe=ce?"measured":"unmeasured",nt=qe==="measured"?["targetId",...h?["surfaceId"]:[],"route","observations","fresh","status","comparison"]:["targetId",...h?["surfaceId"]:[],"route","observations","fresh","status","reason","comparison"];if((!u(i,nt)||i.status!==qe)&&a("journal-observation-target-status-conflict"),ce)(!u(i.comparison,["agreed","authoritative","fresh"])||i.comparison.agreed!==Pe||m(i.comparison.authoritative)!==m(N)||m(i.comparison.fresh)!==m(y.observed))&&a("journal-observation-comparison-conflict");else{const r=y.status==="unmeasured"?y.reason:ie.find(v=>v.status==="unmeasured")?.reason;(i.reason!==r||i.comparison!==null)&&a("journal-observation-comparison-conflict"),x=x??r}if(S.reached++,ce){S.compared++,Pe&&S.agreed++;for(const r of ie){if(r.agreed)continue;const v={targetId:i.targetId,surfaceId:h?i.surfaceId:i.targetId,route:i.route,persona:r.persona},H=r.typed&&r.comparisonReason==="missing-required-summary"?ft({agreed:!1,reason:r.comparisonReason},v):null,le=r.context==="fresh-context"||!y.agreed?"coherence.cross-principal-divergence":r.refetched?"coherence.refetched-stale":"coherence.stale-after-write";f.push({...H??{kind:le,...v,severity:"P1"},rootCauseId:o})}y.agreed||f.push({kind:"coherence.write-not-durable",targetId:i.targetId,surfaceId:h?i.surfaceId:i.targetId,route:i.route,severity:"P0",rootCauseId:o})}C.push(i)}S.unmeasured=Math.max(0,S.planned-S.compared),t.targets.length>S.planned&&a("journal-observation-target-conflict"),t.targets.length<S.planned&&(x=x??"observation-incomplete"),t.mutationAccepted&&(t.cleanup.cleanupOk!==!0||t.cleanup.verificationOk!==!0)&&(x="cleanup-unmeasured-or-failed",f.push({kind:"crud.cleanup-failed",severity:"P1",rootCauseId:o}));const _=1+(c.observers?.length??0),$e=(c.observers??[]).filter(i=>i.persona!==c.writer).length,se=1+$e+c.targets.length*(1+$e);(t.personasCompleted>_||t.contextsCompleted>se)&&a("journal-observation-completion-conflict"),(t.personasCompleted<_||t.contextsCompleted<se)&&(x=x??"observation-incomplete");const et={targets:S,personas:{planned:_,completed:t.personasCompleted,unmeasured:_-t.personasCompleted},contexts:{planned:se,completed:t.contextsCompleted,unmeasured:se-t.contextsCompleted},cleanups:{planned:1,completed:t.cleanup.cleanupOk===!0&&t.cleanup.verificationOk===!0?1:0,unmeasured:t.cleanup.cleanupOk===!0&&t.cleanup.verificationOk===!0?0:1}},Re=At(t,e,C,n);Re&&(x=x??Re);const be={status:x?"invalid":"measured",counts:et,findings:f,cases:C,mutationAccepted:t.mutationAccepted};return t.causal&&(be.causal=t.causal),x&&(be.reason=x),be}let l=null;function oe(){l=St(l),It(A,l)}function M(e,t=null){const o=`${A}.quarantine-${Date.now()}-${process.pid}`;let n=null;if(p.existsSync(A)){const a=p.readFileSync(A);n=ue("sha256").update(a).digest("hex"),p.renameSync(A,o)}l={namespace:"coherence-flow-journal",version:1,runIdDigest:E($),envelope:I?{material:I,digest:I.digest}:null,active:null,records:[],invalid:{reason:e,detail:t,quarantinePath:o,quarantineDigest:n,at:new Date().toISOString()}},oe(),s.resume={journal:A,replayed:[],invalid:l.invalid},w(s,e)}function Ot(e){const t=b.resolve(e.quarantinePath);if(b.dirname(t)!==b.dirname(A)||!b.basename(t).startsWith(`${b.basename(A)}.quarantine-`))return"quarantine-path";if(!p.existsSync(t))return"quarantine-missing";const o=p.lstatSync(t),n=b.join(p.realpathSync(b.dirname(t)),b.basename(t));return!o.isFile()||o.isSymbolicLink()||p.realpathSync(t)!==n?"quarantine-not-regular":!/^[a-f0-9]{64}$/.test(e.quarantineDigest??"")||ue("sha256").update(p.readFileSync(t)).digest("hex")!==e.quarantineDigest?"quarantine-digest":null}function $t(e,t){try{if(!e||typeof e!="object"||Array.isArray(e)||e.namespace!=="coherence-flow-record"||e.version!==1)return"journal-record-namespace-or-version";const o=["namespace","version","envelope","flowExecutionId","flow","cells","observationEvidence","observationEvidenceDigest","payload","payloadDigest","timing","cleanup","failure","recordDigest"];if(Object.keys(e).sort().join("\0")!==o.sort().join("\0"))return"journal-record-shape";const n=(D,x)=>D&&typeof D=="object"&&!Array.isArray(D)&&Object.keys(D).sort().join("\0")===[...x].sort().join("\0");if(!n(e.envelope,["material","digest"])||!n(e.flow,["material","materialDigest"])||!n(e.payload,["caseEntry","outcome"])||!n(e.timing,["startedAt","endedAt","durationMs"])||typeof e.timing.startedAt!="string"||typeof e.timing.endedAt!="string"||!Number.isFinite(e.timing.durationMs)||e.timing.durationMs<0)return"journal-record-shape";const a={...e};if(delete a.recordDigest,e.recordDigest!==E(a))return"journal-record-digest";if(e.envelope?.digest!==I.digest||we(I,e.envelope?.material))return"journal-record-envelope";if(e.flow?.materialDigest!==E(e.flow?.material)||e.flowExecutionId!==e.flow?.materialDigest)return"journal-flow-digest";if(!Array.isArray(e.cells)||!z.has(e.payload?.caseEntry?.flow))return"journal-flow-not-selected";const u=z.get(e.payload.caseEntry.flow);if(u.flowExecutionId!==e.flowExecutionId||m(e.flow.material)!==m(u.flowExecutionMaterial))return"journal-flow-material-conflict";for(const D of e.cells)if(D?.cellId!==E(D?.material))return"journal-cell-digest";if(m(e.cells)!==m(u.cells))return"journal-cell-material-conflict";if(e.observationEvidenceDigest!==E(e.observationEvidence))return"journal-observation-evidence-digest";if(e.payloadDigest!==E(e.payload))return"journal-payload-digest";const g=e.payload.caseEntry,c=O.findIndex(D=>D.name===g.flow),h=O[c];if(g.flowExecutionId!==e.flowExecutionId||m(g.flowExecutionMaterial)!==m(e.flow.material)||m(g.cells)!==m(e.cells))return"journal-payload-semantic-conflict";const d=De(h),j=Xe(h,e.observationEvidence,d,e.timing),S={id:`${h.name}:${c}`,flow:h.name,observers:h.crosscheck.observers,rootCauseId:d,...u,...j};if(m(e.payload.outcome)!==m(j)||m(g)!==m(S))return"journal-derived-payload-conflict";const f={required:h.cleanup_required===!0,verified:j.mutationAccepted?j.counts.cleanups.completed===1:null},C=j.status==="measured"?null:{kind:j.reason??"flow-invalid"};return m(e.cleanup)!==m(f)||m(e.failure)!==m(C)?"journal-derived-record-conflict":t.has(e.flowExecutionId)?"journal-duplicate-flow-id":(t.add(e.flowExecutionId),null)}catch(o){return/^journal-/.test(o?.message??"")?o.message:"journal-record-shape"}}if(P)if(!p.existsSync(A))l={namespace:"coherence-flow-journal",version:1,runIdDigest:E($),envelope:{material:I,digest:I.digest},active:null,records:[],invalid:null},oe();else{try{l=JSON.parse(p.readFileSync(A,"utf8"))}catch{M("journal-malformed-json")}const e=["namespace","version","runIdDigest","envelope","active","records","invalid","documentDigest"];(!l||typeof l!="object"||Array.isArray(l)||Object.keys(l).sort().join("\0")!==e.sort().join("\0")||!Array.isArray(l.records))&&M("journal-document-shape"),(l.namespace!=="coherence-flow-journal"||l.version!==1)&&M("journal-namespace-or-version");const t={...l};delete t.documentDigest,l.documentDigest!==E(t)&&M("journal-document-digest"),l.runIdDigest!==E($)&&M("journal-run-id-digest");const o=we(I,l.envelope?.material);if((l.envelope?.digest!==I.digest||o)&&M("journal-envelope-mismatch",o),l.invalid){const a=["reason","detail","quarantinePath","quarantineDigest","at"];(typeof l.invalid!="object"||Array.isArray(l.invalid)||Object.keys(l.invalid).sort().join("\0")!==a.sort().join("\0")||typeof l.invalid.reason!="string"||typeof l.invalid.quarantinePath!="string")&&M("journal-tombstone-shape");const u=Ot(l.invalid);u&&M(`journal-tombstone-integrity:${u}`,l.invalid.reason),s.resume={journal:A,replayed:[],invalid:l.invalid},w(s,`journal-tombstoned:${l.invalid.reason}`)}if(l.active){const a=["flowExecutionId","flow","startedAt","mutationCapable"];(typeof l.active!="object"||Array.isArray(l.active)||Object.keys(l.active).sort().join("\0")!==a.sort().join("\0"))&&M("journal-active-shape"),M("interrupted-running-flow",l.active.flowExecutionId??null)}const n=new Set;for(const a of l.records??[]){const u=$t(a,n);u&&M(u,a?.flowExecutionId??null)}}const Me=new Map((l?.records??[]).map(e=>[e.flowExecutionId,e]));P&&(s.resume={journal:A,replayed:[...Me.values()].map(e=>e.payload.caseEntry.flow),invalid:null});let Ae;try{Ae=await import(ot(b.join(k,"verify.coherence.mjs")).href)}catch{w(s,"missing-or-invalid-coherence-adapter")}typeof Ae.createCoherenceDriver!="function"&&w(s,"adapter-missing-createCoherenceDriver"),s.cases=O.map((e,t)=>({id:`${e.name}:${t}`,flow:e.name,observers:e.crosscheck.observers,...z.get(e.name),rootCauseId:De(e),status:"invalid",reason:"not-yet-run",findings:[],counts:{targets:{planned:e.crosscheck.targets.length,reached:0,compared:0,agreed:0,unmeasured:e.crosscheck.targets.length}}})),Ce(s),G(s);const Ye=new Map;let ge=null,Ze=!1;async function Rt(e,t){const o=new Date().toISOString(),n=e.crosscheck,a=Be($,e.name),u={...L,fixtureNamespace:a},g={flow:e.name,route:e.route,writer:n.writer,observers:n.observers,targets:n.targets,authoritativeReadback:n.authoritative_readback,fixtureNamespace:a,...n.causal?{causal:n.causal}:{},...e.identity?{identityVersion:1,flowIdentity:e.identity,writerIdentity:n.writerIdentity}:{}};let c;try{const d=await Ae.createCoherenceDriver({repo:k,base:J,lane:U,flow:e,spec:g,permit:u}),j={driver:d,spec:g,flow:e,index:t,startedAt:o,mutation:null,cleanupAttempted:!1};ge=j;const S=d.mutate.bind(d),f=Object.create(d);f.mutate=async D=>{if(P){const _=z.get(e.name);l.active={flowExecutionId:_.flowExecutionId,flow:e.name,startedAt:o,mutationCapable:!0},oe()}const x=await S(D);return j.mutation=x,x},c=(await it({spec:g,driver:f})).observationEvidence,ge=null}catch{ge=null,c=Qe("adapter-case-failed",{causalContract:!!e.crosscheck?.causal})}const h=new Date().toISOString();return{index:t,flow:e,observationEvidence:c,startedAt:o,endedAt:h,durationMs:Math.max(0,Date.parse(h)-Date.parse(o))}}function Oe(e){const t=z.get(e.flow.name),o=De(e.flow),n=Xe(e.flow,e.observationEvidence,o,{durationMs:e.durationMs}),u={caseEntry:{id:`${e.flow.name}:${e.index}`,flow:e.flow.name,observers:e.flow.crosscheck.observers,rootCauseId:o,...t,...n},outcome:n},g={namespace:"coherence-flow-record",version:1,envelope:I?{material:I,digest:I.digest}:{material:null,digest:null},flowExecutionId:t.flowExecutionId,flow:{material:t.flowExecutionMaterial,materialDigest:t.flowExecutionId},cells:t.cells,observationEvidence:e.observationEvidence,observationEvidenceDigest:E(e.observationEvidence),payload:u,payloadDigest:E(u),timing:{startedAt:e.startedAt,endedAt:e.endedAt,durationMs:e.durationMs},cleanup:{required:e.flow.cleanup_required===!0,verified:n.mutationAccepted===!0?n.counts.cleanups.completed===1:null},failure:n.status==="measured"?null:{kind:n.reason??"flow-invalid"}};return{...g,recordDigest:E(g)}}function ye(e,t){const o=O[t],n={...e.payload.caseEntry,id:`${o.name}:${t}`,timing:e.timing};s.cases[t]=n,s.findings.push(...n.findings??[]),e.payload.outcome&&Ye.set(o.name,e.payload.outcome),Ce(s),G(s)}async function Ft(e){if(Ze)return;Ze=!0;const t=ge;(!t?.mutation?.accepted||t.cleanupAttempted)&&(P&&l?.active&&M("terminated-with-ambiguous-running-flow",e),s.status="invalid",s.reason=`terminated-${e.toLowerCase()}`,G(s),process.exit(2)),t.cleanupAttempted=!0;let o={ok:!1},n={ok:!1};try{o=await t.driver.cleanup({spec:t.spec,mutation:t.mutation})}catch{}try{n=await t.driver.verifyCleanup({spec:t.spec,mutation:t.mutation})}catch{}(o?.ok!==!0||n?.ok!==!0)&&M("termination-cleanup-unverified",e);const a=new Date().toISOString(),u=Qe(`terminated-${e.toLowerCase()}`,{mutationAccepted:!0,cleanupOk:!0,verificationOk:!0,causalContract:!!t.spec?.causal}),g={index:t.index,flow:t.flow,observationEvidence:u,startedAt:t.startedAt,endedAt:a,durationMs:Math.max(0,Date.parse(a)-Date.parse(t.startedAt))};if(P){const c=Oe(g);l.records.push(c),l.records.sort((h,d)=>h.flowExecutionId.localeCompare(d.flowExecutionId)),l.active=null,oe(),ye(c,t.index)}s.status="invalid",s.reason=`terminated-${e.toLowerCase()}`,G(s),process.exit(2)}process.on("SIGTERM",()=>{Ft("SIGTERM")});const Pt=O.map((e,t)=>async()=>{const o=z.get(e.name);if(Me.has(o.flowExecutionId)){const a=Me.get(o.flowExecutionId);return ye(a,t),a}const n=await Rt(e,t);if(P){const a=Oe(n);l.records.push(a),l.records.sort((u,g)=>u.flowExecutionId.localeCompare(g.flowExecutionId)),l.active=null,oe(),ye(a,t)}else{const a=Oe({...n});a.envelope={material:null,digest:null},ye(a,t)}return n});if(await yt(Pt,ae),s.findings.sort((e,t)=>String(e.rootCauseId??"").localeCompare(String(t.rootCauseId??""))||String(e.kind??"").localeCompare(String(t.kind??""))||String(e.surfaceId??"").localeCompare(String(t.surfaceId??""))||String(e.persona??"").localeCompare(String(t.persona??""))),ee){try{s.plan=jt({source:ee,runId:T?.runId,dimension:R,declaredFlows:q,executedByFlow:Ye})}catch(e){w(s,`coherence-identity-invalid:${e.message}`)}s.plan.status==="measured"&&(s.plan.order=O.map(e=>e.name))}Ce(s);try{const e=JSON.parse(p.readFileSync(new URL("../schemas/coherence-observed.schema.json",import.meta.url),"utf8")),t=je(e,s);if(t.length)throw new TypeError(`coherence-observed-schema:${t[0].path||"(root)"}`);lt(s)}catch(e){w(s,`coherence-identity-invalid:${e.message}`)}G(s),process.exitCode=s.status==="invalid"?2:s.findings.some(e=>e.severity==="P0"||e.severity==="P1")?1:0;
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import O from"node:fs";import T from"node:path";import{createRequire as st}from"node:module";import{pathToFileURL as Fe,fileURLToPath as it}from"node:url";import{scheduledPersonaRuns as Ce,findingsForOutcome as ut,interpolateTemplate as I,dateValue as lt}from"../lib/qa/commands/impl/crud.mjs";import{cleanupRequestAllowed as ct,endpointCovers as dt,evaluateMutationPolicy as pt,isBeaconPath as Me,loadMutationDeclaration as ft,requestAllowed as mt}from"../lib/qa/mutation-policy.mjs";import{laneRequiresOracle as ze}from"../lib/qa/lanes.mjs";import{comparePersona as ht,probeIdentity as yt,IDENTITY_ADAPTER as wt}from"../lib/qa/auth/oracle.mjs";import{findPlaywright as gt}from"../lib/qa/doctor.mjs";import{loadPersonas as bt}from"../lib/qa/auth/personas.mjs";import{createPersonaCoordinator as vt}from"../lib/qa/auth/coordinator.mjs";import{restoreSessionStorage as kt}from"../lib/qa/auth/launcher.mjs";const St=T.dirname(it(import.meta.url)),he=process.argv.slice(2),$=(t,a)=>{const r=he.indexOf("--"+t);return r>=0&&he[r+1]!==void 0?he[r+1]:a},ke=t=>he.includes("--"+t),Kt=t=>new Promise(a=>setTimeout(a,t)),A=T.resolve($("repo",".")),w=($("base","")||"").replace(/\/$/,""),G=$("json",T.join(A,".verify","crud-observed.json")),_t=$("flow",null),re=$("auth",null)?T.resolve(A,$("auth")):null,jt=Number($("max-mutations","20"))||20,oe=$("lane","public-runtime"),Se=$("fixture-namespace",`qa-${Date.now().toString(36)}`),se=Number($("nav-timeout","30000")),B=$("runid",null)??"qa"+Date.now().toString(36)+Math.random().toString(36).slice(2,6),ie=Object.freeze(["create","read","update","delete","reload_reconciled","navigation_reconciled","authorized","cleanup"]);function $t(){return Object.fromEntries(ie.map(t=>[t,{true:0,false:0,unmeasured:0}]))}let V=null;function _(t){console.error("crud-runner: "+t),V&&V.release();try{O.mkdirSync(T.dirname(G),{recursive:!0}),O.writeFileSync(G,JSON.stringify({status:"invalid",runId:B,base:w,lane:oe,fixtureNamespace:Se,attempts:0,counts:$t(),invalid:{reason:t},flows:[],findings:[],reason:t},null,2)+`
3
+ `)}catch{}process.exit(2)}function xt(){if(!ke("auth-lease-fd"))return null;const t=$("auth-lease-fd",null);(!/^\d+$/.test(t)||Number(t)<3)&&_("--auth-lease-fd must name an inherited descriptor >= 3");let a;try{a=JSON.parse(O.readFileSync(Number(t),"utf8"))}catch{_("auth lease descriptor is not readable JSON")}return(!a||typeof a.dir!="string"||typeof a.signature!="string"||Object.keys(a).sort().join(",")!=="dir,signature")&&_("auth lease descriptor must contain exactly dir and signature"),a}const He=xt(),_e=ft(A),Y=pt({command:"crud",mutate:ke("mutate"),base:w,overrideNonLocal:ke("i-know-this-is-not-production"),lane:oe,declaration:_e.declaration,declarationReason:_e.reason,flowName:_t,maxMutations:jt,fixtureNamespace:Se,cleanupCapable:!0});Y.ok||_(Y.reason);const ye=_e.declaration,X=Y.flows;function Je(t){if(t==null)return"/";let a=String(t).split("?")[0].split("#")[0];return a.length>1&&(a=a.replace(/\/+$/,"")),a||"/"}let je=[];try{je=bt(A).personas}catch(t){_(String(t.message??t))}const $e=$("launcher",T.join(A,"verify.auth-launcher.mjs")),We=$("oracle-path","/__qa/identity"),we=new Map;async function Rt(t){const a=[...new Set(Ce(t,ye).map(({persona:e})=>e))];if(re){if(O.existsSync(re)||_(`--auth ${re} does not exist`),ze(oe)){const e=[];for(const n of a){const s=je.find(d=>d.name===n);s||_(`persona "${n}" is not declared in verify.roles.json`);const i=await yt({base:w,oraclePath:We,statePath:re,adapterPath:T.join(A,wt)});i.status!=="measured"&&_(`persona "${n}" identity-${i.reason??i.status}`),e.push({persona:s,response:i.response})}for(const n of e)ht(n.persona,n.response,{peers:e.filter(i=>i!==n)}).status!=="valid"&&_(`persona "${n.persona.name}" identity proof failed`)}for(const e of a)we.set(e,{statePath:re,headers:null});return}O.existsSync($e)||_(`no persona launcher at ${$e} and no --auth -- every persona (${a.join(", ")}) would run anonymous and measure the login page. Write verify.auth-launcher.mjs (see README, auth section) or pass --auth <state.json>.`),V=vt({repo:A,runId:B,signature:He?.signature??"crud",launcherPath:$e,base:w,personas:je,oraclePath:We,oracleRequired:ze(oe),lease:He});const r=await V.provision(a);r.status!=="measured"&&_(`persona "${r.persona??"unknown"}" could not be provisioned (${r.status}${r.reason?": "+r.reason:""})`);for(const[e,n]of r.states)we.set(e,{statePath:n.statePath,headers:n.headers,session:n.session??null})}function Et(t){try{const a=T.join(A,".verify","research.json");if(!O.existsSync(a))return null;const e=(JSON.parse(O.readFileSync(a,"utf8")).routes??[]).find(s=>Je(s.path)===Je(t));if(!e||!Array.isArray(e.forms)||!e.forms.length)return null;const n={};for(const s of e.forms)s&&s.name&&(n[s.name]=s.type??"text");return Object.keys(n).length?n:null}catch{return null}}const ue=X.some(t=>t.journey)?await qt():{file:null,byName:new Map,error:null};ue.error&&_(`journeys file failed to load: ${ue.error}`);for(const t of X){if(!t.journey)continue;const a=ue.byName.get(t.journey);(!a||typeof a.run!="function")&&_(`flow names journey "${t.journey}" but no entry with that name and a run() is exported by ${ue.file??"the repo"}`)}const Tt=t=>t?.chromium?t:t?.default?.chromium?t.default:null,Pt=T.dirname(St);async function Ot(){const t=gt(A,Pt);if(!t)return null;try{const a=st(T.join(t.root,"package.json"));return Tt(await import(Fe(a.resolve(t.pkg)).href))}catch{return null}}const Ge=await Ot();Ge||_("Playwright not found (checked repo, .playwright, cwd, PLAYWRIGHT_HOME) -- npm i -D @playwright/test && npx playwright install chromium");let xe;try{xe=await Ge.chromium.launch()}catch(t){_("could not launch chromium -- "+String(t).split(`
4
+ `)[0])}const It=()=>{const t=[];Object.defineProperty(window,"__qa_crud_net",{get:()=>t,configurable:!0});const a=n=>{const s=n&&(n.id??n.data?.id??n.result?.id??n.record?.id);return s==null?null:String(s)},r=window.fetch;window.fetch=async function(...n){const s=n[0],i=n[1]||{},d=typeof s=="string"?s:s&&s.url||"",y=(i.method||s&&s.method||"GET").toUpperCase(),o=await r.apply(this,n);let p=null;if(y!=="GET")try{(o.headers.get("content-type")||"").includes("json")&&(p=a(await o.clone().json()))}catch{}return t.push({url:d,method:y,status:o.status,id:p,end:performance.now()}),o};const e=window.XMLHttpRequest;if(e){const n=e.prototype.open,s=e.prototype.send;e.prototype.open=function(i,d,...y){return this.__qa={url:String(d),method:String(i).toUpperCase()},n.call(this,i,d,...y)},e.prototype.send=function(...i){const d=this.__qa;return d&&this.addEventListener("loadend",()=>{let y=null;if(d.method!=="GET")try{(this.getResponseHeader("content-type")||"").includes("json")&&(this.responseType===""||this.responseType==="text")&&(y=a(JSON.parse(this.responseText)))}catch{}t.push({url:d.url,method:d.method,status:this.status,id:y,end:performance.now()})}),s.apply(this,i)}}};function q(t){try{return new URL(t,w).pathname}catch{return String(t)}}function Be(t){try{const a=new URL(t,w);return a.origin===new URL(w).origin?a.pathname:null}catch{return null}}async function At(t){const a=we.get(t);(!a||!a.statePath)&&_(`persona "${t}" was never provisioned -- refusing to run it anonymous`);const r=await xe.newContext({storageState:a.liveState??a.statePath,serviceWorkers:"block",...a.headers&&Object.keys(a.headers).length?{extraHTTPHeaders:a.headers}:{}});return await kt(r,a.session),await r.addInitScript(It),r}const Re=(t=0)=>Y.used+1+t<=Y.maxMutations;async function U(t){await t.waitForLoadState("domcontentloaded").catch(()=>{}),await t.waitForTimeout(500)}async function Ee(t,a){return t.evaluate(r=>(document.body&&document.body.innerText||"").includes(r),a).catch(()=>!1)}async function Te(t,a,r=5e3){const e=t.locator(a+":visible").first();try{return await e.waitFor({state:"visible",timeout:r}),e}catch{return null}}const Ve=Number($("form-timeout","10000"))||1e4;async function Ye(t,a,r=Ve){const e=t.locator(a+":visible");try{return await e.first().waitFor({state:"visible",timeout:r}),e}catch{return null}}async function qt(){const t=[T.join(A,"verify.journeys.mjs"),T.join(A,".verify","journeys.mjs")].find(a=>O.existsSync(a));if(!t)return{file:null,byName:new Map,error:null};try{const a=await import(Fe(t).href),r=a.default??a.journeys??[];return{file:t,byName:new Map(r.filter(e=>e&&e.name).map(e=>[e.name,e])),error:null}}catch(a){return{file:t,byName:new Map,error:String(a).split(`
5
+ `)[0].slice(0,200)}}}const le=ue;function Xe(t){if(!t.journey)return{journey:null,gap:null};const a=le.byName.get(t.journey);return a&&typeof a.run=="function"?{journey:a,gap:null}:le.error?{journey:null,gap:`journeys file failed to load: ${le.error}`}:le.file?{journey:null,gap:`flow names journey "${t.journey}" but no entry with that name and a run() is exported by ${le.file}`}:{journey:null,gap:`flow names journey "${t.journey}" but the repo has no verify.journeys.mjs`}}const Ke=t=>({base:w,settle:()=>U(t)});async function ge(t,a,r,e=null){for(let n=0;n<25;n++){await t.waitForTimeout(200);const i=(await t.evaluate(({mark:d,methods:y})=>(window.__qa_crud_net??[]).filter(o=>y.includes(o.method)&&o.end>d),{mark:a,methods:r}).catch(()=>null))?.find(d=>!Me(ye,q(d.url))&&(!e||dt(e,q(d.url),d.method==="POST")));if(i)return i}return null}async function Pe(t,a,r,e=null){switch(t){case"email":return a+"@example.com";case"number":return"2";case"url":return"https://example.com/"+a;case"date":return lt({min:await r.getAttribute("min").catch(()=>null),max:await r.getAttribute("max").catch(()=>null),override:e,salt:a});default:return a}}async function Qe(t,a,r){const e=[];if(a&&Object.keys(a).length)for(const[n,s]of Object.entries(a)){const i=t.locator(`[name="${n}"]:visible`).first(),d=typeof s=="object"&&s?s.type:s,y=typeof s=="object"&&s&&typeof s.value=="string"?s.value:null;if(await i.count().catch(()=>0)){const o=d==="date"?await Pe(d,r,i,y):y??await Pe(d,r,i);o!=null&&(await i.fill(o).catch(()=>{}),e.push(n))}}if(!e.length){const n=t.locator("input:visible:not([type=hidden]):not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=file]), textarea:visible"),s=await n.count().catch(()=>0);for(let i=0;i<s;i++){const d=n.nth(i),y=(await d.getAttribute("type").catch(()=>null)??"text").toLowerCase(),o=await Pe(y,r,d);o!=null&&await d.fill(o).catch(()=>{}),e.push(await d.getAttribute("name").catch(()=>null))}}return e.filter(Boolean)}async function Ze(t,a){if(!a)return await t.evaluate(e=>{e.requestSubmit?e.requestSubmit():e.dispatchEvent(new Event("submit",{bubbles:!0,cancelable:!0}))}).catch(()=>{}),null;const r=t.locator(a).first();try{return await r.click({timeout:5e3}),null}catch{return await r.dispatchEvent("click").catch(()=>{}),`submit control (${a}) was not reachable by a real click; the click was dispatched synthetically`}}async function et(t,a){try{return await t.goto(w+a,{waitUntil:"domcontentloaded",timeout:se}),await U(t),await t.reload({waitUntil:"domcontentloaded",timeout:se}),await U(t),{ok:!0}}catch{return{ok:!1,reason:"reload failed before a durable observation could be taken"}}}async function Ut(t,a,r,e,n={},s=()=>{},i=!1){if(typeof a!="string"||!a.trim())return{ok:"unmeasured",reason:"cleanup endpoint declaration is not a string"};const d=a.includes("{fixtureId}")||a.includes("{sentinel}"),y=d||i?d?a:a.replace(/\/$/,"")+"/{fixtureId}":null;if(!y)return{ok:"unmeasured",reason:"cleanup endpoint must explicitly name {fixtureId} or use an app-owned journey cleanup"};const o=I(y,{fixtureId:r,...n},{encodePath:!0});if(!o)return{ok:"unmeasured",reason:"cleanup endpoint contains an unresolved runtime token"};const p=o,m=Be(p);if(!m)return{ok:"unmeasured",reason:"cleanup endpoint is not a same-origin path"};s(m);const L=await t.evaluate(async P=>{try{return(await fetch(P,{credentials:"same-origin"})).status}catch{return-1}},p).catch(()=>-1);if(e.push({method:"GET",path:q(p),status:L}),L<200||L>=300)return s(null),{ok:"unmeasured",reason:"cleanup target was not confirmed by an app readback"};let x;try{x=await t.evaluate(async P=>{try{return(await fetch(P,{method:"DELETE",credentials:"same-origin"})).status}catch{return-1}},p).catch(()=>-1)}finally{s(null)}e.push({method:"DELETE",path:q(p),status:x});const H=await t.evaluate(async P=>{try{return(await fetch(P,{credentials:"same-origin"})).status}catch{return-1}},p).catch(()=>-1);e.push({method:"GET",path:q(p),status:H});const Z=x>=200&&x<300&&(H===404||H===410);return{ok:Z,reason:Z?null:`cleanup DELETE returned ${x} but a follow-up GET returned ${H}`}}async function Nt(t,a,r){const e={role:a,create:"unmeasured",read:"unmeasured",update:"unmeasured",delete:"unmeasured",reload_reconciled:"unmeasured",navigation_reconciled:"unmeasured",authorized:"unmeasured",cleanup:"unmeasured",gap:null,reasons:{}},n=[],s=(t.denied??[]).includes(a);let i=null,d=!1;const y=new Set;let o=!1,p=null,m=null,L=!1,x=null,H=!0;const Z=new Set(r.pages()),P=l=>{x??=l,Q=!0},ce=async l=>{const c=l.request();if(!["POST","PUT","PATCH","DELETE"].includes(c.method().toUpperCase()))return l.continue();let f;try{const v=new URL(c.url());if(v.origin!==new URL(w).origin)return P("write request must stay on the declared base origin"),l.abort();f=v.pathname}catch{return P("write request URL is invalid"),l.abort()}if(!H)return P("browser write attempted after its CRUD flow completed"),l.abort("blockedbyclient");if(i===null&&Me(ye,f))return l.continue();const R=(i===null?mt:ct)(Y,{method:c.method(),url:c.url(),base:w,flowName:t.name,cleanupPath:i});return R.ok?(i!==null&&c.method().toUpperCase()==="DELETE"&&(d=!0,y.add(c)),l.continue()):(P(R.reason),l.abort("blockedbyclient"))};await r.route("**/*",ce);const u=await r.newPage(),at=(l,c,f,h,R={},v=!1)=>Ut(l,c,f,h,R,E=>{i=E},v),nt=async(l,c)=>{if(!m||typeof m.cleanup!="function")return{ok:"unmeasured",reason:"cleanup requires an app-owned journey cleanup() or an explicit cleanup endpoint"};const f=v=>{const E=I(v,{fixtureId:c.id,sentinel:c.sentinel},{encodePath:!0});return!(!E||(i=Be(E),!i))};o=!0,d=!1,y.clear();const h=[],R=v=>{try{const E=v.request();y.has(E)&&E.method().toUpperCase()==="DELETE"&&q(v.url())===i&&h.push(v.status())}catch{}};typeof r.on=="function"&&r.on("response",R);try{const v=await m.cleanup(u,{base:w,sentinel:c.sentinel,fixtureId:c.id,settle:()=>U(u),reserveCleanup:f}),E=h.some(ee=>ee>=200&&ee<300),J=d&&E&&(v===!0||v?.ok===!0);return{ok:J,reason:J?null:"app-owned cleanup did not reserve and complete its exact DELETE"}}catch{return{ok:!1,reason:"app-owned cleanup failed before verification"}}finally{typeof r.off=="function"&&r.off("response",R),i=null}},de=async(l,c)=>{const f=m?.readback??m?.authoritativeReadback;if(typeof f!="function")return null;try{const h=await f(u,{base:w,phase:c,sentinel:l.sentinel,fixtureId:l.id,settle:()=>U(u)});return typeof h=="boolean"?{measured:!0,ok:h}:h&&typeof h=="object"&&typeof h.ok=="boolean"?{measured:!0,ok:h.ok}:{measured:!1,reason:"app-owned readback returned no explicit boolean result"}}catch{return{measured:!1,reason:"app-owned readback failed before a durable observation could be taken"}}},Oe=async(l,c)=>{if(typeof m?.resolveRoute!="function")return c;try{const f=await m.resolveRoute(u,{base:w,sentinel:l.sentinel,fixtureId:l.id,settle:()=>U(u)}),h=typeof f=="string"?f:f?.route;if(typeof h!="string"||!h.startsWith("/")||h.startsWith("//")||h.includes("\\")||/[{}]/.test(h))return null;try{if(new URL(h,w).origin!==new URL(w).origin)return null}catch{return null}return h}catch{return null}},rt=l=>{const c=t.cleanup_endpoint??t.declared_endpoint;if(typeof c!="string"||l?.id==null)return null;const h=c.includes("{fixtureId}")||c.includes("{sentinel}")?c:c.replace(/\/$/,"")+"/{fixtureId}";return I(h,{fixtureId:l.id,sentinel:l.sentinel},{encodePath:!0})},Ie=async l=>{const c=await u.evaluate(async f=>{try{return(await fetch(f,{credentials:"same-origin"})).status}catch{return-1}},l).catch(()=>-1);return n.push({method:"GET",path:q(l),status:c}),c},ot=async()=>{if(!p||L||o)return;if(o=!0,t.cleanup_required===!1){e.reasons.cleanup??="flow declares cleanup_required:false -- the record this run created is left in place by declaration";return}const l=p,c=t.cleanup_endpoint??t.declared_endpoint;let f;m&&typeof m.cleanup=="function"?(o=!1,f=await nt(c,l)):c?f=await at(u,c,l.id,n,{sentinel:l.sentinel},!t.cleanup_endpoint):f={ok:"unmeasured",reason:"cleanup requires an app-owned journey cleanup() or an explicit cleanup endpoint"},e.cleanup=f.ok,f.reason&&(e.reasons.cleanup??=f.reason)};try{({journey:m}=Xe(t));const{gap:l}=Xe(t);if(l)return e.reasons.journey=l,e.gap=l,{outcome:e,requests:n};const c=I(t.route,{});if(c)await u.goto(w+c,{waitUntil:"domcontentloaded",timeout:se}),await U(u);else if(!m)return e.reasons.route="flow route contains an unresolved runtime token",e.gap=e.reasons.route,{outcome:e,requests:n};if(m){try{await m.run(u,Ke(u))}catch{return e.reasons.journey=`journey "${t.journey}" failed before the create surface was reached (exception)`,e.gap=e.reasons.journey,{outcome:e,requests:n}}await U(u)}const f=!!m&&typeof m.resolveRoute!="function"&&!/[{](?:fixtureId|sentinel)[}]/.test(t.route??"")&&typeof(m.readback??m.authoritativeReadback)!="function",h=`qa-${B}-${a}-1`,R=I(t.form_selector??"form",{sentinel:h}),v=I(t.submit_selector,{sentinel:h});if(R===null||v===null)return e.reasons.create="create selector contains an unresolved runtime token",e.gap=e.reasons.create,{outcome:e,requests:n};const E=typeof m?.create=="function",J=E?null:await Ye(u,R);if(!E&&!J)return e.reasons.create=`no visible create surface (${R}) on the route within ${Ve}ms`+(m?` after journey "${t.journey}"`:" -- cover this flow with a journey"),e.gap=e.reasons.create,{outcome:e,requests:n};const ee=J?J.first():null;if(!Re(1))return e.gap="mutation budget exhausted (--max-mutations) before this persona could start",{outcome:e,requests:n};let M=h;const Ae=await u.evaluate(()=>performance.now());if(E){let j;try{j=await m.create(u,{...Ke(u),sentinel:h})}catch{const k=await ge(u,Ae,["POST"],t.declared_endpoint);return k&&(n.push({method:k.method,path:q(k.url),status:k.status}),k.status>=200&&k.status<300&&(p={id:k.id,sentinel:M})),e.reasons.create=x??`journey "${t.journey}" create() failed after its write attempt (exception)`,e.gap=e.reasons.create,{outcome:e,requests:n}}j&&typeof j.sentinel=="string"&&j.sentinel.trim()&&(M=j.sentinel.trim())}else{await Qe(ee,t.fields??Et(t.route),h);const j=await Ze(ee,v);j&&(e.reasons.create_submit=j)}const g=await ge(u,Ae,["POST"],t.declared_endpoint);if(g&&n.push({method:g.method,path:q(g.url),status:g.status}),s)return g?g.status===401||g.status===403?e.authorized=!0:g.status>=200&&g.status<300?(e.authorized=!1,e.reasons.authorized=`denied persona's write to ${t.declared_endpoint??"(declared endpoint)"} was allowed (status ${g.status})`):e.reasons.authorized=`write returned ${g.status}, inconclusive for a denial check`:e.reasons.authorized="no write was observed for a denied persona -- nothing to check",e.gap="persona is declared denied for this flow; only authorization was exercised, not the full CRUD cycle",g?.status>=200&&g.status<300&&(p={id:g.id,sentinel:M}),e.authorized===!1&&(g.id!=null&&t.declared_endpoint?p={id:g.id,sentinel:M}:(e.cleanup="unmeasured",e.reasons.cleanup="no id was captured from the accepted write; cannot target a direct cleanup delete")),{outcome:e,requests:n};if(!g)return e.reasons.create=x??"submit produced no write (client validation? a different trigger?) -- unverified, not a defect",e.gap=e.reasons.create,{outcome:e,requests:n};if(g.status>=400)return e.create=!1,e.reasons.create=`create returned ${g.status}`,{outcome:e,requests:n};e.create=!0;const D=g.id;if(p={id:D,sentinel:M},D!=null&&!I("{fixtureId}",{fixtureId:D},{encodePath:!0}))return e.gap="create returned an unsafe fixture ID; dependent route, selector, and cleanup steps are unmeasured",e.reasons.cleanup="fixture ID is not a safe single path segment",{outcome:e,requests:n};let te=await Oe(p,I(t.route,{fixtureId:D,sentinel:M},{encodePath:!0}));if(!te)return e.gap="reload route contains an unresolved runtime token",e.reasons.cleanup=D==null?"no id was captured from the create response":e.reasons.cleanup,{outcome:e,requests:n};const be=await et(u,te);if(!be.ok)return e.reasons.read=be.reason,e.gap=be.reason,{outcome:e,requests:n};const F=await de(p,"create"),qe=F?.measured===!1?!1:F?F.ok:await Ee(u,M);if(e.read=F?.measured===!0?F.ok:f||!qe?"unmeasured":!0,e.reload_reconciled=e.read,f||F?.measured===!1)return e.reasons.read=f?"journey requires an app-owned authoritative readback":F.reason,e.gap=e.reasons.read,{outcome:e,requests:n};if(F?.measured===!0&&!F.ok)return e.reasons.read="app-owned authoritative readback did not find the created fixture",e.gap=e.reasons.read,{outcome:e,requests:n};if(!qe)return e.reasons.read=f?"journey requires an app-owned authoritative readback":"not visible after a reload",e.gap=f?e.reasons.read:"create returned success but the sentinel was not visible after a reload",{outcome:e,requests:n};let ae=M;const pe=t.update_selector===!1?null:I(t.update_selector??'[aria-label*="edit" i]',{fixtureId:D,sentinel:ae}),Ue=pe?await Te(u,pe):null;if(t.update_selector===!1)e.reasons.update="flow declares no update control (update_selector: false)";else if(!pe)e.reasons.update="update selector contains an unresolved runtime token";else if(!Ue)e.reasons.update=`no update control found (${pe})`;else if(!Re())e.reasons.update="mutation budget exhausted";else{await Ue.click(),await u.waitForTimeout(200);const j=await Ye(u,R,5e3),k=j?j.last():null;if(!k)e.reasons.update=`clicking the edit control revealed no visible ${R}`;else{const z=`qa-${B}-${a}-2`;await Qe(k,null,z);const ne=await u.evaluate(()=>performance.now()),me=await Ze(k,v);me&&(e.reasons.update_submit=me);const b=await ge(u,ne,["PUT","PATCH","POST"],t.declared_endpoint);if(!b)e.reasons.update="submit produced no write -- unverified, not a defect";else if(n.push({method:b.method,path:q(b.url),status:b.status}),b.status>=400)e.update=!1,e.reasons.update=`update returned ${b.status}`;else{p.sentinel=z;const S=await Oe(p,I(t.route,{fixtureId:D,sentinel:z},{encodePath:!0}));S||(e.reasons.update="updated route contains an unresolved runtime token");let W=!1;if(S){const ve=await et(u,S);W=ve.ok,ve.ok||(e.reasons.update=ve.reason)}const C=W?await de(p,"update"):null,De=C?.measured===!1?!1:C?C.ok:S?await Ee(u,z):!1;e.update=C?.measured===!0?C.ok:f||!S||!W||!De?"unmeasured":!0,e.reload_reconciled=e.reload_reconciled&&e.update,C?.measured===!1?e.reasons.update=C.reason:C?.measured===!0&&!C.ok?e.reasons.update="app-owned authoritative readback did not find the updated fixture":De?(ae=z,te=S):e.reasons.update="update was accepted but the new value was not visible after a reload"}}}await u.goto(w+"/",{waitUntil:"domcontentloaded",timeout:se}).catch(()=>{}),await U(u);const Ne=await Te(u,`a[href="${te}"]`);Ne?(await Ne.click().catch(()=>{}),await U(u),e.navigation_reconciled=await Ee(u,ae),e.navigation_reconciled||(e.reasons.navigation_reconciled="sentinel not visible after client-side navigation from /")):e.reasons.navigation_reconciled="no link to the route found on /",await u.goto(w+te,{waitUntil:"domcontentloaded",timeout:se}).catch(()=>{}),await U(u);const fe=t.delete_selector===!1?null:I(t.delete_selector??'[aria-label*="delete" i]',{fixtureId:D,sentinel:ae}),Le=fe?await Te(u,fe):null;if(t.delete_selector===!1)e.reasons.delete="flow declares no delete control (delete_selector: false)";else if(!fe)e.reasons.delete="delete selector contains an unresolved runtime token";else if(!Le)e.reasons.delete=`no delete control found (${fe})`;else if(!Re())e.reasons.delete="mutation budget exhausted";else{const j=rt({id:D,sentinel:ae}),k=typeof(m?.readback??m?.authoritativeReadback)=="function",z=k?await de(p,"delete-before"):null,ne=k?z?.measured?z.ok:null:j?await Ie(j):-1;if(k?ne!==!0:ne<200||ne>=300)e.reasons.delete="delete target was not confirmed by an app readback",e.delete="unmeasured";else{u.once("dialog",S=>S.accept().catch(()=>{}));const me=await u.evaluate(()=>performance.now());await Le.click().catch(()=>{});const b=await ge(u,me,["DELETE","POST"],t.declared_endpoint);if(!b)e.reasons.delete="click produced no write -- unverified, not a defect";else if(n.push({method:b.method,path:q(b.url),status:b.status}),k)if(b.status>=400&&b.status!==404&&b.status!==410)e.delete="unmeasured",e.reasons.delete=`delete response was not authoritative (status ${b.status}) -- a readback cannot tell a refused delete from a hidden record`;else{const S=await de(p,"delete-after"),W=S?.measured?S.ok:null;W===!1?(e.delete=!0,L=!0):W===!0?(e.delete=!1,e.reasons.delete="delete response was accepted but the app readback still found the record"):(e.delete="unmeasured",e.reasons.delete="delete follow-up readback was not authoritative")}else if(b.status>=400)b.status===404||b.status===410?(e.delete=!0,L=!0):(e.delete="unmeasured",e.reasons.delete="delete response was not authoritative");else{const S=await Ie(j);S===404||S===410?(e.delete=!0,L=!0):S>=200&&S<300?(e.delete=!1,e.reasons.delete="delete was accepted but the record remained in the app readback"):(e.delete="unmeasured",e.reasons.delete="delete follow-up readback was not authoritative")}}}return L?e.cleanup=!0:t.cleanup_required===!1&&(e.reasons.cleanup="flow declares cleanup_required:false -- the record this run created is left in place by declaration"),e.gap||(e.gap=Object.values(e.reasons)[0]??null),{outcome:e,requests:n}}catch{return e.reasons.error=x??"CRUD flow failed before completion (exception)",e.gap=e.gap??e.reasons.error,{outcome:e,requests:n}}finally{try{await ot()}catch{e.cleanup=!1,e.reasons.cleanup??="cleanup failed before completion"}H=!1;for(const l of r.pages().filter(c=>!Z.has(c)))try{await l.close()}catch(c){P("browser page close failure: "+String(c).split(`
6
+ `)[0].slice(0,160))}r.pages().some(l=>!Z.has(l))&&P("browser page close failure left a flow page open"),x&&(e.reasons.authorization=x,e.gap??=x)}}const K=[];let Q=!1;await Rt(X.filter(t=>t.route&&t.declared_endpoint));for(const{flow:t,persona:a}of Ce(X,ye)){if(!t.route||!t.declared_endpoint){K.push({name:t.name,persona:a,route:t.route??null,outcome:{role:a,create:"unmeasured",read:"unmeasured",update:"unmeasured",delete:"unmeasured",reload_reconciled:"unmeasured",navigation_reconciled:"unmeasured",authorized:"unmeasured",cleanup:"unmeasured",gap:'flow is missing "route" or "declared_endpoint"'},requests:[]});continue}const r=await At(a),{outcome:e,requests:n}=await Nt(t,a,r);try{we.get(a).liveState=await r.storageState()}catch(s){Q=!0;const i="browser storage-state capture failure: "+String(s).split(`
7
+ `)[0].slice(0,160);e.reasons.storage_state=i,e.gap??=i}try{await r.close()}catch(s){Q=!0;const i="browser context close failure: "+String(s).split(`
8
+ `)[0].slice(0,160);e.reasons.context_close=i,e.gap??=i}K.push({name:t.name,persona:a,route:t.route,outcome:e,requests:n})}try{await xe.close()}catch(t){Q=!0;const a=K.at(-1)?.outcome;if(a){const r="browser close failure: "+String(t).split(`
9
+ `)[0].slice(0,160);a.reasons.browser_close=r,a.gap??=r}}V&&V.release();function Lt(t,a){const r=t.flatMap(o=>ut(X.find(p=>p.name===o.name)??{name:o.name,route:o.route},o.persona,o.outcome)),e=Object.fromEntries(ie.map(o=>[o,Object.fromEntries(["true","false","unmeasured"].map(p=>[p,t.filter(m=>String(m.outcome[o])===p).length]))])),n=Object.fromEntries(ie.map(o=>[o,{planned:t.length,measured:e[o].true+e[o].false,unmeasured:e[o].unmeasured,failed:e[o].false}])),s=[...new Set(t.flatMap(o=>Object.values(o.outcome.reasons??{}).filter(Boolean)))],i=Q?"browser mutation authorization or context closure failed":t.some(o=>ie.some(p=>o.outcome[p]===!0||o.outcome[p]===!1))?"CRUD run ended with partial observations":"no CRUD operation reached a measured state",d={status:a,runId:B,base:w,lane:oe,fixtureNamespace:Se,attempts:t.length,counts:e,coverage:n,...a==="invalid"?{invalid:{reason:i,gaps:s}}:{},flows:t.map(o=>({name:o.name,persona:o.persona,route:o.route,outcome:{...o.outcome,reasons:void 0},requests:o.requests,gaps:[...new Set(Object.values(o.outcome.reasons??{}).filter(Boolean))]})),findings:r};O.mkdirSync(T.dirname(G),{recursive:!0});const y=G+".tmp-"+process.pid;return O.writeFileSync(y,JSON.stringify(d,null,2)+`
10
+ `),O.renameSync(y,G),d}const Dt=K.some(t=>["create","read","update","delete","authorized","cleanup"].some(a=>t.outcome[a]===!0||t.outcome[a]===!1)),tt=K.length&&Dt&&!Q?"measured":"invalid",N=Lt(K,tt);console.log(`
11
+ qa measure crud ${w} ${X.length} flow(s) runId ${B}`),console.log(` attempts=${N.attempts} measured=${N.flows.filter(t=>Object.values(t.outcome).some(a=>a===!0||a===!1)).length}`),console.log(` coverage ${ie.map(t=>`${t}=${N.coverage[t].measured}/${N.coverage[t].planned} measured,${N.coverage[t].unmeasured} unmeasured`).join(" ")}`);for(const t of N.flows)console.log(` ${t.name}:${t.persona} create=${t.outcome.create} read=${t.outcome.read} update=${t.outcome.update} delete=${t.outcome.delete} auth=${t.outcome.authorized} cleanup=${t.outcome.cleanup}`+(t.gaps.length?` gap: ${t.gaps[0]}`:""));console.log(` ${N.findings.length} finding(s)`);for(const t of N.findings)console.log(` ${t.severity} ${t.kind} ${t.at} ${t.detail}`);console.log(` -> ${G}
12
+ `),tt==="invalid"&&(console.error(` INVALID report: ${N.invalid?.reason??"CRUD run did not complete"}`),console.error(" INVALID -- partial CRUD observations were retained; see coverage and invalid details above"),process.exit(2)),process.exit(N.findings.some(t=>t.severity==="P0"||t.severity==="P1")?1:0);
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import p from"node:fs";import c from"node:path";import $e from"node:crypto";import{fileURLToPath as ee}from"node:url";import{run as te}from"../lib/qa/proc.mjs";import{writeJson as k,acquireLock as je,exists as A}from"../lib/qa/fs.mjs";import{normalizeFindings as Se,cellsOf as B,normRoute as g}from"../lib/qa/reports.mjs";import{newRunId as Oe}from"../lib/qa/runs.mjs";import{EXIT as b}from"../lib/qa/cli.mjs";const U=c.resolve(c.dirname(ee(import.meta.url)),"..");function v(e,n,t){const r=e.indexOf("--"+n);return r>=0?e[r+1]:t}function ne(e,n){return e.includes("--"+n)}const ke=new Set(["repo","base","budget-cells","wall-ms","families","queue","auth-lease-fd"]),Re=new Set(["mutate","json","no-install","help"]),Ne=new Set(["routes","json-out","parallel","no-warm","states"]);function Ie(e){const n=[];for(let t=0;t<e.length;t++){const r=e[t];if(!r.startsWith("--"))continue;const s=r.indexOf("="),i=s>0?r.slice(2,s):r.slice(2);if(ke.has(i)){s<0&&t++;continue}Re.has(i)||Ne.has(i)||(n.push(r),s<0&&e[t+1]!==void 0&&!e[t+1].startsWith("--")&&(n.push(e[t+1]),t++))}return n}function H(e){return Array.isArray(e)?`[${e.map(H).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().map(n=>`${JSON.stringify(n)}:${H(e[n])}`).join(",")}}`:JSON.stringify(e)}function j(e){return $e.createHash("sha256").update(e).digest("hex")}function Le(e){return"sha256:"+j(H(e))}function se(e){try{return"sha256:"+j(p.readFileSync(e))}catch{return"missing"}}function Ae({sweepDigest:e,probeDigest:n,base:t,routes:r,flags:s}){return Le({sweepDigest:e,probeDigest:n,base:"sha256:"+j(String(t??"")),routes:[...r].sort(),flags:[...s]})}function re(e){return typeof e=="string"&&e.startsWith("route:")?e.slice(6):String(e??"")}function oe(e,n){return e===n||e.startsWith(n.replace(/\/$/,"")+"/")}function qe(e,n){return(e.owns??["/"]).some(t=>oe(n,t))&&!(e.excludes??[]).some(t=>oe(n,t))}function De(e,n){return e.filter(t=>qe(t,n)).map(t=>t.name).sort()}const ie=["latency-baseline-vs-injection","role-pair","mutation-vs-no-mutation","navigation-vs-reload","cache-enabled-vs-disabled"],ae={"navigation-vs-reload":"no bin/sweep.mjs flag drives a same-page reload-vs-navigate comparison -- not executable by this lane","cache-enabled-vs-disabled":"no bin/sweep.mjs flag toggles cache-enabled vs disabled -- not executable by this lane"},T={"latency-baseline-vs-injection":2,"role-pair":1,"mutation-vs-no-mutation":2};function _e({candidates:e,families:n,roles:t=[],mutateAllowed:r=!1}){const s=[];for(const i of e){const o=re(i.id);for(const u of n){if(!ie.includes(u)){s.push({route:o,family:u,candidate:i,eligible:!1,cost:0,reason:`unknown family "${u}"`});continue}if(ae[u]){s.push({route:o,family:u,candidate:i,eligible:!1,cost:0,reason:ae[u]});continue}if(u==="role-pair"){const l=De(t,o);if(l.length<2){s.push({route:o,family:u,candidate:i,eligible:!1,cost:0,reason:l.length?`only one role ("${l[0]}") owns this route in verify.roles.json -- role-pair needs two`:"no role in verify.roles.json owns this route"});continue}s.push({route:o,family:u,candidate:i,eligible:!0,cost:T[u],controlRole:l[0],treatmentRole:l[1]});continue}if(u==="mutation-vs-no-mutation"){if(!r){s.push({route:o,family:u,candidate:i,eligible:!1,cost:0,reason:"--mutate was not passed to qa measure experiments"});continue}if(i.mutation!==!0){s.push({route:o,family:u,candidate:i,eligible:!1,cost:0,reason:"queue candidate is not flagged mutation:true"});continue}if(i.production!==!1){s.push({route:o,family:u,candidate:i,eligible:!1,cost:0,reason:"queue candidate does not declare production:false"});continue}s.push({route:o,family:u,candidate:i,eligible:!0,cost:T[u]});continue}s.push({route:o,family:u,candidate:i,eligible:!0,cost:T[u]})}}return s}function Fe(e,{budgetCells:n}){let t=0;return{combos:e.map(s=>s.eligible?t+s.cost>n?{...s,run:!1,reason:`budget-cells exhausted (${t}/${n} spent; this trial needs ${s.cost} more)`}:(t+=s.cost,{...s,run:!0}):{...s,run:!1}),cellsUsed:t}}function q(e,n,{role:t=null}={}){const r=g(n);return Se({sweep:e},{}).filter(s=>!(!(s.route&&g(s.route)===r||s.subject===r)||t!=null&&s.role!==t))}function D(e,n){const t=e.find(r=>/^route\.status-\d+$/.test(r.kind));return t?Number(t.kind.slice(13)):e.some(r=>r.kind==="route.unreachable")?null:n?200:null}function _({arm:e,route:n,status:t,outcome:r,metric:s,integrityHash:i,evidenceId:o}){return{arm:e,route:n,status:t,outcome:r,metric:s,integrity_hash:i,evidence_id:o}}function ue(e,n){const t=new Set(e.map(r=>r.kind));return n.filter(r=>(r.severity==="P0"||r.severity==="P1")&&!t.has(r.kind)).map(r=>({kind:r.kind,severity:r.severity,detail:r.detail,at:r.at??null}))}function F(e,n=3){return String(e??"").split(`
3
+ `).filter(Boolean).slice(-n).join(" ").slice(0,300)}function Ee(e){if(!A(e))return{ok:!1,missing:!0,reason:`no experiment queue at ${e} -- run qa run <repo> --base <base> --once to generate the Node-owned default queue, or pass --queue <path>`};let n;try{n=JSON.parse(p.readFileSync(e,"utf8"))}catch(t){return{ok:!1,missing:!1,reason:`experiment queue is not valid JSON: ${e} (${String(t).split(`
4
+ `)[0]})`}}return!n||typeof n!="object"||!Array.isArray(n.accepted)?{ok:!1,missing:!1,reason:`experiment queue has an unexpected shape (no "accepted" array): ${e}`}:{ok:!0,queue:n}}function Ce(e){const n=c.join(e,"verify.roles.json"),t=c.join(e,".verify","roles.json"),r=A(n)?n:A(t)?t:null;if(!r)return[];try{const s=JSON.parse(p.readFileSync(r,"utf8"));return!s||typeof s!="object"||Array.isArray(s)?[]:Object.entries(s).map(([i,o])=>({name:i,owns:o?.owns??["/"],excludes:o?.excludes??[]}))}catch{return[]}}function W(e,n,t){const r=n.route.replace(/[^a-zA-Z0-9._-]+/g,"_").replace(/^_+/,"")||"root";return c.join(e,`${n.family}--${r}--${t}.json`)}async function K(e,n,t,r){const s=["--repo",e.repo,"--base",e.base,"--routes",n,"--json",t,"--parallel","1","--no-warm",...r,...e.extra,...e.authLease?["--auth-lease-fd","3"]:[]];return te(process.execPath,[e.sweepBin,...s],{stdout:"capture",stderr:"capture",timeoutMs:e.remainingMs,...e.authLease?{fd3:e.authLease}:{}})}function Me({route:e,controlPath:n,treatmentPath:t,integrityHash:r}){let s,i;try{s=JSON.parse(p.readFileSync(n,"utf8"))}catch(f){return{status:"invalid",reason:`control report unreadable: ${f.message}`}}try{i=JSON.parse(p.readFileSync(t,"utf8"))}catch(f){return{status:"invalid",reason:`treatment report unreadable: ${f.message}`}}const o=B(s).find(f=>g(f.route)===g(e))??null,u=B(i).find(f=>g(f.route)===g(e))??null,l=q(s,e),d=q(i,e),x=_({arm:"control",route:e,status:D(l,!!o),outcome:[...new Set(l.map(f=>f.kind))].sort(),metric:o?.textLen??null,integrityHash:r,evidenceId:"sha256:"+j(p.readFileSync(n))}),w=_({arm:"treatment",route:e,status:D(d,!!u),outcome:[...new Set(d.map(f=>f.kind))].sort(),metric:u?.textLen??null,integrityHash:r,evidenceId:"sha256:"+j(p.readFileSync(t))});return{status:"measured",controlObservation:x,treatmentObservation:w,effect:ue(l,d)}}async function Pe(e,n){const t=W(n.trialDir,e,"roles"),r=await K(n,e.route,t,[]);if(r.code===2)return{status:"invalid",reason:`sweep exited 2 -- ${F(r.stderr)}`};let s;try{s=JSON.parse(p.readFileSync(t,"utf8"))}catch(h){return{status:"invalid",reason:`report unreadable: ${h.message}`}}const i=h=>B(s).find(y=>g(y.route)===g(e.route)&&y.role===h)??null,o=i(e.controlRole),u=i(e.treatmentRole);if(!o||!u)return{status:"invalid",reason:`role "${o?e.treatmentRole:e.controlRole}" did not reach ${e.route} in this sweep (redirected or unmeasured)`};const l=q(s,e.route,{role:e.controlRole}),d=q(s,e.route,{role:e.treatmentRole}),x="sha256:"+j(p.readFileSync(t)),w=_({arm:"control",route:e.route,status:D(l,!0),outcome:[...new Set(l.map(h=>h.kind))].sort(),metric:o.textLen??null,integrityHash:n.integrityHash,evidenceId:x}),f=_({arm:"treatment",route:e.route,status:D(d,!0),outcome:[...new Set(d.map(h=>h.kind))].sort(),metric:u.textLen??null,integrityHash:n.integrityHash,evidenceId:x});return{status:"measured",controlObservation:w,treatmentObservation:f,effect:ue(l,d)}}async function Je(e,n){if(e.family==="role-pair")return Pe(e,n);if(e.family==="mutation-vs-no-mutation"&&!n.mutateAllowed)return{status:"invalid",reason:"--mutate was not passed to qa measure experiments"};const t=e.family==="latency-baseline-vs-injection"?["--states","slow"]:["--mutate"],r=W(n.trialDir,e,"control"),s=W(n.trialDir,e,"treatment"),i=await K(n,e.route,r,[]);if(i.code===2)return{status:"invalid",reason:`control sweep exited 2 -- ${F(i.stderr)}`};const o=await K(n,e.route,s,t);return o.code===2?{status:"invalid",reason:`treatment sweep exited 2 -- ${F(o.stderr)}`}:Me({route:e.route,controlPath:r,treatmentPath:s,integrityHash:n.integrityHash})}function Ve(e){const n=[];n.push(` budget ${e.budget.cellsUsed}/${e.budget.cells} cells \xB7 ${e.budget.elapsedMs}ms/${e.budget.wallMs}ms wall`),n.push(` executed ${e.executed.length} (${e.executed.filter(t=>t.status==="measured").length} measured, ${e.executed.filter(t=>t.status==="invalid").length} invalid) \xB7 skipped ${e.skipped.length}`);for(const t of e.executed)n.push(` ${t.status==="measured"?"measured":"INVALID "} ${t.family} ${t.route}${t.reason?" -- "+t.reason:""}`);for(const t of e.skipped.slice(0,20))n.push(` unmeasured ${t.family} ${t.route} -- ${t.reason}`);if(e.skipped.length>20&&n.push(` ... and ${e.skipped.length-20} more`),e.effect.length){n.push("",` effect (treatment introduced a P0/P1 the control lacked) -- ${e.effect.length}`);for(const t of e.effect)n.push(` ${t.severity} ${t.family} ${t.route} ${t.kind}`)}else n.push(""," no measured treatment introduced a P0/P1 finding the control lacked");return n.join(`
5
+ `)}async function Be(){const e=process.argv.slice(2),n=ne(e,"json"),t=(...a)=>{(n?process.stderr:process.stdout).write(a.join(`
6
+ `)+`
7
+ `)},r=(...a)=>process.stderr.write(a.join(`
8
+ `)+`
9
+ `),s=a=>{n&&process.stdout.write(JSON.stringify(a,null,2)+`
10
+ `)},i=a=>{console.error("experiment: "+a),process.exitCode=b.INVALID},o=c.resolve(v(e,"repo",".")),u=v(e,"base",null),l=Number(v(e,"budget-cells","20")),d=Number(v(e,"wall-ms","300000")),x=v(e,"families",ie.join(",")).split(",").map(a=>a.trim()).filter(Boolean),w=ne(e,"mutate"),f=c.resolve(v(e,"queue",c.join(o,".verify","autonomous","experiment-queue.json")));if(!u)return i("--base URL is required");if(!Number.isInteger(l)||l<1)return i("--budget-cells must be a positive integer");if(!Number.isInteger(d)||d<1)return i("--wall-ms must be a positive integer");if(!A(o))return i(`no such directory: ${o}`);const h=c.join(o,".verify","autonomous","experiment-execution.json"),y=Ee(f);if(!y.ok){const a={status:y.missing?"unmeasured":"invalid",executed:[],skipped:[],effect:[],budget:{cells:l,cellsUsed:0,wallMs:d,elapsedMs:0},integrity_hash:null,runnerExit:null,experimentId:null,reason:y.reason};k(h,a),t("",` qa measure experiments ${o}`,"",` ${y.reason}`),s({ok:!1,exit:b.INVALID,...a}),process.exitCode=b.INVALID;return}const Y=y.queue.accepted.filter(a=>a&&typeof a.id=="string"),le=Y.map(a=>re(a.id)),z=Ie(e),X=c.join(U,"bin","sweep.mjs"),ce=c.join(U,"bin","experiment-runner.mjs"),de=se(X),fe=se(c.join(U,"bin","probe.js")),R=Ae({sweepDigest:de,probeDigest:fe,base:u,routes:le,flags:z}),me=Ce(o),pe=_e({candidates:Y,families:x,roles:me,mutateAllowed:w}),{combos:he,cellsUsed:ye}=Fe(pe,{budgetCells:l});let E=null;const N=v(e,"auth-lease-fd",null);if(N!=null){if(!/^\d+$/.test(N)||Number(N)<3)return i("--auth-lease-fd must name an inherited descriptor >= 3");try{E=p.readFileSync(Number(N),"utf8")}catch{return i("--auth-lease-fd could not be read")}if(!E)return i("--auth-lease-fd is empty")}const ge=Oe(),be=c.join(o,".verify","experiments",ge),xe={repo:o,base:u,sweepBin:X,trialDir:be,extra:z,mutateAllowed:w,integrityHash:R,authLease:E},S=[],C=[],G=[],M=[],P=Date.now();let J=!1;t("",` qa measure experiments ${o} --base ${u}`,` families: ${x.join(", ")} \xB7 budget: ${l} cells / ${d}ms`);const ve=je(o,"experiments");try{for(const a of he){if(!a.eligible||!a.run){C.push({route:a.route,family:a.family,reason:a.reason??"not eligible"});continue}if(!J&&Date.now()-P>=d&&(J=!0),J){C.push({route:a.route,family:a.family,reason:`wall-ms budget exhausted (${d}ms)`});continue}t(` ${a.family} ${a.route} ...`);const O=Math.max(3e4,d-(Date.now()-P)),m=await Je(a,{...xe,remainingMs:O});m.status==="measured"?(S.push({route:a.route,family:a.family,status:"measured",control:m.controlObservation,treatment:m.treatmentObservation}),G.push(m.controlObservation,m.treatmentObservation),M.push(...m.effect.map(we=>({route:a.route,family:a.family,...we})))):S.push({route:a.route,family:a.family,status:"invalid",reason:m.reason})}}finally{ve()}const $=S.filter(a=>a.status==="measured");let I=null,Q=null;if($.length){const a={family:"route-experiments",controls:$.map(m=>`${m.family}:${m.route}:control`),treatments:$.map(m=>`${m.family}:${m.route}:treatment`),integrity_hash:R,repetitions:1,mutation_risk:$.some(m=>m.family==="mutation-vs-no-mutation")?"contains-mutation":"none"};k(c.join(o,".verify","experiment.json"),a),k(c.join(o,".verify","experiment-observations.json"),G);const O=await te(process.execPath,[ce,c.join(o,".verify","experiment.json"),c.join(o,".verify","experiment-observations.json"),"--observed-integrity-hash",R],{stdout:"capture",stderr:"capture"});Q=O.code;try{I=JSON.parse(O.stdout)}catch{I={error:"experiment-runner.mjs did not print valid JSON",stderr:F(O.stderr,5)}}k(c.join(o,".verify","autonomous","experiment-observation.json"),I)}else r(" no family/route combination was measured -- experiment-runner.mjs was not invoked");const Z=$.length?"measured":S.length?"invalid":"unmeasured",L=$.length===0?b.INVALID:M.length?b.FINDINGS:b.OK,V={status:Z,executed:S,skipped:C,effect:M,budget:{cells:l,cellsUsed:ye,wallMs:d,elapsedMs:Date.now()-P},integrity_hash:R,runnerExit:Q,experimentId:I?.experiment_id??null};k(h,V),t("",Ve(V),"",` ${Z.toUpperCase()} exit ${L}`,` -> ${h}`),s({ok:L===b.OK,exit:L,...V}),process.exitCode=L}const Ue=(()=>{try{return p.realpathSync(c.resolve(process.argv[1]??""))===p.realpathSync(ee(import.meta.url))}catch{return!1}})();Ue&&Be().catch(e=>{console.error("experiment: "+(e?.stack??e)),process.exitCode=b.INVALID});export{ie as FAMILY_NAMES,Fe as applyBudget,v as arg,_ as buildObservation,Ae as computeIntegrityHash,ue as effectFrom,Ie as extraSweepArgs,se as fileDigest,D as firstNavStatus,ne as flag,Le as hashValue,De as owningRoles,_e as planCombos,q as recordsFor,qe as roleOwnsRoute,re as routeOf,j as sha256,H as stableJson,oe as underPrefix};
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import O from"node:fs";import S from"node:crypto";const i=process.argv.slice(2),p=i[0],J="usage: experiment-runner.mjs <schedule.json> [observations.json] [--observed-integrity-hash <hash>]";(!p||p.startsWith("--"))&&(console.error(J),process.exit(2));function n(e){console.error(`experiment: ${e}`),process.exit(2)}function F(e){const s=i.indexOf(e);return s<0?null:((!i[s+1]||i[s+1].startsWith("--"))&&n(`${e} requires a value`),i[s+1])}function $(e,s){try{return JSON.parse(O.readFileSync(e,"utf8"))}catch(y){n(`invalid ${s}: ${String(y).split(`
3
+ `)[0]}`)}}function r(e){return typeof e=="string"&&e.trim()?e.trim():null}function _(e){return Array.isArray(e)?`[${e.map(_).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().map(s=>`${JSON.stringify(s)}:${_(e[s])}`).join(",")}}`:JSON.stringify(e)}function g(e){return`sha256:${S.createHash("sha256").update(_(e)).digest("hex")}`}const t=$(p,"schedule");(!t||typeof t!="object"||Array.isArray(t))&&n("schedule must be an object");const b=r(t.family),o=t.controls??(r(t.control)?[t.control]:[]),c=t.treatments??(r(t.treatment)?[t.treatment]:[]);(!b||!Array.isArray(o)||!o.length||!Array.isArray(c)||!c.length)&&n("schedule requires family plus non-empty controls and treatments");const u=r(t.integrity_hash??t.input_integrity_hash);u||n("schedule requires integrity_hash");const d=F("--observed-integrity-hash")??r(t.observed_integrity_hash),a=!!(d&&u===d),l=t.repetitions??t.repeats??1;(!Number.isInteger(l)||l<1||l>100)&&n("repetitions must be an integer from 1 through 100");const A=i[1]&&!i[1].startsWith("--")?i[1]:null,j=A?$(A,"observations"):t.observations??[];Array.isArray(j)||n("observations must be an array");const q={family:b,controls:o,treatments:c,fixture_hash:t.fixture_hash??t.fixtureHash??null,deployment_hash:t.deployment_hash??t.deploymentHash??null,auth_hash:t.auth_hash??t.authHash??null,input_hash:t.input_hash??t.inputHash??null,harness_hash:t.harness_hash??t.harnessHash??null},f=g(q),m=[];d?a||m.push("integrity hash mismatch"):m.push("observed integrity hash missing");const H=[];for(let e=0;e<l;e+=1)for(const[s,y]of[["control",o],["treatment",c]])for(const x of y)H.push({trial_id:g({scheduleHash:f,arm:s,condition:x,repeat:e}).slice(0,23),arm:s,condition:x,repeat:e+1,status:a?"ready":"invalid",evidence_id:null});const h=j.map((e,s)=>((!e||typeof e!="object"||Array.isArray(e))&&n(`observation ${s+1} must be an object`),{observation_id:r(e.observation_id)??g({scheduleHash:f,row:e,index:s}).slice(0,23),arm:r(e.arm),route:typeof e.route=="string"?e.route.split("?")[0].replace(/\/\d+(?=\/|$)/g,"/:id"):null,status_class:Number.isFinite(e.status)?`${Math.floor(e.status/100)}xx`:e.status_class??null,outcome:e.outcome??e.result??null,metric:typeof e.metric=="number"&&Number.isFinite(e.metric)?e.metric:null,evidence_id:r(e.evidence_id),valid:a&&e.integrity_hash===u})),v=a&&h.length>0&&h.every(e=>e.valid),N=a?h.length?"recorded":"ready":"invalid";console.log(JSON.stringify({schema_version:1,experiment_id:`experiment-${f.slice(7,23)}`,family:b,schedule:{controls:o,treatments:c,repetitions:l,mutation_risk:t.mutation_risk??"none"},schedule_hash:f,trials:H,observations:h,integrity:{expected:u,actual:d,valid:a,confounders:m},causal_result:{valid:v,effect:0,confounders:v?[]:m,evidence_ids:h.filter(e=>e.valid&&e.evidence_id).map(e=>e.evidence_id)},status:N},null,2)),process.exitCode=v?0:N==="invalid"?2:1;
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import A from"node:fs";import c from"node:path";import{fileURLToPath as de}from"node:url";import{isMainThread as Le,parentPort as Be,Worker as Ue}from"node:worker_threads";import{buildCoherenceIr as He,sourceUnits as Fe}from"../lib/qa/coherence/ir.mjs";import{nestedShadowAt as Ze,scanModuleReferences as Qe}from"../lib/qa/coherence/symbols.mjs";import{balancedSlice as Ke,callParen as C,maskNonCode as Ve,splitTopLevel as he}from"../lib/qa/coherence/structural-js.mjs";import{loadSourceSnapshot as Je,computeCacheIdentity as Ge,listDir as Ye,readCacheableReport as Xe}from"../lib/qa/source-snapshot.mjs";import{loadMutationDeclaration as et}from"../lib/qa/mutation-policy.mjs";import{parseJsonc as tt}from"../lib/qa/fs.mjs";Le&&await new Promise(()=>{const e=Date.now(),t=new Ue(de(import.meta.url),{argv:process.argv.slice(2),execArgv:process.execArgv,stdout:!0,stderr:!0});t.stdout.pipe(process.stdout),t.stderr.pipe(process.stderr);let n=!1,s=null,o=!1,r=!1;const i=a=>{n||(n=!0,process.exit(a))},l=()=>{s===null||!o||!r||i(s)};t.stdout.on("end",()=>{o=!0,l()}),t.stderr.on("end",()=>{r=!0,l()}),t.on("exit",a=>{s=a,l()}),t.on("error",a=>{console.error(a),i(1)}),t.on("message",a=>{a?.type==="coherence-start"&&process.env.INVENTORY_INTERRUPT_BARRIER==="1"&&process.stderr.write(`inventory: progress ${JSON.stringify({phase:"coherence-start",elapsedMs:Date.now()-e})}
3
+ `)});for(const[a,d]of[["SIGINT",130],["SIGTERM",143]])process.once(a,()=>{process.stderr.write(`inventory: progress ${JSON.stringify({phase:"interrupted",elapsedMs:Date.now()-e,signal:a})}
4
+ `),t.terminate(),i(d)})});const D=2,me=c.resolve(c.dirname(de(import.meta.url)),".."),st=process.argv.slice(2).filter(e=>!e.startsWith("--")),f=c.resolve(st[0]??"."),ge=process.argv.includes("--stdout"),nt=process.argv.includes("--no-cache"),ot=Date.now();function Y(e,t={}){process.stderr.write(`inventory: progress ${JSON.stringify({phase:e,elapsedMs:Date.now()-ot,...t})}
5
+ `)}const rt=c.join(f,"verify.crud.json"),B=A.existsSync(rt)?et(f):{ok:!0,declaration:null};B.ok||(console.error(`inventory: ${B.reason}`),process.exit(2));const it=new Set([".ts",".tsx",".js",".jsx",".mts",".mjs",".vue",".svelte",".astro"]),at=/(^|\/)(node_modules|\.next|\.git|dist|build|out|coverage|\.turbo|\.vercel|venv|site-packages|vendor|storybook-static)(\/|$)|(^|\/)\.[^/]+\/|worktrees\/|\.(test|spec|stories)\.[tj]sx?$/,ct=12,_=Je(f,{skipRules:at}),u=e=>c.relative(f,e),U=_.files.map(e=>c.join(f,e)),S=U.filter(e=>it.has(c.extname(e))),X=e=>_.read(u(e)),p=new Map(S.map(e=>[e,X(e)])),j=ge?null:Ge(f,_,{skillRoot:me,toolFiles:["bin/inventory.mjs",...Ye(me,"lib/qa/coherence"),"lib/qa/source-snapshot.mjs"],schemaVersion:D,flags:{}}).identity;if(j&&!nt){const e=c.join(f,".verify","inventory.json"),t=Xe(e,{identity:j,schemaVersion:D});if(t){const n=new Date().toISOString();t.cache={hit:!0,identity:j,schemaVersion:D,sourceDigest:_.identity.sourceDigest,computedAt:t.cache.computedAt,reusedAt:n},A.writeFileSync(e,JSON.stringify(t,null,2)+`
6
+ `),process.stderr.write(`inventory: cache hit (identity ${j.slice(7,19)})
7
+ `),process.argv.includes("--quiet")||(console.log(`
8
+ `+f),console.log(" "+t.counts.routes+" routes "+t.counts.queries+" queries "+t.counts.mutations+" mutations (cached)"),console.log(" -> "+e+`
9
+ `)),process.exit(0)}}Y("source-loaded",{files:p.size});function ee(e){try{return JSON.parse(X(e))}catch{return null}}const lt=U.filter(e=>c.basename(e)==="package.json").map(ee).filter(Boolean),te=(...e)=>lt.some(t=>e.some(n=>t?.dependencies?.[n]||t?.devDependencies?.[n])),ut=e=>U.some(t=>e.test(c.basename(t))),ye=te("next")||ut(/^next\.config\./),pt=te("nuxt","unplugin-vue-router","vue-router"),ft=te("astro"),ve=U.filter(e=>c.basename(e)==="package.json").filter(e=>{const t=ee(e);return!!(t?.dependencies?.["expo-router"]||t?.devDependencies?.["expo-router"])}).map(e=>c.dirname(e));function dt(){return S.filter(e=>/(^|\/)app\/.*\/page\.(t|j)sx?$/.test(e)||/(^|\/)app\/page\.(t|j)sx?$/.test(e)).map(e=>{const n="/"+u(e).replace(/^.*?(^|\/)app\//,"").replace(/(^|\/)page\.(t|j)sx?$/,"").split("/").filter(s=>s&&!/^\(.*\)$/.test(s)&&!s.startsWith("@")).join("/");return{path:n==="/"?"/":n.replace(/\/$/,""),file:u(e),kind:"next-app"}})}function ht(){return S.filter(e=>/(^|\/)pages\/.*\.(t|j)sx?$/.test(e)&&!/\/(_app|_document|_error)\./.test(e)&&!/(^|\/)pages\/api\//.test(e)).map(e=>{let n="/"+u(e).replace(/^.*?(^|\/)pages\//,"").replace(/\.(t|j)sx?$/,"").replace(/\/index$/,"").replace(/^index$/,"");return{path:n==="/"?"/":n.replace(/\/$/,""),file:u(e),kind:"next-pages"}})}const mt=/\bfrom\s*["'`](react-router-dom|react-router|@tanstack\/react-router|@remix-run\/[^"'`]*|vue-router|@solidjs\/router)["'`]/,we=/createBrowserRouter|createHashRouter|createMemoryRouter|createRoutesFromElements|<Routes|useRoutes\(/,gt=e=>mt.test(e)||we.test(e);function yt(e,t){const n=[];for(const s of t.matchAll(/\bfrom\s*["'`](\.{1,2}\/[^"'`]+)["'`]/g)){const o=c.resolve(c.dirname(e),s[1]);n.push(o,...[".tsx",".ts",".jsx",".js","/index.tsx","/index.ts","/index.jsx","/index.js"].map(r=>o+r))}return n}const $e=new Set;for(const[e,t]of p)if(we.test(t))for(const n of yt(e,t))p.has(n)&&$e.add(n);const vt=(e,t)=>gt(t)||$e.has(e);function wt(e,t){if(!t.startsWith("."))return null;const n=c.resolve(c.dirname(e),t);return[n,...[".tsx",".ts",".jsx",".js"].flatMap(s=>[n+s,c.join(n,"index"+s)])].find(s=>p.has(s))??null}function $t(){const e=[];for(const[t,n]of p){for(const s of n.matchAll(/<Route\s+[^>]*path=["'`]([^"'`]+)["'`][^>]*?(?:element=\{\s*<([A-Z][\w]*)|component=\{\s*([A-Z][\w]*))/gs))e.push({path:s[1].startsWith("/")?s[1]:"/"+s[1],file:u(t),component:s[2]??s[3],kind:"react-router"});if(vt(t,n))for(const s of n.matchAll(/\{\s*path:\s*["'`]([^"'`]+)["'`][\s\S]{0,220}?(?:element:\s*<([A-Z][\w]*)|element:\s*([A-Z][\w]*)\s*[,}]|[Cc]omponent:\s*([A-Z][\w]*)|lazy|loader:|children:)/g)){const o=/\blazy:\s*\(\s*\)\s*=>\s*import\(\s*["'`]([^"'`]+)["'`]\s*\)/.exec(n.slice(s.index,s.index+240)),r=o?wt(t,o[1]):null;e.push({path:s[1].startsWith("/")?s[1]:"/"+s[1],file:u(r??t),component:s[2]??s[3]??s[4],kind:"route-config"})}}return e}function xt(){return S.filter(e=>/(^|\/)src\/routes\/.*\.[jt]sx$/.test(e)).filter(e=>!/(^|\/)__|(^|\/)-|\/route\.[jt]sx$|\.lazy\.[jt]sx$/.test(u(e))).map(e=>({path:"/"+u(e).replace(/^.*?(^|\/)src\/routes\//,"").replace(/\.[jt]sx$/,"").split("/").filter(s=>s&&!s.startsWith("_")).map(s=>s.startsWith("$")?":"+s.slice(1):s).filter(s=>s!=="index").join("/"),file:u(e),kind:"file-router"})).map(e=>({...e,path:e.path==="/"?"/":e.path.replace(/\/$/,"")}))}function St(){return S.filter(e=>/(^|\/)app\/routes\/.*\.[jt]sx?$/.test(e)).filter(e=>!/\.(server|client)\.[jt]sx?$/.test(e)).map(e=>{let t=u(e).replace(/^.*?(^|\/)app\/routes\//,"").replace(/\.[jt]sx?$/,"");t=t.replace(/\/route$/,""),t=t.replace(/\[\.\]/g,"\0");const s="/"+t.split(/[./]/).filter(o=>o&&o!=="_index").filter(o=>!o.startsWith("_")).map(o=>o.startsWith("$")?o==="$"?"*":":"+o.slice(1):o).map(o=>o.replace(/\u0000/g,".")).join("/");return{path:s==="/"?"/":s.replace(/\/$/,""),file:u(e),kind:"remix"}})}function Rt(){return S.filter(e=>/(^|\/)src\/routes\/.*\+page\.svelte$/.test(e)).map(e=>{const s="/"+u(e).replace(/^.*?(^|\/)src\/routes\//,"").replace(/\+page\.svelte$/,"").split("/").filter(o=>o&&!/^\(.*\)$/.test(o)).map(o=>o.replace(/^\[\.\.\.(.+)\]$/,"*").replace(/^\[(.+)\]$/,":$1")).join("/");return{path:s==="/"?"/":s.replace(/\/$/,""),file:u(e),kind:"sveltekit"}})}function bt(){return S.filter(e=>/(^|\/)src\/pages\/.*\.astro$/.test(e)).filter(e=>!/(^|\/)_/.test(u(e).replace(/^.*?(^|\/)src\/pages\//,""))).map(e=>{const s="/"+u(e).replace(/^.*?(^|\/)src\/pages\//,"").replace(/\.astro$/,"").replace(/(^|\/)index$/,"").split("/").filter(Boolean).map(o=>o.replace(/^\[\.\.\.(.+)\]$/,"*").replace(/^\[(.+)\]$/,":$1")).join("/");return{path:s==="/"?"/":s.replace(/\/$/,""),file:u(e),kind:"astro"}})}function At(){return S.filter(e=>/(^|\/)(src\/)?pages\/.*\.vue$/.test(e)).map(e=>{const s="/"+u(e).replace(/^.*?(^|\/)(src\/)?pages\//,"").replace(/\.vue$/,"").replace(/(^|\/)index$/,"").split("/").filter(Boolean).map(o=>o.replace(/^\[\.\.\.(.+)\]$/,"*").replace(/^\[(.+)\]$/,":$1")).join("/");return{path:s==="/"?"/":s.replace(/\/$/,""),file:u(e),kind:"vue-pages"}})}function Et(){const e=[];for(const t of ve)for(const n of["app","src/app"]){const s=c.join(t,n)+c.sep;for(const o of S){if(!o.startsWith(s)||!/\.[jt]sx?$/.test(o))continue;const r=o.slice(s.length);if(r.split(c.sep).some(a=>a.startsWith("_")||a.startsWith("+"))||!/\bexport\s+default\b/.test(p.get(o)??""))continue;const l="/"+r.replace(/\.[jt]sx?$/,"").split(c.sep).filter(a=>a&&!/^\(.*\)$/.test(a)).map(a=>a.replace(/^\[\.\.\.(.+)\]$/,"*").replace(/^\[(.+)\]$/,":$1")).filter(a=>a!=="index").join("/");e.push({path:l==="/"?"/":l.replace(/\/$/,""),file:u(o),kind:"expo-router"})}}return e}function kt(){const e=ee(c.join(f,"verify.routes.json"));return Array.isArray(e)?e.map(t=>typeof t=="string"?{path:t}:t).filter(t=>t&&typeof t.path=="string"&&t.path.startsWith("/")).map(t=>({path:t.path,...t.file?{file:t.file}:{},kind:"declared"})):[]}const _t=[...ye?dt():[],...ye?ht():[],...$t(),...xt(),...St(),...Rt(),...ft?bt():[],...pt?At():[],...ve.length?Et():[],...kt()].filter(e=>!/\.[a-z]{2,4}$/i.test(e.path)).filter((e,t,n)=>n.findIndex(s=>s.path===e.path)===t).sort((e,t)=>e.path.localeCompare(t.path));function xe(e,t,n){const s=[];if(t.startsWith("."))s.push(c.resolve(c.dirname(e),t));else for(const[o,r]of n)(t===o||t.startsWith(o+"/"))&&s.push(c.resolve(r,t.slice(o.length+1)));for(const o of s){const i=[o,...[".ts",".tsx",".js",".jsx"].flatMap(l=>[o+l,c.join(o,"index"+l)])].find(l=>p.has(l));if(i)return i}return null}function jt(){const e=[],t=new Set,n=(o,r,i=0)=>{if(i>4||t.has(o)||!A.existsSync(o))return;t.add(o);const l=tt(X(o));if(!l){console.error(`qa: ${u(o)} is not readable as JSON/JSONC -- its "paths" aliases were NOT applied; imports through them will not resolve`);return}const a=c.dirname(o),d=l?.compilerOptions?.baseUrl??".";for(const[h,m]of Object.entries(l?.compilerOptions?.paths??{})){const g=h.replace(/\/\*$/,""),x=String(m?.[0]??"").replace(/\/\*$/,"");g&&x&&r.push([g,c.resolve(a,d,x)])}typeof l.extends=="string"&&l.extends.startsWith(".")&&n(c.resolve(a,l.extends),r,i+1)},s=new Set([c.join(f,"tsconfig.json"),c.join(f,"jsconfig.json")]);for(const o of S)for(let r=c.dirname(o);r.startsWith(f)&&r!==f;r=c.dirname(r))for(const i of["tsconfig.json","jsconfig.json"])A.existsSync(c.join(r,i))&&s.add(c.join(r,i));for(const o of s){const r=[];n(o,r),r.length&&e.push({dir:c.dirname(o),entries:r})}return e.sort((o,r)=>r.dir.length-o.dir.length),e.length||e.push({dir:f,entries:[["@",f],["@",c.join(f,"src")],["~",c.join(f,"src")]]}),e}const Se=jt();function Re(e){const t=Se.filter(n=>e.startsWith(n.dir+c.sep)||e===n.dir);return[...t,...Se.filter(n=>!t.includes(n))].flatMap(n=>n.entries)}const se=new Map;function be(e){if(se.has(e))return se.get(e);const t=Re(e),n=Qe({file:u(e),source:p.get(e)??""}).map(s=>({...s,resolvedFile:xe(e,s.specifier,t)}));return se.set(e,n),n}function H(e){return[...new Set(be(e).map(t=>t.resolvedFile).filter(Boolean))]}function Ot(e){const t=new Set([e]);let n=[e];for(let s=0;s<ct&&n.length;s++){const o=[];for(const r of n)for(const i of H(r))t.has(i)||(t.add(i),o.push(i));n=o}return t}function O(e){const t=e.match(/^\s*\[\s*["'`]([\w.:-]+)["'`]/);if(t)return t[1];const n=e.match(/\b[A-Za-z_$][\w$]*\s*\.\s*([A-Za-z_$][\w$]*)/);if(n)return n[1];const s=e.match(/^\s*["'`]([\w.:/-]+)["'`]/);return s?s[1].replace(/^\//,"").split("/")[0]:null}const F=String.raw`(?:<[^()]*?>)?`,Tt=new RegExp(String.raw`\b([A-Za-z_$][\w$]*)\.(?:get|post|put|patch|delete)\s*`+F+String.raw`\s*\(\s*[\`"']([^\`"']+)`,"i"),It=/[`"'](\/(?:api|v\d+|rest|graphql)\/[^`"'\s]+)[`"']/;function Ae(e,t){if(!e||!t)return null;const n=p.get(t)??"",s=new RegExp(String.raw`\b(?:const|let|var)\s+${e}\s*=\s*(?:axios\.)?(?:create|extend)\s*\(\s*\{[\s\S]{0,500}?\b(?:baseURL|prefixUrl)\s*:\s*[\`"']([^\`"']+)[\`"']`).exec(n)?.[1];if(s!=null)return s;const o=new RegExp(String.raw`\bimport\s+${e}\s+from\s+[\`"']([^\`"']+)[\`"']`).exec(n)?.[1],r=o?xe(t,o,Re(t)):null;return r?/\bexport\s+default\s+(?:axios\.)?(?:create|extend)\s*\(\s*\{[\s\S]{0,500}?\b(?:baseURL|prefixUrl)\s*:\s*[\`"']([^\`"']+)[\`"']/.exec(p.get(r)??"")?.[1]??null:null}function Ee(e,t){return!e||/^[a-z][a-z\d+.-]*:\/\//i.test(t)?t:`${e.replace(/\/+$/,"")}/${t.replace(/^\/+/,"")}`||"/"}function z(e,t=null){const n=e.match(Tt);return n?Ee(Ae(n[1],t),n[2]):(e.match(/fetch\s*\(\s*[`"']([^`"']+)/)??e.match(It)??[])[1]??null}const Mt=new RegExp(String.raw`\.(get|post|put|patch|delete)\s*`+F+String.raw`\s*\(`,"i"),Z=e=>((e.match(Mt)??[])[1]??"").toUpperCase()||null,Nt=/^(api|v\d+|admin-panel|admin|rest|graphql|public|internal)$/;function ke(e){return e?e.split("?")[0].split("/").filter(Boolean).filter(t=>!t.startsWith("$")&&!t.startsWith(":")&&!/^\{.*\}$/.test(t)&&!/^\d+$/.test(t)).filter(t=>!Nt.test(t)):[]}function T(e){if(!e)return null;const t=e.split("?")[0].split("/").filter(Boolean);return ke(e)[0]??t[0]??null}function Q(e,t){const n=new RegExp(`(?:export\\s+)?(?:async\\s+)?function\\s+${e}\\s*\\(|(?:export\\s+)?const\\s+${e}\\s*=`),s=[];for(const o of t){const r=p.get(o),i=r&&r.match(n);i&&s.push([o,r,i.index])}return s}const K=new Map;function ne(e,t){if(!e)return null;const n=t+"::"+e;if(K.has(n))return K.get(n);K.set(n,null);const s=t?H(t):[],o=s.length?[...new Set(s.flatMap(H))]:[];let r=Q(e,t?[t]:[]);if(r.length!==1&&(r=Q(e,s)),r.length!==1&&(r=Q(e,o)),r.length!==1&&(r=Q(e,[...p.keys()])),r.length!==1)return null;const[i,l,a]=r[0],d=z(l.slice(a,a+1500),i);return d&&K.set(n,d),d??null}function q(e,t,n=6e3){let s=0;for(let o=t;o<Math.min(e.length,t+n);o++){const r=e[o];if(r==="("||r==="{"||r==="[")s++;else if((r===")"||r==="}"||r==="]")&&(s--,s===0))return e.slice(t,o+1)}return e.slice(t,t+n)}const I=(e,t)=>e.slice(0,t).split(`
10
+ `).length,P=(e,t,n,s)=>{const o=t.slice(0,n);return{file:u(e),start:n,end:n+s.length,line:o.split(`
11
+ `).length,column:n-o.lastIndexOf(`
12
+ `)}};function oe(e,t){const s=[...e.slice(0,t).matchAll(/(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)|(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*=/g)].map(o=>o[1]??o[2]);return s.reverse().find(o=>/^use[A-Z]/.test(o))??s[0]??null}function Wt(){const e=new Set,t=new Set,n=/(?:export\s+)?(?:async\s+)?function\s+(use[A-Z]\w*)\s*[(<]|(?:export\s+)?const\s+(use[A-Z]\w*)\s*=/g;for(const s of p.values())for(const o of s.matchAll(n)){const r=o[1]??o[2];if(!r)continue;const i=s.indexOf("{",o.index),l=i<0?"":q(s,i,4e3),a=/return\s+use(?:Suspense)?(?:Infinite)?Query\s*[(<]|=>\s*use(?:Suspense)?(?:Infinite)?Query\s*[(<]/.test(l),d=/return\s+useMutation\s*[(<]|=>\s*useMutation\s*[(<]/.test(l);if(!a&&!d)continue;const m=((s.slice(o.index,i<0?o.index:i).match(/\(([^)]*)\)/)??[])[1]??"").split(",").map(y=>y.trim().split(/[:=\s]/)[0]).filter(y=>/^[A-Za-z_$][\w$]*$/.test(y)),g=l.match(/(?:return\b|=>)\s*(use(?:Mutation|(?:Suspense)?(?:Infinite)?Query))\b/),x=g?C(l,g.index+g[0].length):-1,w=x<0?null:Ke(l,x),k=w?he(w.text.slice(1,-1))[0]?.trim():"",R=k?.startsWith("{")&&k.endsWith("}")?he(k.slice(1,-1)).filter(y=>/^(?:queryKey|mutationKey|mutationFn|queryFn)\s*:/.test(y)).map(y=>y.slice(y.indexOf(":")+1)):[];d&&R.some(y=>z(y))||!m.some(y=>R.some(qe=>{const L=Ve(qe),fe=y.replace(/[$]/g,"\\$&");return L.trim()===y||!new RegExp("\\b(?:const|let|var|function|class)\\s+"+fe+"\\b").test(L)&&[...L.matchAll(new RegExp("\\b"+fe+"\\b(?=\\s*(?:\\?\\.|\\.|\\())","g"))].some(Pe=>!Ze(L,y,Pe.index,{masked:!0}))}))||(a&&e.add(r),d&&t.add(r))}for(const s of t)e.delete(s);return{queryWrappers:e,mutationWrappers:t}}const{queryWrappers:Ct,mutationWrappers:_e}=Wt();function je(e){const t=e.slice(1,-1),n=[];let s=0,o=0;for(let r=0;r<t.length;r++){const i=t[r];"([{".includes(i)?s++:")]}".includes(i)?s--:i===","&&s===0&&(n.push(t.slice(o,r)),o=r+1)}return n.push(t.slice(o)),n.map(r=>r.trim())}const Dt=e=>je(e)[0]??"";function Oe(e,t,n){if(!t.size)return[];const s=p.get(e)??"",o=[],r=new RegExp("\\b("+[...t].join("|")+")\\b","g");for(const i of s.matchAll(r)){const l=C(s,i.index+i[0].length);if(l<0||/\b(function|const|let|var)\s+$/.test(s.slice(Math.max(0,i.index-24),i.index)))continue;const a=q(s,l),d=Dt(a),m=(je(a)[1]??"").match(/^([A-Za-z_$][\w$]*)$/)?.[1]??"",g={entity:O(d),key:d.trim().slice(0,120),endpoint:z(a,e)??ne(m,e),file:u(e),line:I(s,i.index),span:P(e,s,i.index,i[0]),via:i[1]};if(n!=="mutation"){o.push(g);continue}const x=[...a.matchAll(/invalidateQueries\s*\(\s*\{?\s*(?:queryKey:\s*)?([^\n,)]+)/g)].map(R=>O(R[1])).filter(Boolean),w=[...a.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)].map(R=>R[1]),k=oe(s,i.index);o.push({...g,hook:k,writes:(g.endpoint?T(g.endpoint):null)??g.entity??x[0],verb:Z(a),invalidates:[...new Set(x)],clearsCache:re.test(a)||w.some(R=>ie.has(R)),clearsIndirectly:w.some(R=>ie.has(R)),nonWrite:ae(g.via,k,g.endpoint)||Z(a)==="GET"})}return o}function zt(e){const t=p.get(e)??"",n=[...Oe(e,Ct,"query")];for(const s of t.matchAll(/\buse(?:Suspense)?(?:Infinite)?Query\b/g)){const o=C(t,s.index+s[0].length);if(o<0)continue;const r=q(t,o),i=(r.match(/queryKey:\s*([^\n,]+)/)??[])[1];if(!i)continue;const l=(r.match(/queryFn:\s*([A-Za-z_$][\w$]*)/)??[])[1]??"";n.push({entity:O(i),key:i.trim().slice(0,120),endpoint:z(r,e)??ne(l,e),file:u(e),line:I(t,s.index),span:P(e,t,s.index,s[0])})}for(const s of t.matchAll(/\buseSWR\s*(?:<[^>]*>)?\s*\(\s*(["'`][^"'`]+["'`]|\[[\s\S]{0,120}?\])/g))n.push({entity:O(s[1]),key:s[1].replace(/\s+/g," ").slice(0,120),endpoint:null,file:u(e),line:I(t,s.index),span:P(e,t,s.index,"useSWR"),hook:"swr"});return n}const re=/(?:invalidate|reset|remove|refetch)Queries\s*\(|set(?:Query|Queries)Data\s*\(|(?:query|mutation)(?:Cache|Client)\s*\.\s*clear\s*\(/;function qt(){const e=new Set,t=/(?:function\s+([A-Za-z_$][\w$]*)\s*\(|(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?(?:\(|function)|([A-Za-z_$][\w$]*)\s*:\s*(?:async\s*)?\()/g;for(const n of p.values())for(const s of n.matchAll(t)){const o=s[1]??s[2]??s[3];!o||e.has(o)||re.test(n.slice(s.index,s.index+1200))&&e.add(o)}return e}const ie=qt(),Pt=/^(exports?|downloads?|prints?|tracks?|analytics?|telemetry|logs?|previews?|validates?|checks?|searches|search|verify|verifies)$/i,Lt=/^reports?$/i,Bt=/^(generates?|runs?|builds?|renders?|exports?|downloads?|prints?|previews?|fetch|fetches|get|gets|load|loads|view|views|open|opens)$/i,Ut=e=>String(e??"").replace(/([a-z\d])([A-Z])/g,"$1 $2").split(/[^A-Za-z\d]+/).filter(Boolean),ae=(...e)=>e.some(t=>{const n=Ut(t);return n.some(s=>Pt.test(s))||n.some(s=>Lt.test(s))&&n.some(s=>Bt.test(s))}),Ht=_e.size>0||[...p.values()].some(e=>/\buseMutation\b/.test(e)),Ft=/^(params|searchParams|query|headers|formData|cache|caches|localStorage|sessionStorage|storage|map|set|cookies|queryClient|store|state|registry)$/i,Te=/^(?:\/(?!\/)|https?:\/\/|\$\{)/,Zt=new RegExp(String.raw`\b([A-Za-z_$][\w$]*)\s*\.\s*(post|put|patch|delete)\s*`+F+String.raw`\s*\(\s*[\`"']([^\`"']*)`,"gi");function Ie(e,t,n,s,o,r){const i=oe(t,n)??"";return{writes:T(o),verb:s.toUpperCase(),endpoint:o,fnName:i,hook:/^use[A-Z]/.test(i)?i:null,invalidates:[],clearsCache:!1,clearsIndirectly:!1,via:"raw-write",nonWrite:ae(i,o),file:u(e),line:I(t,n),span:P(e,t,n,r)}}function Qt(e){const t=p.get(e)??"",n=[];for(const s of t.matchAll(Zt)){const o=Ae(s[1],e);Ft.test(s[1])||!Te.test(s[3])&&!o||n.push(Ie(e,t,s.index,s[2],Ee(o,s[3]),s[0]))}for(const s of t.matchAll(/\bfetch\b/g)){const o=C(t,s.index+s[0].length);if(o<0)continue;const r=q(t,o),i=(r.match(/^\(\s*[`"']([^`"']*)/)??[])[1],l=(r.match(/method\s*:\s*[`"'](post|put|patch|delete)[`"']/i)??[])[1];!l||!i||!Te.test(i)||n.push(Ie(e,t,s.index,l,i,s[0]))}return n}function Kt(e){const t=p.get(e)??"",n=[...Oe(e,_e,"mutation")];Ht||n.push(...Qt(e));for(const s of t.matchAll(/\buseMutation\b/g)){const o=C(t,s.index+s[0].length);if(o<0)continue;const r=q(t,o),i=[...r.matchAll(/invalidateQueries\s*\(\s*\{?\s*(?:queryKey:\s*)?([^\n,)]+)/g)].map(w=>O(w[1])).filter(Boolean),l=[...r.matchAll(/set(?:Query|Queries)Data\s*\(\s*([^\n,]+)/g)].map(w=>O(w[1])).filter(Boolean),a=(r.match(/mutationFn:\s*([A-Za-z_$][\w$]*)/)??[])[1]??"",d=z(r,e)??ne(a,e),h=oe(t,s.index),m=re.test(r),x=[...r.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)].map(w=>w[1]).some(w=>ie.has(w));n.push({writes:(d?T(d):null)??l[0]??i[0],verb:Z(r),endpoint:d,fnName:a,hook:h,invalidates:[...new Set([...i,...l])],clearsCache:m||x,clearsIndirectly:x,nonWrite:ae(a,h,d)||Z(r)==="GET"||(r.match(/\.(get|post|put|patch|delete)\s*\(/)??[])[1]==="get",file:u(e),line:I(t,s.index),span:P(e,t,s.index,s[0])})}return n}const v=_t.map(e=>{if(!e.file)return{...e,modules:0,files:[],entities:[],queries:[],mutations:[]};const t=c.resolve(f,e.file),n=Ot(t),s=[...n].flatMap(zt),o=[...n].flatMap(Kt);return{...e,modules:n.size,files:[...n].map(u).sort().slice(0,400),entities:[...new Set(s.map(r=>r.entity).filter(Boolean))].sort(),queries:s,mutations:o}}),Vt=Object.fromEntries([...p].map(([e,t])=>[u(e),t]));process.env.INVENTORY_INTERRUPT_BARRIER==="1"&&Be?.postMessage({type:"coherence-start"}),Y("coherence-start",{files:p.size,routes:v.length});const $=He({units:Fe({sourceByFile:Vt,importsByFile:Object.fromEntries([...p.keys()].map(e=>[u(e),H(e).map(u).sort()])),moduleRefsByFile:Object.fromEntries([...p.keys()].map(e=>[u(e),be(e).map(t=>({...t,resolvedFile:t.resolvedFile?u(t.resolvedFile):null}))]))}),routes:v,entities:B.declaration?.schema_version===2?B.declaration.entities??[]:[]});Y("coherence-complete",{reads:$.reads.length,mutations:$.mutations.length});const Me=new Map($.reads.filter(e=>e.span).map(e=>[`${e.file}:${e.span.start}`,e]));for(const e of v){const t=new Set($.surfaces.filter(n=>n.route===e.path).flatMap(n=>n.readIds));e.queries=e.queries.filter(n=>n.span&&t.has(Me.get(`${n.file}:${n.span.start}`)?.id)).map(n=>{const s=Me.get(`${n.file}:${n.span.start}`),o=s.pattern.segments[0];return{...n,endpoint:s.endpoint,entity:o?.kind==="literal"&&typeof o.value=="string"?o.value:null}}),e.entities=[...new Set(e.queries.map(n=>n.entity).filter(Boolean))].sort()}const Jt=new Map($.cacheOperations.map(e=>[e.id,e])),Ne=new Map($.cacheKeys.map(e=>[e.id,e]));for(const e of v)for(const t of e.mutations){const n=$.mutations.filter(o=>o.file===t.file&&o.line===t.line&&o.endpoint===t.endpoint);if(n.length!==1)continue;const s=n[0].operationIds.map(o=>Jt.get(o)).filter(Boolean);t.invalidates=[...new Set(s.map(o=>Ne.get(o.material.keyId)?.material.segments[0]).filter(o=>o?.kind==="literal"&&typeof o.value=="string").map(o=>o.value))],t.clearsCache||=s.length>0,t.clearsIndirectly||=s.some(o=>o.material.receiver.resolution!=="resolved"||o.material.predicate||o.material.keyMatch==="unknown"||Ne.get(o.material.keyId)?.material.segments.some(r=>r.kind!=="literal"))}const E={};for(const e of v)for(const t of e.entities)(E[t]??=[]).push(e.path);for(const e of Object.keys(E))E[e]=[...new Set(E[e])].sort();const M={},We={};for(const e of v)for(const t of e.queries){const n=T(t.endpoint);n&&((M[n]??=[]).push(e.path),t.entity&&(We[n]??=new Set).add(t.entity))}for(const e of Object.keys(M))M[e]=[...new Set(M[e])].sort();const Gt=v.flatMap(e=>e.mutations.map(t=>({...t,route:e.path}))),V=new Map(Gt.map(e=>[`${e.file}:${e.line}`,e])),b=[],Yt=.4,Xt=10,es=/^(auth|authentication|session|sessions|me|csrf|config|configuration|settings)$/i,N=e=>String(e??"").toLowerCase().replace(/[-_./]/g,"").replace(/s$/,"");for(const e of V.values()){if(e.nonWrite||e.via==="raw-write")continue;const t=T(e.endpoint),n=(t?M[t]:null)??(e.writes?E[e.writes]:null)??null,s=t&&es.test(t)||n&&n.length>=Xt&&n.length>v.length*Yt,o=s?null:n,r=e.hook?`${e.hook}()`:`${e.file}:${e.line}`;if(!e.invalidates.length){if(e.clearsCache)continue;const h=o?.length?"P1":e.writes?"P2":"P3";b.push({severity:h,kind:"no-invalidation",unresolved:!o?.length,route:e.route,entity:e.writes??t,hook:e.hook,mutation:`${e.file}:${e.line}`,verb:e.verb,endpoint:e.endpoint,invalidates:[],staleRoutes:o,detail:`${r} writes ${e.endpoint??e.writes??"server state"} and invalidates no query`+(o?.length?`; ${o.length} route(s) may render it and could retain stale data after this write`:s?`; "${t}" is an ambient concern`+(n?.length?` read by ${n.length} of ${v.length} routes`:" (session/config, not a rendered collection)")+", so the blast radius is not meaningful":e.writes?`; nothing found rendering "${e.writes}"`:"; could not determine what goes stale")});continue}if(e.clearsIndirectly)continue;const i=t?[...We[t]??[]]:[],l=(h,m)=>N(h)===N(m)||N(h).includes(N(m))||N(m).includes(N(h)),a=[...new Set([e.writes,...ke(e.endpoint)].filter(Boolean))],d=e.invalidates.some(h=>a.some(m=>l(h,m))||i.some(m=>l(h,m)));e.writes&&!d&&o?.length&&b.push({severity:"P1",kind:"partial-invalidation",route:e.route,entity:e.writes??t,hook:e.hook,mutation:`${e.file}:${e.line}`,verb:e.verb,endpoint:e.endpoint,invalidates:e.invalidates,staleRoutes:o,detail:`${r} writes "${e.writes}" but invalidates only [${e.invalidates.join(", ")}] -- none of which any reader of "${e.writes}" queries under; ${o.length} route(s) could retain stale data after this write`})}const ce=[];for(let e=b.length-1;e>=0;e--){const[t,n]=String(b[e].mutation??"").split(":");if(!t||!n)continue;let s;try{s=A.readFileSync(c.join(f,t),"utf8").split(`
13
+ `)}catch{continue}/verify-ignore:\s*sync-risk\b/.test(s.slice(Math.max(0,+n-7),+n).join(`
14
+ `))&&ce.push(...b.splice(e,1))}b.sort((e,t)=>e.severity.localeCompare(t.severity)||(t.staleRoutes?.length??-1)-(e.staleRoutes?.length??-1));const Ce={};for(const e of v.flatMap(t=>t.queries))!e.entity||!e.endpoint||/\$\{|\$\w/.test(e.endpoint)||!T(e.endpoint)||(Ce[e.endpoint]??={entity:e.entity,keys:new Set}).keys.add(e.key.replace(/\s/g,""));const De=Object.entries(Ce).filter(([,e])=>e.keys.size>1).map(([e,t])=>({entity:t.entity,endpoint:e,keys:[...t.keys],detail:`${e} is cached under ${t.keys.size} different key shapes -- one resource, two caches that can disagree`})),ts=new RegExp(String.raw`\.(?:post|patch|delete|put)\s*`+F+String.raw`\s*\(|method:\s*["'\`](?:POST|PATCH|DELETE|PUT)["'\`]`,"gi"),J=[...p.values()].reduce((e,t)=>e+(t.match(ts)??[]).length,0),ss=10,ze=[...V.values()].filter(e=>e.via!=="raw-write"),le=V.size-ze.length,ue=ze.length>0||J<ss?{status:"measured",rawWriteCallSites:J}:{status:"unmeasured",rawWriteCallSites:J,rawWriteMutations:le,reason:`found ${J} write-shaped call site(s) (.post/.patch/.delete/.put, or a fetch with method: POST/PATCH/DELETE/PUT) but recognised 0 through useMutation/mutationFn/invalidateQueries/useSWR -- the data layer's own vocabulary was not recognised`+(le?`; ${le} of them were inventoried as raw client writes (endpoint and verb only, no cache model), so they are available to plan against but cannot be judged for staleness`:"")+", so sync-risk coverage is unmeasured rather than a clean result"},ns=/\bcreateHashRouter\s*\(|<HashRouter\b|\buseHashLocation\b|\bcreateWebHashHistory\s*\(|\bcreateHashHistory\s*\(/,pe=[];for(const[e,t]of p){const n=t.match(ns);n&&pe.push({file:u(e),line:I(t,n.index),match:n[0]})}const W=pe.length?{mode:"hash",evidence:pe.slice(0,20),note:"route paths are logical ids; this app serves each of them at <base>/#<path>. Runtime navigation and landed-route identity are still pathname-only, so a sweep against <base> reaches only the root route and records every other one unreached (#269)."}:{mode:"path",evidence:[]},G=JSON.stringify({root:f,atlasVersion:2,routing:W,generated:new Date().toISOString(),counts:{files:S.length,routes:v.length,entities:Object.keys(E).length,queries:new Set(v.flatMap(e=>e.queries.map(t=>`${t.file}:${t.line}`))).size,queryUsages:v.reduce((e,t)=>e+t.queries.length,0),mutations:V.size,syncRisks:b.length,syncRisksWaived:ce.length,duplicateSources:De.length,coherenceReads:$.reads.length,coherenceMutations:$.mutations.length,coherenceInvalidations:$.cacheOperations.length,coherenceSurfaces:$.surfaces.length},routes:v,matrix:E,resourceMatrix:M,syncRisks:b,syncRisksWaived:ce,syncRiskCoverage:ue,duplicateSources:De,coherence:$,cache:j?{hit:!1,identity:j,schemaVersion:D,sourceDigest:_.identity.sourceDigest,computedAt:new Date().toISOString()}:{hit:!1,identity:null,schemaVersion:D,sourceDigest:_.identity.sourceDigest,computedAt:new Date().toISOString()}},null,2)+`
15
+ `;if(ge)process.stdout.write(G);else{const e=c.join(f,".verify");A.mkdirSync(e,{recursive:!0});const t=c.join(e,"inventory.json");A.writeFileSync(t,G);const n=JSON.parse(G).counts,s=JSON.parse(G).syncRisks.filter(o=>o.severity==="P1").length;console.log(`
16
+ `+f),console.log(" "+n.routes+" routes "+n.queries+" queries "+n.mutations+" mutations"),W.mode==="hash"&&console.log(" HASH-ROUTED ("+W.evidence[0].file+":"+W.evidence[0].line+" "+W.evidence[0].match+"): "+W.note),ue.status==="unmeasured"&&console.log(" UNMEASURED: "+ue.reason),console.log(" "+n.syncRisks+" sync risks ("+s+" with a resolved blast radius) "+n.duplicateSources+" duplicate cache keys"+(n.syncRisksWaived?" ("+n.syncRisksWaived+" waived by verify-ignore comments)":"")),console.log(" -> "+t+`
17
+ `)}