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.
- package/LICENSE +22 -0
- package/NOTICE +9 -0
- package/README.md +100 -0
- package/SKILL.md +65 -0
- package/agent/prebuilt/darwin-universal/qa-agent +0 -0
- package/agent/prebuilt/linux-arm64/qa-agent +0 -0
- package/agent/prebuilt/linux-x64/qa-agent +0 -0
- package/agent/release-manifest.json +31 -0
- package/bin/autonomous-cache.mjs +2 -0
- package/bin/bench-generate.mjs +232 -0
- package/bin/bench-graph-query.mjs +2 -0
- package/bin/bench-score.mjs +10 -0
- package/bin/classify.mjs +15 -0
- package/bin/coherence-playwright.mjs +1 -0
- package/bin/coherence-runner.mjs +6 -0
- package/bin/crud-runner.mjs +12 -0
- package/bin/experiment-executor.mjs +10 -0
- package/bin/experiment-runner.mjs +3 -0
- package/bin/inventory.mjs +17 -0
- package/bin/probe.js +879 -0
- package/bin/qa.mjs +16 -0
- package/bin/rules.mjs +5 -0
- package/bin/run-benchmarks.mjs +2 -0
- package/bin/runtime-packs.mjs +3 -0
- package/bin/semantic-explorer.mjs +3 -0
- package/bin/shadow-collector.mjs +3 -0
- package/bin/shadow-replay.mjs +6 -0
- package/bin/soak-marathon.mjs +15 -0
- package/bin/sweep.mjs +43 -0
- package/bin/validate-priors.mjs +3 -0
- package/bin/verify-artifacts.mjs +3 -0
- package/bin/verify-manifest.mjs +2 -0
- package/install-skill.sh +45 -0
- package/lib/qa/agent-availability.mjs +1 -0
- package/lib/qa/agent-result.mjs +1 -0
- package/lib/qa/analysis.mjs +5 -0
- package/lib/qa/atlas-coverage.mjs +11 -0
- package/lib/qa/atlas-v1.mjs +1 -0
- package/lib/qa/atlas.mjs +2 -0
- package/lib/qa/auth/coordinator.mjs +1 -0
- package/lib/qa/auth/launcher.mjs +3 -0
- package/lib/qa/auth/lease.mjs +1 -0
- package/lib/qa/auth/oracle.mjs +1 -0
- package/lib/qa/auth/personas.mjs +3 -0
- package/lib/qa/autonomous-cache.mjs +3 -0
- package/lib/qa/block-ledger.mjs +1 -0
- package/lib/qa/capture-report.mjs +63 -0
- package/lib/qa/ci.mjs +11 -0
- package/lib/qa/cli.mjs +538 -0
- package/lib/qa/coherence/analyze.mjs +1 -0
- package/lib/qa/coherence/cache-key.mjs +6 -0
- package/lib/qa/coherence/causes.mjs +1 -0
- package/lib/qa/coherence/concurrency.mjs +1 -0
- package/lib/qa/coherence/contracts.mjs +1 -0
- package/lib/qa/coherence/entity-aliases.mjs +1 -0
- package/lib/qa/coherence/enums.mjs +1 -0
- package/lib/qa/coherence/family-report.mjs +1 -0
- package/lib/qa/coherence/flow-pool.mjs +1 -0
- package/lib/qa/coherence/graph.mjs +1 -0
- package/lib/qa/coherence/identity.mjs +1 -0
- package/lib/qa/coherence/ir.mjs +4 -0
- package/lib/qa/coherence/navigation.mjs +1 -0
- package/lib/qa/coherence/observe.mjs +2 -0
- package/lib/qa/coherence/offline.mjs +1 -0
- package/lib/qa/coherence/plan-join.mjs +1 -0
- package/lib/qa/coherence/provenance.mjs +2 -0
- package/lib/qa/coherence/report.mjs +1 -0
- package/lib/qa/coherence/representations.mjs +1 -0
- package/lib/qa/coherence/risk.mjs +2 -0
- package/lib/qa/coherence/structural-js.mjs +11 -0
- package/lib/qa/coherence/symbols.mjs +6 -0
- package/lib/qa/coherence/tanstack.mjs +4 -0
- package/lib/qa/coherence/views.mjs +1 -0
- package/lib/qa/coherence-planner.mjs +1 -0
- package/lib/qa/command-catalog.mjs +2 -0
- package/lib/qa/commands/dev.mjs +1 -0
- package/lib/qa/commands/fix.mjs +1 -0
- package/lib/qa/commands/impl/analysis.mjs +1 -0
- package/lib/qa/commands/impl/atlas.mjs +1 -0
- package/lib/qa/commands/impl/coherence.mjs +1 -0
- package/lib/qa/commands/impl/crud.mjs +2 -0
- package/lib/qa/commands/impl/doctor.mjs +1 -0
- package/lib/qa/commands/impl/experiments.mjs +1 -0
- package/lib/qa/commands/impl/fp.mjs +1 -0
- package/lib/qa/commands/impl/graph.mjs +1 -0
- package/lib/qa/commands/impl/invariants.mjs +1 -0
- package/lib/qa/commands/impl/lanes.mjs +7 -0
- package/lib/qa/commands/impl/ledger.mjs +11 -0
- package/lib/qa/commands/impl/loop.mjs +1 -0
- package/lib/qa/commands/impl/marathon.mjs +1 -0
- package/lib/qa/commands/impl/next.mjs +38 -0
- package/lib/qa/commands/impl/report.mjs +4 -0
- package/lib/qa/commands/impl/research.mjs +4 -0
- package/lib/qa/commands/impl/selftest.mjs +1 -0
- package/lib/qa/commands/impl/serve.mjs +1 -0
- package/lib/qa/commands/impl/shadow.mjs +1 -0
- package/lib/qa/commands/impl/stability.mjs +1 -0
- package/lib/qa/commands/impl/triage.mjs +1 -0
- package/lib/qa/commands/impl/validate.mjs +4 -0
- package/lib/qa/commands/impl/version.mjs +1 -0
- package/lib/qa/commands/impl/watch.mjs +1 -0
- package/lib/qa/commands/init.mjs +6 -0
- package/lib/qa/commands/measure.mjs +1 -0
- package/lib/qa/commands/run.mjs +1 -0
- package/lib/qa/commands/show.mjs +1 -0
- package/lib/qa/commands/verify.mjs +6 -0
- package/lib/qa/deepening.mjs +1 -0
- package/lib/qa/denominators.mjs +1 -0
- package/lib/qa/depth.mjs +4 -0
- package/lib/qa/doctor.mjs +2 -0
- package/lib/qa/experiments-default.mjs +1 -0
- package/lib/qa/fix-lanes.mjs +3 -0
- package/lib/qa/forced-states.mjs +1 -0
- package/lib/qa/fp.mjs +2 -0
- package/lib/qa/fs.mjs +6 -0
- package/lib/qa/graph-schema.mjs +2 -0
- package/lib/qa/graph.mjs +3 -0
- package/lib/qa/harness-plan.mjs +3 -0
- package/lib/qa/init.mjs +5 -0
- package/lib/qa/integrity.mjs +1 -0
- package/lib/qa/invariants.mjs +84 -0
- package/lib/qa/jcs.mjs +1 -0
- package/lib/qa/lanes.mjs +1 -0
- package/lib/qa/loop.mjs +7 -0
- package/lib/qa/marathon-coherence.mjs +1 -0
- package/lib/qa/marathon-phases.mjs +1 -0
- package/lib/qa/marathon-wait.mjs +3 -0
- package/lib/qa/marathon.mjs +5 -0
- package/lib/qa/mode-coverage.mjs +1 -0
- package/lib/qa/mutation-policy.mjs +1 -0
- package/lib/qa/pattern-ledger.mjs +3 -0
- package/lib/qa/pending.mjs +1 -0
- package/lib/qa/proc.mjs +4 -0
- package/lib/qa/recommend.mjs +1 -0
- package/lib/qa/release-manifest.mjs +1 -0
- package/lib/qa/report-html.mjs +178 -0
- package/lib/qa/reports.mjs +2 -0
- package/lib/qa/request-evidence.mjs +1 -0
- package/lib/qa/research.mjs +20 -0
- package/lib/qa/routing.mjs +1 -0
- package/lib/qa/runs.mjs +1 -0
- package/lib/qa/schema.mjs +1 -0
- package/lib/qa/serve.mjs +4 -0
- package/lib/qa/server.mjs +1 -0
- package/lib/qa/source-snapshot.mjs +3 -0
- package/lib/qa/stability.mjs +3 -0
- package/lib/qa/state-evidence.mjs +1 -0
- package/lib/qa/sweep-incremental.mjs +1 -0
- package/lib/qa/triage.mjs +2 -0
- package/lib/qa/verdict.mjs +1 -0
- package/lib/qa/watch.mjs +2 -0
- package/package.json +62 -0
- package/references/ATLAS-COVERAGE.md +231 -0
- package/references/atlas-v1.md +318 -0
- package/references/atlas.md +338 -0
- package/references/verify.crud.example.json +1 -0
- package/references/verify.impact.example.json +1 -0
- package/references/verify.shadow.example.json +1 -0
- package/schemas/agent-result.schema.json +135 -0
- package/schemas/analysis.schema.json +15 -0
- package/schemas/classify.schema.json +61 -0
- package/schemas/coherence-ir.schema.json +1585 -0
- package/schemas/coherence-observed.schema.json +1171 -0
- package/schemas/coherence-plan.schema.json +77 -0
- package/schemas/coherence-risk.schema.json +46 -0
- package/schemas/crud-observed.schema.json +118 -0
- package/schemas/experiment-execution.schema.json +82 -0
- package/schemas/graph-edge.schema.json +22 -0
- package/schemas/graph-node.schema.json +81 -0
- package/schemas/graph.schema.json +2493 -0
- package/schemas/identity-oracle.schema.json +19 -0
- package/schemas/inventory.schema.json +2167 -0
- package/schemas/lanes-plan.schema.json +52 -0
- package/schemas/next.schema.json +54 -0
- package/schemas/personas.schema.json +52 -0
- package/schemas/research.schema.json +192 -0
- package/schemas/run.schema.json +160 -0
- package/schemas/shadow-replay.schema.json +68 -0
- package/schemas/stability.schema.json +92 -0
- package/schemas/sweep.schema.json +239 -0
- package/schemas/v1/agent-result.schema.json +106 -0
- package/schemas/v1/run.schema.json +96 -0
- package/schemas/verify-crud.schema.json +37 -0
- package/schemas/verify-read-only.schema.json +37 -0
- package/templates/github-workflow.yml +80 -0
- package/templates/pre-commit.sample +22 -0
- package/verify.sh +385 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import A from"node:path";import{normRoute as $,cellsOf as P,SEVERITIES as z}from"./reports.mjs";import{boardHeaderLine as I}from"./fix-lanes.mjs";function r(n){return String(n??"").replace(/[&<>"']/g,e=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[e])}function x(n,e){return n=String(n??""),n.length>e?n.slice(0,e-1)+"\u2026":n}function k(n,e){const t=n.meta.exit,a=e.denominators,i=a.routesRequested??a.routesInventory,o=a.routesInventory!=null?`over ${i??"?"} of ${a.routesInventory} routes`:null;return t===2?{label:"INVALID",detail:`the run could not measure the app${o?` (${o})`:""}`}:t===1||e.gating?{label:"FAIL",detail:`${e.counts.P0} P0, ${e.counts.P1} P1 findings${o?` ${o}`:""}`}:{label:"PASS",detail:`no P0/P1 findings${o?` ${o}`:""}`}}function F(n){if(!n)return'<p class="muted">static run \u2014 no runtime sweep, so no integrity gate to report</p>';const e=n.integrity??{};return'<div class="gauges">'+[["cells measured / planned",`${e.cellsMeasured??"?"} / ${e.cellsPlanned??"?"}`,!1],["bounced to login",e.bouncedToLogin??0,(e.bouncedToLogin??0)>0],["below text floor",e.cellsBelowTextFloor??0,(e.cellsBelowTextFloor??0)>0],["distinct surfaces",e.distinctSurfaces??"?",!1],["integrity",e.ok===!0?"ok":e.ok===!1?"FAILED":"unmeasured",e.ok===!1]].map(([a,i,o])=>`<div class="gauge"><div class="gauge-label">${r(a)}</div><div class="gauge-value${o?" bad":""}">${r(String(i))}</div></div>`).join("")+"</div>"}function L(n){if(!n||!n.length)return'<p class="muted">no trajectory yet \u2014 run `qa verify`</p>';const e=900,t=220,a=36,i=22,o=14,p=10,u=n.length,f=Math.max(1,...n.map(c=>c.total??0)),m=c=>a+(u===1?(e-a-p)/2:c*(e-a-p)/(u-1)),b=c=>t-i-Math.max(0,Number(c)||0)/f*(t-o-i),s=(c,d)=>`<polyline points="${n.map((v,h)=>`${m(h).toFixed(1)},${b(v[c]).toFixed(1)}`).join(" ")}" fill="none" stroke="${d}" stroke-width="2"/>`,l=`<line x1="${a}" y1="${t-i}" x2="${e-p}" y2="${t-i}" stroke="var(--axis)"/><line x1="${a}" y1="${o}" x2="${a}" y2="${t-i}" stroke="var(--axis)"/><text x="4" y="${o+4}" class="axis-label">${r(String(f))}</text><text x="4" y="${t-i}" class="axis-label">0</text>`;return`<svg viewBox="0 0 ${e} ${t}" role="img" aria-label="trajectory: total, P0, P1 findings per run">`+l+s("total","var(--total-line)")+s("P0","var(--p0-line)")+s("P1","var(--p1-line)")+`</svg><div class="legend-row"><span class="sw sw-total"></span>total<span class="sw sw-p0"></span>P0<span class="sw sw-p1"></span>P1<span class="muted"> \u2014 ${r(String(u))} run(s)</span></div>`}function S({inventory:n,sweep:e}){const t=(n?.routes??[]).map(s=>s.path);if(!t.length)return{cols:[],rows:[]};const a=e?.roles?.length?e.roles.map(s=>typeof s=="string"?s:s.name):["default"],i=e?P(e):[],o=[...new Set(i.map(s=>s.width).filter(s=>s!=null))].sort((s,l)=>s-l),p=[];for(const s of a)for(const l of o.length?o:[null])p.push({role:s,width:l});const u=new Map;for(const s of i)u.set(`${$(s.route)}|${s.role??"default"}|${s.width??""}`,s);const f=new Set((e?.unreached??[]).map(s=>`${$(s.route)}|${s.role??"default"}`)),m=new Set((e?.unowned??[]).map(s=>$(typeof s=="string"?s:s.route))),b=t.map(s=>({route:s,cells:p.map(({role:l,width:c})=>{const d=u.get(`${$(s)}|${l}|${c??""}`);if(d){if(d.redirectedTo)return{status:"bounced",detail:d.redirectedTo};if(d.skipped)return{status:"skipped",detail:String(d.skipped)};const g=(d.findings??[]).map(h=>h.severity),v=z.find(h=>g.includes(h));return{status:v?"finding":"clean",severity:v??null,count:g.length}}return e?m.has($(s))?{status:"unowned"}:f.has(`${$(s)}|${l}`)?{status:"unreached"}:{status:"unmeasured"}:{status:"unmeasured"}})}));return{cols:p,rows:b}}function M(n){if(!n.rows.length)return'<p class="muted">no routes in inventory</p>';if(!n.cols.length)return'<p class="muted">no cells measured</p>';const e=n.cols.map(a=>`<th>${r(a.role)}${a.width?" @"+r(String(a.width)):""}</th>`).join(""),t=n.rows.map(a=>{const i=a.cells.map(o=>{const p="cell cell-"+o.status+(o.severity?" sev-"+o.severity:""),u=o.status==="finding"?o.severity:o.status==="clean"?"ok":o.status,f=o.detail?` title="${r(o.detail)}"`:"";return`<td class="${p}"${f}>${r(u)}</td>`}).join("");return`<tr><td class="route-name">${r(a.route)}</td>${i}</tr>`}).join("");return`<div class="matrix-wrap"><table class="matrix"><thead><tr><th>route</th>${e}</tr></thead><tbody>${t}</tbody></table></div>`}function R(n){return n.length?`<table class="sortable"><thead><tr><th data-sort="text">severity</th><th data-sort="num">count</th><th data-sort="text">kind</th><th data-sort="text">where</th><th data-sort="text">determinism</th><th>fids</th><th>sample</th></tr></thead><tbody>${n.map(t=>{const a=t.phase==="static"||t.phase==="sync"?`${t.files.length} file(s)`+(t.routes.length?`, ${t.routes.length} route(s)`:""):`${t.routes.length} route(s)`+(t.at&&t.at!=="(page)"?` at ${r(x(t.at,50))}`:""),i=t.fids.slice(0,3).map(r).join(", ")+(t.fids.length>3?` +${t.fids.length-3} more`:""),o=r(t.determinism)+(t.allKnownFp?' <span class="badge badge-fp">known-fp</span>':"");return`<tr${t.allKnownFp?' class="known-fp-row"':""}><td class="sev-${r(t.severity)}">${r(t.severity)}</td><td class="num">${r(String(t.count))}</td><td>${r(t.kind)}</td><td>${a}</td><td>${o}</td><td class="mono small">${i}</td><td class="small">${r(x(String(t.sample?.detail??""),140))}</td></tr>`}).join("")}</tbody></table>`:'<p class="muted">no findings in this run</p>'}function T(n){if(!n.nodes.length)return'<p class="muted">no mutations, entities or routes to graph</p>';const e={mutation:[],entity:[],route:[]};for(const l of n.nodes)(e[l.kind]??e.route).push(l);for(const l of Object.keys(e))e[l].sort((c,d)=>c.label.localeCompare(d.label));const t={mutation:20,entity:320,route:620},a=260,i=24,o=34,p=new Map;for(const[l,c]of Object.entries(e))c.forEach((d,g)=>p.set(d.id,{x:t[l],y:o+g*i,kind:l}));const u=Math.max(1,e.mutation.length,e.entity.length,e.route.length),f=o+u*i+20,m=900,b=n.edges.map(l=>{const c=p.get(l.from),d=p.get(l.to);if(!c||!d)return"";const g=c.x+a-20,v=c.y+9,h=d.x,y=d.y+9,w=(g+h)/2,j=l.stale?"var(--stale)":"var(--edge)";return`<path d="M${g},${v} C${w},${v} ${w},${y} ${h},${y}" fill="none" stroke="${j}" stroke-width="${l.stale?2:1}" opacity="0.85"/>`}).join(""),s=[...p.entries()].map(([l,c])=>{const d=n.nodes.find(h=>h.id===l),g=d.stale?"var(--stale-fill)":d.kind==="mutation"?"var(--mut-fill)":d.kind==="entity"?"var(--ent-fill)":"var(--route-fill)",v=d.stale?"var(--stale)":"var(--node-stroke)";return`<g><rect x="${c.x}" y="${c.y}" width="${a-20}" height="18" rx="4" fill="${g}" stroke="${v}"/><title>${r(d.label)}</title><text x="${c.x+6}" y="${c.y+13}" font-size="11">${r(x(d.label,36))}</text></g>`}).join("");return`<svg viewBox="0 0 ${m} ${f}" role="img" aria-label="sync graph: mutations to entities to routes, stale routes in red"><text x="${t.mutation}" y="16" class="col-label">mutations</text><text x="${t.entity}" y="16" class="col-label">entities</text><text x="${t.route}" y="16" class="col-label">routes</text>`+b+s+"</svg>"}function C(n){return n.length?'<ul class="tells">'+n.map(e=>`<li class="tell tell-${r(e.severity)}"><strong>${r(e.id)}</strong> \u2014 ${r(e.evidence)}<br><span class="muted">${r(e.meaning)}</span></li>`).join("")+"</ul>":`<p class="muted">no structural tells fired \u2014 the run's shape looks like the app, not the harness</p>`}function E(n){if(!n)return'<p class="muted">not measured \u2014 run `qa verify --runs N` to classify findings as stable / intermittent / ghost</p>';const e=Array.isArray(n.classes)?n.classes:Array.isArray(n.findings)?n.findings:null;return!e||!e.length?'<p class="muted">.verify/stability.json is present but carries no classes/findings array</p>':`<table><thead><tr><th>fid</th><th>class</th><th>k</th><th>n</th><th>gating</th></tr></thead><tbody>${e.map(a=>{const i=a.class??a.status??"";return`<tr><td class="mono small">${r(a.fid??"")}</td><td>${r(String(i))}</td><td class="num">${r(String(a.k??""))}</td><td class="num">${r(String(a.n??""))}</td><td>${a.gating?"yes":"no"}</td></tr>`}).join("")}</tbody></table>`}function q(n){if(!n)return'<p class="muted">not written yet \u2014 run `qa show ledger`</p>';const e=n.split(`
|
|
2
|
+
`).filter(u=>u.trim().startsWith("|"));if(e.length<2)return`<pre>${r(x(n,4e3))}</pre>`;const t=u=>u.trim().replace(/^\|/,"").replace(/\|$/,"").split("|").map(f=>f.trim()),a=t(e[0]),i=e.slice(2).map(t),o="<tr>"+a.map(u=>`<th>${r(u)}</th>`).join("")+"</tr>",p=i.map(u=>"<tr>"+u.map(f=>`<td>${r(f)}</td>`).join("")+"</tr>").join("");return`<div class="matrix-wrap"><table>${o?`<thead>${o}</thead>`:""}<tbody>${p}</tbody></table></div>`}function N(n){if(!n)return'<p class="muted">runtime coherence evidence not linked</p>';const e=n.mutations??{},t=n.targets??{},a=n.obligations??{},i=(n.clusters??[]).map(o=>`<tr><td>${r(o.severity)}</td><td>${r(o.rootCauseId)}</td><td>${r(o.surfaces.join(", "))}</td><td>${r((o.routes??[]).join(", "))}</td><td>${r(o.kinds.join(", "))}</td></tr>`).join("");return`<p><span class="badge">${r(n.status)}</span> provenance: ${r(n.provenance?.status??"unmeasured")}</p><p>mutations: ${r(String(e.executed??0))} executed / ${r(String(e.discovered??0))} discovered; targets: planned ${r(String(t.planned??0))}, reached ${r(String(t.reached??0))}, compared ${r(String(t.compared??0))}, agreed ${r(String(t.agreed??0))}, unmeasured ${r(String(t.unmeasured??0))}</p><p>obligations: ${r(String(a.agreed??0))} agreed / ${r(String(a.compared??0))} compared / ${r(String(a.reached??0))} reached / ${r(String(a.planned??0))} planned / ${r(String(a.resolved??0))} resolved / ${r(String(a.discovered??0))} discovered</p>`+(i?`<table><thead><tr><th>severity</th><th>root cause</th><th>surfaces</th><th>routes</th><th>kinds</th></tr></thead><tbody>${i}</tbody></table>`:"")+((n.unmeasuredReasons??[]).length?`<p class="muted">Still unmeasured: ${r(n.unmeasuredReasons.join("; "))}</p>`:"")}function O(n){if(!n?.classes)return"";const e=Object.values(n.classes).sort((t,a)=>t.id.localeCompare(a.id)).map(t=>`<tr><td>${r(t.id)}</td><td>${r(t.kind)}</td><td>${r(t.severity)}</td><td>${r(t.status)}</td><td>${["open","merged"].includes(t.status)?t.fids.length:0}/${t.fids.length}</td><td>${r(t.routes.join(", "))}</td><td>${t.attempts.length}</td><td>${t.pr?`<a href="${r(t.pr)}">PR</a>`:""}</td></tr>`).join("");return`<section id="lanes"><h2>Lanes</h2><p>${r(I(n))}</p><table><thead><tr><th>class</th><th>kind</th><th>sev</th><th>status</th><th>closed/total</th><th>routes</th><th>attempts</th><th>PR</th></tr></thead><tbody>${e}</tbody></table></section>`}const B=`
|
|
3
|
+
:root {
|
|
4
|
+
--bg:#f7f8fa; --fg:#1b1f24; --muted:#6b7280; --border:#d8dce1; --card:#ffffff;
|
|
5
|
+
--p0:#b3261e; --p1:#c2410c; --p2:#a16207; --p3:#4b5563;
|
|
6
|
+
--pass:#1a7f37; --fail:#b3261e; --invalid:#6b21a8;
|
|
7
|
+
--stale:#c2410c; --edge:#94a3b8; --axis:#94a3b8;
|
|
8
|
+
--mut-fill:#eef2ff; --ent-fill:#ecfdf5; --route-fill:#fff7ed; --stale-fill:#fee2e2; --node-stroke:#475569;
|
|
9
|
+
--total-line:#334155; --p0-line:#b3261e; --p1-line:#c2410c;
|
|
10
|
+
}
|
|
11
|
+
@media (prefers-color-scheme: dark) {
|
|
12
|
+
:root {
|
|
13
|
+
--bg:#0b0d10; --fg:#e5e7eb; --muted:#9aa4b2; --border:#2a2f37; --card:#111418;
|
|
14
|
+
--p0:#f87171; --p1:#fb923c; --p2:#facc15; --p3:#9aa4b2;
|
|
15
|
+
--pass:#4ade80; --fail:#f87171; --invalid:#c084fc;
|
|
16
|
+
--stale:#fb923c; --edge:#475569; --axis:#5b6472;
|
|
17
|
+
--mut-fill:#1e2233; --ent-fill:#132b21; --route-fill:#2a2013; --stale-fill:#3a1a1a; --node-stroke:#94a3b8;
|
|
18
|
+
--total-line:#cbd5e1; --p0-line:#f87171; --p1-line:#fb923c;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
* { box-sizing:border-box; }
|
|
22
|
+
body { margin:0; background:var(--bg); color:var(--fg); font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif; }
|
|
23
|
+
header.verdict { padding:22px 24px; border-bottom:4px solid var(--border); background:var(--card); }
|
|
24
|
+
header.verdict-PASS { border-color:var(--pass); }
|
|
25
|
+
header.verdict-FAIL { border-color:var(--fail); }
|
|
26
|
+
header.verdict-INVALID { border-color:var(--invalid); }
|
|
27
|
+
.verdict-label { font-size:28px; font-weight:700; letter-spacing:0.02em; }
|
|
28
|
+
.verdict-PASS .verdict-label { color:var(--pass); }
|
|
29
|
+
.verdict-FAIL .verdict-label { color:var(--fail); }
|
|
30
|
+
.verdict-INVALID .verdict-label { color:var(--invalid); }
|
|
31
|
+
.verdict-detail { margin-top:4px; font-size:14px; color:var(--muted); }
|
|
32
|
+
.verdict-repo { margin-top:2px; font-size:12px; color:var(--muted); font-family:ui-monospace,SFMono-Regular,Menlo,monospace; }
|
|
33
|
+
main { padding:0 24px 56px; max-width:1200px; margin:0 auto; }
|
|
34
|
+
section { margin-top:36px; }
|
|
35
|
+
h2 { font-size:14px; text-transform:uppercase; letter-spacing:0.06em; color:var(--muted); border-bottom:1px solid var(--border); padding-bottom:6px; margin-bottom:12px; }
|
|
36
|
+
table { border-collapse:collapse; width:100%; font-size:13px; }
|
|
37
|
+
th, td { border:1px solid var(--border); padding:4px 8px; text-align:left; vertical-align:top; }
|
|
38
|
+
th { background:var(--card); }
|
|
39
|
+
th[data-sort] { cursor:pointer; }
|
|
40
|
+
th[data-sort]::after { content:' \\21C5'; color:var(--muted); font-size:10px; }
|
|
41
|
+
.mono { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; }
|
|
42
|
+
.small { font-size:11px; }
|
|
43
|
+
.num { text-align:right; }
|
|
44
|
+
.muted { color:var(--muted); }
|
|
45
|
+
td.sev-P0 { color:var(--p0); font-weight:700; }
|
|
46
|
+
td.sev-P1 { color:var(--p1); font-weight:600; }
|
|
47
|
+
td.sev-P2 { color:var(--p2); }
|
|
48
|
+
td.sev-P3 { color:var(--p3); }
|
|
49
|
+
tr.known-fp-row { opacity:0.6; }
|
|
50
|
+
.badge { display:inline-block; font-size:10px; padding:1px 6px; border-radius:10px; text-transform:uppercase; letter-spacing:0.03em; margin-left:6px; }
|
|
51
|
+
.badge-fp { background:var(--border); color:var(--muted); }
|
|
52
|
+
.gauges { display:flex; gap:16px; flex-wrap:wrap; }
|
|
53
|
+
.gauge { background:var(--card); border:1px solid var(--border); border-radius:8px; padding:10px 14px; min-width:150px; }
|
|
54
|
+
.gauge-label { font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:0.04em; }
|
|
55
|
+
.gauge-value { font-size:20px; font-weight:600; margin-top:2px; }
|
|
56
|
+
.gauge-value.bad { color:var(--fail); }
|
|
57
|
+
.matrix-wrap { overflow-x:auto; }
|
|
58
|
+
table.matrix td.route-name { white-space:nowrap; font-family:ui-monospace,monospace; font-size:12px; }
|
|
59
|
+
td.cell { text-align:center; font-size:11px; min-width:56px; }
|
|
60
|
+
td.cell-clean { background:rgba(26,127,55,0.16); }
|
|
61
|
+
td.cell-finding.sev-P0 { background:rgba(179,38,30,0.32); font-weight:700; color:var(--fg); }
|
|
62
|
+
td.cell-finding.sev-P1 { background:rgba(194,65,12,0.24); color:var(--fg); }
|
|
63
|
+
td.cell-finding.sev-P2 { background:rgba(161,98,7,0.18); color:var(--fg); }
|
|
64
|
+
td.cell-finding.sev-P3 { background:rgba(75,85,99,0.14); color:var(--fg); }
|
|
65
|
+
td.cell-bounced { background:rgba(107,33,168,0.18); color:var(--muted); }
|
|
66
|
+
td.cell-skipped, td.cell-unreached, td.cell-unowned, td.cell-unmeasured {
|
|
67
|
+
background:repeating-linear-gradient(45deg, var(--border), var(--border) 4px, transparent 4px, transparent 8px);
|
|
68
|
+
color:var(--muted);
|
|
69
|
+
}
|
|
70
|
+
.tells { list-style:none; padding:0; margin:0; }
|
|
71
|
+
.tell { padding:8px 0; border-bottom:1px solid var(--border); font-size:13px; }
|
|
72
|
+
.tell-invalid strong { color:var(--fail); }
|
|
73
|
+
.tell-suspicious strong { color:var(--p1); }
|
|
74
|
+
.tell-note strong { color:var(--muted); }
|
|
75
|
+
.legend-row { font-size:12px; color:var(--muted); display:flex; gap:6px; align-items:center; margin-top:6px; }
|
|
76
|
+
.sw { display:inline-block; width:10px; height:10px; border-radius:2px; margin-left:8px; margin-right:2px; }
|
|
77
|
+
.sw:first-child { margin-left:0; }
|
|
78
|
+
.sw-total { background:var(--total-line); }
|
|
79
|
+
.sw-p0 { background:var(--p0-line); }
|
|
80
|
+
.sw-p1 { background:var(--p1-line); }
|
|
81
|
+
svg { max-width:100%; height:auto; }
|
|
82
|
+
svg text.col-label { font-size:11px; fill:var(--muted); font-weight:600; }
|
|
83
|
+
svg text.axis-label { font-size:10px; fill:var(--muted); }
|
|
84
|
+
svg text { fill:var(--fg); }
|
|
85
|
+
pre { white-space:pre-wrap; word-break:break-word; font-size:12px; background:var(--card); border:1px solid var(--border); padding:10px; border-radius:6px; }
|
|
86
|
+
`,H=`
|
|
87
|
+
(function () {
|
|
88
|
+
document.querySelectorAll('table.sortable').forEach(function (table) {
|
|
89
|
+
var ths = table.querySelectorAll('th[data-sort]');
|
|
90
|
+
var allTh = table.querySelectorAll('th');
|
|
91
|
+
ths.forEach(function (th) {
|
|
92
|
+
th.addEventListener('click', function () {
|
|
93
|
+
var idx = Array.prototype.indexOf.call(allTh, th);
|
|
94
|
+
var tbody = table.querySelector('tbody');
|
|
95
|
+
var rows = Array.prototype.slice.call(tbody.querySelectorAll('tr'));
|
|
96
|
+
var type = th.getAttribute('data-sort');
|
|
97
|
+
var dir = th.getAttribute('data-dir') === 'asc' ? 'desc' : 'asc';
|
|
98
|
+
ths.forEach(function (t) { t.removeAttribute('data-dir'); });
|
|
99
|
+
th.setAttribute('data-dir', dir);
|
|
100
|
+
rows.sort(function (a, b) {
|
|
101
|
+
var av = (a.children[idx] && a.children[idx].textContent || '').trim();
|
|
102
|
+
var bv = (b.children[idx] && b.children[idx].textContent || '').trim();
|
|
103
|
+
if (type === 'num') { av = parseFloat(av) || 0; bv = parseFloat(bv) || 0; }
|
|
104
|
+
if (av < bv) return dir === 'asc' ? -1 : 1;
|
|
105
|
+
if (av > bv) return dir === 'asc' ? 1 : -1;
|
|
106
|
+
return 0;
|
|
107
|
+
});
|
|
108
|
+
rows.forEach(function (r) { tbody.appendChild(r); });
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
})();
|
|
113
|
+
`;function W({repo:n,run:e,triage:t,trajectory:a,syncGraph:i,stability:o,ledgerMd:p,lanesPlan:u=null,coherence:f=null}){const m=k(e,t),b=S({inventory:e.inventory,sweep:e.sweep});return`<!doctype html>
|
|
114
|
+
<html lang="en">
|
|
115
|
+
<head>
|
|
116
|
+
<meta charset="utf-8">
|
|
117
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
118
|
+
<title>${`qa show report \u2014 ${r(A.basename(String(n??""))||String(n??""))} \u2014 run ${r(e.id)}`}</title>
|
|
119
|
+
<style>${B}</style>
|
|
120
|
+
</head>
|
|
121
|
+
<body>
|
|
122
|
+
<header class="verdict verdict-${r(m.label)}">
|
|
123
|
+
<div class="verdict-label">${r(m.label)}</div>
|
|
124
|
+
<div class="verdict-detail">${r(m.detail)} · exit ${r(String(e.meta.exit))} · run ${r(e.id)} · ${r(e.meta.mode??"unknown")}</div>
|
|
125
|
+
<div class="verdict-repo">${r(String(n??""))}</div>
|
|
126
|
+
</header>
|
|
127
|
+
<main>
|
|
128
|
+
<section id="integrity">
|
|
129
|
+
<h2>Integrity</h2>
|
|
130
|
+
${F(e.sweep)}
|
|
131
|
+
</section>
|
|
132
|
+
|
|
133
|
+
<section id="trajectory">
|
|
134
|
+
<h2>Trajectory</h2>
|
|
135
|
+
${L(a)}
|
|
136
|
+
</section>
|
|
137
|
+
|
|
138
|
+
<section id="matrix">
|
|
139
|
+
<h2>Cell matrix</h2>
|
|
140
|
+
${M(b)}
|
|
141
|
+
</section>
|
|
142
|
+
|
|
143
|
+
<section id="groups">
|
|
144
|
+
<h2>Ranked findings (${r(String(t.groups.length))} groups, ${r(String(t.counts.findings))} findings${t.counts.knownFp?`, ${r(String(t.counts.knownFp))} known-fp`:""})</h2>
|
|
145
|
+
${R(t.groups)}
|
|
146
|
+
</section>
|
|
147
|
+
|
|
148
|
+
<section id="sync-graph">
|
|
149
|
+
<h2>Sync graph</h2>
|
|
150
|
+
${i&&i.measured?T(i):'<p class="muted">no inventory \u2014 sync graph unmeasured</p>'}
|
|
151
|
+
</section>
|
|
152
|
+
|
|
153
|
+
<section id="coherence">
|
|
154
|
+
<h2>Cross-route coherence</h2>
|
|
155
|
+
${N(f)}
|
|
156
|
+
</section>
|
|
157
|
+
|
|
158
|
+
<section id="tells">
|
|
159
|
+
<h2>Structural tells</h2>
|
|
160
|
+
${C(t.tells)}
|
|
161
|
+
</section>
|
|
162
|
+
|
|
163
|
+
<section id="stability">
|
|
164
|
+
<h2>Stability</h2>
|
|
165
|
+
${E(o)}
|
|
166
|
+
</section>
|
|
167
|
+
|
|
168
|
+
<section id="ledger">
|
|
169
|
+
<h2>Ledger</h2>
|
|
170
|
+
${q(p)}
|
|
171
|
+
</section>
|
|
172
|
+
|
|
173
|
+
${O(u)}
|
|
174
|
+
</main>
|
|
175
|
+
<script>${H}</script>
|
|
176
|
+
</body>
|
|
177
|
+
</html>
|
|
178
|
+
`}export{S as buildCellMatrix,W as renderReport,k as verdictOf};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import d from"node:fs";import f from"node:path";import{createHash as P,randomBytes as X}from"node:crypto";import{fileURLToPath as Q}from"node:url";import{readJson as g}from"./fs.mjs";import{atlasVersionOf as j,atlasIdFor as C}from"./atlas.mjs";import{validate as V}from"./schema.mjs";import{canonicalize as S}from"./jcs.mjs";import{validateCoherenceObservedIdentities as z}from"./coherence/identity.mjs";import{annotateKnownFps as Y,repoScope as ee,runtimeEvidenceScope as te,runtimeMeasurementScope as F}from"./fp.mjs";const L=["P0","P1","P2","P3"],Oe=e=>{const t=L.indexOf(e);return t<0?L.length:t},M=e=>P("sha256").update(e).digest("hex"),ne=f.resolve(f.dirname(Q(import.meta.url)),"..","..","schemas"),m=Object.freeze({crud:Object.freeze({phase:"crud",ambient:["crud-observed.json"],archive:"crud-observed.json",schema:"crud-observed.schema.json"}),shadow:Object.freeze({phase:"shadow",ambient:["autonomous","shadow-replay.json"],archive:"shadow-replay.json",schema:"shadow-replay.schema.json"}),experiments:Object.freeze({phase:"experiment",ambient:["autonomous","experiment-execution.json"],archive:"experiment-execution.json",schema:"experiment-execution.schema.json"}),coherence:Object.freeze({phase:"coherence",ambient:["coherence-observed.json"],archive:"coherence-observed.json",schema:"coherence-observed.schema.json"})}),re=Object.freeze({crud:"measure crud",shadow:"measure shadow",experiments:"measure experiments",coherence:"measure coherence"}),R=new Map,A=/^[A-Za-z0-9][A-Za-z0-9._-]*$/,T=/^\d{8}T\d{9}Z-[0-9a-f]{4}$/,ie=/^[0-9a-f]{64}$/,O=/^sha256:[0-9a-f]{64}$/;function J(e){const t=m[e];if(!t)throw new Error(`unknown adapter evidence ${e}`);return R.has(e)||R.set(e,g(f.join(ne,t.schema),{required:!0,what:t.schema})),R.get(e)}function q(e,t){const r=f.relative(e,t);return r===""||!f.isAbsolute(r)&&r!==".."&&!r.startsWith(`..${f.sep}`)}function ae(e){let t=e;for(;!d.existsSync(t);){const r=f.dirname(t);if(r===t)break;t=r}return t}function I(e,t,r){const a=ae(t),n=d.realpathSync(a);if(!q(e,n))throw new Error(`${r} resolves outside the repository`);if(d.existsSync(t)){if(d.lstatSync(t).isSymbolicLink())throw new Error(`${r} is a symlink`);if(!q(e,d.realpathSync(t)))throw new Error(`${r} resolves outside the repository`)}}function oe(e,t){if(!t||typeof t!="object")throw new Error("evidenceIdentity is missing");if(!A.test(t.marathonRunId??""))throw new Error("marathon run id must be a safe path component");if(t.repoRealPath!==e)throw new Error("evidenceIdentity.repoRealPath does not match the repository path");if(!O.test(t.sourceDigest??""))throw new Error("evidenceIdentity.sourceDigest is invalid");if(t.base!==null&&typeof t.base!="string")throw new Error("evidenceIdentity.base is invalid");if(typeof t.lane!="string"||!t.lane)throw new Error("evidenceIdentity.lane is invalid");if(!Number.isInteger(t.flowWorkers)||t.flowWorkers<1)throw new Error("evidenceIdentity.flowWorkers is invalid");for(const r of["planDigest","flowDeclarationDigest","principalDigest"])if(t[r]!==null&&!O.test(t[r]??""))throw new Error(`evidenceIdentity.${r} is invalid`);if(!O.test(t.configurationDigest??""))throw new Error("evidenceIdentity.configurationDigest is invalid");if(t.toolProtocol!=="marathon-adapter/v1")throw new Error("evidenceIdentity.toolProtocol is invalid")}function N(e,t,r){const a=d.realpathSync(e);if(oe(a,t),!A.test(r??""))throw new Error("invocationId must be a safe path component");const n=f.join(a,".verify","marathon",t.marathonRunId,"adapters",r),o=f.join(n,"raw.json");if(!q(a,n))throw new Error("adapter invocation path resolves outside the repository");if(I(a,n,"adapter invocation directory"),d.existsSync(n)&&d.realpathSync(n)!==n)throw new Error("adapter invocation directory does not resolve to the expected invocation path");return{repoRealPath:a,dir:n,rawPath:o}}function se(e,t,r,{publication:a="pending"}={}){const n=m[t];if(!n)throw new Error(`required adapter ${t} is unknown`);if(!r||typeof r!="object")throw new Error(`required adapter ${t} invocation descriptor is missing`);const o=(i,c)=>{if(!(i in r)||!c(r[i]))throw new Error(`required adapter ${t} descriptor ${i} is invalid`)};o("adapter",i=>i===t),o("phase",i=>i===n.phase),o("invocationId",i=>A.test(i)),o("command",i=>typeof i=="string"&&i.length>0);for(const i of["startedAt","endedAt"])o(i,c=>typeof c=="string"&&Number.isFinite(Date.parse(c)));o("exit",i=>i===0||i===1),o("signal",i=>i===null),o("timedOut",i=>i===!1),o("status",i=>i==="measured"),o("publication",i=>i===a),o("sha256",i=>ie.test(i)),o("bytes",i=>Number.isInteger(i)&&i>=0),o("rawPath",i=>typeof i=="string"&&i.length>0),o("quarantinePath",i=>i===null||typeof i=="string"),o("evidenceIdentity",i=>i&&typeof i=="object"),o("required",i=>i===!0),o("reason",i=>i===null),o("counts",i=>i&&typeof i=="object"&&["total","P0","P1","P2","P3"].every(c=>Number.isInteger(i[c])&&i[c]>=0)),a==="published"&&o("archiveRunId",i=>T.test(i));const s=N(e,r.evidenceIdentity,r.invocationId);if(r.rawPath!==s.rawPath)throw new Error(`required adapter ${t} rawPath is not the expected invocation path`);if(a==="pending"){if(!d.existsSync(s.rawPath))throw new Error(`required adapter ${t} staged artifact is missing`);const i=d.lstatSync(s.rawPath);if(!i.isFile()||i.isSymbolicLink())throw new Error(`required adapter ${t} staged artifact is not a regular file`);if(d.realpathSync(s.rawPath)!==s.rawPath)throw new Error(`required adapter ${t} rawPath is not the expected invocation path`)}return s}function ce(e,t,r){if(!Buffer.isBuffer(r))throw new Error(`required adapter ${e} artifact bytes are missing`);if(r.length!==t.bytes)throw new Error(`required adapter ${e} byte count mismatch`);if(P("sha256").update(r).digest("hex")!==t.sha256)throw new Error(`required adapter ${e} digest mismatch`);let a;try{a=JSON.parse(r.toString("utf8"))}catch(s){throw new Error(`required adapter ${e} is not valid JSON: ${s.message}`)}const n=W(e,a,t.evidenceIdentity);if(n.length)throw new Error(`required adapter ${e} evidence invalid: ${n.join("; ")}`);const o=K(Z({[e]:a}));if(["total","P0","P1","P2","P3"].some(s=>t.counts[s]!==o[s]))throw new Error(`required adapter ${e} descriptor counts do not match normalized raw findings`);return a}function qe(e,t,r,{publication:a=r?.publication}={}){const n=r?.adapter,o=m[n];if(se(e,n,r,{publication:a}),a==="published"&&r.archiveRunId!==t?.id)throw new Error(`required adapter ${n} archiveRunId does not match the archived run`);if(!t||!T.test(t.id??""))throw new Error(`required adapter ${n} archived run is invalid`);const s=d.realpathSync(e),i=f.join(s,".verify","runs",t.id);if(!d.existsSync(t.dir)||d.lstatSync(t.dir).isSymbolicLink()||d.realpathSync(t.dir)!==i)throw new Error(`required adapter ${n} archived run directory is invalid`);const c=t.meta?.evidence?.[n];if(!c||typeof c!="object")throw new Error(`${n} has no archived evidence reference`);if(!Array.isArray(t.meta?.adapters)||!t.meta.adapters.includes(n))throw new Error(`${n} is absent from the archived adapter list`);const u={path:o.archive,sha256:r.sha256,bytes:r.bytes,status:"measured",phase:r.phase,command:r.command,startedAt:r.startedAt,endedAt:r.endedAt,exit:r.exit,signal:r.signal};for(const[w,y]of Object.entries(u))if(c[w]!==y)throw new Error(`${n} archived evidence reference ${w} does not match its invocation`);if(S(c.evidenceIdentity)!==S(r.evidenceIdentity))throw new Error(`${n} archived evidence identity does not match its invocation`);const l=f.join(i,o.archive);if(I(s,l,`${n} archived artifact`),!d.existsSync(l))throw new Error(`${n} archived artifact is missing`);const h=d.lstatSync(l);if(!h.isFile()||h.isSymbolicLink()||d.realpathSync(l)!==l)throw new Error(`${n} archived artifact is not the expected regular file`);return ce(n,r,d.readFileSync(l))}function W(e,t,r=null){const a=V(J(e),t).map(o=>`${o.path}: ${o.message}`),n=[...a];if(t?.status!=="measured"&&n.push(`status: expected measured, got ${JSON.stringify(t?.status??null)}`),r&&(t?.base!=null&&t.base!==r.base&&n.push("base: evidence identity mismatch"),t?.lane!=null&&t.lane!==r.lane&&n.push("lane: evidence identity mismatch"),t?.marathonRunId!=null&&t.marathonRunId!==r.marathonRunId&&n.push("marathonRunId: evidence identity mismatch"),e==="coherence"&&t?.runId!==r.marathonRunId&&n.push("runId: evidence identity mismatch"),e==="coherence")){if(t?.schemaVersion!==4)n.push(`schemaVersion: expected 4 for required coherence evidence, got ${JSON.stringify(t?.schemaVersion??null)}`);else if(a.length===0)try{z(t).current!==!0&&n.push("semantic: coherence evidence is legacy non-proof")}catch(o){n.push(`semantic: ${o.message}`)}t?.envelope?.outerEvidenceIdentity?.material?S(t.envelope.outerEvidenceIdentity.material)!==S(r)&&n.push("envelope: outer evidence identity mismatch"):n.push("envelope: outer evidence identity is missing")}return n}function Ne({repo:e,adapter:t,evidenceIdentity:r,command:a,now:n=()=>new Date}){const o=m[t];if(!o)throw new Error(`unknown adapter evidence ${t}`);const s=`${t}-${n().toISOString().replace(/[^0-9]/g,"")}-${X(3).toString("hex")}`,{repoRealPath:i,dir:c}=N(e,r,s);if(d.mkdirSync(c,{recursive:!0}),d.realpathSync(c)!==c)throw new Error("adapter invocation directory does not resolve to the expected invocation path");const u=f.join(i,".verify",...o.ambient);I(i,u,`${t} ambient artifact`);let l=null;return d.existsSync(u)&&(l=f.join(c,`ambient-before-${o.archive}`),d.renameSync(u,l)),{adapter:t,phase:o.phase,command:a,evidenceIdentity:r,invocationId:s,dir:c,ambientPath:u,quarantinePath:l,startedAt:n().toISOString()}}function De(e,t,{now:r=()=>new Date}={}){const{adapter:a}=e,n=m[a],o=f.join(e.dir,"raw.json");let s=null,i=null,c=null;const u=re[a]??a;let l=t?.timedOut?`qa ${u} timed out`:t?.signal?`qa ${u} terminated by signal ${t.signal}`:t?.code!==0&&t?.code!==1?`qa ${u} exited ${Number.isInteger(t?.code)?t.code:2}`:null;const h=p=>{l=l?`${l}; ${p}`:p};let w=!0;try{const p=N(e.evidenceIdentity.repoRealPath,e.evidenceIdentity,e.invocationId);(e.dir!==p.dir||e.ambientPath!==f.join(p.repoRealPath,".verify",...n.ambient))&&(h(`${a} invocation paths changed after preparation`),w=!1),I(p.repoRealPath,e.ambientPath,`${a} ambient artifact`)}catch(p){h(p.message),w=!1}if(w)if(!d.existsSync(e.ambientPath))h(`child produced no new ${a} artifact`);else{const p=d.lstatSync(e.ambientPath);if(!p.isFile()||p.isSymbolicLink())h(`new ${a} artifact is not a regular file`);else{d.renameSync(e.ambientPath,o),i=d.readFileSync(o),c=P("sha256").update(i).digest("hex");try{s=JSON.parse(i.toString("utf8"))}catch(x){h(`new ${a} artifact is not valid JSON: ${x.message}`)}if(s){const x=W(a,s,e.evidenceIdentity);x.length&&h(`${a} evidence ${x.join("; ")}`)}}}const y=l?"invalid":"measured",$=y==="measured"?Z({[a]:s}):[];return{invocation:{adapter:a,phase:n.phase,invocationId:e.invocationId,command:e.command,startedAt:e.startedAt,endedAt:r().toISOString(),exit:Number.isInteger(t?.code)?t.code:2,signal:t?.signal??null,timedOut:!!t?.timedOut,termination:t?.termination??null,status:y,publication:"pending",sha256:c,bytes:i?.length??0,rawPath:i?o:null,quarantinePath:e.quarantinePath,evidenceIdentity:e.evidenceIdentity,required:!0,reason:l,counts:K($)},document:s}}function b(e){if(e==null)return"(page)";let t=String(e);return t.match(/^\((journey|states|mutate|dark|navigation loop|journeys)(?::\s*(.*))?\)$/)?t:(t=t.split("?")[0].split("#")[0],t.length>1&&(t=t.replace(/\/+$/,"")),t||"/")}function le(e){const t=String(e??"").match(/^\((journey|states|mutate|dark|navigation loop|journeys)/);return t?{journey:"journey",journeys:"journey",states:"state",mutate:"mutate",dark:"dark","navigation loop":"leak"}[t[1]]:"runtime"}function de(e){const t=String(e??"").match(/^\((?:states|mutate|dark):\s*(.*)\)$/);return b(t?t[1]:e)}function D(e,t,r){if(!e||!t||!Number.isInteger(r)||r<1)return null;try{const n=(d.readFileSync(f.join(e,t),"utf8").split(`
|
|
2
|
+
`)[r-1]??"").trim();return n?M(n).slice(0,10):null}catch{return null}}function v(e){return M(e).slice(0,12)}const _={inventory:e=>!e||typeof e!="object"?"not an object":Array.isArray(e.routes)?!e.counts||typeof e.counts!="object"?"counts missing":Array.isArray(e.syncRisks)?null:"syncRisks is not an array":"routes is not an array",classify:e=>!e||typeof e!="object"?"not an object":Array.isArray(e.findings)?!e.counts||typeof e.counts!="object"?"counts missing":null:"findings is not an array",sweep:e=>!e||typeof e!="object"?"not an object":Array.isArray(e.routes)?e.routes.some(t=>!t||typeof t!="object"||!Array.isArray(t.findings))?"a route record has no findings array":!e.summary||typeof e.summary!="object"?"summary missing":null:"routes is not an array"};function _e(e,{requireSweep:t=!1}={}){return{inventory:g(f.join(e,"inventory.json"),{shape:_.inventory,what:"inventory.json"}),classify:g(f.join(e,"classify.json"),{shape:_.classify,what:"classify.json"}),sweep:g(f.join(e,"sweep.json"),{shape:_.sweep,what:"sweep.json",required:t})}}function Ce(e){const t=(...r)=>f.join(e,...r);return{crud:g(t(...m.crud.ambient),{what:m.crud.archive}),shadow:g(t(...m.shadow.ambient),{what:m.shadow.archive}),experiments:g(t(...m.experiments.ambient),{what:m.experiments.archive}),coherence:g(t(...m.coherence.ambient),{what:m.coherence.archive})}}function ue(e,t){const r=new Map;for(const n of e){const o=D(t,n.file,n.line)??`L${n.line}`,s=`${n.rule}|${n.file}|${o}`;(r.get(s)??r.set(s,[]).get(s)).push(n)}const a=new Map;for(const n of r.values())n.sort((o,s)=>(o.line??0)-(s.line??0)),n.forEach((o,s)=>a.set(o,s));return a}function B(e,t,r,a){const n=D(t,e.file,e.line)??`L${e.line}`;return{fid:v(`st|${e.rule}|${e.file}|${n}|${a??0}`),phase:"static",kind:e.rule,severity:e.severity,file:e.file,line:e.line,anchor:n,at:null,route:null,detail:e.detail,atlas:e.atlas??null,waived:!!r}}function H(e,t,r,a){const[n,o]=String(e.mutation??"").split(":"),s=Number(o)||null,i=D(t,n,s)??(s?`L${s}`:"L?"),c="sync."+e.kind;return{fid:v(`sy|${e.kind}|${n}|${i}|${e.entity??""}`),phase:"sync",kind:c,severity:e.severity,file:n||null,line:s,anchor:i,at:e.entity??null,route:e.route??null,detail:e.detail,atlas:C({kind:c,atlasVersion:a}),unresolved:!!e.unresolved,staleRoutes:e.staleRoutes??null,endpoint:e.endpoint??null,hook:e.hook??null,waived:!!r}}const U=e=>String(e??"").replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi,"<uuid>").replace(/:r[0-9a-z]+:/gi,"<genid>").replace(/\b[0-9a-f]{8,}\b/gi,"<hex>").replace(/\d+/g,"#").replace(/\s+/g," ").trim(),he=e=>U(e).slice(0,80),fe=e=>U(e);function pe(e,t,r,a){const n=e.pack?"pack":le(t.route),o=b(t.route),s=e.at??"(page)",i=s==="(page)"||s==="(journey)"?he(e.detail):fe(s),c=n==="pack"?`pk|${e.pack}`:n==="runtime"?"rt":"jn",u={fid:v(`${c}|${e.kind}|${o}|${t.role??""}|${i}`),phase:n,kind:e.kind,severity:e.severity,route:o,subject:de(t.route),role:t.role??null,width:t.width??null,at:s,file:null,line:null,detail:e.detail,atlas:null,flaky:!!t.flaky,pack:e.pack??null,ruleVersion:e.packVersion??null,failureClass:e.failureClass??null,measured:!t.unmeasured&&!t.skipped&&!t.redirectedTo&&e.kind!=="state.not-intercepted",waived:!1},l=te(u,r);return a&&l&&(u.repoScope=a,u.evidenceScope=l.id),u}const k=e=>!!e&&e.status==="measured",me=e=>{if(!k(e)||e.schemaVersion!==4||V(J("coherence"),e).length)return!1;try{return z(e).current===!0}catch{return!1}};function we(e){const t=b(e.route??"/");return{fid:v(`cr|${e.kind}|${t}|${e.at??""}`),phase:"crud",kind:e.kind,severity:e.severity,route:t,subject:t,role:e.persona??e.role??null,width:null,at:e.at??"(crud)",file:null,line:null,detail:e.detail,atlas:null,flaky:!1,waived:!1}}function ye(e){const t=`${e.method} ${e.endpoint_template??"(unknown endpoint)"}`;return{fid:v(`sh|shadow.drift|${e.endpoint_template??""}|${e.method}`),phase:"shadow",kind:"shadow.drift",severity:"P1",route:null,subject:null,role:null,width:null,at:t,file:null,line:null,detail:`a GET that answered ${e.expected_class} in the recorded trace now answers ${e.observed_class}`,atlas:"L1.2",flaky:!1,waived:!1}}function ge(e){const t=b(e.route??"/");return{fid:v(`ex|${e.family}|${e.kind}|${t}|${e.at??""}`),phase:"experiment",kind:e.kind,severity:e.severity,route:t,subject:t,role:null,width:null,at:e.at??`(experiment ${e.family})`,file:null,line:null,detail:e.detail??`the ${e.family} treatment introduced this where the control did not`,atlas:null,flaky:!1,waived:!1}}function ve(e,t,r,a){const n=(e.routes??[]).map(b),o=n[0]??"/",s=t.get(e.mutationId)??null,i=e.source?.file??s?.file??null,c=Number.isInteger(e.source?.line)?e.source.line:Number.isInteger(s?.line)?s.line:null,u=s?.hook?`${s.hook}() (${i}:${c})`:`${i}:${c}`,l=(e.surfaceIds??[]).length;let h=`writes ${e.family??"(unknown)"}; ${l} surface(s) on ${n.length} route(s) uncovered: ${(e.components??[]).join(", ")}`;return(e.unresolvedReadIds??[]).length&&(h+=`; ${e.unresolvedReadIds.length} read(s) unresolved`),{fid:v(`cc|${e.kind}|${e.rootCauseId}`),phase:"coherence",kind:e.kind,severity:"P3",route:o,subject:o,routes:n,role:null,width:null,at:u,file:i,line:c,detail:h,atlas:C({kind:e.kind,atlasVersion:a}),flaky:!1,waived:!!e.waived,rootCauseId:e.rootCauseId}}function $e(e){const t=b(e.route??"/");return{fid:v(`co|${e.kind}|${t}|${e.surfaceId??""}|${e.persona??""}`),phase:"coherence",kind:e.kind,severity:e.severity,route:t,subject:t,role:e.persona??null,width:null,at:e.surfaceId??e.route??"(coherence)",file:null,line:null,detail:e.detail??e.kind,atlas:null,flaky:!1,waived:!1}}function Z(e={}){const t=[],r=(a,n,o)=>({...a,source:n,evidenceRunId:o?.runId??o?.run_id??null});if(k(e.crud))for(const a of e.crud.findings??[])t.push(r(we(a),"crud",e.crud));if(k(e.shadow))for(const a of e.shadow.drift??[])t.push(r(ye(a),"shadow",e.shadow));if(k(e.experiments))for(const a of e.experiments.effect??[])t.push(r(ge(a),"experiment",e.experiments));if(me(e.coherence))for(const a of e.coherence.findings??[])t.push(r($e(a),"coherence",e.coherence));return t}function G({inventory:e,classify:t,sweep:r},{repo:a=null,knownFps:n=void 0}={}){const o=[];if(t){const s=j(t),i=ue([...t.findings??[],...t.waived??[]],a);for(const c of t.findings??[])o.push({...B(c,a,!1,i.get(c)),atlasVersion:s});for(const c of t.waived??[])o.push({...B(c,a,!0,i.get(c)),atlasVersion:s})}if(e){const s=j(e);for(const i of e.syncRisks??[])o.push({...H(i,a,!1,s),atlasVersion:s});for(const i of e.syncRisksWaived??[])o.push({...H(i,a,!0,s),atlasVersion:s})}if(r){const s=j(r),i=a?ee(a):null;for(const c of r.routes??[])for(const u of c.findings??[])o.push({...pe(u,c,r,i),atlasVersion:s})}if(e?.coherence?.candidates?.length){const s=j(e),i=new Map((e.coherence.mutations??[]).map(c=>[c.id,c]));for(const c of e.coherence.candidates)o.push({...ve(c,i,a,s),atlasVersion:s})}return n!==null&&Y(o,n),o}function Ve(e,{repo:t,knownFps:r=void 0}={}){const a=G({sweep:e},{repo:t,knownFps:r}),n=a.filter(o=>!o.waived);return{raw:a.length,accepted:a.length-n.length,live:n.length,gating:n.filter(o=>o.severity==="P0"||o.severity==="P1").length}}function ze({inventory:e,classify:t,sweep:r=null,mode:a,previous:n=null,repo:o,now:s=new Date}){if(a==="runtime"&&!F(r))throw new Error("runtime measurement scope is invalid or unmeasured");const i=G({inventory:e,classify:t,sweep:r},{repo:o}),c=i.filter(p=>!p.waived||p.adjudication).length,u=i.filter(p=>p.adjudication).length,l=i.filter(p=>!p.waived).length,h=a==="runtime"?F(r):{id:"static",material:{protocol:"static/v1"}},w=n?.schema_version===2&&n.scopes&&typeof n.scopes=="object"?{...n.scopes}:{};let y=n?.schema_version===2?n.legacy??null:n;a==="static"&&n?.schema_version!==2&&n?.mode==="static"&&Number.isInteger(n.best)&&(w.static={best:n.best,last:n.last??n.best,at:n.at??null,material:h.material},y=null);const $=w[h.id]??null;if($&&l>$.best)return{regression:!0,total:l,raw:c,accepted:u,best:$.best,scope:h.id,state:n};const E=$?Math.min($.best,l):l;return w[h.id]={best:E,last:l,at:s.toISOString(),material:h.material},{regression:!1,total:l,raw:c,accepted:u,best:E,scope:h.id,state:{schema_version:2,mode:a,currentScope:h.id,scopes:w,...y?{legacy:y}:{}}}}function Fe(e){return e?(e.routes??[]).filter(t=>!String(t.route).startsWith("(")):[]}function Le(e,t=[]){const r=o=>new Set(o.map(s=>String(s.route))).size,a=e.filter(o=>!o.redirectedTo),n=new Set(t.map(String)).size;return{routesRequested:r(e),routesReached:r(a),cellsRequested:e.length,cellsReached:a.length,routesDynamicSkipped:n,routesInInventory:r(e)+n}}function K(e){const t={total:0,P0:0,P1:0,P2:0,P3:0};for(const r of e)r.waived||(t.total++,t[r.severity]=(t[r.severity]??0)+1);return t}export{m as ADAPTER_EVIDENCE_SPECS,L as SEVERITIES,se as assertAdapterInvocationDescriptor,Fe as cellsOf,De as completeAdapterInvocation,K as countBySeverity,v as fidOf,U as idsOut,D as lineAnchor,Ce as loadEvidence,_e as loadReports,b as normRoute,Z as normalizeEvidenceFindings,G as normalizeFindings,le as phaseOf,Ne as prepareAdapterInvocation,ze as ratchetUpdate,Le as routeCounts,Ve as runtimeGateSummary,fe as selectorIdentity,Oe as sevRank,_ as shapes,de as subjectRoute,W as validateAdapterEvidence,ce as validateAdapterInvocationBytes,qe as validateArchivedAdapterInvocation};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const n=e=>e.length>=3&&new Set(e).size<e.length;export{n as hasConcreteRepeat};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import E from"node:fs";import d from"node:path";import{spawnSync as fe}from"node:child_process";import{parseJsonc as pe}from"./fs.mjs";const he=new Set([".ts",".tsx",".js",".jsx",".mts",".mjs"]),me=new Set([".json"]),ee=/(^|\/)(node_modules|\.next|\.git|dist|build|out|coverage|\.turbo|\.vercel|venv|site-packages|vendor|storybook-static)(\/|$)|(^|\/)\.[^/]+\/|worktrees\/|\.(test|spec|stories)\.[tj]sx?$/,ge=/(^|\/)(_proto|proto|design-lab|playground|sandbox|scratch)(\/|$)/;function I(e){try{return E.readFileSync(e,"utf8")}catch{return""}}function J(e,n){return e&&e.has(n)?e.get(n):I(n)}const C=(e,n)=>e.slice(0,Math.max(0,n)).split(`
|
|
2
|
+
`).length,R=e=>e==="high"?0:e==="medium"?1:2,te=e=>String(e??"").toLowerCase().replace(/[-_./]/g,"").replace(/s$/,"");function $e(e){const n=fe("git",["-C",e,"ls-files","--cached","--others","--exclude-standard","-z"],{encoding:"utf8",maxBuffer:67108864});return n.status!==0||!n.stdout?null:n.stdout.split("\0").filter(Boolean).map(t=>d.join(e,t))}function ye(e){const n=[],t=s=>{let o;try{o=E.readdirSync(s,{withFileTypes:!0})}catch{return}for(const c of o){const i=d.join(s,c.name);c.isDirectory()?t(i):n.push(i)}};return t(e),n}function ve(e){let n=[];try{n=E.readFileSync(d.join(e,".verifyignore"),"utf8").split(`
|
|
3
|
+
`).map(s=>s.replace(/#.*/,"").trim()).filter(Boolean)}catch{}const t=n.map(s=>new RegExp(s.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,"[^\\0]*").replace(/\?/g,".")));return s=>ge.test(s)||t.some(o=>o.test(s))}function we(e){const n=ve(e);return($e(e)??ye(e)).filter(s=>{const o=d.extname(s);if(!he.has(o)&&!me.has(o))return!1;const c=d.relative(e,s);return!(ee.test(c)||ee.test(s)||n(c))})}function be(e,n){const t=[],s=new Set,o=(i,l,r=0)=>{if(r>4||s.has(i)||!E.existsSync(i))return;s.add(i);const a=pe(I(i));if(!a){console.error(`qa: ${d.relative(e,i)||i} is not readable as JSON/JSONC -- its "paths" aliases were NOT applied; imports through them will not resolve`);return}const h=d.dirname(i),$=a?.compilerOptions?.baseUrl??".";for(const[m,f]of Object.entries(a?.compilerOptions?.paths??{})){const y=m.replace(/\/\*$/,""),v=String(f?.[0]??"").replace(/\/\*$/,"");y&&v&&l.push([y,d.resolve(h,$,v)])}typeof a.extends=="string"&&a.extends.startsWith(".")&&o(d.resolve(h,a.extends),l,r+1)},c=new Set([d.join(e,"tsconfig.json"),d.join(e,"jsconfig.json")]);for(const i of n)for(let l=d.dirname(i);l.startsWith(e)&&l!==e;l=d.dirname(l))for(const r of["tsconfig.json","jsconfig.json"]){const a=d.join(l,r);E.existsSync(a)&&c.add(a)}for(const i of c){const l=[];o(i,l),l.length&&t.push({dir:d.dirname(i),entries:l})}return t.sort((i,l)=>l.dir.length-i.dir.length),t.length||t.push({dir:e,entries:[["@",e],["@",d.join(e,"src")],["~",d.join(e,"src")]]}),t}function xe(e,n){const t=e.filter(s=>n.startsWith(s.dir+d.sep)||n===s.dir);return[...t,...e.filter(s=>!t.includes(s))].flatMap(s=>s.entries)}function ke(e,n,t,s){const o=[];if(n.startsWith("."))o.push(d.resolve(d.dirname(e),n));else for(const[c,i]of t)(n===c||n.startsWith(c+"/"))&&o.push(d.resolve(i,n.slice(c.length+1)));for(const c of o){const l=[c,...[".ts",".tsx",".js",".jsx"].flatMap(r=>[c+r,d.join(c,"index"+r)])].find(r=>s.has(r));if(l)return l}return null}function je(e,n,t,s){const o=[...n.matchAll(/(?:^|\n)\s*import\s+(?:[\s\S]*?\s+from\s+)?["'`]([^"'`]+)["'`]/g),...n.matchAll(/import\(\s*["'`]([^"'`]+)["'`]\s*\)/g),...n.matchAll(/(?:^|\n)\s*export\s+[\s\S]*?\s+from\s+["'`]([^"'`]+)["'`]/g)].map(i=>i[1]),c=xe(t,e);return[...new Set(o)].map(i=>ke(e,i,c,s)).filter(Boolean)}function Se(e,n){const t=new Map;return function(o){let c=t.get(o);return c||(c=je(o,e.get(o)??"",n,e),t.set(o,c),c)}}function Ae(e,n,{depth:t=4,maxFiles:s=60}={}){const o=new Set([e]);let c=[e];for(let i=0;i<t&&c.length&&o.size<s;i++){const l=[];for(const r of c)for(const a of n(r))o.has(a)||o.size>=s||(o.add(a),l.push(a));c=l}return[...o]}function W(e,n,t=4e3){if(n<0)return"{}";let s=0;for(let o=n;o<Math.min(e.length,n+t);o++){const c=e[o];if(c==="{")s++;else if(c==="}"&&(s--,s===0))return e.slice(n,o+1)}return e.slice(n,Math.min(e.length,n+t))}function Oe(e){const n=[];let t=0,s=0;for(let o=0;o<e.length;o++){const c=e[o];"{[(".includes(c)?t++:"}])".includes(c)?t--:c===","&&t===0&&(n.push(e.slice(s,o)),s=o+1)}return n.push(e.slice(s)),n}function ne(e){const n=e.slice(1,-1);return Oe(n).map(t=>t.trim()).filter(Boolean).map(t=>{const s=t.match(/^['"]?([A-Za-z_$][\w$-]*)['"]?\s*:\s*([\s\S]*)$/);return s?{key:s[1],value:s[2].trim()}:null}).filter(Boolean)}function Re(e){const n=e.match(/z\.enum\(\s*\[\s*(['"`])((?:(?!\1).)*)\1/);return n?{type:"enum",enumValues:[n[2]]}:/z\.number\(/.test(e)?{type:"number",enumValues:null}:/\.email\(\)/.test(e)?{type:"email",enumValues:null}:/z\.boolean\(/.test(e)?{type:"boolean",enumValues:null}:{type:null,enumValues:null}}function Ce(e){return/^-?\d+(\.\d+)?$/.test(e)?"number":/^(true|false)$/.test(e)?"boolean":/^['"`]/.test(e)?"string":null}function Fe(e,n,t,s=4e3){const o=new RegExp(`</${n}\\s*>`,"i"),c=e.slice(t,t+s),i=c.match(o);return i?c.slice(0,i.index):c}function Te(e){const n=[];for(const t of e.matchAll(/<option\b([^>]*)>([\s\S]*?)<\/option>/gi)){const s=t[1],o=t[2].replace(/\{[^}]*\}/g,"").trim(),c=s.match(/\bvalue\s*=\s*["']([^"']*)["']/)||s.match(/\bvalue\s*=\s*\{\s*['"]([^'"]*)['"]\s*\}/),i=(c?c[1]:o).trim();i&&n.push(i)}return n}function se(e,n,t=3e3){let s=0,o=null;const c=Math.min(e.length,n+t);for(let i=n;i<c;i++){const l=e[i];if(o){l===o&&e[i-1]!=="\\"&&(o=null);continue}if(l==='"'||l==="'"){o=l;continue}if(l==="{"){s++;continue}if(l==="}"){s--;continue}if(l===">"&&s<=0)return e.slice(n,i+1)}return e.slice(n,c)}const De=/^[A-Za-z_$][\w.\[\]-]*$/;function Ee(e,n){const t=new Map,s=(i,l,r,a,h,$,m=null)=>{if(i=String(i??"").replace(/^["'`]|["'`]$/g,"").trim(),!De.test(i))return;const f=t.get(i);!f||R(r)<R(f.confidence)?t.set(i,{name:i,type:$??f?.type??null,confidence:r,source:`${n}:${C(e,l)}`,via:a,tag:h??f?.tag??null,enumValues:m??f?.enumValues??null}):t.set(i,{...f,type:f.type??$??null,tag:f.tag??h??null,enumValues:f.enumValues??m??null})},o=new Set;for(const i of e.matchAll(/<(?:label|Label)\b[^>]*\bhtmlFor\s*=\s*["']([^"']+)["']/g))o.add(i[1]);for(const i of e.matchAll(/<(?:label|Label)\b[^>]*\bhtmlFor\s*=\s*\{\s*['"]([^'"]+)['"]\s*\}/g))o.add(i[1]);for(const i of e.matchAll(/<(input|select|textarea|Input|Select|Textarea)\b/g)){const l=i[1].toLowerCase(),r=se(e,i.index),a=r.slice(i[0].length),h=a.match(/\bname\s*=\s*["']([^"']+)["']/)||a.match(/\bname\s*=\s*\{\s*['"]([^'"]+)['"]\s*\}/),$=a.match(/\bid\s*=\s*["']([^"']+)["']/)||a.match(/\bid\s*=\s*\{\s*['"]([^'"]+)['"]\s*\}/),m=a.match(/\btype\s*=\s*["']([^"']+)["']/);let f=null;if(l==="select"){const k=Fe(e,i[1],i.index+r.length),b=Te(k);b.length&&(f=[b[0]])}if(h){s(h[1],i.index,"high","name-attr",l,m?m[1]:null,f);continue}if(!$)continue;const y=o.has($[1]),v=/\bonChange\s*=/.test(a);!y&&!v||s($[1],i.index,y?"high":"medium",y?"id+label":"id-attr+onChange",l,m?m[1]:null,f)}for(const i of e.matchAll(/\bregister\(\s*['"`]([^'"`]+)['"`]/g))s(i[1],i.index,"high","register","input",null);for(const i of e.matchAll(/<(FormField|Field)\b[^>]*\bname\s*=\s*["']([^"']+)["']/g))s(i[2],i.index,"medium",i[1],null,null);for(const i of e.matchAll(/<(FormField|Field)\b[^>]*\bname\s*=\s*\{\s*['"]([^'"]+)['"]\s*\}/g))s(i[2],i.index,"medium",i[1],null,null);for(const i of e.matchAll(/\bdefaultValues\s*:\s*\{/g)){const l=W(e,e.indexOf("{",i.index));for(const{key:r,value:a}of ne(l))s(r,i.index,"medium","defaultValues",null,Ce(a))}const c=new Set;for(const i of e.matchAll(/\bzodResolver\(\s*([A-Za-z_$][\w$]*)\s*\)/g))c.add(i[1]);for(const i of e.matchAll(/\bz\.object\(\s*\{/g)){const l=e.slice(Math.max(0,i.index-120),i.index),r=(l.match(/(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*$/)??[])[1];if(!/\bzodResolver\(\s*$/.test(l)&&!(r&&c.has(r)))continue;const h=W(e,e.indexOf("{",i.index));for(const{key:$,value:m}of ne(h)){const{type:f,enumValues:y}=Re(m);s($,i.index,"medium","zod",null,f,y)}}return[...t.values()].sort((i,l)=>i.name.localeCompare(l.name))}function Me(e,n){const t=[{re:/<(button|Button)\b/g,selector:"button[type=submit]"},{re:/<input\b/g,selector:"input[type=submit]"}];for(const{re:s,selector:o}of t){s.lastIndex=0;let c;for(;c=s.exec(e);){const i=se(e,c.index);if(/\btype\s*=\s*["']submit["']/.test(i)||/\btype\s*=\s*\{\s*['"]submit['"]\s*\}/.test(i))return{selector:o,source:`${n}:${C(e,c.index)}`,confidence:"high"}}}return null}function _e(e,n){for(const{rel:t,src:s}of e){if(!s)continue;const o=s.match(new RegExp(`<Link\\b[^>]*href\\s*=\\s*["'\`]${z(n)}["'\`]`));if(o)return{file:t,line:C(s,o.index),clientNavigation:!0}}for(const{rel:t,src:s}of e){if(!s)continue;const o=s.match(new RegExp(`<a\\b[^>]*href\\s*=\\s*["'\`]${z(n)}["'\`]`));if(o)return{file:t,line:C(s,o.index),clientNavigation:!1}}return null}function Ne(e){let n=0;for(const t of String(e))n=n*31+t.charCodeAt(0)>>>0;return 1e3+n%9e3}function oe(e,n){const t=`qa-${n}-${e.name}`,s=`${e.via}, ${e.confidence} confidence, seen at ${e.source}`;if(e.tag==="select"){if(e.enumValues&&e.enumValues.length){const o=e.enumValues[0];return{resolved:!0,code:` await page.selectOption('[name="${e.name}"]', '${o}'); // value: '${o}' derived from the field's own literal option/enum, ${s}`}}return{resolved:!1,code:` // TODO selector: field "${e.name}" is a <select> but no literal option/enum value was found in source (${s}) \u2014 open the app and confirm a real option with playwright-cli`}}if(e.tag==="input"||e.tag==="textarea"){if(e.type==="boolean"||e.type==="checkbox")return{resolved:!0,code:` await page.check('[name="${e.name}"]'); // value: true, derived: field type is ${e.type}, ${s}`};let o,c;return e.enumValues&&e.enumValues.length?(o=e.enumValues[0],c="derived: first literal of the field's own zod enum"):e.type==="email"?(o=`${t}@example.com`,c="derived: field type is email"):e.type==="number"?(o=Ne(n),c="derived: field type is number"):(o=t,c="guessed: no type information found in source, using a sentinel string"),{resolved:!0,code:` await page.fill('[name="${e.name}"]', '${o}'); // value ${c}, ${s}`}}return{resolved:!1,code:` // TODO selector: field "${e.name}" (${s}) \u2014 control type not determined statically`}}const Le=/(^|\/)(e2e|tests?|playwright|cypress)(\/|$)/,Ve=/\.test\.[jt]sx?$/,Ie=/(^|\/)fixtures(\/|$)/;function ze(e){return Le.test(e)||Ve.test(e)||Ie.test(e)}function qe(e){const n=new Map,t=(s,o,c,i,l,r)=>{if(s=String(s??"").trim(),!/^[A-Za-z_][\w-]*$/.test(s))return;const a=n.get(s);(!a||R(l)<R(a.confidence))&&n.set(s,{name:s,confidence:l,source:`${o}:${C(i,c)}`,via:r})};for(const{rel:s,src:o}of e)if(o){for(const c of o.matchAll(/\benum\s+Role\s*\{([^}]*)\}/g))for(const i of c[1].split(",")){const l=i.split("=")[0].trim();l&&t(l,s,c.index,o,"high","enum Role")}for(const c of o.matchAll(/\bROLES\s*=\s*\[([^\]]*)\]/g))for(const i of c[1].matchAll(/['"]([\w-]+)['"]/g))t(i[1],s,c.index,o,"high","ROLES array");for(const c of o.matchAll(/\bloginAs\(\s*[\w.]+\s*,\s*['"]([\w-]+)['"]/g))t(c[1],s,c.index,o,"high","loginAs()");for(const c of o.matchAll(/\basRole\(\s*['"]([\w-]+)['"]/g))t(c[1],s,c.index,o,"high","asRole()");for(const c of o.matchAll(/\brole\s*:\s*['"]([\w-]+)['"]/g))t(c[1],s,c.index,o,"medium","role: '\u2026'")}return[...n.values()].sort((s,o)=>s.name.localeCompare(o.name))}function Je(e,n,t=null){const s=new Set,o=[];for(const c of n){const i=d.relative(e,c),l=J(t,c);if(l){if(/from\s+['"]msw['"]|require\(\s*['"]msw['"]\s*\)/.test(l)){s.add("msw");const r=l.match(/\bsetupWorker\s*\(/)||l.match(/\b(?:http|rest)\.(?:get|post|put|patch|delete)\s*\(/);r&&o.push({file:i,line:C(l,r.index),detail:r[0].trim()})}/from\s+['"]miragejs['"]|require\(\s*['"]miragejs['"]\s*\)/.test(l)&&(s.add("mirage"),o.push({file:i,line:1,detail:"imports miragejs"})),/from\s+['"]json-server['"]|require\(\s*['"]json-server['"]\s*\)/.test(l)&&(s.add("json-server"),o.push({file:i,line:1,detail:"imports json-server"}))}}return n.some(c=>/(^|\/)(mocks|__mocks__)(\/|$)/.test(d.relative(e,c)))&&s.add("mocks-dir"),{detected:s.size>0,kinds:[...s].sort(),evidence:o.slice(0,20)}}const We=/(^|\/)(openapi|swagger)([\w.-]*)\.json$/i,z=e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");function P(e,n,t=0){return!n||t>2?[]:n.$ref?P(e,e?.components?.schemas?.[String(n.$ref).split("/").pop()],t+1):n.type==="array"&&n.items?P(e,n.items,t+1):Array.isArray(n.required)?n.required:[]}function Pe(e){const n=e?.responses??{},s=(n[200]??n[201]??n.default)?.content??{};return(s["application/json"]??Object.values(s)[0])?.schema??null}function Be(e){const n=String(e).split(/\{[^}]+\}/g).map(z);return new RegExp(n.join("(?:\\{[\\w.-]+\\}|:[\\w.-]+)"),"g")}function Ue(e,n){n.lastIndex=0;let t;for(;t=n.exec(e);){const s=e[t.index-1];if(s==='"'||s==="'"||s==="`")return t;t.index===n.lastIndex&&n.lastIndex++}return null}function Ze(e,n,{window:t=400,cap:s=4e3}={}){const o=e.indexOf("{",n);return o<0||o-n>t?null:W(e,o,s)}function Ke(e,n,t=null){const s=n.filter(i=>We.test(d.relative(e,i))),o=n.filter(i=>/(^|\/)(mocks?|__mocks__)(\/|$)/i.test(d.relative(e,i))),c=[];for(const i of s){let l;try{l=JSON.parse(J(t,i))}catch{continue}const r=d.relative(e,i);for(const[a,h]of Object.entries(l?.paths??{})){const $=Be(a);for(const[m,f]of Object.entries(h??{})){if(!/^(get|post|put|patch|delete)$/i.test(m))continue;const y=P(l,Pe(f));if(y.length)for(const v of o){const k=d.relative(e,v),b=J(t,v);if(!b)continue;const M=Ue(b,$);if(!M)continue;const _=Ze(b,M.index+M[0].length);if(_==null)continue;const A=y.filter(j=>!new RegExp("\\b"+z(j)+"\\b").test(_));A.length&&c.push({path:a,method:m.toUpperCase(),mockFile:k,specFile:r,missingKeys:A,missingCount:A.length,confidence:"low"})}}}}return c.sort((i,l)=>l.missingCount-i.missingCount),c}const Qe=/^(total|count|sum|balance|totalCount|pageCount)$/;function Xe(e){const n=[],t=new Set;for(const{rel:s,src:o}of e)if(o)for(const c of o.matchAll(/\b([A-Za-z_$][\w$]*)\s*\??\s*:/g)){if(!Qe.test(c[1]))continue;const i=C(o,c.index),l=`${s}:${c[1]}:${i}`;t.has(l)||(t.add(l),n.push({field:c[1],confidence:"medium",source:`${s}:${i}`}))}return n}function Ge(){return new Date().toISOString().replace(/[-:.]/g,"")+"-adhoc"}function ot(e,{inventory:n,runId:t=Ge(),rolesFileName:s="verify.roles.json"}={}){const o=we(e),c=new Map(o.map(u=>[u,I(u)])),i=be(e,o),l=Se(c,i),r=new Map,a=(u,S)=>{let g=r.get(u);return g||(g=Ee(S,u),r.set(u,g),g)},h=new Map,$=(u,S)=>{let g=h.get(u);return g||(g=Xe([{rel:u,src:S}]),h.set(u,g),g)},m=o.map(u=>({rel:d.relative(e,u),src:c.get(u)})),f=new Map,y=u=>(f.has(u)||f.set(u,_e(m,u)),f.get(u)),v=n.routes??[],k=new Map;for(const u of v)k.set(u.file,(k.get(u.file)??0)+1);const b=v.map(u=>{const S=d.resolve(e,u.file),g=(k.get(u.file)??0)>1,N=c.has(S)&&!g?Ae(S,l,{depth:4,maxFiles:60}):[S],w=new Map,Q=[];let F=null;for(const p of N){const x=d.relative(e,p),T=c.get(p)??"";for(const O of a(x,T)){const D=w.get(O.name);!D||R(O.confidence)<R(D.confidence)?w.set(O.name,O):w.set(O.name,{...D,type:D.type??O.type,tag:D.tag??O.tag,enumValues:D.enumValues??O.enumValues})}Q.push(...$(x,T)),F||(F=Me(T,x))}const L=[...w.values()].sort((p,x)=>p.name.localeCompare(x.name)),V=(u.mutations??[]).length,le=/[:[]/.test(u.path),X=(u.mutations??[]).filter(p=>!p.nonWrite&&p.writes).map(p=>({writes:p.writes,hook:p.hook??null,file:p.file,line:p.line,verb:p.verb??null,endpoint:p.endpoint??null})),G=[],H=new Set;for(const p of X)for(const x of n.matrix?.[p.writes]??[]){if(x===u.path||H.has(x))continue;H.add(x);const T=/[:[]/.test(x);G.push({path:x,entity:p.writes,dynamic:T,link:T?null:y(x)})}const ae=L.length>0&&L.every(p=>oe(p,"r").resolved),ue=!g&&V>0&&ae&&!!F,Y=[...L.map(p=>R(p.confidence)),...F?[R(F.confidence)]:[]],de=V>0?Y.length?["high","medium","low"][Math.max(...Y)]:"low":null;return{path:u.path,file:u.file,entities:u.entities??[],mutationsCount:V,dynamic:le,forms:L,draftJourney:V>0,derivedAssertions:Q,mutations:X,submit:F,navTargets:G,journeyReady:ue,journeyConfidence:de,ownershipAmbiguous:g,modules:N.length,seedNote:null}});for(const u of b){if(!u.dynamic)continue;const S=new Set((u.entities??[]).map(te));let g=null;e:for(const N of v)for(const w of N.mutations??[])if(!(w.nonWrite||!w.writes)&&S.has(te(w.writes))){g=`no mutation on this route itself; ${N.path}'s mutation (${w.hook?w.hook+"()":`${w.file}:${w.line}`}) writes "${w.writes}", which this route reads \u2014 seed by running that journey first and carrying its created id into this route's dynamic segment`;break e}u.seedNote=g??"dynamic route; no mutation in the inventory was found writing a matching entity \u2014 seed manually with a known record id before sweeping it"}const M=o.filter(u=>ze(d.relative(e,u))).map(u=>({rel:d.relative(e,u),src:c.get(u)})),_=qe(M),A=[...new Set(_.map(u=>u.name))];let j=null;const U=d.join(e,s);if(E.existsSync(U))try{const u=JSON.parse(I(U));j={path:s,principals:Object.keys(u??{}).sort()}}catch{j={path:s,principals:[],malformed:!0}}const ie=j?A.filter(u=>!j.principals.includes(u)):A,re=j?j.principals.filter(u=>!A.includes(u)):[],q=Je(e,o,c),Z=Ke(e,o,c),ce=Object.keys(n.matrix??{}).sort().map(u=>({entity:u,question:`What is the authoritative rule for "${u}" (a legal threshold, a domain convention, a computed field's exact definition) that no static read of the source can confirm?`})),K=[];return q.detected&&K.push("a run against mocks tests the mocks: a green sweep here is evidence about the mock layer, not the real backend (SKILL.md Phase 1b)"),{generated:new Date().toISOString(),runId:t,repo:e,counts:{routes:b.length,routesWithForms:b.filter(u=>u.forms.length>0).length,routesWithMutations:b.filter(u=>u.mutationsCount>0).length,principals:A.length,mockLayerDetected:q.detected,contractDriftCandidates:Z.length},routes:b,principals:{detected:_,rolesFile:j,missingFromRolesFile:ie,missingFromCode:re},mocks:q,contractDrift:Z,residueQuestions:ce,notes:K}}function He(e){const n=[];for(const t of e.navTargets)t.dynamic?n.push(` // expected transition (unresolved, dynamic route): "${t.entity}" also renders on ${t.path} per inventory.matrix \u2014 this needs a known record id, which no static read can invent; seed it the way the ledger's seed note for that route describes`):t.link?.clientNavigation?n.push(` await page.click('a[href="${t.path}"]'); // transition: "${t.entity}" also renders on ${t.path} per inventory.matrix, link found at ${t.link.file}:${t.link.line}`):t.link?n.push(` // expected transition (unresolved, native link): "${t.entity}" also renders on ${t.path}, but the only literal source is <a> at ${t.link.file}:${t.link.line}; prove client-side interception before using it, because a document load would erase the warm-cache evidence`):n.push(` // TODO selector: "${t.entity}" also renders on ${t.path} per inventory.matrix, but no static link to it was found \u2014 find the client-side nav control with playwright-cli (a page.goto reload would mask a real staleness bug here)`);return n}const B=5;function Ye(e){const n=[],t=e.derivedAssertions.slice(0,B);for(const s of t)n.push(` // derived assertion candidate: "${s.field}" (${s.confidence} confidence, seen at ${s.source}) \u2014 SKILL.md Phase 2 tier 2:`),n.push(" // once you know the selectors, assert the rendered value equals the recomputation from the rendered rows:"),n.push(` // const rendered = Number((await page.locator('TODO selector for ${s.field}').textContent() ?? '').replace(/[^0-9.-]/g, ''));`),n.push(" // const rowTexts = await page.locator('TODO row selector').allTextContents();"),n.push(" // const recomputed = rowTexts.reduce((sum, t) => sum + Number((t.match(/-?\\d+(\\.\\d+)?/) || ['0'])[0]), 0);"),n.push(` // if (rendered !== recomputed) throw new Error(\`${s.field} mismatch: rendered \${rendered} recomputed \${recomputed}\`);`);return e.derivedAssertions.length>B&&n.push(` // ... ${e.derivedAssertions.length-B} more derived-assertion candidate(s) for this route; see .verify/research.json/.md`),n}function it(e,n){const s=e.routes.filter(r=>r.draftJourney).map(r=>{const a=[];r.dynamic&&a.push(` // TODO seed: ${r.seedNote??"dynamic route; seed source not determined"}`);let h=r.forms.length>0;for(const v of r.forms){const k=oe(v,n);k.resolved||(h=!1),a.push(k.code)}r.forms.length||a.push(" // TODO selector: no form fields found by static analysis for this mutation route"),r.submit?a.push(` await page.click('${r.submit.selector}'); // resolved: a real type="submit" control was found at ${r.submit.source}`):(a.push(" // TODO selector: submit control for this form was not statically identified"),a.push(" await page.click('button[type=submit]'); // TODO selector: confirm this is the right submit control")),a.push(...He(r)),a.push(...Ye(r));const $=h&&!!r.submit,m=r.journeyConfidence??"low",y=` {
|
|
4
|
+
name: '${r.path.replace(/^\//,"").replace(/[^\w-]+/g,"-").replace(/(^-+|-+$)/g,"")||"root"}-mutation',
|
|
5
|
+
start: '${r.path}',
|
|
6
|
+
resolved: ${$},
|
|
7
|
+
confidence: '${m}',
|
|
8
|
+
run: async (page, { base }) => {
|
|
9
|
+
${a.join(`
|
|
10
|
+
`)}
|
|
11
|
+
},
|
|
12
|
+
}`;return{path:r.path,resolved:$,code:y}}),o=s.filter(r=>r.resolved).sort((r,a)=>r.path.localeCompare(a.path)),c=s.filter(r=>!r.resolved).sort((r,a)=>r.path.localeCompare(a.path)),i=["// Generated by `qa init --study` \u2014 a STUDY, not a verdict. Every field/submit","// marked `resolved: true` was filled or clicked using a real name/id","// attribute, register() call, FormField, defaultValues, z.object() key, or",'// type="submit" control found in source, with a value derived from that',"// field's own type/zod schema where possible (see each line's own comment)","// and a generic sentinel string otherwise. A line still carrying a","// `// TODO selector` marker was NOT confirmed against a running app \u2014 use","// the playwright-cli skill to discover the real selector, then promote.","//",`// ${o.length} journey(s) ready for review (every field and the submit control were resolved from source).`,`// ${c.length} journey(s) need a human or a playwright-cli session (at least one TODO marker remains).`,"//","// The sweep never loads this path. After review, promote with:","// cp .verify/journeys.draft.mjs verify.journeys.mjs (run from the repo root)",`// Sentinels are qa-${n}-<field>.`,""].join(`
|
|
13
|
+
`),l=[];return o.length&&(l.push(` // ---- ready for review (${o.length}): every field and the submit control were resolved from source ----`),l.push(o.map(r=>r.code).join(`,
|
|
14
|
+
`)+",")),c.length&&(l.push(` // ---- needs a human or a playwright-cli session (${c.length}): at least one TODO marker remains ----`),l.push(c.map(r=>r.code).join(`,
|
|
15
|
+
`)+",")),`${i}export default [
|
|
16
|
+
${l.join(`
|
|
17
|
+
`)}
|
|
18
|
+
];
|
|
19
|
+
`}function rt(e,n){const t=[];t.push("# Research (Phase 1 study)","",`Generated ${e.generated} \xB7 run ${e.runId}`,""),t.push("## Numbers",""),t.push(`- routes in inventory: ${e.counts.routes}`),t.push(`- ledger rows: ${n.count} (expected ${n.expected}${n.count===n.expected?", match":", **MISMATCH**"})`),t.push(`- routes with forms discovered: ${e.counts.routesWithForms}`),t.push(`- routes with mutations: ${e.counts.routesWithMutations}`),t.push(`- principals detected: ${e.counts.principals}`),t.push(`- mock layer detected: ${e.mocks.detected}`),t.push(`- contract drift candidates: ${e.counts.contractDriftCandidates}`,""),t.push("## Ledger","",`${n.count} rows; see \`.verify/ledger.md\`.`,""),t.push("## Journeys to write","");const s=e.routes.filter(r=>r.draftJourney),o=s.filter(r=>r.journeyReady),c=s.filter(r=>!r.journeyReady);s.length?(t.push(`ready for review: ${o.length} (every field and the submit control resolved from source)`),t.push(`need a human or a playwright-cli session: ${c.length}`,"")):t.push("No mutation routes found; nothing to draft.","");for(const r of s){t.push(`### ${r.path} (${r.journeyReady?"ready for review":"needs a human"}${r.journeyConfidence?`, ${r.journeyConfidence} confidence`:""})`),r.dynamic&&t.push(`- dynamic route; ${r.seedNote}`),r.forms.length||t.push("- no form fields found by static analysis; draft journey has TODO placeholders only");for(const a of r.forms)t.push(`- field \`${a.name}\`${a.type?` (type ${a.type})`:""}${a.enumValues?.length?` = '${a.enumValues[0]}'`:""} \u2014 ${a.confidence} confidence, via ${a.via}, seen at ${a.source}`);t.push(r.submit?`- submit: \`${r.submit.selector}\` \u2014 resolved at ${r.submit.source}`:"- submit: not found in source");for(const a of r.navTargets)t.push(`- transition to ${a.path} (entity "${a.entity}")${a.dynamic?" \u2014 dynamic target, needs a known record id":a.link?` \u2014 link found at ${a.link.file}:${a.link.line}`:" \u2014 no static link found"}`);t.push("")}t.push("## Derived assertion candidates","");const i=e.routes.flatMap(r=>r.derivedAssertions.map(a=>({route:r.path,...a})));if(!i.length)t.push("None found (no `total|count|sum|balance|totalCount|pageCount` field reachable from a route).","");else{for(const r of i)t.push(`- \`${r.field}\` on ${r.route} \u2014 ${r.confidence} confidence, ${r.source}`);t.push("")}t.push("## Principals","");const l=e.principals.detected.map(r=>r.name);e.principals.rolesFile?t.push(`Declared in \`${e.principals.rolesFile.path}\` (${e.principals.rolesFile.principals.length}): ${e.principals.rolesFile.principals.join(", ")||"(none)"}`):t.push("No verify.roles.json at the repo root."),t.push(`Detected in code (${l.length}): ${l.join(", ")||"(none)"}`),e.principals.rolesFile?(t.push(`- declared but not detected in code: ${e.principals.missingFromCode.join(", ")||"(none)"}`),t.push(`- detected in code but not declared: ${e.principals.missingFromRolesFile.join(", ")||"(none)"}`)):l.length>1&&t.push('More than one principal was detected \u2014 a role sweep without ownership reports most of its P1s as the tool describing its own cookie jar (SKILL.md "Roles").'),t.push("","Sources for each detected mention:");for(const r of e.principals.detected)t.push(`- \`${r.name}\` \u2014 ${r.confidence} confidence, via ${r.via}, ${r.source}`);t.push(""),t.push("## Mock layer",""),t.push(`Detected: ${e.mocks.detected} (${e.mocks.kinds.join(", ")||"none"})`);for(const r of e.mocks.evidence)t.push(`- ${r.file}:${r.line} \u2014 ${r.detail}`);for(const r of e.notes)t.push("",`**Note:** ${r}`);if(t.push(""),t.push("## Contract drift candidates",""),!e.contractDrift.length)t.push("None found.","");else{for(const a of e.contractDrift.slice(0,25))t.push(`- ${a.method} ${a.path} (${a.specFile} vs ${a.mockFile}): missing ${a.missingKeys.join(", ")} \u2014 ${a.confidence} confidence`);e.contractDrift.length>25&&t.push(`- \u2026 ${e.contractDrift.length-25} more`),t.push("")}if(t.push("## Residue questions",""),!e.residueQuestions.length)t.push("No entities found.","");else{for(const r of e.residueQuestions)t.push(`- **${r.entity}**: ${r.question}`);t.push("")}return t.push("## Preconditions checklist (Phase 0, before the first sweep)",""),t.push("- [ ] serving a production build, not the dev server (41s vs 6-12min per sweep; dev-server compile timings read as app failures)"),t.push("- [ ] app reachable at --base"),t.push("- [ ] dev database confirmed if --mutate will submit real forms"),t.push("- [ ] clean git branch (one commit per fix, revertible)"),t.push(`- [ ] principals counted before the first sweep: ${e.counts.principals} detected`+(e.principals.rolesFile?`, ${e.principals.rolesFile.path} present`:", verify.roles.json ABSENT \u2014 write it first if more than one principal exists")),t.push("- [ ] nothing else building while the sweep runs"),t.push("- [ ] a run that aborts is resumed with --resume, not restarted from the top"),t.push(""),t.join(`
|
|
20
|
+
`)}export{Ae as boundedWalk,ot as buildResearch,we as enumerateFiles,Ee as extractForms,Me as extractSubmitControl,Ke as findContractDrift,Xe as findDerivedAssertions,_e as findLinkTarget,Je as findMockLayer,qe as findPrincipals,it as generateJourneysDraft,ze as isPrincipalSource,Ne as numericSentinel,I as readSafe,rt as researchMarkdown,oe as resolveFieldStep};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import c from"node:fs";const o=t=>String(t||"").replace(/\/$/,"")||"/";function a(t){return t&&t.mode==="hash"?"hash":"path"}function s(t){try{return a(JSON.parse(c.readFileSync(t,"utf8")).routing)}catch{return"path"}}function i(t,u){const e=String(t??"").replace(/\/$/,"");return a({mode:u})!=="hash"?{mode:"path",routeToUrl:r=>e+r,urlToRoute:r=>{try{return o(new URL(r).pathname)}catch{return null}}}:{mode:"hash",routeToUrl:r=>e+"/#"+(String(r??"/").startsWith("/")?r:"/"+r),urlToRoute:r=>{let n;try{n=new URL(r).hash}catch{return null}const h=String(n).replace(/^#/,"").split("?")[0];return o("/"+h.replace(/^\/+/,""))}}}export{i as routeAdapter,a as routingMode,s as routingModeFromInventory};
|
package/lib/qa/runs.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import y from"node:fs";import u from"node:path";import{randomBytes as Z}from"node:crypto";import{createHash as V}from"node:crypto";import{readJson as I,writeJson as G,appendLine as Q,readLines as X,copyIfExists as Y,listDirs as ee,rmrf as ne,exists as H}from"./fs.mjs";import{ADAPTER_EVIDENCE_SPECS as te,assertAdapterInvocationDescriptor as ie,loadReports as J,loadEvidence as se,normalizeFindings as oe,normalizeEvidenceFindings as N,countBySeverity as re,cellsOf as ue,normRoute as ce,validateAdapterInvocationBytes as le}from"./reports.mjs";import{InvalidError as g,redactArgs as ae}from"./cli.mjs";import{collectOrphanedPending as de}from"./pending.mjs";const S=e=>u.join(e,".verify","runs"),W=e=>u.join(e,".verify","trajectory.jsonl");let T=0;function fe(e=new Date){let t=e.getTime();return t<=T&&(t=T+1),T=t,`${new Date(t).toISOString().replace(/[-:.]/g,"")}-${Z(2).toString("hex")}`}function Ae(e,{mode:t,args:i=[],exit:s,produced:a={inventory:!0,classify:!0,sweep:!1},keep:h=50,extra:c={},id:v,dir:R,link:m=[],graphDir:x=null,adapterInvocations:o=null}){const p=ae(i),j=v??fe(),w=R??u.join(S(e),j);let F=a;if(!R){y.mkdirSync(w,{recursive:!0});const n=u.join(e,".verify");F={};for(const r of["inventory","classify","sweep"])a[r]&&(F[r]=Y(u.join(n,r+".json"),u.join(w,r+".json")))}const q={};for(const n of x?["nodes.jsonl","edges.jsonl"]:[]){const r=u.join(x,n);if(!H(r))continue;const d=y.lstatSync(r);if(!d.isFile()||d.isSymbolicLink())throw new g(`canonical graph ${r} is not a regular file`);const l=u.join(w,n);y.copyFileSync(r,l),q[n]=V("sha256").update(y.readFileSync(l)).digest("hex")}let k;try{k=J(w)}catch(n){k={inventory:null,classify:null,sweep:null},c={...c,archiveError:n.message}}let B=[];const D=[],P={};if(m.length){if(o===null)try{const n=I(u.join(e,".verify","marathon.json"))?.pendingAdapterInvocations;Array.isArray(n)&&m.every(r=>n.some(d=>d?.adapter===r&&d?.publication==="pending"))&&(o=n)}catch{}if(o!==null){const n={};for(const r of m){const d=te[r];if(!d)throw new g(`required adapter ${r} is unknown`);const l=o.find(A=>A?.adapter===r&&A?.publication==="pending");if(!l)throw new g(`required adapter ${r} has no pending invocation descriptor`);try{ie(e,r,l)}catch(A){throw new g(A.message)}const C=y.readFileSync(l.rawPath);let L;try{L=le(r,l,C)}catch(A){throw new g(A.message)}const _=u.join(w,d.archive);y.copyFileSync(l.rawPath,_);const O=y.readFileSync(_),z=V("sha256").update(O).digest("hex");if(z!==l.sha256||O.length!==C.length)throw new g(`required adapter ${r} archived bytes failed verification`);n[r]=L,D.push(r),P[r]={path:d.archive,sha256:z,bytes:O.length,status:"measured",phase:d.phase,command:l.command,startedAt:l.startedAt,endedAt:l.endedAt,exit:l.exit,signal:l.signal??null,evidenceIdentity:l.evidenceIdentity}}B=N(n)}else try{const n=se(u.join(e,".verify")),r={};for(const d of m)d in n&&(r[d]=n[d],D.push(d));if(m.includes("coherence")&&n.coherence){const d=u.join(e,".verify","coherence-observed.json"),l=u.join(w,"coherence-observed.json");y.copyFileSync(d,l),P.coherence={path:"coherence-observed.json",sha256:V("sha256").update(y.readFileSync(l)).digest("hex")}}B=N(r)}catch(n){c={...c,evidenceError:n.message}}}const M=[...oe(k,{repo:e}),...B],b=re(M),f=k.sweep,$={id:j,at:new Date().toISOString(),mode:t,args:p,exit:s,produced:F,autonomous:c.autonomous??null,graph:Object.keys(q).length?q:null,adapters:D,evidence:Object.keys(P).length?P:null,counts:b,summary:f?.summary??null,integrity:f?.integrity??null,aborted:f?.aborted??null,resume:f?.resume?{requested:f.resume.requested,compatible:f.resume.compatible,reused:f.resume.reused}:null,warm:f?.warm??null,routesInventory:k.inventory?.counts?.routes??null,cells:f?ue(f).length:0,findings:M.map(n=>({fid:n.fid,phase:n.phase,kind:n.kind,severity:n.severity,route:n.route??null,subject:n.subject??null,role:n.role??null,width:n.width??null,at:n.at??null,file:n.file??null,line:n.line??null,anchor:n.anchor??null,flaky:!!n.flaky,unresolved:!!n.unresolved,waived:!!n.waived,measured:n.measured??void 0,detail:n.detail??"",atlas:n.atlas??null,atlasVersion:n.atlasVersion??void 0,staleRoutes:n.staleRoutes??void 0,pack:n.pack??void 0,ruleVersion:n.ruleVersion??void 0,repoScope:n.repoScope??void 0,evidenceScope:n.evidenceScope??void 0,adjudication:n.adjudication??void 0,source:n.source??void 0,evidenceRunId:n.evidenceRunId??void 0})),...c};return G(u.join(w,"run.json"),$),Q(W(e),{id:j,at:$.at,mode:t,exit:s,total:b.total,P0:b.P0,P1:b.P1,P2:b.P2,P3:b.P3,routesInventory:$.routesInventory,routesRequested:f?.summary?.routesRequested??null,routesReached:f?.summary?.routesReached??null,cellsMeasured:f?.integrity?.cellsMeasured??null,integrityOk:f?.integrity?.ok??null,...c.checkpoint?{checkpoint:c.checkpoint}:{}}),he(e,h,j),{id:j,dir:w,meta:$,reports:k,records:M}}function he(e,t,i=null){de(e);const s=E(e),a=i&&!s.includes(i)?[...s,i].sort():s;for(const h of a.slice(0,Math.max(0,a.length-t)))h!==i&&ne(u.join(S(e),h))}function E(e){return ee(S(e)).filter(t=>/^\d{8}T\d{9}Z-[0-9a-f]{4}$/.test(t)).sort()}function Ee(e){return E(e).map(t=>{const i=I(u.join(S(e),t,"run.json"));return i?{...i,findings:void 0,findingCount:i.findings?.length??0}:{id:t,corrupt:!0}})}function pe(e){return!!e&&e.exit!==2&&e?.integrity?.ok!==!1}function Pe(e,{before:t}={}){const i=E(e),s=t?i.indexOf(t):i.length,a=[];for(let h=s-1;h>=0;h--){const c=i[h],v=I(u.join(S(e),c,"run.json"));if(pe(v))return{id:c,skipped:a};a.push(c)}return{id:null,skipped:a}}function me(e,t){const i=E(e);if(!i.length)throw new g(`no archived runs under ${S(e)} \u2014 run \`qa verify\` first`);if(!t||t==="latest")return i[i.length-1];if(t==="previous"){if(i.length<2)throw new g("only one archived run; nothing to compare against");return i[i.length-2]}if(i.includes(t))return t;const s=i.filter(a=>a.startsWith(t));if(s.length===1)return s[0];throw new g(s.length?`run id "${t}" is ambiguous (${s.length} matches)`:`run "${t}" not found; ids: ${i.slice(-5).join(", ")}`)}function $e(e,t){const i=me(e,t),s=u.join(S(e),i),a=I(u.join(s,"run.json"),{required:!0,what:"run.json",shape:c=>Array.isArray(c.findings)?null:"findings index missing"}),h=J(s);return{id:i,dir:s,meta:a,...h}}function K(e){return e.meta.findings}const ye=new Set(["runtime","journey","state","mutate","dark","leak","pack"]),U=(e,t,i)=>`${ce(e)}|${t??"default"}|${i??""}`;function ge(e){const t=e.sweep;if(!t)return null;const i=new Set;for(const s of t.routes??[])s.skipped||s.unmeasured||s.redirectedTo||!String(s.route??"").startsWith("(")&&s.textLen==null||i.add(U(s.route,s.role,s.width));return i}function Ie(e,t){const i=new Map(K(e).filter(o=>!o.waived).map(o=>[o.fid,o])),s=new Map(K(t).filter(o=>!o.waived).map(o=>[o.fid,o])),a=ge(t),h=o=>!ye.has(o.phase)||a!=null&&a.has(U(o.route,o.role,o.width)),c=[],v=[],R=[],m=[];for(const[o,p]of i)s.has(o)?R.push(p):h(p)?c.push(p):m.push(p);for(const[o,p]of s)i.has(o)||v.push(p);const x=o=>o.reduce((p,j)=>(p[j.severity]=(p[j.severity]??0)+1,p),{});return{from:e.id,to:t.id,fixed:c,new:v,persisting:R,unmeasured:m,counts:{fixed:c.length,new:v.length,persisting:R.length,unmeasured:m.length,fixedBySeverity:x(c),newBySeverity:x(v),unmeasuredBySeverity:x(m)},comparable:(e.meta.mode??null)===(t.meta.mode??null),modes:[e.meta.mode??null,t.meta.mode??null]}}function Fe(e){return X(W(e))}function qe(e){const t=E(e);return t.length?u.join(S(e),t[t.length-1]):H(u.join(e,".verify","inventory.json"))?u.join(e,".verify"):null}export{Ae as archiveRun,Ie as diffRuns,qe as latestReportsDir,E as listRunIds,Ee as listRuns,$e as loadRun,fe as newRunId,he as pruneRuns,me as resolveRunId,Pe as resolveValidRunId,K as runRecords,S as runsDir,Fe as trajectory,W as trajectoryPath};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function u(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function $(e,t){return e==="integer"?typeof t=="number"&&Number.isFinite(t)&&Number.isInteger(t):e==="number"?typeof t=="number"&&Number.isFinite(t):u(t)===e}function l(e,t){return(Array.isArray(e)?e:[e]).some(f=>$(f,t))}function g(e,t){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(e&&t&&typeof e=="object"){if(Array.isArray(e)!==Array.isArray(t))return!1;if(Array.isArray(e))return e.length!==t.length?!1:e.every((n,y)=>g(n,t[y]));const r=Object.keys(e),f=Object.keys(t);return r.length!==f.length?!1:r.every(n=>Object.prototype.hasOwnProperty.call(t,n)&&g(e[n],t[n]))}return!1}function m(e,t){if(typeof e!="string"||!e.startsWith("#/"))throw new Error(`unsupported $ref (only "#/$defs/<name>" is supported): ${JSON.stringify(e)}`);const r=e.slice(2).split("/").map(n=>n.replace(/~1/g,"/").replace(/~0/g,"~"));let f=t;for(const n of r){if(f==null||typeof f!="object"||!(n in f))throw new Error(`$ref target not found: ${e}`);f=f[n]}return f}function d(e,t){return typeof t=="number"?`${e}[${t}]`:e===""?String(t):`${e}.${t}`}function p(e,t,r,f,n){if(e===!0||e===void 0||e===null)return;if(e===!1){n.push({path:r||"(root)",message:"no value is allowed here (schema is `false`)"});return}if(typeof e!="object")return;if(e.$ref!==void 0){let o;try{o=m(e.$ref,f)}catch(i){n.push({path:r||"(root)",message:i.message});return}p(o,t,r,f,n);return}e.const!==void 0&&!g(t,e.const)&&n.push({path:r||"(root)",message:`expected const ${JSON.stringify(e.const)}, got ${JSON.stringify(t)}`}),e.enum!==void 0&&(!Array.isArray(e.enum)||!e.enum.some(o=>g(o,t)))&&n.push({path:r||"(root)",message:`expected one of ${JSON.stringify(e.enum)}, got ${JSON.stringify(t)}`});let y=!0;if(e.type!==void 0&&(y=l(e.type,t),!y)){const o=Array.isArray(e.type)?e.type.join(" | "):e.type;n.push({path:r||"(root)",message:`expected type ${o}, got ${u(t)}`})}if(e.oneOf!==void 0){const o=e.oneOf.filter(i=>{const s=[];return p(i,t,r,f,s),s.length===0});o.length!==1&&n.push({path:r||"(root)",message:`expected exactly one oneOf branch to match, ${o.length} did`})}if(e.anyOf!==void 0&&(e.anyOf.some(i=>{const s=[];return p(i,t,r,f,s),s.length===0})||n.push({path:r||"(root)",message:"expected at least one anyOf branch to match, none did"})),typeof t=="number"&&(e.minimum!==void 0&&t<e.minimum&&n.push({path:r||"(root)",message:`expected >= ${e.minimum}, got ${t}`}),e.maximum!==void 0&&t>e.maximum&&n.push({path:r||"(root)",message:`expected <= ${e.maximum}, got ${t}`})),typeof t=="string"&&(e.minLength!==void 0&&t.length<e.minLength&&n.push({path:r||"(root)",message:`expected minLength ${e.minLength}, got ${t.length}`}),e.pattern!==void 0)){let o;try{o=new RegExp(e.pattern)}catch{n.push({path:r||"(root)",message:`schema pattern is invalid: ${e.pattern}`}),o=null}o&&!o.test(t)&&n.push({path:r||"(root)",message:`does not match pattern ${e.pattern}`})}if(u(t)==="object"){if(e.propertyNames!==void 0)for(const i of Object.keys(t)){const s=[];p(e.propertyNames,i,"",f,s),s.length&&n.push({path:r||"(root)",message:"property name does not satisfy its schema"})}if(Array.isArray(e.required))for(const i of e.required)Object.prototype.hasOwnProperty.call(t,i)||n.push({path:d(r,i),message:"required property is missing"});const o=new Set(Object.keys(e.properties??{}));if(e.properties)for(const[i,s]of Object.entries(e.properties))Object.prototype.hasOwnProperty.call(t,i)&&p(s,t[i],d(r,i),f,n);if(e.additionalProperties===!1)for(const i of Object.keys(t))o.has(i)||n.push({path:d(r,i),message:"additional property is not allowed"});else if(e.additionalProperties&&typeof e.additionalProperties=="object")for(const i of Object.keys(t))o.has(i)||p(e.additionalProperties,t[i],d(r,i),f,n)}Array.isArray(t)&&e.items!==void 0&&t.forEach((o,i)=>p(e.items,o,d(r,i),f,n)),Array.isArray(t)&&(e.minItems!==void 0&&t.length<e.minItems&&n.push({path:r||"(root)",message:`expected at least ${e.minItems} items, got ${t.length}`}),e.maxItems!==void 0&&t.length>e.maxItems&&n.push({path:r||"(root)",message:`expected at most ${e.maxItems} items, got ${t.length}`}))}function O(e,t){const r=[];return p(e,t,"",e,r),r}function c(e){const t=[];return!e||typeof e!="object"?(t.push("schema is not a JSON object"),t):((typeof e.$schema!="string"||!e.$schema)&&t.push('missing "$schema"'),(typeof e.$id!="string"||!e.$id)&&t.push('missing "$id"'),t)}export{c as checkSchemaDocument,O as validate};
|
package/lib/qa/serve.mjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{redactValue as F}from"./cli.mjs";import u from"node:fs";import T from"node:net";import o from"node:path";import{spawn as E,spawnSync as L}from"node:child_process";import{readJson as R,writeJson as A,exists as l}from"./fs.mjs";import{run as G}from"./proc.mjs";import{probe as O}from"./server.mjs";function p(e){return o.join(e,".verify","serve.json")}function q(e){return o.join(e,".verify","serve.log")}function y(e){if(!e||!Number.isInteger(e))return!1;try{return process.kill(e,0),!0}catch(t){return t&&t.code==="EPERM"}}function J(e){return l(o.join(e,"bun.lockb"))||l(o.join(e,"bun.lock"))?"bun":l(o.join(e,"pnpm-lock.yaml"))?"pnpm":l(o.join(e,"yarn.lock"))?"yarn":"npm"}function C(e,t){return`${e} run ${t}`}function x(e){return R(p(e),{what:"serve state"})}function N(e,t){A(p(e),t)}function P(e){try{u.unlinkSync(p(e))}catch{}}function B(e,t="127.0.0.1"){return new Promise(r=>{const s=T.createServer();s.once("error",()=>{r(!1)}),s.once("listening",()=>{s.close(()=>r(!0))});try{s.listen(e,t)}catch{r(!1)}})}async function K(e,{timeoutMs:t=18e4,intervalMs:r=300}={}){const s=Date.now();let a=null;for(;Date.now()-s<t;){try{return{live:!0,status:(await fetch(e,{signal:AbortSignal.timeout(Math.min(3e3,t))})).status,elapsedMs:Date.now()-s}}catch(i){a=i}await new Promise(i=>setTimeout(i,r))}return{live:!1,status:null,elapsedMs:Date.now()-s,error:a?String(a?.message??a):null}}function c(e,t){u.mkdirSync(o.dirname(e),{recursive:!0}),u.appendFileSync(e,t.endsWith(`
|
|
2
|
+
`)?t:t+`
|
|
3
|
+
`)}function j(e,t){if(e){if(process.platform==="win32"){try{L("taskkill",["/PID",String(e),"/T","/F"])}catch{}return}try{process.kill(-e,t)}catch{try{process.kill(e,t)}catch{}}}}async function ee(e,{prod:t=!1,port:r=3e3,buildCmd:s=null,startCmd:a=null,timeoutMs:i=18e4,host:S="127.0.0.1"}={}){const n=q(e),b=p(e),m=l(b)?x(e):null;if(m&&y(m.pid))return{ok:!1,reason:`qa run serve is already running (pid ${m.pid}, ${m.base}); stop it first with --stop`,exit:2,logPath:n};if(m&&P(e),!Number.isInteger(r)||r<=0||r>65535)return{ok:!1,reason:`--port must be an integer between 1 and 65535, got ${r}`,exit:2,logPath:null};if(!await B(r,S))return{ok:!1,reason:`port ${r} is already in use`,exit:2,logPath:null};const h=J(e),D=t?"prod":"dev",v=t?s??C(h,"build"):null,d=a??C(h,t?"start":"dev"),k=`http://${S}:${r}`,I={...process.env,PORT:String(r)};if(u.mkdirSync(o.dirname(n),{recursive:!0}),u.writeFileSync(n,`=== qa run serve ${new Date().toISOString()} repo=${e} pm=${h} mode=${D} ===
|
|
4
|
+
`),t){c(n,`--- build: ${v} ---`);const f=await G("bash",["-lc",v],{cwd:e,env:I,stdout:"capture",stderr:"capture",timeoutMs:Math.max(i,3e5)});if(c(n,f.stdout||""),c(n,f.stderr||""),f.code!==0)return c(n,`--- build failed: exit ${f.code}${f.timedOut?" (timed out)":""} ---`),{ok:!1,reason:`build failed (exit ${f.code}): ${v}`,exit:2,logPath:n,buildCmd:v};c(n,"--- build ok ---")}c(n,`--- start: ${d} ---`);const g=u.openSync(n,"a");let w;try{w=E("bash",["-lc",d],{cwd:e,env:I,detached:!0,stdio:["ignore",g,g]}),w.unref()}finally{u.closeSync(g)}if(w.pid==null)return{ok:!1,reason:`failed to spawn: ${d}`,exit:2,logPath:n,startCmd:d};const $=await K(k,{timeoutMs:i});if(!$.live)return j(w.pid,"SIGTERM"),c(n,`--- never became live within ${i}ms: ${$.error??"no response"} ---`),{ok:!1,reason:`server did not become live at ${k} within ${Math.round(i/1e3)}s`,exit:2,logPath:n,startCmd:d};const M=V({pid:w.pid,port:r,base:k,mode:D,buildCmd:v,cmd:d,pm:h,repo:e});return N(e,M),{ok:!0,...M,logPath:n,elapsedMs:$.elapsedMs}}function V({pid:e,port:t,base:r,mode:s,buildCmd:a,cmd:i,pm:S,repo:n,startedAt:b}){return{pid:e,port:t,base:r,mode:s,startedAt:b??new Date().toISOString(),buildCmd:a==null?a:F("build-cmd",a),cmd:i==null?i:F("start-cmd",i),pm:S,repo:n}}async function te(e){if(!l(p(e)))return{ok:!0,stopped:!1,reason:"not running (no .verify/serve.json)"};const t=x(e);if(!t||!y(t.pid))return P(e),{ok:!0,stopped:!1,reason:"recorded process was already dead",pid:t?.pid??null};j(t.pid,"SIGTERM");const r=Date.now()+5e3;for(;Date.now()<r&&y(t.pid);)await new Promise(s=>setTimeout(s,100));return y(t.pid)&&j(t.pid,"SIGKILL"),P(e),{ok:!0,stopped:!0,pid:t.pid,base:t.base}}async function re(e,{timeoutMs:t=5e3}={}){if(!l(p(e)))return{started:!1,reason:"not started (no .verify/serve.json)"};const r=x(e);if(!r)return{started:!1,reason:"not started (no .verify/serve.json)"};const s=y(r.pid),a=await O(r.base,{timeoutMs:t});return{started:!0,state:r,pidAlive:s,...a}}export{B as checkPortFree,J as detectPackageManager,x as readServeState,P as rmServeState,C as scriptCmdStr,q as serveLogPath,p as servePath,V as serveRecord,ee as startServer,re as statusServer,te as stopServer,K as waitForLive,N as writeServeState};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const f=[["/_next/static/development",/\/_next\/static\/development\//],["Next dev unhashed chunks",/\/_next\/static\/chunks\/(?:webpack|main-app|app-pages-internals)\.js(?:[?"'])/],["Next Turbopack dev chunks",/\/_next\/static\/chunks\/(?:%5B|\[)[^"'\s]*\._\.js/],["/@vite/client",/\/@vite\/client/],["webpack-hmr",/webpack-hmr/],["react-refresh",/react-refresh/],["__vite_plugin_react_preamble_installed__",/__vite_plugin_react_preamble_installed__/]],b=["/_next/webpack-hmr","/@vite/client"],v=new Set(["chunks","css","development","media"]);async function _(c,{timeoutMs:i,...e}={}){const n=new AbortController,s=setTimeout(()=>n.abort(),i);try{return await fetch(c,{...e,signal:n.signal})}finally{clearTimeout(s)}}async function w(c,{timeoutMs:i=8e3}={}){const e=[];let n,s="";try{n=await _(c,{timeoutMs:i,redirect:"follow"}),s=await n.text()}catch(t){return{reachable:!1,status:null,serverType:"unknown",evidence:[`unreachable: ${t?.message??t}`]}}let o=!1;for(const[t,r]of f)r.test(s)&&(e.push(`dev tell: body contains ${t}`),o=!0);const p=Math.min(i,4e3);await Promise.all(b.map(async t=>{try{const r=new URL(t,c).toString(),a=await _(r,{timeoutMs:p}),m=a.headers.get("content-type")||"";a.status>=200&&a.status<300&&!/text\/html/i.test(m)&&(e.push(`dev tell: GET ${t} -> ${a.status} (${m||"no content-type"})`),o=!0)}catch{}}));let l=!1;const u=s.match(/\/_next\/static\/([^/"'\s]+)\/(?:_buildManifest|_ssgManifest)\.js/);u&&!v.has(u[1])&&(e.push(`prod tell: build manifest ${u[1]}`),l=!0);const h=s.match(/["'](\/[^"'\s]*_next\/static\/[^"'\s]+\.js)["']/)??s.match(/["'](\/[^"'\s]*\/assets\/[^"'\s]+\.js)["']/);if(h)try{const t=new URL(h[1],c).toString(),a=(await _(t,{timeoutMs:p,method:"HEAD"})).headers.get("cache-control")||"";/immutable/i.test(a)&&(e.push(`prod tell: cache-control immutable on ${h[1]}`),l=!0)}catch{}const d=o?"dev":l?"prod":"unknown";return{reachable:!0,status:n.status,serverType:d,evidence:e}}export{w as probe};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import l from"node:fs";import s from"node:path";import{spawnSync as v}from"node:child_process";import{createHash as x}from"node:crypto";import{canonicalize as b}from"./jcs.mjs";function y(e){return x("sha256").update(e).digest("hex")}const w=/(^|\/)(_proto|proto|design-lab|playground|sandbox|scratch)(\/|$)/;function F(e){let i=[];try{i=l.readFileSync(s.join(e,".verifyignore"),"utf8").split(`
|
|
2
|
+
`).map(n=>n.replace(/#.*/,"").trim()).filter(Boolean)}catch{}const o=i.map(n=>new RegExp(n.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,"[^\\0]*").replace(/\?/g,".")));return n=>w.test(n)||o.some(f=>f.test(n))}function S(e,i,o=e,n=[]){let f;try{f=l.readdirSync(o,{withFileTypes:!0})}catch{return n}for(const u of f){const c=s.join(o,u.name);i.test(s.relative(e,c))||(u.isDirectory()?S(e,i,c,n):n.push(c))}return n}function C(e){const i=v("git",["-C",e,"ls-files","--cached","--others","--exclude-standard","-z"],{encoding:"utf8",maxBuffer:67108864});return i.status!==0||!i.stdout?null:i.stdout.split("\0").filter(Boolean).map(o=>s.join(e,o))}function I(e,{skipRules:i}={}){if(!(i instanceof RegExp))throw new TypeError("loadSourceSnapshot: skipRules must be a RegExp");const o=F(e),n=C(e)??S(e,i),f=[...new Set(n.filter(t=>!i.test(s.relative(e,t))&&!o(s.relative(e,t))).map(t=>s.relative(e,t)))].sort(),u=new Map,c=new Map;function h(t){if(u.has(t))return u.get(t);let a="";try{a=l.readFileSync(s.join(e,t),"utf8")}catch{}return u.set(t,a),a}function d(t){if(c.has(t))return c.get(t);const a=y(Buffer.from(h(t),"utf8"));return c.set(t,a),a}const p=f.map(t=>({path:t,sha256:d(t),bytes:Buffer.byteLength(h(t),"utf8")})).sort((t,a)=>t.path<a.path?-1:t.path>a.path?1:0),m=y(Buffer.from(p.map(t=>`${t.path}:${t.sha256}`).join(`
|
|
3
|
+
`),"utf8"));return{root:e,files:f,read:h,sha256:d,manifest:p,identity:{sourceDigest:m,fileCount:f.length,bytes:p.reduce((t,a)=>t+a.bytes,0)}}}function B(e){try{return y(l.readFileSync(e))}catch{return null}}function M(e,i,{skillRoot:o,toolFiles:n=[],schemaVersion:f,flags:u={}}={}){const c=r=>B(r),h=["package.json","tsconfig.json","tsconfig.base.json","next.config.js","next.config.mjs","next.config.ts","next.config.cjs","vite.config.js","vite.config.mjs","vite.config.ts","vite.config.cjs"],d={};for(const r of h){const j=c(s.join(e,r));j&&(d[r]=j)}let p=[];try{p=l.readdirSync(s.join(e,".verify","rules")).filter(r=>r.endsWith(".mjs")).sort()}catch{}const m=Object.fromEntries(p.map(r=>[r,c(s.join(e,".verify","rules",r))]));let t=null;try{const r=v("git",["-C",o,"rev-parse","HEAD"],{encoding:"utf8"});r.status===0&&r.stdout&&(t=r.stdout.trim())}catch{}const a=Object.fromEntries(n.map(r=>[r,c(s.join(o,r))])),g={manifest:i.manifest.map(r=>`${r.path}:${r.sha256}`),verifyignore:c(s.join(e,".verifyignore")),"verify.routes.json":c(s.join(e,"verify.routes.json")),"verify.roles.json":c(s.join(e,"verify.roles.json")),"verify.crud.json":c(s.join(e,"verify.crud.json")),rulePacks:m,config:d,toolCommit:t,toolFileHashes:a,schemaVersion:f,flags:u};return{identity:"sha256:"+x("sha256").update(Buffer.from(b(g),"utf8")).digest("hex"),identityInput:g}}function O(e,i){try{return l.readdirSync(s.join(e,i),{withFileTypes:!0}).filter(o=>o.isFile()).map(o=>s.posix.join(i,o.name)).sort()}catch{return[]}}function T(e,{identity:i,schemaVersion:o}){let n;try{n=JSON.parse(l.readFileSync(e,"utf8"))}catch{return null}return!n||!n.cache||n.cache.identity!==i||n.cache.schemaVersion!==o?null:n}export{M as computeCacheIdentity,O as listDir,I as loadSourceSnapshot,T as readCacheableReport};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{normalizeFindings as P,cellsOf as $,normRoute as v}from"./reports.mjs";function j(n){return Math.ceil(2*n/3)}function M(n,{n:s}={}){const l=s??n.length,f=[];n.forEach((t,i)=>{const a=`run ${i+1}`;if(!t||t.report==null){f.push(`${a}: produced no report (exit ${t?.exit??"unknown"})`);return}if(t.exit===2&&f.push(`${a}: sweep exited 2`),!t.report.integrity||t.report.integrity.ok!==!0){const o=t.report.integrity?.failed??[];f.push(`${a}: integrity failed -- ${o.length?o.join("; "):"no integrity verdict in the report"}`)}});const e=f.length===0,w=j(l),p=new Map;for(const t of n){if(!t?.report)continue;const i=P({sweep:t.report},{}),a=new Set;for(const o of i){if(a.has(o.fid))continue;a.add(o.fid);let r=p.get(o.fid);r||(r={fid:o.fid,k:0,sample:o},p.set(o.fid,r)),r.k++}}const d=[...p.values()].map(t=>{const i=t.k>=l?"stable":t.k>=w?"intermittent":"ghost",a=(i==="stable"||i==="intermittent")&&(t.sample.severity==="P0"||t.sample.severity==="P1");return{fid:t.fid,k:t.k,n:l,class:i,gating:a,phase:t.sample.phase,kind:t.sample.kind,severity:t.sample.severity,route:t.sample.route??null,at:t.sample.at??null,detail:t.sample.detail}}),h=new Map;for(const t of n)if(t?.report)for(const i of $(t.report)){const a=`${v(i.route)}|${i.role??""}|${i.width??""}`;let o=h.get(a);o||(o={route:v(i.route),role:i.role??null,width:i.width??null,fingerprints:[],metrics:{}},h.set(a,o)),i.fingerprint!=null&&o.fingerprints.push(i.fingerprint);for(const[r,c]of Object.entries(i.metrics??{}))typeof c!="number"||Number.isNaN(c)||(o.metrics[r]??=[]).push(c)}const y=[],b=[];for(const t of h.values()){const i=new Set(t.fingerprints).size;t.fingerprints.length>1&&i>1&&y.push({route:t.route,role:t.role,width:t.width,samples:t.fingerprints.length,distinct:i});for(const[a,o]of Object.entries(t.metrics)){const r=[...o].sort((D,F)=>D-F),c=r[0],u=r[r.length-1],m=r.length>>1,R=r.length%2?r[m]:(r[m-1]+r[m])/2,S=c>0?u/c>3:u>0;b.push({route:t.route,role:t.role,width:t.width,metric:a,min:c,median:Math.round(R*100)/100,max:u,samples:o.length,envSensitive:S})}}const g=n.find(t=>t?.report)?.report??null,x={runs:l,routes:g?new Set($(g).map(t=>v(t.route))).size:0,cells:g?$(g).length:0},k=d.filter(t=>t.gating);return{n:l,threshold:w,valid:e,invalidReasons:f,denominators:x,findings:d,stable:d.filter(t=>t.class==="stable").length,intermittent:d.filter(t=>t.class==="intermittent").length,ghost:d.filter(t=>t.class==="ghost").length,gating:k.length>0,gatingFindings:k,needsReproduction:d.filter(t=>t.class==="ghost"),surfaceDrift:y,metrics:b}}function z(n){const s=[],l=n.denominators;if(s.push(` denominators ${l.runs} runs \xB7 ${l.routes} routes \xB7 ${l.cells} cells`),!n.valid){s.push(""," INVALID -- this measurement did not run cleanly:");for(const e of n.invalidReasons)s.push(` - ${e}`);return s.push(' Do not merge it, do not average it, and do not read "no findings" from it.'),s.join(`
|
|
2
|
+
`)}if(s.push(` findings stable:${n.stable} intermittent:${n.intermittent} ghost:${n.ghost} (needs reproduction, never gating)`),n.gatingFindings.length){s.push("",` gating (stable or intermittent P0/P1) -- ${n.gatingFindings.length}`);for(const e of n.gatingFindings.slice(0,20))s.push(` ${e.severity} ${e.class} (${e.k}/${e.n}) ${e.kind} ${e.route??""}${e.at&&e.at!=="(page)"?" at "+String(e.at).slice(0,40):""}`)}else s.push(""," no gating stable/intermittent P0/P1 findings");if(n.needsReproduction.length){s.push("",` needs reproduction (ghost, < ${n.threshold}/${n.n}, never gating) -- ${n.needsReproduction.length}`);for(const e of n.needsReproduction.slice(0,10))s.push(` ${e.severity} (${e.k}/${e.n}) ${e.kind} ${e.route??""}${e.at&&e.at!=="(page)"?" at "+String(e.at).slice(0,40):""}`)}if(n.surfaceDrift.length){s.push("",` surface drift (fingerprint differs between runs; informational) -- ${n.surfaceDrift.length}`);for(const e of n.surfaceDrift.slice(0,10))s.push(` ${e.route}${e.role?" as "+e.role:""}${e.width?" @"+e.width+"px":""} ${e.distinct}/${e.samples} distinct surfaces`)}const f=n.metrics.filter(e=>e.envSensitive);if(f.length){s.push("",` env-sensitive metrics (max/min > 3) -- ${f.length}`);for(const e of f.slice(0,10))s.push(` ${e.route}${e.role?" as "+e.role:""}${e.width?" @"+e.width+"px":""} ${e.metric} min:${e.min} median:${e.median} max:${e.max}`)}return s.join(`
|
|
3
|
+
`)}export{M as classify,z as formatStability,j as gatingThreshold};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash as a}from"node:crypto";function o(t){const e=String(t??"").toLowerCase().replace(/\s+/g," ").trim().replace(/[0-9a-f]{8}-[0-9a-f-]{27,}/g,"<id>").replace(/\b(?:[0-9a-f]{16,}|\d{4,})\b/g,"<id>");return"sha256:"+a("sha256").update(e).digest("hex")}function f(t){const e=t.find(r=>r.kind==="empty"),i=t.filter(r=>r.kind==="error"||r.kind==="malformed");if(!e?.saysError)return!1;if(!i.length)return!0;const n=e.semanticSignature??e.errorSignature??e.fingerprint;return!!n&&i.some(r=>r.saysError&&(r.semanticSignature??r.errorSignature??r.fingerprint)===n)}export{f as emptyInjectionHitErrorState,o as semanticStateSignature};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash as p}from"node:crypto";function f(e){return e===null||typeof e!="object"?JSON.stringify(e):Array.isArray(e)?"["+e.map(f).join(",")+"]":"{"+Object.keys(e).sort().map(t=>JSON.stringify(t)+":"+f(e[t])).join(",")+"}"}function m(e){return p("sha256").update(f(e)).digest("hex")}function I({role:e,route:n,width:t,state:r=null,theme:s="light",exerciseMode:l=null,sourceIdentity:i,serverIdentity:o}){return{role:e??null,route:n??null,width:Number.isFinite(t)?t:null,state:r??null,theme:s??"light",exerciseMode:l??null,sourceIdentity:i??null,serverIdentity:o??null}}function M(e,n){return!e||!n?!1:e.role===n.role&&e.route===n.route&&e.width===n.width&&e.state===n.state&&e.theme===n.theme&&e.exerciseMode===n.exerciseMode&&e.sourceIdentity===n.sourceIdentity&&e.serverIdentity===n.serverIdentity}const g=/build|version|release|deployment/i,y=/^[a-z0-9][a-z0-9._-]{2,80}$/i;function w(e={},n=""){const t=[],r={},s={};for(const[u,d]of Object.entries(e??{}))s[String(u).toLowerCase()]=d;s.etag&&(t.push("header:etag"),r.etag=s.etag),s["last-modified"]&&(t.push("header:last-modified"),r.lastModified=s["last-modified"]);for(const[u,d]of Object.entries(s)){if(u==="etag"||u==="last-modified"||!g.test(u))continue;const h=String(d??"").trim();y.test(h)&&(t.push("header:"+u),r["header:"+u]=h)}const l=String(n??""),i=/<meta[^>]+name=["']([^"']*(?:build|version)[^"']*)["'][^>]*content=["']([^"']*)["'][^>]*>/gi;let o;for(;o=i.exec(l);)o[2]&&(t.push("meta:"+o[1].toLowerCase()),r["meta:"+o[1].toLowerCase()]=o[2]);const c=[...l.matchAll(/<script[^>]+src=["']([^"']+)["']/gi)].map(u=>u[1]).sort();c.length&&(t.push("script-chunks"),r.scriptChunks=c);const a=/"buildId"\s*:\s*"([^"]+)"/.exec(l);return a&&(t.push("nextData:buildId"),r.nextBuildId=a[1]),t.length?(t.sort(),{value:"sha256:"+m(r),signals:t}):{value:null,signals:[]}}function j(e,n){const t=e&&typeof e=="object"?{...e}:{routes:[]},r=Array.isArray(t.routes)?[...t.routes]:[],s=new Map;r.forEach((i,o)=>{i&&typeof i.route=="string"&&!i.route.startsWith("(")&&s.set((i.role??"")+" "+i.route+" @"+i.width,o)});let l=0;for(const i of n){const o=i&&typeof i=="object"?i.record:null;if(!o||typeof o!="object"||typeof o.route!="string")continue;const c=(o.role??"")+" "+o.route+" @"+o.width;s.has(c)?r[s.get(c)]=o:(s.set(c,r.length),r.push(o)),l++}return t.routes=r,{report:t,count:l}}function k(e,n){const{p95Ms:t,rate429:r=0,rate5xx:s=0}=e,{workers:l,baselineP95Ms:i,min:o=1,max:c=6}=n,a=i>0?t/i:1;return r>0||s>=.05||a>2?{workers:Math.max(o,l-1),reason:r>0?"429":s>=.05?"5xx-rate":"p95-regression"}:a<1.5&&r===0&&s<.02?{workers:Math.min(c,l+1),reason:"stable"}:{workers:l,reason:"hold"}}function O(e,n){if(!e.length)return 0;const t=[...e].sort((s,l)=>s-l),r=Math.min(t.length-1,Math.max(0,Math.ceil(n/100*t.length)-1));return t[r]}export{I as cellIdentity,M as cellIdentityEqual,m as digestOf,j as journalReplay,k as nextWorkers,O as percentile,w as serverIdentityFrom};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import S from"node:fs";import x from"node:path";import{sevRank as v,cellsOf as w,normRoute as g,idsOut as P}from"./reports.mjs";import{annotateKnownFps as U}from"./fp.mjs";import{listRunIds as O,runsDir as R}from"./runs.mjs";import{surfacesOf as F}from"./integrity.mjs";import{classifyCoherencePlan as I}from"./coherence/plan-join.mjs";function J(n){const s=new Map;if(!n)return s;try{const r=O(n);if(!r.length)return s;const t=x.join(R(n),r[r.length-1],"agent-result.json"),e=JSON.parse(S.readFileSync(t,"utf8"))?.dimensions?.routePriority;if(e?.status!=="measured")return s;for(const i of e.value?.items??[])i&&typeof i.route=="string"&&Number.isFinite(i.confidence)&&s.set(g(i.route),{confidence:i.confidence,coherenceUncovered:Number.isFinite(i.coherenceUncovered)?i.coherenceUncovered:0})}catch{}return s}function W(n){if(!n)return null;try{const s=O(n);if(!s.length)return null;const r=x.join(R(n),s[s.length-1],"agent-result.json");return JSON.parse(S.readFileSync(r,"utf8"))}catch{return null}}function V(n){const s=n?.dimensions?.coherenceObligations;if(!s)return null;if(s.status!=="measured")return`coherence obligations: unmeasured (${s.reason??"not measured"})`;try{if(I(s.value)!=="current")return"coherence obligations: unmeasured (legacy-coherence-plan-non-proof)"}catch(i){return`coherence obligations: unmeasured (${i.message})`}const r=s.value??{},t=r.coverage??{},l=(r.perRoute??[]).reduce((i,o)=>i+(o.uncovered??0),0),e=r.plan??{};return`coherence obligations: ${t.discovered??0} discovered (${t.resolved??0} resolved, ${t.unresolved??0} unresolved), ${l} statically uncovered, plan: ${(e.selected??[]).length} journeys within budget ${e.budget??0}`}function X(n,{top:s=3}={}){if(!n)return null;if(n.status!=="measured")return`coherence risk: unmeasured (${n.reason??"not measured"})`;const r=(n.families??[]).filter(e=>e.risk!=null).slice(0,s);if(!r.length)return`coherence risk: no family could be ranked over ${n.windowDays}d`;const t=r.map(e=>`${e.family} (${e.risk})`).join(", "),l=n.counts?.unmeasured??0;return`coherence risk: measure next ${t} over ${n.windowDays}d${l?`; ${l} family(ies) unmeasured`:""}`}const N=P;function q(n,s=new Map){const r=new Map;for(const e of n){if(e.waived)continue;let i;e.phase==="static"?i=`static|${e.kind}`:e.phase==="sync"?i=`sync|${e.kind}|${e.at??""}`:e.phase==="coherence"&&e.rootCauseId?i=`coherence|${e.kind}|${e.rootCauseId}`:e.at&&e.at!=="(page)"&&e.at!=="(journey)"?i=`${e.phase}|${e.kind}|${P(e.at)}`:i=`${e.phase}|${e.kind}|${N(e.detail).slice(0,60)}`;let o=r.get(i);if(o||(o={key:i,phase:e.phase,kind:e.kind,severity:e.severity,at:e.at??null,fids:[],routes:new Set,files:new Set,roles:new Set,count:0,flaky:0,unresolved:0,knownFp:0,sample:e,atlas:e.atlas??null},r.set(i,o)),o.count++,o.fids.push(e.fid),v(e.severity)<v(o.severity)&&(o.severity=e.severity,o.sample=e),e.routes?.length)for(const a of e.routes)o.routes.add(a);else e.route&&o.routes.add(e.subject??e.route);if(e.staleRoutes)for(const a of e.staleRoutes)o.routes.add(a);e.file&&o.files.add(e.file),e.role&&o.roles.add(e.role),e.flaky&&o.flaky++,e.unresolved&&o.unresolved++,e.knownFp&&o.knownFp++}const t=e=>{const i=s.get(g(e));return typeof i=="object"&&i?i.confidence:i??0},l=e=>{const i=s.get(g(e));return typeof i=="object"&&i?i.coherenceUncovered??0:0};return[...r.values()].map(e=>({...e,routes:[...e.routes].sort(),files:[...e.files].sort(),roles:[...e.roles].sort(),leverage:Math.max(e.routes.size,e.files.size,1),priority:[...e.routes].reduce((i,o)=>Math.max(i,t(o)),0),coherenceUncovered:[...e.routes].reduce((i,o)=>Math.max(i,l(o)),0),determinism:e.flaky===e.count&&e.count>0?"flaky":e.unresolved===e.count&&e.count>0?"unresolved":"stable",allKnownFp:e.count>0&&e.knownFp===e.count,sharedSurface:e.phase==="runtime"&&e.routes.size>=2&&e.at&&e.at!=="(page)"}))}const j={sync:0,crud:1,mutate:2,journey:3,shadow:4,state:5,runtime:6,experiment:7,dark:8,static:9,pack:10,leak:11};function D(n){const s={stable:0,unresolved:1,flaky:2};return[...n].sort((r,t)=>v(r.severity)-v(t.severity)||(r.allKnownFp?1:0)-(t.allKnownFp?1:0)||(t.priority??0)-(r.priority??0)||t.leverage-r.leverage||(t.coherenceUncovered??0)-(r.coherenceUncovered??0)||s[r.determinism]-s[t.determinism]||(j[r.phase]??9)-(j[t.phase]??9)||t.count-r.count||r.key.localeCompare(t.key))}function M({inventory:n,sweep:s,classify:r}){const t={routesInventory:n?.counts?.routes??null,filesScanned:r?.files??n?.counts?.files??null};if(s){const l=s.summary??{},e=w(s),i=e.filter(o=>!o.redirectedTo&&!o.skipped&&o.textLen!=null);t.routesRequested=l.routesRequested??null,t.routesReached=l.routesReached??null,t.routesInInventory=l.routesInInventory??null,t.routesDynamicSkipped=l.routesDynamicSkipped??0,t.routesUnreached=l.routesUnreached??0,t.routesUnowned=l.routesUnowned??0,t.roles=l.roles??null,t.cellsPlanned=e.length,t.cellsMeasured=i.length,t.distinctSurfaces=F(s,i).distinctSurfaces,t.integrityOk=s.integrity?!!s.integrity.ok:null,t.aborted=s.aborted??null,t.widths=[...new Set(e.map(o=>o.width).filter(o=>o!=null))].sort((o,a)=>o-a)}return t}const T=["/_next/","/_nuxt/","/_astro/","/static/","/assets/","/build/","/dist/","/__vite","/@vite/","/@fs/","/node_modules/"];function E(n){if(T.some(t=>n.startsWith(t)))return!1;const s=n.slice(n.lastIndexOf("/")+1),r=s.lastIndexOf(".");if(r>0){const t=s.slice(r+1);if(t.length>0&&t.length<=5&&/^[A-Za-z0-9]+$/.test(t))return!1}return!0}function $(n){const r=String(n).split(/[?#]/)[0].replace(/\[[^\]]*\]/g,":id");return(r.length>1?r.replace(/\/+$/,""):r)||"/"}function C({inventory:n,sweep:s}){if(!n?.routes?.length||!s)return null;const r=new Set(n.routes.map(a=>$(a.path))),t=new Set,l=new Set;for(const a of w(s))if(!a.redirectedTo){t.add(g(a.route));for(const u of a.requests??[]){if(!u?.method||!u?.pathTemplate)continue;const c=$(u.pathTemplate);r.has(c)||!E(c)||l.add(`${String(u.method).toUpperCase()} ${c}`)}}const e=new Set;for(const a of n.routes)if(t.has(g(a.path))){for(const u of a.queries??[])u.endpoint&&e.add(`${(u.verb??"GET").toUpperCase()} ${$(u.endpoint)}`);for(const u of a.mutations??[])u.endpoint&&e.add(`${(u.verb??"POST").toUpperCase()} ${$(u.endpoint)}`)}const i=[...l].filter(a=>!e.has(a)).sort(),o=[...e].filter(a=>!l.has(a)).sort();return{reachedRoutes:t.size,observedNotDeclared:i,declaredNotObserved:o}}function _({inventory:n,sweep:s,classify:r,prod:t=!1},l){const e=[],i=(o,a,u,c)=>e.push({id:o,severity:a,evidence:u,meaning:c});if(s){const a=w(s).filter(d=>!d.redirectedTo&&!d.skipped&&d.textLen!=null);if(s.integrity&&s.integrity.ok===!1&&i("integrity-failed","invalid",(s.integrity.failed??[]).join("; "),"the run did not measure the app; nothing in it is a finding"),a.length>=6){const d=a.map(m=>m.textLen),p=d.reduce((m,k)=>m+k,0)/d.length,b=Math.sqrt(d.reduce((m,k)=>m+(k-p)**2,0)/d.length);p>0&&b/p<.05&&i("same-weight","suspicious",`${a.length} measured cells weigh ${Math.round(p)}\xB1${Math.round(b)} chars`,"every page weighs the same: they are the same page, measured under many names")}const u=F(s,a);u.sameSurface&&i("same-surface","suspicious",`${u.distinctSurfaces} distinct surfaces over ${u.cellsMeasured} cells`+(u.maxRoutesPerFingerprint!=null?`, widest cluster ${u.maxRoutesPerFingerprint} route(s) per fingerprint`:""),"one page measured under many names (auth redirect or not-found shell)");const c=s.summary?.routesReached??a.length;for(const d of l)d.phase!=="runtime"||!d.at||d.at==="(page)"||d.routes.length>=5&&c&&d.routes.length>=c*.5&&i("one-selector-many-routes","suspicious",`${d.kind} at ${d.at} on ${d.routes.length} of ${c} reached routes`,"N routes landed on one surface, or one shared component: one finding, not N");s.warm&&s.warm.requested>=5&&s.warm.compiled<s.warm.requested*.5&&i("warm-failed","suspicious",`warm pass compiled ${s.warm.compiled} of ${s.warm.requested}`,"most routes did not answer over plain HTTP before the sweep; navigation timeouts below may be the compiler");const f=l.filter(d=>d.phase==="runtime"),h=f.filter(d=>/^perf\.(lcp|long-task|layout-shift|layout-thrash)$/.test(d.kind)).reduce((d,p)=>d+p.count,0),y=f.reduce((d,p)=>d+p.count,0);!t&&y>=6&&h>=y*.5&&i("perf-without-prod","note",`${h} of ${y} runtime findings are timing, measured without --prod`,"against a dev server these are the compiler; build the app and pass --prod before grading them")}if(n&&s){const o=C({inventory:n,sweep:s});o?.observedNotDeclared.length&&i("contract-observed-not-declared","suspicious",o.observedNotDeclared.slice(0,5).join(", ")+(o.observedNotDeclared.length>5?`, +${o.observedNotDeclared.length-5} more`:""),"the app called an endpoint the import walk never resolved: repair the import resolution (a path alias, a generic fetch wrapper) rather than filing an unqualified app defect"),o?.declaredNotObserved.length&&i("contract-declared-not-observed","note",o.declaredNotObserved.slice(0,5).join(", ")+(o.declaredNotObserved.length>5?`, +${o.declaredNotObserved.length-5} more`:""),"resolved from source but never called on any reached route this run: not necessarily dead -- a forced state or a journey may be what exercises it")}if(s?.roles?.length>1&&n?.routes?.length){const o=(c,f)=>c===f||c.startsWith(f.replace(/\/$/,"")+"/"),a=(c,f)=>(c.owns??[]).some(h=>o(f,h))&&!(c.excludes??[]).some(h=>o(f,h)),u=n.routes.map(c=>c.path);for(const c of s.roles)u.every(f=>a(c,f))&&i("role-owns-everything","suspicious",`role "${c.name}" owns ${u.length} of ${u.length} routes`,'a bare "/" in owns is a prefix catch-all, so the other entries and every other role are moot')}if(n){const o=n.routes??[];if(o.length>=3){const f=o.filter(h=>(h.modules??0)<=1).length;f>=o.length*.8&&i("modules-one","suspicious",`${f} of ${o.length} routes resolved to 1 module`,'the import walk resolved nothing at hop 1; path aliases are wrong, the app is not "clean"')}const a=n.counts?.queries??0,u=Object.keys(n.resourceMatrix??{}).length;a>=20&&u<a/10&&i("resource-matrix-tiny","suspicious",`${u} resources for ${a} queries`,"the endpoint matcher missed the client's spelling; no blast radius resolves and every sync risk demotes itself");const c=n.syncRisks??[];c.length>=3&&c.every(f=>f.unresolved)&&i("all-sync-unresolved","suspicious",`${c.length} of ${c.length} sync risks unresolved`,"a detector that resolves 0 of N is broken, not tuned")}if(r&&r.files>=10)for(const[o,a]of Object.entries(r.byRule??{}))a>=r.files*.5&&i("rule-fires-everywhere","suspicious",`${o} fired ${a} times over ${r.files} files`,"a rule that fires in most files is matching a convention, not a defect");return e}function Z({records:n,inventory:s,sweep:r,classify:t,prod:l=!1,knownFps:e=void 0,priorities:i=new Map}){const o=e===null?0:U(n,e),a=D(q(n,i)),u=n.filter(h=>!h.waived),c={P0:0,P1:0,P2:0,P3:0};for(const h of u)c[h.severity]=(c[h.severity]??0)+1;const f={};for(const h of u)f[h.phase]=(f[h.phase]??0)+1;return{counts:{findings:u.length,groups:a.length,waived:n.length-u.length,knownFp:o,...c,byPhase:f},prioritised:i.size>0,denominators:M({inventory:s,sweep:r,classify:t}),tells:_({inventory:s,sweep:r,classify:t,prod:l},a),groups:a,waived:n.filter(h=>h.waived),gating:c.P0+c.P1>0}}function Q(n,{limit:s=20}={}){const r=[],t=n.denominators,l=[];if(t.routesInventory!=null&&l.push(`${t.routesInventory} routes in inventory`),t.routesRequested!=null&&l.push(`swept ${t.routesReached}/${t.routesInInventory??t.routesRequested} routes`+(t.routesDynamicSkipped?` (${t.routesDynamicSkipped} dynamic not attempted)`:"")+(t.routesUnreached?` (${t.routesUnreached} not reached)`:"")+(t.routesUnowned?` (${t.routesUnowned} unowned)`:"")),t.cellsPlanned!=null&&l.push(`${t.cellsMeasured}/${t.cellsPlanned} cells measured`+(t.widths?.length?` at ${t.widths.join(",")}px`:"")),t.distinctSurfaces!=null&&l.push(`${t.distinctSurfaces} distinct surfaces`),t.integrityOk!=null&&l.push(`integrity ${t.integrityOk?"ok":"FAILED"}`),r.push(" denominators "+(l.join(" \xB7 ")||"static only")),r.push(` findings ${n.counts.findings} (${n.counts.groups} distinct) P0:${n.counts.P0} P1:${n.counts.P1} P2:${n.counts.P2} P3:${n.counts.P3}`+(n.counts.waived?` waived:${n.counts.waived}`:"")+(n.counts.knownFp?` known-fp:${n.counts.knownFp}`:"")),n.tells.length){r.push(""),r.push(" shape");for(const e of n.tells)r.push(` ${e.severity==="invalid"?"INVALID ":e.severity==="suspicious"?"SUSPECT ":"note "} ${e.id}: ${e.evidence}`);for(const e of n.tells)r.push(` ${e.id} -> ${e.meaning}`)}if(n.groups.length){r.push(""),r.push(n.prioritised?" ranked (severity, kernel route priority, leverage, determinism)":" ranked (severity, leverage, determinism)");for(const e of n.groups.slice(0,s)){const i=e.phase==="static"||e.phase==="sync"?`${e.files.length} file(s)`+(e.routes.length?`, ${e.routes.length} route(s)`:""):`${e.routes.length} route(s)`+(e.at&&e.at!=="(page)"?` at ${e.at.slice(0,50)}`:""),o=[e.determinism!=="stable"?e.determinism:null,e.sharedSurface?"shared-surface":null,e.allKnownFp?"known-fp":null,n.prioritised&&e.priority?"p="+e.priority.toFixed(2):null].filter(Boolean).join(",");r.push(` ${e.severity} ${String(e.count).padStart(3)}x ${e.kind.padEnd(34)} ${i}${o?" ["+o+"]":""}`),r.push(` ${String(e.sample.detail??"").slice(0,120)}`)}n.groups.length>s&&r.push(` ... and ${n.groups.length-s} more groups`)}return r.join(`
|
|
2
|
+
`)}export{V as coherenceObligationsLine,X as coherenceRiskLine,C as contractBlindSpot,M as denominators,Q as formatTriage,q as groupFindings,W as loadAgentResult,J as loadRoutePriorities,D as rankGroups,_ as structuralTells,Z as triage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{EXIT as n}from"./cli.mjs";const A=new Set(["invalid"]);function N(r=[]){const t=new Map;for(const e of r)e?.adapter&&(e.publication==="superseded"?t.delete(e.adapter):t.set(e.adapter,e));return[...t.values()]}function v({measured:r,gateExit:t,remediation:e={ran:!0,reason:"complete"},adapterInvocations:l=[]}){if(!r||t===n.INVALID)return a("INVALID",n.INVALID,"the verifier could not produce trustworthy evidence");const i=N(l),u=i.find(s=>s.required===!0&&s.status!=="measured");if(u)return a("INVALID",n.INVALID,`${u.adapter} adapter evidence is unusable: ${u.reason??u.status}`);if(e.ran&&A.has(e.reason))return a("INVALID",n.INVALID,e.detail??"a remediation phase could not produce trustworthy evidence");const o=i.reduce((s,d)=>s+(d.status==="measured"?(d.counts?.P0??0)+(d.counts?.P1??0):0),0),c=t!==n.OK||o>0,p=c?"FAIL":"PASS",I=c?n.FINDINGS:n.OK,f=c?`gating findings in the measured scope${o?` (${o} from validated adapter evidence)`:""}`:"no gating findings in the measured scope";if(!e.ran&&e.reason!=="complete"){const s=e.detail?`${e.reason}: ${e.detail}`:e.reason;return a(p,I,`${f}; remediation did not run (${s})`)}return a(p,I,f)}function a(r,t,e){return{verdict:r,exit:t,reason:e,line:`${r} exit ${t} \u2014 ${e}`}}export{N as currentAdapterInvocations,v as verdictFor};
|
package/lib/qa/watch.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import d from"node:fs";import h from"node:path";const w=[".verify","node_modules",".git",".next","dist","build","out"];function x(t,s=w){return String(t).split(h.sep).filter(Boolean).some(n=>s.includes(n))}function g(t=process.platform,s=process.versions.node){return t==="darwin"||t==="win32"?!0:t==="linux"?Number(String(s).split(".")[0])>=20:!1}function v(t,s){const l=new Map;return(function n(e){let u;try{u=d.readdirSync(e,{withFileTypes:!0})}catch{return}for(const r of u){if(s.includes(r.name))continue;const o=h.join(e,r.name);if(r.isDirectory()){n(o);continue}if(r.isFile())try{l.set(h.relative(t,o),d.statSync(o).mtimeMs)}catch{}}})(t),l}function T(t,{ignore:s=w,onChange:l,pollMs:n=2e3,recursive:e=g(),watchFn:u=null}={}){let r=!1;const o=i=>{!r&&i&&!x(i,s)&&l(i)};if(e)try{const i=(u??((c,p,f)=>d.watch(c,p,f)))(t,{recursive:!0},(c,p)=>o(p));return{stop(){if(!r){r=!0;try{i.close()}catch{}}}}}catch{}let a=v(t,s);const m=setInterval(()=>{if(r)return;const i=v(t,s);for(const[c,p]of i)(!a.has(c)||a.get(c)!==p)&&o(c);for(const c of a.keys())i.has(c)||o(c);a=i},n);return{stop(){r||(r=!0,clearInterval(m))}}}function k({debounceMs:t=400,run:s,once:l=!1}){let n=null,e=!1,u=!1,r=!1,o=!1,a;const m=new Promise(f=>{a=f});function i(f,y){o||(o=!0,n&&clearTimeout(n),a(y?Promise.reject(y):f))}function c(){n=null,!(r||o)&&(e=!0,Promise.resolve().then(s).then(f=>{if(e=!1,r){i(f);return}if(l){i(f);return}u&&(u=!1,p())},f=>{e=!1,i(void 0,f)}))}function p(){if(!(r||o)){if(e){u=!0;return}n&&clearTimeout(n),n=setTimeout(c,t)}}return{notify:p,stop(){r||(r=!0,n&&clearTimeout(n),e||i())},done:m}}function D(t){const s=e=>e.file?`${e.file}:${e.line}`:(e.route??"")+(e.at&&e.at!=="(page)"?` at ${String(e.at).slice(0,40)}`:""),l=e=>` ${e.fid} ${e.severity} ${e.kind} ${s(e)}`,n=(e,u)=>[` ${e} (${u.length})`,...u.map(l)];return[...n("fixed",t.fixed),...n("new",t.new),...n("persisting",t.persisting),...n("unmeasured",t.unmeasured??[])].join(`
|
|
2
|
+
`)}export{w as DEFAULT_IGNORE,k as createScheduler,D as formatDiff,x as isIgnored,g as supportsRecursiveWatch,T as watchRepo};
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "automated-qa",
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "Evidence-first frontend verification: static inventory, runtime invariants, and the qa researcher CLI that drives them.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"qa",
|
|
7
|
+
"frontend",
|
|
8
|
+
"verification",
|
|
9
|
+
"playwright",
|
|
10
|
+
"testing",
|
|
11
|
+
"cli",
|
|
12
|
+
"static-analysis",
|
|
13
|
+
"sarif",
|
|
14
|
+
"evidence",
|
|
15
|
+
"regression"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/Benmore-Studio/automated-qa#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/Benmore-Studio/automated-qa/issues"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/Benmore-Studio/automated-qa.git"
|
|
24
|
+
},
|
|
25
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
26
|
+
"author": "Arkash Jain (https://github.com/ArkashJ)",
|
|
27
|
+
"bin": {
|
|
28
|
+
"qa": "bin/qa.mjs",
|
|
29
|
+
"automated-qa": "bin/qa.mjs",
|
|
30
|
+
"qa-install-skill": "install-skill.sh"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "node --test test/*.test.mjs",
|
|
37
|
+
"selftest": "node bin/qa.mjs dev selftest --all",
|
|
38
|
+
"selftest:static": "bash selftest-static.sh",
|
|
39
|
+
"selftest:cli": "bash selftest-cli.sh",
|
|
40
|
+
"selftest:coherence": "bash selftest-coherence.sh",
|
|
41
|
+
"selftest:runtime": "bash selftest.sh",
|
|
42
|
+
"selftest:atlas": "bash selftest-atlas.sh",
|
|
43
|
+
"selftest:agent": "bash selftest-agent.sh",
|
|
44
|
+
"selftest:tournament": "bash selftest-tournament.sh",
|
|
45
|
+
"selftest:e2e": "bash selftest-e2e.sh",
|
|
46
|
+
"lint": "for f in bin/*.mjs lib/qa/*.mjs lib/qa/commands/*.mjs; do node --check \"$f\"; done && bash -n verify.sh bin/*.sh selftest*.sh"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"bin",
|
|
50
|
+
"lib",
|
|
51
|
+
"schemas",
|
|
52
|
+
"templates",
|
|
53
|
+
"references",
|
|
54
|
+
"agent/prebuilt",
|
|
55
|
+
"agent/release-manifest.json",
|
|
56
|
+
"verify.sh",
|
|
57
|
+
"install-skill.sh",
|
|
58
|
+
"SKILL.md",
|
|
59
|
+
"LICENSE",
|
|
60
|
+
"NOTICE"
|
|
61
|
+
]
|
|
62
|
+
}
|