@ornexus/neocortex 4.60.62 → 4.60.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dist/release-source.json +1 -1
  2. package/dist/sbom.cdx.json +77 -50
  3. package/install.ps1 +1 -1
  4. package/install.sh +1 -1
  5. package/package.json +12 -5
  6. package/packages/client/dist/cli.js +8 -3
  7. package/packages/client/dist/commands/activate.js +8 -8
  8. package/packages/client/dist/commands/architecture-memory-terminal.js +4 -2
  9. package/packages/client/dist/commands/architecture-non-ui-continuation.d.ts +3 -0
  10. package/packages/client/dist/commands/architecture-non-ui-continuation.js +1 -0
  11. package/packages/client/dist/commands/invoke.d.ts +19 -16
  12. package/packages/client/dist/commands/invoke.js +86 -79
  13. package/packages/client/dist/commands/planning-generation.js +1 -1
  14. package/packages/client/dist/commands/planning-memory.d.ts +1 -0
  15. package/packages/client/dist/commands/planning-memory.js +1 -0
  16. package/packages/client/dist/commands/private-session.d.ts +1 -0
  17. package/packages/client/dist/commands/private-session.js +1 -0
  18. package/packages/client/dist/commands/qa-control.d.ts +4 -0
  19. package/packages/client/dist/commands/qa-control.js +1 -0
  20. package/packages/client/dist/commands/qa-scan-browser-launch.d.ts +8 -0
  21. package/packages/client/dist/commands/qa-scan-browser-launch.js +1 -0
  22. package/packages/client/dist/commands/qa-scan-browser-route.d.ts +3 -0
  23. package/packages/client/dist/commands/qa-scan-browser-route.js +1 -0
  24. package/packages/client/dist/commands/qa-scan-capture.d.ts +43 -0
  25. package/packages/client/dist/commands/qa-scan-capture.js +5 -5
  26. package/packages/client/dist/commands/qa-scan-discovery.d.ts +7 -0
  27. package/packages/client/dist/commands/qa-scan-discovery.js +3 -3
  28. package/packages/client/dist/commands/qa-scan-review.d.ts +8 -1
  29. package/packages/client/dist/commands/qa-scan-review.js +5 -5
  30. package/packages/client/dist/commands/qa-synthetic-backend-capture.d.ts +62 -0
  31. package/packages/client/dist/commands/qa-synthetic-backend-capture.js +1 -0
  32. package/packages/client/dist/commands/qa-synthetic-backend-review.d.ts +104 -0
  33. package/packages/client/dist/commands/qa-synthetic-backend-review.js +1 -0
  34. package/packages/client/dist/commands/qa-synthetic-backend.d.ts +52 -0
  35. package/packages/client/dist/commands/qa-synthetic-backend.js +7 -0
  36. package/packages/client/dist/commands/qa-synthetic-broker-port.d.ts +30 -0
  37. package/packages/client/dist/commands/qa-synthetic-broker-port.js +1 -0
  38. package/packages/client/dist/commands/qa-synthetic-browser.d.ts +1 -0
  39. package/packages/client/dist/commands/qa-synthetic-browser.js +1 -0
  40. package/packages/client/dist/commands/refactor-characterization.d.ts +12 -3
  41. package/packages/client/dist/commands/refactor-characterization.js +21 -12
  42. package/packages/client/dist/commands/refactor-control.d.ts +17 -0
  43. package/packages/client/dist/commands/refactor-control.js +3 -0
  44. package/packages/client/dist/commands/refactor-cpp-assertions.d.ts +4 -0
  45. package/packages/client/dist/commands/refactor-cpp-assertions.js +3 -0
  46. package/packages/client/dist/commands/refactor-review.d.ts +13 -0
  47. package/packages/client/dist/commands/refactor-review.js +6 -5
  48. package/packages/client/dist/commands/refactor-root.d.ts +165 -1
  49. package/packages/client/dist/commands/refactor-root.js +12 -7
  50. package/packages/client/dist/commands/refresh-memory.d.ts +1 -0
  51. package/packages/client/dist/commands/refresh-memory.js +3 -3
  52. package/packages/client/dist/commands/shared-architecture-non-ui-contract.d.ts +19 -0
  53. package/packages/client/dist/commands/shared-architecture-non-ui-contract.js +1 -0
  54. package/packages/client/dist/errors/error-messages.js +1 -1
  55. package/packages/client/dist/git/direct-yolo-checkpoint.js +4 -4
  56. package/packages/client/dist/git/git-lifecycle-store.js +7 -7
  57. package/packages/client/dist/license/license-client.d.ts +3 -1
  58. package/packages/client/dist/license/license-client.js +1 -1
  59. package/packages/client/dist/memory/project-memory-path-scope.d.ts +4 -0
  60. package/packages/client/dist/memory/project-memory-path-scope.js +1 -0
  61. package/packages/client/dist/memory/project-memory-writer.d.ts +4 -1
  62. package/packages/client/dist/memory/project-memory-writer.js +16 -16
  63. package/packages/client/dist/package.json +1 -0
  64. package/packages/client/dist/private-execution/clean-baseline.d.ts +7 -0
  65. package/packages/client/dist/private-execution/clean-baseline.js +2 -2
  66. package/packages/client/dist/private-execution/codex-executor.d.ts +48 -4
  67. package/packages/client/dist/private-execution/codex-executor.js +9 -2
  68. package/packages/client/dist/private-execution/codex-host-runtime.d.ts +10 -0
  69. package/packages/client/dist/private-execution/codex-host-runtime.js +1 -0
  70. package/packages/client/dist/private-execution/codex-lifecycle-guard.d.ts +4 -0
  71. package/packages/client/dist/private-execution/codex-lifecycle-guard.js +1 -0
  72. package/packages/client/dist/private-execution/codex-scoped-permissions.d.ts +8 -0
  73. package/packages/client/dist/private-execution/codex-scoped-permissions.js +1 -0
  74. package/packages/client/dist/private-execution/delivery.d.ts +18 -1
  75. package/packages/client/dist/private-execution/delivery.js +11 -3
  76. package/packages/client/dist/private-execution/diagnosis-root-review.d.ts +112 -0
  77. package/packages/client/dist/private-execution/diagnosis-root-review.js +2 -0
  78. package/packages/client/dist/private-execution/frozen-handoff.d.ts +2 -0
  79. package/packages/client/dist/private-execution/frozen-handoff.js +7 -0
  80. package/packages/client/dist/private-execution/implementation-validation.d.ts +24 -0
  81. package/packages/client/dist/private-execution/implementation-validation.js +2 -0
  82. package/packages/client/dist/private-execution/isolated-admission.d.ts +11 -7
  83. package/packages/client/dist/private-execution/isolated-admission.js +17 -10
  84. package/packages/client/dist/private-execution/isolated-session.d.ts +68 -0
  85. package/packages/client/dist/private-execution/isolated-session.js +5 -2
  86. package/packages/client/dist/private-execution/pact-test-source.d.ts +1 -1
  87. package/packages/client/dist/private-execution/pact-test-source.js +1 -1
  88. package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.d.ts +2 -0
  89. package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +5 -5
  90. package/packages/client/dist/private-execution/planning-generation-checkpoint.d.ts +13 -0
  91. package/packages/client/dist/private-execution/planning-generation-checkpoint.js +4 -3
  92. package/packages/client/dist/private-execution/planning-memory-checkpoint.d.ts +32 -0
  93. package/packages/client/dist/private-execution/planning-memory-checkpoint.js +3 -0
  94. package/packages/client/dist/private-execution/planning-research-checkpoint.js +4 -4
  95. package/packages/client/dist/private-execution/root-checkpoint.js +1 -1
  96. package/packages/client/dist/private-execution/root-memory-git-evidence.d.ts +30 -0
  97. package/packages/client/dist/private-execution/root-memory-git-evidence.js +3 -0
  98. package/packages/client/dist/private-execution/root-memory-recovery.d.ts +16 -0
  99. package/packages/client/dist/private-execution/root-memory-recovery.js +1 -0
  100. package/packages/client/dist/private-execution/root-memory-request.d.ts +28 -0
  101. package/packages/client/dist/private-execution/root-memory-request.js +1 -0
  102. package/packages/client/dist/private-execution/root-memory-scope.d.ts +28 -0
  103. package/packages/client/dist/private-execution/root-memory-scope.js +1 -0
  104. package/packages/client/dist/private-execution/root-memory-writer.d.ts +22 -0
  105. package/packages/client/dist/private-execution/root-memory-writer.js +1 -0
  106. package/packages/client/dist/private-execution/session-lease.d.ts +19 -0
  107. package/packages/client/dist/private-execution/session-lease.js +1 -0
  108. package/packages/client/dist/private-execution/ux-source-validation.js +1 -1
  109. package/packages/client/dist/runner-cli.js +0 -0
  110. package/packages/client/dist/yolo/change-evidence-collector.d.ts +2 -2
  111. package/packages/client/dist/yolo/change-evidence-collector.js +1 -1
  112. package/packages/client/dist/yolo/spec-envelope-collector.js +2 -2
  113. package/packages/shared/dist/invocation/index.js +4 -2
  114. package/packages/shared/dist/invocation/packed-invoke.d.ts +12 -0
  115. package/packages/shared/dist/invocation/packed-invoke.js +58 -0
  116. package/packages/shared/dist/invocation/packed-invoke.test.d.ts +1 -0
  117. package/packages/shared/dist/invocation/packed-invoke.test.js +21 -0
  118. package/packages/shared/dist/package.json +1 -0
  119. package/packages/shared/dist/planning/diagnosis-state.js +5 -2
  120. package/packages/shared/dist/private-qa-structural-admission.js +14 -0
  121. package/packages/shared/dist/private-yolo-predecessor-decision.js +32 -0
  122. package/packages/shared/dist/project-memory-targets.js +243 -0
  123. package/packages/shared/dist/project-validation-commands.js +96 -0
  124. package/packages/shared/dist/yolo/change-evidence.js +35 -0
  125. package/packages/shared/dist/yoloop/branch-unit.js +10 -1
  126. package/packages/shared/dist/yoloop/root-memory-effect.js +57 -0
  127. package/targets-stubs/antigravity/gemini.md +1 -1
  128. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  129. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  130. package/targets-stubs/claude-code/neocortex-root.md +2 -2
  131. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  132. package/targets-stubs/claude-code/neocortex.md +2 -2
  133. package/targets-stubs/codex/AGENTS.md +1 -1
  134. package/targets-stubs/cursor/agent.md +2 -2
  135. package/targets-stubs/gemini-cli/agent.md +2 -2
  136. package/targets-stubs/opencode/neocortex-root.md +1 -1
  137. package/targets-stubs/vscode/neocortex.agent.md +2 -2
@@ -1 +1 @@
1
- import{sealPlanningGeneration as a}from"../private-execution/planning-generation-checkpoint.js";const s=64*1024;async function c(n){const o=[];let r=0;for await(const e of n){const t=Buffer.isBuffer(e)?e:Buffer.from(e);if(r+=t.length,r>s)throw new Error("planning-generation-review-too-large");o.push(t)}if(r===0)throw new Error("planning-generation-review-missing");return JSON.parse(Buffer.concat(o).toString("utf8"))}async function g(n){return l(n,process.stdin,console)}async function l(n,o,r){if(n[0]!=="seal"||n.length!==1&&n.length!==3||n.length===3&&n[1]!=="--project-root")return r.error("usage: planning-generation seal [--project-root <path>] < review.json"),1;try{const e=await c(o),t=a(n.length===3?n[2]:process.cwd(),e);return r.log(JSON.stringify({ok:!0,...t,publicSafe:!0})),0}catch(e){const t=e instanceof Error?e.message:"",i=/^planning-generation-[a-z-]+$/.test(t)?t:"planning-generation-seal-failed";return r.error(JSON.stringify({ok:!1,reasonCode:i,publicSafe:!0})),1}}export{g as planningGenerationCliHandler,l as planningGenerationCliHandlerWithIO};
1
+ import{sealPlanningGeneration as c,verifyCommittedPlanningGeneration as s}from"../private-execution/planning-generation-checkpoint.js";const f=64*1024;async function l(e){const o=[];let r=0;for await(const n of e){const i=Buffer.isBuffer(n)?n:Buffer.from(n);if(r+=i.length,r>f)throw new Error("planning-generation-review-too-large");o.push(i)}if(r===0)throw new Error("planning-generation-review-missing");return JSON.parse(Buffer.concat(o).toString("utf8"))}async function u(e){return p(e,process.stdin,console)}async function p(e,o,r){if(e[0]!=="seal"&&e[0]!=="verify"||e.length!==1&&e.length!==3||e.length===3&&e[1]!=="--project-root")return r.error("usage: planning-generation <seal|verify> [--project-root <path>] < input.json"),1;try{const n=await l(o),i=e.length===3?e[2]:process.cwd();let t;if(e[0]==="verify"){if(!n||typeof n!="object"||Array.isArray(n)||Object.keys(n).sort().join("|")!=="claimId|epicId|schemaVersion"||n.schemaVersion!=="PlanningGenerationVerification/1"||typeof n.claimId!="string"||typeof n.epicId!="string")throw new Error("planning-generation-verification-input-invalid");const a=n;t=s(i,a.claimId,a.epicId)}else t=c(i,n);return r.log(JSON.stringify({ok:!0,...t,publicSafe:!0})),0}catch(n){const i=n instanceof Error?n.message:"",t=/^planning-generation-[a-z-]+$/.test(i)?i:e[0]==="verify"?"planning-generation-verification-failed":"planning-generation-seal-failed";return r.error(JSON.stringify({ok:!1,reasonCode:t,publicSafe:!0})),1}}export{u as planningGenerationCliHandler,p as planningGenerationCliHandlerWithIO};
@@ -0,0 +1 @@
1
+ export declare function planningMemoryCliHandler(args: readonly string[]): Promise<number>;
@@ -0,0 +1 @@
1
+ import{sealPlanningMemory as s,verifyCommittedPlanningMemory as f}from"../private-execution/planning-memory-checkpoint.js";async function p(n){if(!["seal","verify"].includes(n[0]??"")||n.length!==1&&n.length!==3||n.length===3&&n[1]!=="--project-root")return console.error("usage: planning-memory <seal|verify> [--project-root <path>] < input.json"),1;try{const t=[];let i=0;for await(const o of process.stdin){const l=Buffer.from(o);if(i+=l.length,i>16384)throw new Error("planning-memory-input-too-large");t.push(l)}const e=JSON.parse(Buffer.concat(t).toString("utf8")),c=n.length===3?n[2]:process.cwd();let r;if(n[0]==="verify"){if(!e||typeof e!="object"||Array.isArray(e)||Object.keys(e).sort().join("|")!=="claimId|epicId|schemaVersion"||e.schemaVersion!=="PlanningMemoryVerification/1"||typeof e.claimId!="string"||typeof e.epicId!="string")throw new Error("planning-memory-input-invalid");const o=e;r=f(c,o.claimId,o.epicId)}else r=s(c,e);return console.log(JSON.stringify({ok:!0,receipt:r,publicSafe:!0})),0}catch{return console.error(JSON.stringify({ok:!1,reasonCode:"planning-memory-evidence-unverified",publicSafe:!0})),1}}export{p as planningMemoryCliHandler};
@@ -0,0 +1 @@
1
+ export declare function privateSessionCliHandler(args: readonly string[]): Promise<number>;
@@ -0,0 +1 @@
1
+ import{validatePrivateYoloImplementation as l}from"../private-execution/implementation-validation.js";import{renewPrivateYoloSessionLease as f}from"../private-execution/session-lease.js";import{acceptPrivateYoloDiagnosisRootReview as m,acceptPrivateYoloResearchRootReview as h,acceptPrivateYoloSpecificationRootReview as w,acceptPrivateYoloTasksRootReview as y,acceptPrivateYoloImplementationRootReview as R,acceptPrivateYoloRefactorAdvisoryRootReview as x,acceptPrivateYoloTestValidationRootReview as S,acceptPrivateYoloQaRootReview as j}from"../private-execution/diagnosis-root-review.js";const s={"accept-project-memory":P,"accept-qa":j,"accept-test-validation":S,"accept-refactor-advisory":x,"validate-implementation":l,"accept-implementation":R,"accept-diagnosis":m,"accept-research":h,"accept-specification":w,"accept-tasks":y};import{acceptPrivateYoloProjectMemoryRootReview as P,discardPrivateYoloRejectedCandidate as Y,inspectPrivateYoloRejectedCandidate as k}from"../private-execution/isolated-session.js";async function O(e){if(!["inspect-rejected","discard-rejected","renew-lease",...Object.keys(s)].includes(e[0]??"")||e.length!==1&&e.length!==3||e.length===3&&e[1]!=="--project-root")return console.error("usage: private-session <inspect-rejected|discard-rejected|renew-lease|accept-diagnosis|accept-research|accept-specification|accept-tasks|validate-implementation|accept-implementation|accept-refactor-advisory|accept-test-validation|accept-qa|accept-project-memory> [--project-root <path>] < root-request.json"),1;try{const o=[];let d=0;for await(const p of process.stdin){const a=Buffer.from(p);if(d+=a.length,d>16384)throw new Error("input-too-large");o.push(a)}const i=JSON.parse(Buffer.concat(o).toString("utf8"));if(Object.hasOwn(s,e[0])){const p=s[e[0]],a=await p(e.length===3?e[2]:process.cwd(),i);return console.log(JSON.stringify({ok:!0,result:a,publicSafe:!0})),0}if(!i||typeof i!="object"||Array.isArray(i))throw new Error("input-invalid");const t=i,c=e[0]==="discard-rejected",r=e[0]==="renew-lease",v=["schemaVersion","storyId","graphRevision","executorStopped",...c?["expectedCandidateDigest","authorizeDiscard"]:[],...r?["expectedSnapshotSha","expiresAt"]:[]];if(Object.keys(t).sort().join("|")!==v.sort().join("|")||t.schemaVersion!==(r?"PrivateYoloSessionLeaseRequest/1":"PrivateYoloRejectedCandidateRequest/1")||typeof t.storyId!="string"||typeof t.graphRevision!="string"||!/^[a-f0-9]{64}$/.test(t.graphRevision)||t.executorStopped!==!0||c&&(t.authorizeDiscard!==!0||typeof t.expectedCandidateDigest!="string")||r&&(typeof t.expectedSnapshotSha!="string"||typeof t.expiresAt!="string"))throw new Error("input-invalid");const n=e.length===3?e[2]:process.cwd(),u=r?await f(n,t.storyId,t.graphRevision,{executorStopped:!0,expectedSnapshotSha:t.expectedSnapshotSha,expiresAt:t.expiresAt}):c?await Y(n,t.storyId,t.graphRevision,{expectedCandidateDigest:t.expectedCandidateDigest,executorStopped:!0,authorizeDiscard:!0}):await k(n,t.storyId,t.graphRevision,!0);return console.log(JSON.stringify({ok:!0,result:u,publicSafe:!0})),0}catch{const o=e[0]==="accept-project-memory"?"private-root-memory-acceptance-unverified":e[0]==="accept-qa"?"private-execution-qa-root-review-unverified":e[0]==="accept-test-validation"?"private-execution-test-validation-root-review-unverified":e[0]==="accept-refactor-advisory"?"private-execution-refactor-advisory-root-review-unverified":e[0]==="validate-implementation"?"private-execution-implementation-validation-unverified":e[0]==="accept-implementation"?"private-execution-implementation-root-review-unverified":e[0]==="accept-diagnosis"?"private-execution-diagnosis-root-review-unverified":e[0]==="accept-research"?"private-execution-research-root-review-unverified":e[0]==="accept-specification"?"private-execution-specification-root-review-unverified":e[0]==="accept-tasks"?"private-execution-tasks-root-review-unverified":e[0]==="renew-lease"?"private-execution-session-renewal-unverified":"private-execution-rejected-candidate-unverified";return console.error(JSON.stringify({ok:!1,reasonCode:o,publicSafe:!0})),1}}export{O as privateSessionCliHandler};
@@ -0,0 +1,4 @@
1
+ /** Signed mechanical QA control; no stages, paths or verdicts are uploaded. */
2
+ import { requestRootControl } from './refactor-control.js';
3
+ export declare function requestQaControl(input: Parameters<typeof requestRootControl>[1], fetchFn?: typeof fetch): Promise<Record<string, unknown>>;
4
+ export declare function qaControlCliHandler(args: readonly string[]): Promise<number>;
@@ -0,0 +1 @@
1
+ import{requestRootControl as e,refactorControlCliHandler as a}from"./refactor-control.js";async function c(o,t=fetch){try{return await e("qa",o,t)}catch(r){throw r instanceof Error&&/^refactor-root-control-[a-z-]+$/.test(r.message)?new Error(r.message.replace(/^refactor-/,"qa-")):r}}function l(o){return a(o,"qa")}export{l as qaControlCliHandler,c as requestQaControl};
@@ -0,0 +1,8 @@
1
+ import { type LaunchOptions } from 'playwright-core';
2
+ type Host = Readonly<{
3
+ platform: NodeJS.Platform;
4
+ arch: string;
5
+ }>;
6
+ export declare function rootQaBrowserLaunchOptions(host?: Host): LaunchOptions;
7
+ export declare function launchRootQaBrowser(host?: Host): Promise<import("playwright-core").Browser>;
8
+ export {};
@@ -0,0 +1 @@
1
+ import{lstatSync as i,realpathSync as h,mkdtempSync as l,mkdirSync as u,rmSync as b}from"node:fs";import{join as c}from"node:path";import{tmpdir as d}from"node:os";import{chromium as m}from"playwright-core";function f(n=process){return Object.freeze({headless:!0,timeout:15e3,...n.platform==="linux"&&n.arch==="arm64"?{executablePath:"/usr/bin/chromium"}:{channel:"chrome"}})}async function v(n=process){const o=f(n);if(o.executablePath)try{for(const r of["/","/usr","/usr/bin",o.executablePath]){const t=i(r);if(h(r)!==r||t.uid!==0||(t.mode&18)!==0||(r===o.executablePath?!t.isFile()||(t.mode&73)===0:!t.isDirectory()))throw new Error("qa-scan-browser-unavailable")}}catch{throw new Error("qa-scan-browser-unavailable")}if(!o.executablePath)return m.launch(o);const e=l(c(d(),"neocortex-qa-browser-")),a=i(e),s=()=>{try{const r=i(e);!r.isSymbolicLink()&&r.isDirectory()&&r.dev===a.dev&&r.ino===a.ino&&r.uid===a.uid&&b(e,{recursive:!0})}catch{}};try{u(c(e,"config"),{mode:448}),u(c(e,"cache"),{mode:448});const r=await m.launch({...o,env:{PATH:"/usr/bin:/bin",LANG:"C.UTF-8",HOME:e,XDG_CONFIG_HOME:c(e,"config"),XDG_CACHE_HOME:c(e,"cache")}});return r.once("disconnected",s),r}catch(r){throw s(),r}}export{v as launchRootQaBrowser,f as rootQaBrowserLaunchOptions};
@@ -0,0 +1,3 @@
1
+ /** Root QA request containment. Browser redirect chains are never an implicit grant. */
2
+ import type { Route } from 'playwright-core';
3
+ export declare function routeRootQaScanRequest(route: Route, allowed: boolean, onDenied: () => void): Promise<void>;
@@ -0,0 +1 @@
1
+ async function c(a,i,n){const e=async()=>{n(),await a.abort("blockedbyclient").catch(()=>{})};if(!i){await e();return}let t;try{if(t=await a.fetch({maxRedirects:0,maxRetries:0,timeout:15e3}),t.status()>=300&&t.status()<400){await e();return}await a.fulfill({response:t})}catch{await e()}finally{await t?.dispose().catch(()=>{})}}export{c as routeRootQaScanRequest};
@@ -1,4 +1,28 @@
1
1
  import { type RootQaScanObservationV1 } from './qa-scan-review.js';
2
+ export interface RootQaScanScenario {
3
+ readonly id: string;
4
+ readonly requirementSource: string;
5
+ readonly requirementIds: readonly string[];
6
+ readonly actions: readonly ({
7
+ readonly kind: 'click';
8
+ readonly selector: string;
9
+ } | {
10
+ readonly kind: 'fill' | 'select';
11
+ readonly selector: string;
12
+ readonly value: string;
13
+ } | {
14
+ readonly kind: 'press';
15
+ readonly selector: string;
16
+ readonly key: string;
17
+ })[];
18
+ readonly checks: readonly {
19
+ readonly id: string;
20
+ readonly selector: string;
21
+ readonly kind: 'text' | 'value' | 'visible' | 'enabled' | 'checked';
22
+ readonly expected: string | boolean;
23
+ }[];
24
+ }
25
+ export declare function validateRootQaScanScenario(value: unknown): RootQaScanScenario;
2
26
  export interface RootQaScanCaptureReceiptV1 {
3
27
  readonly schemaVersion: 1;
4
28
  readonly evidenceContractVersion: 2;
@@ -30,6 +54,25 @@ export interface RootQaScanInteractionReceiptV1 {
30
54
  readonly localOnly: true;
31
55
  readonly publicSafe: true;
32
56
  }
57
+ export declare function scenarioRequirementDigest(root: string, scenario: RootQaScanScenario): string;
58
+ export type RootQaScanScenarioReceipt = RootQaScanInteractionReceiptV1 & {
59
+ readonly scenarioSha256: string;
60
+ readonly requirementSourceSha256: string;
61
+ readonly requirementIds: readonly string[];
62
+ readonly scenarioRecordSha256: string;
63
+ };
64
+ /** Private persisted linkage, reopened by root/worker triage after restart. */
65
+ export declare function readRootQaScanScenarioReceipt(projectRoot: string, surface: string, targetUrl: string, scenarioRecordSha256: string): RootQaScanScenarioReceipt;
66
+ /** Generic synthetic UI scenarios. An operator grants the target, not a DOM marker.
67
+ * Backend mutations, navigation, downloads and sockets remain denied. */
68
+ export declare function captureRootQaScanScenario(options: {
69
+ readonly projectRoot: string;
70
+ readonly surface: string;
71
+ readonly targetUrl: string;
72
+ readonly scenario: RootQaScanScenario;
73
+ readonly authorizeScenario: boolean;
74
+ readonly dataScope: 'synthetic-public';
75
+ }): Promise<RootQaScanScenarioReceipt>;
33
76
  /** Observe only loopback GET/HEAD loads, including popups. A page-initiated
34
77
  * mutation request or WebSocket is blocked even at the named origin; a fresh
35
78
  * browser profile/cookie jar is never borrowed from the user's host. */
@@ -1,11 +1,11 @@
1
- import{createHash as M,randomUUID as P}from"node:crypto";import{closeSync as D,constants as l,fsyncSync as U,openSync as z,writeFileSync as F}from"node:fs";import{join as H,resolve as N}from"node:path";import Q from"axe-core";import{chromium as W}from"playwright-core";import{planRootQaScan as k,rootQaScanEvidenceDirectory as B}from"./qa-scan-review.js";const G=8*1024*1024,X=1e4,$=/^[a-z][a-z0-9-]{0,50}$/;function J(e){return M("sha256").update(e).digest("hex")}function _(e){return Buffer.from(`${JSON.stringify(e)}
2
- `,"utf8")}function Y(e,u,n,i,d,r){if(i.length<8||i.length>G)throw new Error("qa-scan-review-capture-artifact-invalid");return Object.freeze({kind:e,artifactId:`${e}-${P()}.${n}`,captureMethod:u,sha256:J(i),capturedAt:d,rationale:r})}async function oe(e){if(!e.authorizeCapture)throw new Error("qa-scan-review-authorization-or-target-required");const u=N(e.projectRoot),n=k(u,e.surface,e.targetUrl),i=new URL(e.targetUrl);let d;try{d=await W.launch({channel:"chrome",headless:!0,timeout:15e3})}catch{throw new Error("qa-scan-review-browser-unavailable")}try{const r=await d.newContext({viewport:{width:1440,height:900},serviceWorkers:"block",acceptDownloads:!1,permissions:[],ignoreHTTPSErrors:!1});let w=0,h=0;await r.route("**/*",async a=>{let I=!1;try{const S=new URL(a.request().url());I=S.protocol==="http:"&&S.origin===i.origin&&(a.request().method()==="GET"||a.request().method()==="HEAD")}catch{}I?await a.continue():(w++,await a.abort("blockedbyclient"))}),await r.routeWebSocket(/.*/,async a=>{h++,await a.close({code:1008,reason:"blocked"})});const t=await r.newPage();r.on("page",a=>{a!==t&&a.close()});let o=0;t.on("framenavigated",a=>{a===t.mainFrame()&&a.url()!=="about:blank"&&o++}),t.setDefaultTimeout(15e3);let q=0,x=0,m=0,b=0;t.on("console",a=>{a.type()==="error"&&q++}),t.on("pageerror",()=>{x++}),t.on("request",()=>{b++}),t.on("requestfailed",()=>{m++}),t.on("response",a=>{a.status()>=400&&m++});const g=await t.goto(i.href,{waitUntil:"load",timeout:15e3});if(!g||g.status()>=400||t.url()!==i.href||o!==1)throw new Error("qa-scan-review-capture-target-unavailable");const s=()=>{if(t.url()!==i.href||o!==1)throw new Error("qa-scan-review-capture-target-drift")};s();const E=await t.screenshot({type:"png",animations:"disabled",timeout:15e3});s(),await t.addScriptTag({content:Q.source});const p=await t.evaluate("window.axe.run(document)");if(s(),!p||typeof p!="object")throw new Error("qa-scan-review-capture-accessibility-unavailable");const f=p;if(!Array.isArray(f.violations)||!Array.isArray(f.incomplete)||!Array.isArray(f.passes))throw new Error("qa-scan-review-capture-accessibility-unavailable");await t.keyboard.press("Tab");const C=await t.evaluate(`(() => {
1
+ import{createHash as re,randomUUID as ae}from"node:crypto";import{closeSync as I,constants as l,fstatSync as Z,fsyncSync as D,lstatSync as oe,openSync as T,readSync as G,realpathSync as ne,writeFileSync as W}from"node:fs";import{join as z,resolve as B}from"node:path";import ie from"axe-core";import{planRootQaScan as R,rootQaScanEvidenceDirectory as $}from"./qa-scan-review.js";import{routeRootQaScanRequest as J}from"./qa-scan-browser-route.js";import{launchRootQaBrowser as Q}from"./qa-scan-browser-launch.js";const ce=8*1024*1024,se=1e4,M=/^[a-z][a-z0-9-]{0,50}$/,K="Root executed the bounded SDD-linked synthetic UI scenario and checked local DOM results with all action-time network denied.";function V(e,n){return!!e&&typeof e=="object"&&!Array.isArray(e)&&Object.keys(e).sort().join(",")===[...n].sort().join(",")}const Y=(e,n)=>typeof e=="string"&&e.length<=n&&!/[\x00-\x1f\x7f]/.test(e);function le(e){if(!V(e,["id","requirementSource","requirementIds","actions","checks"])||typeof e.id!="string"||!M.test(e.id)||typeof e.requirementSource!="string"||e.requirementSource.length>256||!/^docs\/[A-Za-z0-9_./-]+\.md$/.test(e.requirementSource)||e.requirementSource.split("/").some(t=>!t||t==="."||t==="..")||!Array.isArray(e.requirementIds)||e.requirementIds.length<1||e.requirementIds.length>32||e.requirementIds.some(t=>typeof t!="string"||!/^[A-Z][A-Z0-9-]{1,63}$/.test(t))||new Set(e.requirementIds).size!==e.requirementIds.length||!Array.isArray(e.actions)||e.actions.length<1||e.actions.length>32||!Array.isArray(e.checks)||e.checks.length<1||e.checks.length>32||Buffer.byteLength(JSON.stringify(e))>16384)throw new Error("qa-scan-review-scenario-invalid");for(const t of e.actions){const d=t?.kind==="click"?["kind","selector"]:t?.kind==="press"?["kind","selector","key"]:["kind","selector","value"];if(!V(t,d)||!Y(t.selector,256)||!t.selector||!["click","fill","select","press"].includes(String(t.kind))||["fill","select"].includes(String(t.kind))&&!Y(t.value,512)||t.kind==="press"&&(typeof t.key!="string"||!["Tab","Enter","Space","Escape","ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End"].includes(t.key)))throw new Error("qa-scan-review-scenario-invalid")}const n=new Set;for(const t of e.checks){if(!V(t,["id","selector","kind","expected"])||typeof t.id!="string"||!M.test(t.id)||n.has(t.id)||!Y(t.selector,256)||!t.selector||!["text","value","visible","enabled","checked"].includes(String(t.kind))||(t.kind==="text"||t.kind==="value"?!Y(t.expected,512):typeof t.expected!="boolean"))throw new Error("qa-scan-review-scenario-invalid");n.add(t.id)}const c=structuredClone(e);return Object.freeze({...c,requirementIds:Object.freeze([...c.requirementIds]),actions:Object.freeze(c.actions.map(t=>Object.freeze(t))),checks:Object.freeze(c.checks.map(t=>Object.freeze(t)))})}function ee(e){try{return le(e)}catch{throw new Error("qa-scan-review-scenario-invalid")}}function N(e){return re("sha256").update(e).digest("hex")}function H(e){return Buffer.from(`${JSON.stringify(e)}
2
+ `,"utf8")}function P(e,n,c,t,d,i){if(t.length<8||t.length>ce)throw new Error("qa-scan-review-capture-artifact-invalid");return Object.freeze({kind:e,artifactId:`${e}-${ae()}.${c}`,captureMethod:n,sha256:N(t),capturedAt:d,rationale:i})}async function te(e,n){const c=e.locator(`css=${n}`);if(await c.count()!==1)throw new Error("qa-scan-review-scenario-target-ambiguous");return c}function X(e,n){const c=z(e,n.requirementSource);let t;try{if(ne(c)!==c)throw new Error;t=T(c,l.O_RDONLY|l.O_NOFOLLOW);const d=Z(t);if(!d.isFile()||d.nlink!==1||d.size<1||d.size>65536)throw new Error;const i=Buffer.alloc(65537);let u=0;for(;u<i.length;){const a=G(t,i,u,i.length-u,null);if(!a)break;u+=a}if(u!==d.size||u>65536)throw new Error;const S=new TextDecoder("utf-8",{fatal:!0}).decode(i.subarray(0,u));if(n.requirementIds.some(a=>!new RegExp(`(^|[^A-Z0-9-])${a}([^A-Z0-9-]|$)`).test(S)))throw new Error;return N(i.subarray(0,u))}catch{throw new Error("qa-scan-review-scenario-requirements-unbound")}finally{t!==void 0&&I(t)}}function de(e,n,c,t){try{if(!/^[a-f0-9]{64}$/.test(t))throw new Error;const d=B(e),i=R(d,n,c),u=$(d),S=(x,q)=>{const k=T(x,l.O_RDONLY|l.O_NOFOLLOW);try{const p=Z(k);if(!p.isFile()||p.nlink!==1||p.uid!==process.getuid?.()||(p.mode&511)!==384||p.size<1||p.size>q)throw new Error;const E=Buffer.alloc(q+1);let g=0;for(;g<E.length;){const v=G(k,E,g,E.length-g,null);if(!v)break;g+=v}const A=oe(x);if(g!==p.size||g>q||A.dev!==p.dev||A.ino!==p.ino||A.ctimeMs!==p.ctimeMs||A.mtimeMs!==p.mtimeMs||A.nlink!==1)throw new Error;return E.subarray(0,g)}finally{I(k)}},a=S(z(u,`scenario-${t}.json`),32768);if(N(Buffer.concat([Buffer.from("root-qa-scenario-record-v1\0"),a]))!==t)throw new Error;const f=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(a));if(!V(f,["scenario","receipt"]))throw new Error;const h=ee(f.scenario),o=f.receipt;if(!V(o,["schemaVersion","evidenceContractVersion","kind","sourceHead","planSha256","surface","targetSha256","observation","scenarioSha256","requirementSourceSha256","requirementIds","qaAccepted","checkpointAccepted","workflowContinuationAuthorized","localOnly","publicSafe"])||o.schemaVersion!==1||o.evidenceContractVersion!==2||o.kind!=="root-qa-scan-interaction"||o.sourceHead!==i.sourceHead||o.planSha256!==i.planSha256||o.surface!==n||o.targetSha256!==i.targetSha256||o.scenarioSha256!==N(Buffer.from(JSON.stringify(h)))||o.requirementSourceSha256!==X(d,h)||JSON.stringify(o.requirementIds)!==JSON.stringify(h.requirementIds)||o.qaAccepted!==!1||o.checkpointAccepted!==!1||o.workflowContinuationAuthorized!==!1||o.localOnly!==!0||o.publicSafe!==!0||!V(o.observation,["kind","artifactId","captureMethod","sha256","capturedAt","rationale"])||o.observation.kind!=="interaction"||o.observation.captureMethod!=="browser-interaction"||o.observation.rationale!==K||!/^interaction-[a-f0-9-]{36}\.json$/.test(o.observation.artifactId))throw new Error;const b=Date.parse(o.observation.capturedAt);if(!Number.isFinite(b)||new Date(b).toISOString()!==o.observation.capturedAt||b>Date.now()+5*6e4||Date.now()-b>60*6e4)throw new Error;const m=S(z(u,o.observation.artifactId),16384);if(N(m)!==o.observation.sha256)throw new Error;const w=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(m)),y={schemaVersion:2,syntheticTarget:!0,networkAttemptsAfterAction:0,checks:h.checks.map(x=>({id:`functional-${x.id}`,passed:!0}))};if(JSON.stringify(w)!==JSON.stringify(y)||R(d,n,c).planSha256!==i.planSha256)throw new Error;return Object.freeze({...o,scenarioRecordSha256:t})}catch{throw new Error("qa-scan-review-scenario-record-unverified")}}async function be(e){if(!e.authorizeScenario||e.dataScope!=="synthetic-public")throw new Error("qa-scan-review-scenario-authorization-required");const n=ee(e.scenario),c=N(Buffer.from(JSON.stringify(n))),t=B(e.projectRoot),d=R(t,e.surface,e.targetUrl),i=X(t,n),u=new URL(e.targetUrl);let S;try{S=await Q()}catch{throw new Error("qa-scan-review-browser-unavailable")}let a,f=!1;try{const h=await S.newContext({serviceWorkers:"block",acceptDownloads:!1,permissions:[],ignoreHTTPSErrors:!1,viewport:{width:1280,height:900}});let o=!1,b=0,m=0;await h.route("**/*",async s=>{let r=!1;try{const O=s.request(),_=new URL(O.url());r=!o&&_.protocol==="http:"&&_.origin===u.origin&&["GET","HEAD"].includes(O.method())}catch{}await J(s,r,()=>{b++})}),await h.routeWebSocket(/.*/,async s=>{b++,await s.close({code:1008,reason:"blocked"})});const w=await h.newPage();h.on("page",s=>{s!==w&&(m++,s.close())}),w.on("framenavigated",()=>{o&&m++}),w.on("download",()=>{m++}),w.on("pageerror",()=>{m++}),w.on("console",s=>{s.type()==="error"&&m++});const y=await w.goto(u.href,{waitUntil:"load",timeout:15e3});if(!y||y.status()>=400||w.url()!==u.href||b||m)throw new Error("qa-scan-review-scenario-target-unavailable");o=!0,a=setTimeout(()=>{f=!0,h.close().catch(()=>{})},15e3);const x=performance.now()+15e3,q=()=>{const s=Math.floor(x-performance.now());if(s<1)throw new Error("qa-scan-review-scenario-budget-exhausted");return Math.min(s,2e3)};for(const s of n.actions){const r=await te(w,s.selector);if(!await r.isVisible()||!await r.isEnabled())throw new Error("qa-scan-review-scenario-action-unavailable");const O=q();if(s.kind==="click"?await r.click({timeout:O}):s.kind==="fill"?await r.fill(s.value,{timeout:O}):s.kind==="select"?await r.selectOption(s.value,{timeout:O}):s.kind==="press"&&await r.press(s.key,{timeout:O}),b||m||w.url()!==u.href)throw new Error("qa-scan-review-scenario-effects-denied")}const k=[];for(const s of n.checks){const r=await te(w,s.selector),O=q();if((s.kind==="text"?(await r.textContent({timeout:O}))?.trim():s.kind==="value"?await r.inputValue({timeout:O}):s.kind==="visible"?await r.isVisible():s.kind==="enabled"?await r.isEnabled():await r.isChecked({timeout:O}))!==s.expected)throw new Error("qa-scan-review-scenario-check-failed");k.push({id:`functional-${s.id}`,passed:!0})}if(await w.waitForTimeout(250),b||m||w.url()!==u.href)throw new Error("qa-scan-review-scenario-effects-denied");if(R(t,e.surface,e.targetUrl).planSha256!==d.planSha256)throw new Error("qa-scan-review-capture-source-drift");if(X(t,n)!==i)throw new Error("qa-scan-review-capture-source-drift");const p=H({schemaVersion:2,syntheticTarget:!0,networkAttemptsAfterAction:0,checks:k}),E=P("interaction","browser-interaction","json",p,new Date().toISOString(),K),g=$(t),A=T(z(g,E.artifactId),l.O_WRONLY|l.O_CREAT|l.O_EXCL|l.O_NOFOLLOW,384);try{W(A,p),D(A)}finally{I(A)}const v=T(g,l.O_RDONLY|l.O_DIRECTORY|l.O_NOFOLLOW);try{D(v)}finally{I(v)}if(R(t,e.surface,e.targetUrl).planSha256!==d.planSha256)throw new Error("qa-scan-review-capture-source-drift");const j=Object.freeze({schemaVersion:1,evidenceContractVersion:2,kind:"root-qa-scan-interaction",sourceHead:d.sourceHead,planSha256:d.planSha256,surface:d.surface,targetSha256:d.targetSha256,observation:E,scenarioSha256:c,requirementSourceSha256:i,requirementIds:n.requirementIds,qaAccepted:!1,checkpointAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0}),L=Buffer.from(JSON.stringify({scenario:n,receipt:j})),C=N(Buffer.concat([Buffer.from("root-qa-scenario-record-v1\0"),L])),U=T(z(g,`scenario-${C}.json`),l.O_WRONLY|l.O_CREAT|l.O_EXCL|l.O_NOFOLLOW,384);try{W(U,L),D(U)}finally{I(U)}const F=T(g,l.O_RDONLY|l.O_DIRECTORY|l.O_NOFOLLOW);try{D(F)}finally{I(F)}return de(t,e.surface,e.targetUrl,C)}catch(h){throw f?new Error("qa-scan-review-scenario-budget-exhausted"):h instanceof Error&&/^qa-scan-review-[a-z-]+$/.test(h.message)?h:new Error("qa-scan-review-scenario-unavailable")}finally{a&&clearTimeout(a),await S.close().catch(()=>{})}}async function ye(e){if(!e.authorizeCapture)throw new Error("qa-scan-review-authorization-or-target-required");const n=B(e.projectRoot),c=R(n,e.surface,e.targetUrl),t=new URL(e.targetUrl);let d;try{d=await Q()}catch{throw new Error("qa-scan-review-browser-unavailable")}try{const i=await d.newContext({viewport:{width:1440,height:900},serviceWorkers:"block",acceptDownloads:!1,permissions:[],ignoreHTTPSErrors:!1});let u=0,S=0;await i.route("**/*",async r=>{let O=!1;try{const _=new URL(r.request().url());O=_.protocol==="http:"&&_.origin===t.origin&&(r.request().method()==="GET"||r.request().method()==="HEAD")}catch{}await J(r,O,()=>{u++})}),await i.routeWebSocket(/.*/,async r=>{S++,await r.close({code:1008,reason:"blocked"})});const a=await i.newPage();i.on("page",r=>{r!==a&&r.close()});let f=0;a.on("framenavigated",r=>{r===a.mainFrame()&&r.url()!=="about:blank"&&f++}),a.setDefaultTimeout(15e3);let h=0,o=0,b=0,m=0;a.on("console",r=>{r.type()==="error"&&h++}),a.on("pageerror",()=>{o++}),a.on("request",()=>{m++}),a.on("requestfailed",()=>{b++}),a.on("response",r=>{r.status()>=400&&b++});const w=await a.goto(t.href,{waitUntil:"load",timeout:15e3});if(!w||w.status()>=400||a.url()!==t.href||f!==1)throw new Error("qa-scan-review-capture-target-unavailable");const y=()=>{if(a.url()!==t.href||f!==1)throw new Error("qa-scan-review-capture-target-drift")};y();const x=await a.screenshot({type:"png",animations:"disabled",timeout:15e3});y(),await a.addScriptTag({content:ie.source});const q=await a.evaluate("window.axe.run(document)");if(y(),!q||typeof q!="object")throw new Error("qa-scan-review-capture-accessibility-unavailable");const k=q;if(!Array.isArray(k.violations)||!Array.isArray(k.incomplete)||!Array.isArray(k.passes))throw new Error("qa-scan-review-capture-accessibility-unavailable");await a.keyboard.press("Tab");const p=await a.evaluate(`(() => {
3
3
  const active = document.activeElement;
4
4
  return !!active && active !== document.body && active !== document.documentElement
5
5
  && active.matches(':focus-visible');
6
- })()`),v=await t.evaluate(`(() => {
6
+ })()`),E=await a.evaluate(`(() => {
7
7
  const entry = performance.getEntriesByType('navigation')[0];
8
8
  return entry ? entry.duration : null;
9
- })()`);s(),await t.setViewportSize({width:375,height:812});const O=await t.screenshot({type:"png",animations:"disabled",timeout:15e3});s(),await t.setViewportSize({width:768,height:1024});const T=await t.screenshot({type:"png",animations:"disabled",timeout:15e3});s(),await t.setViewportSize({width:1024,height:768});const c=await t.screenshot({type:"png",animations:"disabled",timeout:15e3});s();const R=typeof v=="number"&&Number.isFinite(v)?Math.max(0,Math.round(v)):null,A=new Date().toISOString(),y=[{kind:"visual",method:"browser-screenshot",suffix:"png",bytes:Buffer.from(E),rationale:"Chrome headless desktop viewport 1440x900 at the exact local target before QA review."},{kind:"responsive",method:"browser-screenshot",suffix:"png",bytes:Buffer.from(O),rationale:"Chrome headless mobile-width viewport 375x812 at the same local target."},{kind:"responsive-tablet",method:"browser-screenshot",suffix:"png",bytes:Buffer.from(T),rationale:"Chrome headless tablet-width viewport 768x1024 at the same local target."},{kind:"responsive-desktop",method:"browser-screenshot",suffix:"png",bytes:Buffer.from(c),rationale:"Chrome headless compact-desktop viewport 1024x768 at the same local target."},{kind:"accessibility",method:"axe-scan",suffix:"json",bytes:_({schemaVersion:1,engine:"axe-core",violationCount:f.violations.length,incompleteCount:f.incomplete.length,passCount:f.passes.length}),rationale:"Axe-core ran against the rendered local document; counts preserve incomplete and violated rules."},{kind:"interaction",method:"browser-interaction",suffix:"json",bytes:_({schemaVersion:1,checks:[{id:"keyboard-focus",passed:C===!0}]}),rationale:"Non-activating Tab key probe observed a visible focus target without clicking application controls."},{kind:"console-network",method:"console-capture",suffix:"json",bytes:_({schemaVersion:1,initialCapture:!0,consoleErrors:q,pageErrors:x,failedRequests:m+w+h,requestsObserved:b}),rationale:"Console, page-error, HTTP and WebSocket routing listeners were installed before navigation."},{kind:"performance",method:"performance-trace",suffix:"json",bytes:_({schemaVersion:1,metric:"load",observedMs:R,budgetMs:X}),rationale:"Browser Navigation Timing load duration was compared with the fixed 10000 ms local budget."}],L=y.map(a=>Y(a.kind,a.method,a.suffix,a.bytes,A,a.rationale));if(s(),k(u,e.surface,e.targetUrl).planSha256!==n.planSha256)throw new Error("qa-scan-review-capture-source-drift");const V=B(u);for(let a=0;a<y.length;a++){const I=H(V,L[a].artifactId),S=z(I,l.O_WRONLY|l.O_CREAT|l.O_EXCL|(l.O_NOFOLLOW??0),384);try{F(S,y[a].bytes),U(S)}finally{D(S)}}const j=z(V,l.O_RDONLY|(l.O_DIRECTORY??0));try{U(j)}finally{D(j)}if(s(),k(u,e.surface,e.targetUrl).planSha256!==n.planSha256)throw new Error("qa-scan-review-capture-source-drift");return Object.freeze({schemaVersion:1,evidenceContractVersion:2,kind:"root-qa-scan-capture",sourceHead:n.sourceHead,planSha256:n.planSha256,surface:n.surface,targetSha256:n.targetSha256,evidenceCount:8,observations:Object.freeze(L),qaAccepted:!1,checkpointAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}catch(r){throw r instanceof Error&&r.message.startsWith("qa-scan-review-")?r:new Error("qa-scan-review-capture-unavailable")}finally{await d.close().catch(()=>{})}}async function ne(e){if(!e.authorizeInteraction||!$.test(e.actionId)||!$.test(e.expectedStatus))throw new Error("qa-scan-review-interaction-authorization-required");const u=N(e.projectRoot),n=k(u,e.surface,e.targetUrl),i=new URL(e.targetUrl);let d;try{d=await W.launch({channel:"chrome",headless:!0,timeout:15e3})}catch{throw new Error("qa-scan-review-browser-unavailable")}try{const r=await d.newContext({viewport:{width:1280,height:800},serviceWorkers:"block",acceptDownloads:!1,permissions:[],ignoreHTTPSErrors:!1});let w=!1,h=0,t=0;await r.route("**/*",async c=>{let R=!1;try{const A=c.request(),y=new URL(A.url());R=!w&&y.protocol==="http:"&&y.origin===i.origin&&(A.method()==="GET"||A.method()==="HEAD")}catch{}R?await c.continue():(w?h++:t++,await c.abort("blockedbyclient"))}),await r.routeWebSocket(/.*/,async c=>{w?h++:t++,await c.close({code:1008,reason:"blocked"})});const o=await r.newPage();let q=0,x=0,m=0;r.on("page",c=>{c!==o&&(q++,c.close())}),o.on("framenavigated",()=>{w&&x++}),o.on("download",()=>{w&&m++});let b=0,g=0;o.on("pageerror",()=>{b++}),o.on("console",c=>{c.type()==="error"&&g++});const s=await o.goto(i.href,{waitUntil:"load",timeout:15e3});if(!s||s.status()>=400||o.url()!==i.href||t!==0||b!==0||g!==0)throw new Error("qa-scan-review-interaction-target-unavailable");const E=o.locator(`button[type="button"][data-neocortex-qa-safe="true"][data-neocortex-qa-action="${e.actionId}"]`),p=o.locator(`[data-neocortex-qa-status="${e.actionId}"]`);if(await o.locator('body[data-neocortex-qa-synthetic="1"]').count()!==1||await E.count()!==1||await p.count()!==1||!await E.isVisible()||!await p.isVisible()||(await p.textContent())?.trim()===e.expectedStatus)throw new Error("qa-scan-review-interaction-contract-unavailable");if(w=!0,await E.click({timeout:5e3}),await o.waitForFunction(`({ actionId, expectedStatus }) =>
9
+ })()`);y(),await a.setViewportSize({width:375,height:812});const g=await a.screenshot({type:"png",animations:"disabled",timeout:15e3});y(),await a.setViewportSize({width:768,height:1024});const A=await a.screenshot({type:"png",animations:"disabled",timeout:15e3});y(),await a.setViewportSize({width:1024,height:768});const v=await a.screenshot({type:"png",animations:"disabled",timeout:15e3});y();const j=typeof E=="number"&&Number.isFinite(E)?Math.max(0,Math.round(E)):null,L=new Date().toISOString(),C=[{kind:"visual",method:"browser-screenshot",suffix:"png",bytes:Buffer.from(x),rationale:"Chrome headless desktop viewport 1440x900 at the exact local target before QA review."},{kind:"responsive",method:"browser-screenshot",suffix:"png",bytes:Buffer.from(g),rationale:"Chrome headless mobile-width viewport 375x812 at the same local target."},{kind:"responsive-tablet",method:"browser-screenshot",suffix:"png",bytes:Buffer.from(A),rationale:"Chrome headless tablet-width viewport 768x1024 at the same local target."},{kind:"responsive-desktop",method:"browser-screenshot",suffix:"png",bytes:Buffer.from(v),rationale:"Chrome headless compact-desktop viewport 1024x768 at the same local target."},{kind:"accessibility",method:"axe-scan",suffix:"json",bytes:H({schemaVersion:1,engine:"axe-core",violationCount:k.violations.length,incompleteCount:k.incomplete.length,passCount:k.passes.length}),rationale:"Axe-core ran against the rendered local document; counts preserve incomplete and violated rules."},{kind:"interaction",method:"browser-interaction",suffix:"json",bytes:H({schemaVersion:1,checks:[{id:"keyboard-focus",passed:p===!0}]}),rationale:"Non-activating Tab key probe observed a visible focus target without clicking application controls."},{kind:"console-network",method:"console-capture",suffix:"json",bytes:H({schemaVersion:1,initialCapture:!0,consoleErrors:h,pageErrors:o,failedRequests:b+u+S,requestsObserved:m}),rationale:"Console, page-error, HTTP and WebSocket routing listeners were installed before navigation."},{kind:"performance",method:"performance-trace",suffix:"json",bytes:H({schemaVersion:1,metric:"load",observedMs:j,budgetMs:se}),rationale:"Browser Navigation Timing load duration was compared with the fixed 10000 ms local budget."}],U=C.map(r=>P(r.kind,r.method,r.suffix,r.bytes,L,r.rationale));if(y(),R(n,e.surface,e.targetUrl).planSha256!==c.planSha256)throw new Error("qa-scan-review-capture-source-drift");const F=$(n);for(let r=0;r<C.length;r++){const O=z(F,U[r].artifactId),_=T(O,l.O_WRONLY|l.O_CREAT|l.O_EXCL|(l.O_NOFOLLOW??0),384);try{W(_,C[r].bytes),D(_)}finally{I(_)}}const s=T(F,l.O_RDONLY|(l.O_DIRECTORY??0));try{D(s)}finally{I(s)}if(y(),R(n,e.surface,e.targetUrl).planSha256!==c.planSha256)throw new Error("qa-scan-review-capture-source-drift");return Object.freeze({schemaVersion:1,evidenceContractVersion:2,kind:"root-qa-scan-capture",sourceHead:c.sourceHead,planSha256:c.planSha256,surface:c.surface,targetSha256:c.targetSha256,evidenceCount:8,observations:Object.freeze(U),qaAccepted:!1,checkpointAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}catch(i){throw i instanceof Error&&i.message.startsWith("qa-scan-review-")?i:new Error("qa-scan-review-capture-unavailable")}finally{await d.close().catch(()=>{})}}async function ve(e){if(!e.authorizeInteraction||!M.test(e.actionId)||!M.test(e.expectedStatus))throw new Error("qa-scan-review-interaction-authorization-required");const n=B(e.projectRoot),c=R(n,e.surface,e.targetUrl),t=new URL(e.targetUrl);let d;try{d=await Q()}catch{throw new Error("qa-scan-review-browser-unavailable")}try{const i=await d.newContext({viewport:{width:1280,height:800},serviceWorkers:"block",acceptDownloads:!1,permissions:[],ignoreHTTPSErrors:!1});let u=!1,S=0,a=0;await i.route("**/*",async v=>{let j=!1;try{const L=v.request(),C=new URL(L.url());j=!u&&C.protocol==="http:"&&C.origin===t.origin&&(L.method()==="GET"||L.method()==="HEAD")}catch{}await J(v,j,()=>{u?S++:a++})}),await i.routeWebSocket(/.*/,async v=>{u?S++:a++,await v.close({code:1008,reason:"blocked"})});const f=await i.newPage();let h=0,o=0,b=0;i.on("page",v=>{v!==f&&(h++,v.close())}),f.on("framenavigated",()=>{u&&o++}),f.on("download",()=>{u&&b++});let m=0,w=0;f.on("pageerror",()=>{m++}),f.on("console",v=>{v.type()==="error"&&w++});const y=await f.goto(t.href,{waitUntil:"load",timeout:15e3});if(!y||y.status()>=400||f.url()!==t.href||a!==0||m!==0||w!==0)throw new Error("qa-scan-review-interaction-target-unavailable");const x=f.locator(`button[type="button"][data-neocortex-qa-safe="true"][data-neocortex-qa-action="${e.actionId}"]`),q=f.locator(`[data-neocortex-qa-status="${e.actionId}"]`);if(await f.locator('body[data-neocortex-qa-synthetic="1"]').count()!==1||await x.count()!==1||await q.count()!==1||!await x.isVisible()||!await q.isVisible()||(await q.textContent())?.trim()===e.expectedStatus)throw new Error("qa-scan-review-interaction-contract-unavailable");if(u=!0,await x.click({timeout:5e3}),await f.waitForFunction(`({ actionId, expectedStatus }) =>
10
10
  document.querySelector('[data-neocortex-qa-status="' + actionId + '"]')
11
- ?.textContent?.trim() === expectedStatus`,{actionId:e.actionId,expectedStatus:e.expectedStatus},{timeout:5e3}),await o.waitForTimeout(250),h!==0||q!==0||x!==0||m!==0||b!==0||g!==0||o.url()!==i.href)throw new Error("qa-scan-review-interaction-effects-denied");const f=_({schemaVersion:2,syntheticTarget:!0,networkAttemptsAfterAction:h,checks:[{id:`functional-${e.actionId}`,passed:!0}]}),C=Y("interaction","browser-interaction","json",f,new Date().toISOString(),"Root synthetic action changed one visible local status without browser network effects.");if(k(u,e.surface,e.targetUrl).planSha256!==n.planSha256)throw new Error("qa-scan-review-capture-source-drift");const v=B(u),O=z(H(v,C.artifactId),l.O_WRONLY|l.O_CREAT|l.O_EXCL|(l.O_NOFOLLOW??0),384);try{F(O,f),U(O)}finally{D(O)}const T=z(v,l.O_RDONLY|(l.O_DIRECTORY??0));try{U(T)}finally{D(T)}if(k(u,e.surface,e.targetUrl).planSha256!==n.planSha256)throw new Error("qa-scan-review-capture-source-drift");return Object.freeze({schemaVersion:1,evidenceContractVersion:2,kind:"root-qa-scan-interaction",sourceHead:n.sourceHead,planSha256:n.planSha256,surface:n.surface,targetSha256:n.targetSha256,observation:C,qaAccepted:!1,checkpointAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}catch(r){throw r instanceof Error&&r.message.startsWith("qa-scan-review-")?r:new Error("qa-scan-review-interaction-unavailable")}finally{await d.close().catch(()=>{})}}export{oe as captureRootQaScan,ne as captureRootQaScanInteraction};
11
+ ?.textContent?.trim() === expectedStatus`,{actionId:e.actionId,expectedStatus:e.expectedStatus},{timeout:5e3}),await f.waitForTimeout(250),S!==0||h!==0||o!==0||b!==0||m!==0||w!==0||f.url()!==t.href)throw new Error("qa-scan-review-interaction-effects-denied");const k=H({schemaVersion:2,syntheticTarget:!0,networkAttemptsAfterAction:S,checks:[{id:`functional-${e.actionId}`,passed:!0}]}),p=P("interaction","browser-interaction","json",k,new Date().toISOString(),"Root synthetic action changed one visible local status without browser network effects.");if(R(n,e.surface,e.targetUrl).planSha256!==c.planSha256)throw new Error("qa-scan-review-capture-source-drift");const E=$(n),g=T(z(E,p.artifactId),l.O_WRONLY|l.O_CREAT|l.O_EXCL|(l.O_NOFOLLOW??0),384);try{W(g,k),D(g)}finally{I(g)}const A=T(E,l.O_RDONLY|(l.O_DIRECTORY??0));try{D(A)}finally{I(A)}if(R(n,e.surface,e.targetUrl).planSha256!==c.planSha256)throw new Error("qa-scan-review-capture-source-drift");return Object.freeze({schemaVersion:1,evidenceContractVersion:2,kind:"root-qa-scan-interaction",sourceHead:c.sourceHead,planSha256:c.planSha256,surface:c.surface,targetSha256:c.targetSha256,observation:p,qaAccepted:!1,checkpointAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}catch(i){throw i instanceof Error&&i.message.startsWith("qa-scan-review-")?i:new Error("qa-scan-review-interaction-unavailable")}finally{await d.close().catch(()=>{})}}export{ye as captureRootQaScan,ve as captureRootQaScanInteraction,be as captureRootQaScanScenario,de as readRootQaScanScenarioReceipt,X as scenarioRequirementDigest,ee as validateRootQaScanScenario};
@@ -17,8 +17,15 @@ export declare function discoverRootQaScanLinks(options: {
17
17
  readonly projectRoot: string;
18
18
  readonly seedUrl: string;
19
19
  readonly authorizeDiscovery: boolean;
20
+ readonly syntheticBackend?: {
21
+ readonly generation: string;
22
+ readonly scenarioSha256: string;
23
+ readonly grantId: string;
24
+ };
20
25
  }): Promise<RootQaScanDiscoveryReceiptV2>;
21
26
  export declare function readRootQaScanDiscovery(projectRoot: string, id: string): RootQaScanDiscoveryReceiptV2;
27
+ /** Root/worker evidence verification only; reopening validates source and the complete inventory. */
28
+ export declare function readRootQaScanDiscoveryTargets(projectRoot: string, id: string): readonly string[];
22
29
  export interface RootQaScanDiscoveredPhaseV2 {
23
30
  readonly schemaVersion: 2;
24
31
  readonly kind: 'root-qa-scan-discovered-phase-checkpoint';
@@ -1,3 +1,3 @@
1
- import{createHash as M}from"node:crypto";import{performance as O}from"node:perf_hooks";import{closeSync as R,constants as w,fstatSync as F,fsyncSync as H,openSync as k,readFileSync as Q,writeFileSync as V}from"node:fs";import{dirname as P,join as W,resolve as b}from"node:path";import{chromium as X}from"playwright-core";import{aggregateRootQaScanReviews as Y,planRootQaScan as z,rootQaScanEvidenceDirectory as G}from"./qa-scan-review.js";const D=/^[a-f0-9]{64}$/,A=16,U=1024,B=32768,K=60*6e4,Z=500,ee=2e3,re=50;function S(r){return M("sha256").update(r).digest("hex")}function te(r){return r!==null&&typeof r=="object"&&!Array.isArray(r)}function _(r,o){return te(r)&&Object.keys(r).sort().join("\0")===[...o].sort().join("\0")}function L(r,o,e){if(!D.test(e))throw new Error("qa-scan-discovery-unverified");return W(G(r),`${o}-${e}.json`)}function C(r,o){const e=Buffer.from(`${JSON.stringify(o)}
2
- `),a=k(r,w.O_WRONLY|w.O_CREAT|w.O_EXCL|(w.O_NOFOLLOW??0),384);try{V(a,e),H(a)}finally{R(a)}const s=k(P(r),w.O_RDONLY|(w.O_DIRECTORY??0));try{H(s)}finally{R(s)}}function T(r,o){const e=k(r,w.O_RDONLY|(w.O_NOFOLLOW??0));let a,s;try{const n=F(e);if(!n.isFile()||n.uid!==process.getuid?.()||(n.mode&63)!==0||n.size<1||n.size>o)throw new Error("qa-scan-discovery-unverified");s=n.size,a=Q(e)}finally{R(e)}if(a.length!==s||a.length>o)throw new Error("qa-scan-discovery-unverified");const t=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(a));if(!a.equals(Buffer.from(`${JSON.stringify(t)}
3
- `)))throw new Error("qa-scan-discovery-unverified");return t}function I(r,o){return Object.freeze({schemaVersion:2,kind:"root-qa-scan-link-discovery",sourceHead:r.sourceHead,seedSha256:r.seedSha256,discoverySha256:o,targetCount:r.targetUrls.length,inventoryScope:"reachable-rendered-same-origin-links",qaAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}async function oe(r){let o,e=O.now();const a=e+ee;for(;O.now()<=a;){const s=await r.locator("a[href]").evaluateAll(n=>n.map(f=>f.href)),t=JSON.stringify(s);if(t!==o)o=t,e=O.now();else if(O.now()-e>=Z)return s;await r.waitForTimeout(re)}throw new Error("qa-scan-discovery-scope-unverified")}async function ue(r){if(!r.authorizeDiscovery)throw new Error("qa-scan-discovery-authorization-required");const o=b(r.projectRoot),e=z(o,"discovery",r.seedUrl),a=new URL(r.seedUrl);if(Buffer.byteLength(a.href)>U)throw new Error("qa-scan-discovery-scope-too-large");let s;try{s=await X.launch({channel:"chrome",headless:!0,timeout:15e3})}catch{throw new Error("qa-scan-discovery-browser-unavailable")}try{const t=await s.newContext({serviceWorkers:"block",acceptDownloads:!1,permissions:[],ignoreHTTPSErrors:!1});let n=0,f=0,g=0,h=0;await t.route("**/*",async i=>{let y=!1;try{const p=i.request(),q=new URL(p.url());y=q.protocol==="http:"&&q.origin===a.origin&&(p.method()==="GET"||p.method()==="HEAD")}catch{}y?await i.continue():(n++,await i.abort("blockedbyclient"))}),await t.routeWebSocket(/.*/,async i=>{n++,await i.close({code:1008,reason:"blocked"})});const c=await t.newPage();t.on("page",i=>{i!==c&&(h++,i.close())}),c.on("framenavigated",i=>{i===c.mainFrame()&&i.url()!=="about:blank"&&g++}),c.on("requestfailed",()=>{f++}),c.on("response",i=>{i.status()>=400&&f++});const v=new Set([a.href]),l=new Set;let J=0;for(;l.size<v.size;){const i=[...v].filter(u=>!l.has(u)).sort((u,d)=>Buffer.from(u).compare(Buffer.from(d)))[0];if(!i||v.size>A)throw new Error("qa-scan-discovery-scope-too-large");let y;try{y=await c.goto(i,{waitUntil:"load",timeout:15e3})}catch{throw new Error("qa-scan-discovery-target-unavailable")}if(!y||y.status()>=400||c.url()!==i||g!==l.size+1||n!==0||f!==0||h!==0)throw new Error("qa-scan-discovery-target-unavailable");const p=await oe(c);if(J+=p.length,J>256)throw new Error("qa-scan-discovery-scope-too-large");for(const u of p){if(typeof u!="string"||Buffer.byteLength(u)>U)throw new Error("qa-scan-discovery-scope-too-large");let d;try{d=new URL(u)}catch{throw new Error("qa-scan-discovery-target-unavailable")}if(!(d.origin!==a.origin||d.protocol!=="http:")){if(d.username||d.password||d.search||d.hash)throw new Error("qa-scan-discovery-target-unavailable");if(v.add(d.href),v.size>A)throw new Error("qa-scan-discovery-scope-too-large")}}const q=await c.locator("a[href]").evaluateAll(u=>u.map(d=>d.href));if(JSON.stringify(q)!==JSON.stringify(p)||c.url()!==i||g!==l.size+1||n!==0||f!==0||h!==0)throw new Error("qa-scan-discovery-scope-unverified");l.add(i)}const N=[...v].sort((i,y)=>Buffer.from(i).compare(Buffer.from(y)));if(N.length<2||N.length>A||l.size!==v.size||g!==l.size||n!==0||f!==0||h!==0)throw new Error("qa-scan-discovery-scope-unverified");if(z(o,"discovery",r.seedUrl).planSha256!==e.planSha256)throw new Error("qa-scan-discovery-source-drift");const m={schemaVersion:2,sourceHead:e.sourceHead,seedUrl:a.href,seedSha256:e.targetSha256,capturedAt:new Date().toISOString(),targetUrls:N,pageCount:l.size},E=S(JSON.stringify(m)),j=L(o,"discovery",E);if(Buffer.byteLength(JSON.stringify({...m,discoverySha256:E}))+1>B)throw new Error("qa-scan-discovery-scope-too-large");return C(j,{...m,discoverySha256:E}),I(m,E)}catch(t){throw t instanceof Error&&t.message.startsWith("qa-scan-discovery-")?t:new Error("qa-scan-discovery-unverified")}finally{await s.close().catch(()=>{})}}function x(r,o){const e=T(L(r,"discovery",o),B);if(!_(e,["schemaVersion","sourceHead","seedUrl","seedSha256","capturedAt","targetUrls","pageCount","discoverySha256"])||e.schemaVersion!==2||e.discoverySha256!==o||typeof e.seedUrl!="string"||typeof e.sourceHead!="string"||typeof e.seedSha256!="string"||Buffer.byteLength(e.seedUrl)>U||!D.test(e.seedSha256)||!Array.isArray(e.targetUrls)||e.targetUrls.length<2||e.targetUrls.length>A||e.pageCount!==e.targetUrls.length||typeof e.capturedAt!="string")throw new Error("qa-scan-discovery-unverified");const a=Date.now()-Date.parse(e.capturedAt);if(!Number.isFinite(a)||new Date(e.capturedAt).toISOString()!==e.capturedAt||a<-5*6e4||a>K)throw new Error("qa-scan-discovery-unverified");const s=z(r,"discovery",e.seedUrl);if(e.sourceHead!==s.sourceHead||e.seedSha256!==s.targetSha256)throw new Error("qa-scan-discovery-unverified");const t=new URL(e.seedUrl),n=e.targetUrls;if(!n.every(h=>typeof h=="string"&&Buffer.byteLength(h)<=U&&(()=>{try{const c=new URL(h);return c.origin===t.origin&&c.protocol==="http:"&&!c.username&&!c.password&&!c.search&&!c.hash&&c.href===h}catch{return!1}})())||!n.includes(t.href)||JSON.stringify(n)!==JSON.stringify([...new Set(n)].sort((h,c)=>Buffer.from(String(h)).compare(Buffer.from(String(c))))))throw new Error("qa-scan-discovery-unverified");const{discoverySha256:f,...g}=e;if(S(JSON.stringify(g))!==o)throw new Error("qa-scan-discovery-unverified");return g}function le(r,o){try{return I(x(b(r),o),o)}catch{throw new Error("qa-scan-discovery-unverified")}}function $(r,o,e){if(!_(e,["schemaVersion","targets"])||e.schemaVersion!==1||!Array.isArray(e.targets))throw new Error("qa-scan-discovery-phase-unverified");const a=x(r,o),s=e.targets.map(n=>{if(!_(n,["surface","targetUrl"])||typeof n.targetUrl!="string")throw new Error("qa-scan-discovery-phase-unverified");return n.targetUrl}).sort((n,f)=>Buffer.from(n).compare(Buffer.from(f)));if(JSON.stringify(s)!==JSON.stringify(a.targetUrls))throw new Error("qa-scan-discovery-phase-unverified");const t=Y(r,e);if(t.sourceHead!==a.sourceHead||t.qaAccepted&&(t.verdict!=="approved"||t.issueCount!==0)||!t.qaAccepted&&(t.verdict!=="issues-found"||t.issueCount<1))throw new Error("qa-scan-discovery-phase-unverified");return Object.freeze({schemaVersion:2,kind:"root-qa-scan-discovered-phase-checkpoint",sourceHead:a.sourceHead,discoverySha256:o,aggregateSha256:t.aggregateSha256,surfaceCount:t.surfaceCount,inventoryScope:"reachable-rendered-same-origin-links",qaAccepted:t.qaAccepted,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}function ye(r,o,e){try{const a=b(r),s=$(a,o,e),t={receipt:s,checksumSha256:S(`qa-scan-discovered-phase-v2\0${JSON.stringify(s)}`)},n=L(a,"discovered-phase",S(`${o}\0${s.aggregateSha256}`));try{C(n,t)}catch(f){if(f.code!=="EEXIST")throw f}if(JSON.stringify(ae(a,o,e))!==JSON.stringify(s))throw new Error("qa-scan-discovery-phase-unverified");return s}catch{throw new Error("qa-scan-discovery-phase-unverified")}}function ae(r,o,e){try{const a=b(r),s=$(a,o,e),t=T(L(a,"discovered-phase",S(`${o}\0${s.aggregateSha256}`)),4096);if(!_(t,["receipt","checksumSha256"])||t.checksumSha256!==S(`qa-scan-discovered-phase-v2\0${JSON.stringify(t.receipt)}`)||JSON.stringify(t.receipt)!==JSON.stringify(s))throw new Error("qa-scan-discovery-phase-unverified");return s}catch{throw new Error("qa-scan-discovery-phase-unverified")}}export{ye as acceptRootQaScanDiscoveredPhase,ue as discoverRootQaScanLinks,ae as readRootQaScanDiscoveredPhase,le as readRootQaScanDiscovery};
1
+ import{createHash as F}from"node:crypto";import{performance as U}from"node:perf_hooks";import{closeSync as k,constants as S,fstatSync as V,fsyncSync as J,openSync as z,readFileSync as P,writeFileSync as W}from"node:fs";import{dirname as X,join as Y,resolve as E}from"node:path";import{aggregateRootQaScanReviews as G,planRootQaScan as B,rootQaScanEvidenceDirectory as K}from"./qa-scan-review.js";import{routeRootQaScanRequest as Z}from"./qa-scan-browser-route.js";import{launchRootQaBrowser as ee}from"./qa-scan-browser-launch.js";import{launchSyntheticBackendBrowser as re}from"./qa-synthetic-browser.js";import{openSyntheticBackendSession as te}from"./qa-synthetic-backend.js";const T=/^[a-f0-9]{64}$/,R=16,A=1024,C=32768,oe=60*6e4,ae=500,se=2e3,ne=50;function q(r){return F("sha256").update(r).digest("hex")}function ce(r){return r!==null&&typeof r=="object"&&!Array.isArray(r)}function L(r,t){return ce(r)&&Object.keys(r).sort().join("\0")===[...t].sort().join("\0")}function N(r,t,e){if(!T.test(e))throw new Error("qa-scan-discovery-unverified");return Y(K(r),`${t}-${e}.json`)}function j(r,t){const e=Buffer.from(`${JSON.stringify(t)}
2
+ `),o=z(r,S.O_WRONLY|S.O_CREAT|S.O_EXCL|(S.O_NOFOLLOW??0),384);try{W(o,e),J(o)}finally{k(o)}const n=z(X(r),S.O_RDONLY|(S.O_DIRECTORY??0));try{J(n)}finally{k(n)}}function x(r,t){const e=z(r,S.O_RDONLY|(S.O_NOFOLLOW??0));let o,n;try{const s=V(e);if(!s.isFile()||s.uid!==process.getuid?.()||(s.mode&63)!==0||s.size<1||s.size>t)throw new Error("qa-scan-discovery-unverified");n=s.size,o=P(e)}finally{k(e)}if(o.length!==n||o.length>t)throw new Error("qa-scan-discovery-unverified");const a=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(o));if(!o.equals(Buffer.from(`${JSON.stringify(a)}
3
+ `)))throw new Error("qa-scan-discovery-unverified");return a}function I(r,t){return Object.freeze({schemaVersion:2,kind:"root-qa-scan-link-discovery",sourceHead:r.sourceHead,seedSha256:r.seedSha256,discoverySha256:t,targetCount:r.targetUrls.length,inventoryScope:"reachable-rendered-same-origin-links",qaAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}async function ie(r){let t,e=U.now();const o=e+se;for(;U.now()<=o;){const n=await r.locator("a[href]").evaluateAll(s=>s.map(i=>i.href)),a=JSON.stringify(n);if(a!==t)t=a,e=U.now();else if(U.now()-e>=ae)return n;await r.waitForTimeout(ne)}throw new Error("qa-scan-discovery-scope-unverified")}async function Se(r){if(!r.authorizeDiscovery)throw new Error("qa-scan-discovery-authorization-required");const t=E(r.projectRoot),e=B(t,"discovery",r.seedUrl),o=new URL(r.seedUrl);if(Buffer.byteLength(o.href)>A)throw new Error("qa-scan-discovery-scope-too-large");let n;const a=r.syntheticBackend?await te({projectRoot:t,surface:"discovery",targetUrl:r.seedUrl,...r.syntheticBackend}):void 0;try{n=a?await re():await ee()}catch{throw new Error("qa-scan-discovery-browser-unavailable")}try{const s=await n.newContext({serviceWorkers:"block",acceptDownloads:!1,permissions:[],ignoreHTTPSErrors:!1});let i=0,g=0,u=0,d=0;await s.route("**/*",async c=>{let y=!1;try{const f=c.request(),p=new URL(f.url());y=p.protocol==="http:"&&p.origin===o.origin&&(f.method()==="GET"||f.method()==="HEAD")}catch{}if(a)try{if(!y)throw new Error("denied");const f=c.request(),p=await a.request(f.url(),f.method(),f.postData(),await f.allHeaders());await c.fulfill({status:p.status,body:p.body,headers:{"content-type":p.contentType,"cache-control":"no-store"}})}catch{i++,await c.abort("blockedbyclient").catch(()=>{})}else await Z(c,y,()=>{i++})}),await s.routeWebSocket(/.*/,async c=>{i++,await c.close({code:1008,reason:"blocked"})});const l=await s.newPage();s.on("page",c=>{c!==l&&(d++,c.close())}),l.on("framenavigated",c=>{c===l.mainFrame()&&c.url()!=="about:blank"&&u++}),l.on("requestfailed",()=>{g++}),l.on("response",c=>{c.status()>=400&&g++});const m=new Set([o.href]),v=new Set;let H=0;for(;v.size<m.size;){const c=[...m].filter(w=>!v.has(w)).sort((w,h)=>Buffer.from(w).compare(Buffer.from(h)))[0];if(!c||m.size>R)throw new Error("qa-scan-discovery-scope-too-large");let y;try{y=await l.goto(c,{waitUntil:"load",timeout:15e3})}catch{throw new Error("qa-scan-discovery-target-unavailable")}if(!y||y.status()>=400||l.url()!==c||u!==v.size+1||i!==0||g!==0||d!==0)throw new Error("qa-scan-discovery-target-unavailable");const f=await ie(l);if(H+=f.length,H>256)throw new Error("qa-scan-discovery-scope-too-large");for(const w of f){if(typeof w!="string"||Buffer.byteLength(w)>A)throw new Error("qa-scan-discovery-scope-too-large");let h;try{h=new URL(w)}catch{throw new Error("qa-scan-discovery-target-unavailable")}if(!(h.origin!==o.origin||h.protocol!=="http:")){if(h.username||h.password||h.search||h.hash)throw new Error("qa-scan-discovery-target-unavailable");if(m.add(h.href),m.size>R)throw new Error("qa-scan-discovery-scope-too-large")}}const p=await l.locator("a[href]").evaluateAll(w=>w.map(h=>h.href));if(JSON.stringify(p)!==JSON.stringify(f)||l.url()!==c||u!==v.size+1||i!==0||g!==0||d!==0)throw new Error("qa-scan-discovery-scope-unverified");v.add(c)}const _=[...m].sort((c,y)=>Buffer.from(c).compare(Buffer.from(y)));if(_.length<1||_.length>R||v.size!==m.size||u!==v.size||i!==0||g!==0||d!==0)throw new Error("qa-scan-discovery-scope-unverified");const $=B(t,"discovery",r.seedUrl);if(a&&await a.verify(),$.planSha256!==e.planSha256)throw new Error("qa-scan-discovery-source-drift");const O={schemaVersion:2,sourceHead:e.sourceHead,seedUrl:o.href,seedSha256:e.targetSha256,capturedAt:new Date().toISOString(),targetUrls:_,pageCount:v.size},b=q(JSON.stringify(O)),M=N(t,"discovery",b);if(Buffer.byteLength(JSON.stringify({...O,discoverySha256:b}))+1>C)throw new Error("qa-scan-discovery-scope-too-large");return j(M,{...O,discoverySha256:b}),I(O,b)}catch(s){throw s instanceof Error&&s.message.startsWith("qa-scan-discovery-")?s:new Error("qa-scan-discovery-unverified")}finally{await n.close().catch(()=>{})}}function D(r,t){const e=x(N(r,"discovery",t),C);if(!L(e,["schemaVersion","sourceHead","seedUrl","seedSha256","capturedAt","targetUrls","pageCount","discoverySha256"])||e.schemaVersion!==2||e.discoverySha256!==t||typeof e.seedUrl!="string"||typeof e.sourceHead!="string"||typeof e.seedSha256!="string"||Buffer.byteLength(e.seedUrl)>A||!T.test(e.seedSha256)||!Array.isArray(e.targetUrls)||e.targetUrls.length<1||e.targetUrls.length>R||e.pageCount!==e.targetUrls.length||typeof e.capturedAt!="string")throw new Error("qa-scan-discovery-unverified");const o=Date.now()-Date.parse(e.capturedAt);if(!Number.isFinite(o)||new Date(e.capturedAt).toISOString()!==e.capturedAt||o<-5*6e4||o>oe)throw new Error("qa-scan-discovery-unverified");const n=B(r,"discovery",e.seedUrl);if(e.sourceHead!==n.sourceHead||e.seedSha256!==n.targetSha256)throw new Error("qa-scan-discovery-unverified");const a=new URL(e.seedUrl),s=e.targetUrls;if(!s.every(u=>typeof u=="string"&&Buffer.byteLength(u)<=A&&(()=>{try{const d=new URL(u);return d.origin===a.origin&&d.protocol==="http:"&&!d.username&&!d.password&&!d.search&&!d.hash&&d.href===u}catch{return!1}})())||!s.includes(a.href)||JSON.stringify(s)!==JSON.stringify([...new Set(s)].sort((u,d)=>Buffer.from(String(u)).compare(Buffer.from(String(d))))))throw new Error("qa-scan-discovery-unverified");const{discoverySha256:i,...g}=e;if(q(JSON.stringify(g))!==t)throw new Error("qa-scan-discovery-unverified");return g}function me(r,t){try{return I(D(E(r),t),t)}catch{throw new Error("qa-scan-discovery-unverified")}}function qe(r,t){try{return Object.freeze([...D(E(r),t).targetUrls])}catch{throw new Error("qa-scan-discovery-unverified")}}function Q(r,t,e){if(!L(e,["schemaVersion","targets"])||e.schemaVersion!==1||!Array.isArray(e.targets))throw new Error("qa-scan-discovery-phase-unverified");const o=D(r,t),n=e.targets.map(s=>{if(!L(s,["surface","targetUrl"])||typeof s.targetUrl!="string")throw new Error("qa-scan-discovery-phase-unverified");return s.targetUrl}).sort((s,i)=>Buffer.from(s).compare(Buffer.from(i)));if(JSON.stringify(n)!==JSON.stringify(o.targetUrls))throw new Error("qa-scan-discovery-phase-unverified");const a=G(r,e);if(a.sourceHead!==o.sourceHead||a.qaAccepted&&(a.verdict!=="approved"||a.issueCount!==0)||!a.qaAccepted&&(a.verdict!=="issues-found"||a.issueCount<1))throw new Error("qa-scan-discovery-phase-unverified");return Object.freeze({schemaVersion:2,kind:"root-qa-scan-discovered-phase-checkpoint",sourceHead:o.sourceHead,discoverySha256:t,aggregateSha256:a.aggregateSha256,surfaceCount:a.surfaceCount,inventoryScope:"reachable-rendered-same-origin-links",qaAccepted:a.qaAccepted,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}function Ee(r,t,e){try{const o=E(r),n=Q(o,t,e),a={receipt:n,checksumSha256:q(`qa-scan-discovered-phase-v2\0${JSON.stringify(n)}`)},s=N(o,"discovered-phase",q(`${t}\0${n.aggregateSha256}`));try{j(s,a)}catch(i){if(i.code!=="EEXIST")throw i}if(JSON.stringify(de(o,t,e))!==JSON.stringify(n))throw new Error("qa-scan-discovery-phase-unverified");return n}catch{throw new Error("qa-scan-discovery-phase-unverified")}}function de(r,t,e){try{const o=E(r),n=Q(o,t,e),a=x(N(o,"discovered-phase",q(`${t}\0${n.aggregateSha256}`)),4096);if(!L(a,["receipt","checksumSha256"])||a.checksumSha256!==q(`qa-scan-discovered-phase-v2\0${JSON.stringify(a.receipt)}`)||JSON.stringify(a.receipt)!==JSON.stringify(n))throw new Error("qa-scan-discovery-phase-unverified");return n}catch{throw new Error("qa-scan-discovery-phase-unverified")}}export{Ee as acceptRootQaScanDiscoveredPhase,Se as discoverRootQaScanLinks,de as readRootQaScanDiscoveredPhase,me as readRootQaScanDiscovery,qe as readRootQaScanDiscoveryTargets};
@@ -3,6 +3,8 @@ type Kind = (typeof KINDS)[number];
3
3
  /** An IHDR-sized prefix is not a screenshot. Check the bounded PNG stream,
4
4
  * chunk checksums and decompressed scanlines before an approved QA verdict. */
5
5
  export declare function validateRootQaScanPng(bytes: Buffer, kind: 'visual' | 'responsive' | 'responsive-tablet' | 'responsive-desktop'): boolean;
6
+ /** A positive root verdict needs closed, complete observation claims, not generic JSON. */
7
+ export declare function validateRootQaScanArtifact(kind: Kind, bytes: Buffer): boolean;
6
8
  /** The capture adapter may write only beneath the same private directory that
7
9
  * the independent root reviewer reopens and hashes. */
8
10
  export declare function rootQaScanEvidenceDirectory(projectRoot: string): string;
@@ -106,8 +108,13 @@ export declare function validateRootQaScanReview(projectRoot: string, surface: s
106
108
  export declare function acceptRootQaScanReview(projectRoot: string, surface: string, targetUrl: string, input: unknown): RootQaScanReviewReceiptV1;
107
109
  /** Rechecks the private report and every artifact against the current physical source. */
108
110
  export declare function readRootQaScanReview(projectRoot: string, surface: string, targetUrl: string): RootQaScanReviewReceiptV1;
111
+ /** Private worker/root read only. CLI projections must return the receipt alone. */
112
+ export declare function readRootQaScanReviewedEvidence(projectRoot: string, surface: string, targetUrl: string): Readonly<{
113
+ receipt: RootQaScanReviewReceiptV1;
114
+ report: RootQaScanReportV1;
115
+ }>;
109
116
  /** Reopens every independent root verdict and its eight physical artifacts.
110
- * This is a multi-surface observation, never Q01–Q03 state or release authority. */
117
+ * Covers the selected nonempty surface set, never server graph or release authority. */
111
118
  export declare function aggregateRootQaScanReviews(projectRoot: string, input: unknown): RootQaScanAggregateV1;
112
119
  /** Revalidates every private review and artifact, then saves an immutable root
113
120
  * synthesis. This is local QA state only, never a server graph advancement. */
@@ -1,5 +1,5 @@
1
- import{createHash as he,randomUUID as Y}from"node:crypto";import{closeSync as x,constants as X,existsSync as D,fstatSync as Z,fsyncSync as J,linkSync as ee,lstatSync as k,mkdirSync as re,openSync as z,readFileSync as j,realpathSync as B,unlinkSync as te,writeFileSync as ne}from"node:fs";import{isAbsolute as ve,join as b,resolve as E}from"node:path";import{inflateSync as pe}from"node:zlib";import{mutationQaGit as T}from"./mutation-qa-git.js";const F=/^[a-f0-9]{64}$/,we=/^[a-z][a-z0-9-]{0,63}$/,ge=/^[a-z][a-z0-9-]{0,63}\.(?:png|json)$/,oe=8*1024*1024,Q=128*1024,V=2,se=Buffer.from([137,80,78,71,13,10,26,10]),ye=Uint32Array.from({length:256},(r,t)=>{let e=t;for(let n=0;n<8;n++)e=(e&1)!==0?3988292384^e>>>1:e>>>1;return e>>>0}),M=["visual","responsive","responsive-tablet","responsive-desktop","accessibility","interaction","console-network","performance"],Se=Object.freeze({visual:"browser-screenshot",responsive:"browser-screenshot","responsive-tablet":"browser-screenshot","responsive-desktop":"browser-screenshot",accessibility:"axe-scan",interaction:"browser-interaction","console-network":"console-capture",performance:"performance-trace"}),me=Object.freeze({visual:"PNG 8-bit non-interlaced CRC+IDAT verified; width=1440, 600<=height<=4096, pixels<=8000000",responsive:"PNG 8-bit non-interlaced CRC+IDAT verified; width=375, 600<=height<=4096","responsive-tablet":"PNG 8-bit non-interlaced CRC+IDAT verified; width=768, 600<=height<=4096","responsive-desktop":"PNG 8-bit non-interlaced CRC+IDAT verified; width=1024, 600<=height<=4096",accessibility:"JSON: schemaVersion=1 engine=axe-core violationCount=0 incompleteCount=0 passCount>0",interaction:"JSON: schemaVersion=2 syntheticTarget=true networkAttemptsAfterAction=0 checks=[{id:functional-safe-slug,passed:true}] (1..32 distinct)","console-network":"JSON: schemaVersion=1 initialCapture=true consoleErrors=0 pageErrors=0 failedRequests=0 requestsObserved>0",performance:"JSON: schemaVersion=1 metric=load 0<=observedMs<=budgetMs<=30000"});function _(r,t=1e6){return Number.isSafeInteger(r)&&r>=0&&r<=t}function qe(r,t,e){let n=4294967295;for(let o=t;o<e;o++)n=ye[(n^r[o])&255]^n>>>8;return(n^4294967295)>>>0}function Ee(r,t){if(r.length<57||!r.subarray(0,8).equals(se))return!1;let e=8,n=0,o=0,s=0,c=0,d=!1,a=!1,g=!1,f=!1,l=0;const p=[];for(;e<r.length;){if(++l>1024||r.length-e<12)return!1;const w=r.readUInt32BE(e),S=e+8+w,$=S+4;if(w>oe||$>r.length)return!1;const A=r.toString("ascii",e+4,e+8);if(!/^[A-Za-z]{4}$/.test(A)||qe(r,e+4,S)!==r.readUInt32BE(S))return!1;if(A==="IHDR"){if(d||e!==8||w!==13||(d=!0,n=r.readUInt32BE(e+8),o=r.readUInt32BE(e+12),c=r[e+17],s=c===0?1:c===2?3:c===4?2:c===6?4:0,!s||r[e+16]!==8||r[e+18]!==0||r[e+19]!==0||r[e+20]!==0||n<1||o<1||n>4096||o>4096||n*o>8e6))return!1}else if(A==="IDAT"){if(!d||f||w===0)return!1;p.push(r.subarray(e+8,S))}else if(A==="PLTE"){if(!d||g||p.length>0||c!==2&&c!==6||w===0||w>768||w%3!==0)return!1;g=!0}else if(A==="IEND"){if(!d||p.length===0||w!==0||$!==r.length)return!1;a=!0;break}else{if(!d||A[0]===A[0]?.toUpperCase())return!1;p.length&&(f=!0)}e=$}const y={visual:1440,responsive:375,"responsive-tablet":768,"responsive-desktop":1024}[t];if(!a||n!==y||o<600)return!1;const i=n*s,v=o*(i+1);try{const w=pe(Buffer.concat(p),{maxOutputLength:v+1});if(w.length!==v)return!1;for(let S=0;S<o;S++)if(w[S*(i+1)]>4)return!1;return!0}catch{return!1}}function Ae(r,t){if(r==="visual"||r==="responsive"||r==="responsive-tablet"||r==="responsive-desktop")return Ee(t,r);let e;try{e=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(t))}catch{return!1}if(r==="accessibility")return m(e,["schemaVersion","engine","violationCount","incompleteCount","passCount"])&&e.schemaVersion===1&&e.engine==="axe-core"&&e.violationCount===0&&e.incompleteCount===0&&_(e.passCount)&&e.passCount>0;if(r==="interaction"){if(!m(e,["schemaVersion","syntheticTarget","networkAttemptsAfterAction","checks"])||e.schemaVersion!==2||e.syntheticTarget!==!0||e.networkAttemptsAfterAction!==0||!Array.isArray(e.checks)||e.checks.length<1||e.checks.length>32)return!1;const n=new Set;return e.checks.every(o=>!m(o,["id","passed"])||typeof o.id!="string"||!/^functional-[a-z][a-z0-9-]{0,50}$/.test(o.id)||n.has(o.id)||o.passed!==!0?!1:(n.add(o.id),!0))}return r==="console-network"?m(e,["schemaVersion","initialCapture","consoleErrors","pageErrors","failedRequests","requestsObserved"])&&e.schemaVersion===1&&e.initialCapture===!0&&e.consoleErrors===0&&e.pageErrors===0&&e.failedRequests===0&&_(e.requestsObserved)&&e.requestsObserved>0:m(e,["schemaVersion","metric","observedMs","budgetMs"])&&e.schemaVersion===1&&e.metric==="load"&&_(e.observedMs,3e4)&&_(e.budgetMs,3e4)&&e.budgetMs>0&&e.observedMs<=e.budgetMs}function C(r){return he("sha256").update(r).digest("hex")}function m(r,t){return r!==null&&typeof r=="object"&&!Array.isArray(r)&&Object.keys(r).sort().join("\0")===[...t].sort().join("\0")}function I(r,t){const e=T(r,["rev-parse","--git-common-dir"]).toString("utf8").trim(),n=ve(e)?E(e):E(r,e);if(B(n)!==n||k(n).isSymbolicLink()||k(n).uid!==process.getuid?.())throw new Error("qa-scan-review-unverified");const o=b(n,"neocortex-qa-scan");t&&re(o,{recursive:!0,mode:448});const s=k(o);if(!s.isDirectory()||s.isSymbolicLink()||s.uid!==process.getuid?.()||(s.mode&63)!==0)throw new Error("qa-scan-review-unverified");return o}function H(r,t){const e=b(I(r,t),"evidence");t&&re(e,{recursive:!0,mode:448});const n=k(e);if(!n.isDirectory()||n.isSymbolicLink()||n.uid!==process.getuid?.()||(n.mode&63)!==0)throw new Error("qa-scan-review-unverified");return e}function ze(r){return H(E(r),!1)}function L(r){if(B(r)!==r||!k(r).isDirectory()||T(r,["rev-parse","--show-toplevel"]).toString("utf8").trim()!==r)throw new Error("qa-scan-review-unverified");if(T(r,["status","--porcelain=v1","-z","--untracked-files=all"]).toString("utf8").split("\0").filter(Boolean).some(n=>!/^.{2} \.neocortex\//.test(n)))throw new Error("qa-scan-review-source-dirty");const e=T(r,["rev-parse","--verify","HEAD"]).toString("utf8").trim();if(!/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(e))throw new Error("qa-scan-review-unverified");return e}function ke(r){let t;try{t=new URL(r)}catch{throw new Error("qa-scan-review-target-invalid")}if(t.protocol!=="http:"||!["127.0.0.1","localhost","[::1]"].includes(t.hostname)||!t.port||t.username||t.password||t.search||t.hash)throw new Error("qa-scan-review-target-invalid");return C(`qa-scan-loopback-target-v1\0${t.href}`)}function P(r,t,e){try{if(!we.test(t))throw new Error("qa-scan-review-surface-invalid");const n=E(r),o=L(n),s=ke(e);H(n,!0);const c={schemaVersion:1,evidenceContractVersion:V,kind:"root-qa-scan-plan",sourceHead:o,surface:t,targetSha256:s,requiredKinds:M,qaAccepted:!1,localOnly:!0};return Object.freeze({...c,planSha256:C(`root-qa-scan-plan-v2\0${JSON.stringify(c)}`)})}catch(n){throw n instanceof Error&&n.message.startsWith("qa-scan-review-")?n:new Error("qa-scan-review-unverified")}}function ie(r,t){const e=new Set,n=[];for(const o of t.observations){if(!m(o,["kind","artifactId","captureMethod","sha256","capturedAt","rationale"])||!M.includes(o.kind)||!ge.test(o.artifactId)||e.has(o.artifactId)||o.captureMethod!==Se[o.kind]||!F.test(o.sha256)||typeof o.rationale!="string"||o.rationale.length<30||o.rationale.length>500||/[\x00-\x1f\x7f]/.test(o.rationale))throw new Error("qa-scan-review-unverified");const s=typeof o.capturedAt=="string"?Date.parse(o.capturedAt):NaN;if(!Number.isFinite(s)||new Date(s).toISOString()!==o.capturedAt||s>Date.now()+5*6e4||Date.now()-s>60*6e4)throw new Error("qa-scan-review-unverified");e.add(o.artifactId);const c=b(H(r,!1),o.artifactId),d=k(c);if(!d.isFile()||d.isSymbolicLink()||d.uid!==process.getuid?.()||(d.mode&63)!==0||d.size<8||d.size>oe||B(c)!==c)throw new Error("qa-scan-review-unverified");const a=z(c,X.O_RDONLY|(X.O_NOFOLLOW??0));let g;try{const f=Z(a);if(!f.isFile()||f.dev!==d.dev||f.ino!==d.ino||f.uid!==d.uid||f.size!==d.size)throw new Error("qa-scan-review-unverified");g=j(a);const l=Z(a),p=k(c);if(l.dev!==f.dev||l.ino!==f.ino||l.size!==f.size||l.mtimeMs!==f.mtimeMs||l.ctimeMs!==f.ctimeMs||p.dev!==f.dev||p.ino!==f.ino)throw new Error("qa-scan-review-unverified")}finally{x(a)}if(C(g)!==o.sha256)throw new Error("qa-scan-review-unverified");if(o.artifactId.endsWith(".png")){if(o.captureMethod!=="browser-screenshot"||!g.subarray(0,8).equals(se))throw new Error("qa-scan-review-unverified")}else{if(o.captureMethod==="browser-screenshot")throw new Error("qa-scan-review-unverified");try{const f=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(g));if(!f||typeof f!="object"||Array.isArray(f))throw new Error}catch{throw new Error("qa-scan-review-unverified")}}if(t.verdict==="approved"&&!Ae(o.kind,g))throw new Error("qa-scan-review-unverified");n.push(`${o.kind}:${o.artifactId}:${o.sha256}`)}return C(`root-qa-scan-evidence-v2\0${n.sort().join("\0")}`)}function G(r,t,e,n){try{const o=P(r,t,e);if(!m(n,["schemaVersion","evidenceContractVersion","kind","sourceHead","planSha256","surface","targetSha256","verdict","observations","issues"])||n.schemaVersion!==1||n.evidenceContractVersion!==V||n.kind!=="root-qa-scan-report"||n.sourceHead!==o.sourceHead||n.planSha256!==o.planSha256||n.surface!==o.surface||n.targetSha256!==o.targetSha256||n.verdict!=="approved"&&n.verdict!=="issues-found"||!Array.isArray(n.observations)||n.observations.length!==M.length||!Array.isArray(n.issues)||n.issues.length>128||Buffer.byteLength(JSON.stringify(n))>Q)throw new Error("qa-scan-review-unverified");const s=n;if(s.observations.map(a=>a.kind).sort().join("\0")!==[...M].sort().join("\0"))throw new Error("qa-scan-review-unverified");const c=ie(E(r),s),d=new Set(s.observations.map(a=>a.artifactId));for(const a of s.issues)if(!m(a,["severity","evidenceArtifactId","summary"])||!["critical","high","medium","low"].includes(a.severity)||!d.has(a.evidenceArtifactId)||typeof a.summary!="string"||a.summary.length<20||a.summary.length>500||/[\x00-\x1f\x7f]/.test(a.summary))throw new Error("qa-scan-review-unverified");if(s.verdict==="approved"!=(s.issues.length===0))throw new Error("qa-scan-review-unverified");if(L(E(r))!==o.sourceHead||ie(E(r),s)!==c)throw new Error("qa-scan-review-unverified");return Object.freeze({plan:o,report:s,evidenceSha256:c,reviewSha256:C(`root-qa-scan-review-v2\0${JSON.stringify(s)}`)})}catch{throw new Error("qa-scan-review-unverified")}}function ae(r){return Object.freeze({schemaVersion:1,evidenceContractVersion:V,kind:"root-qa-scan-review",sourceHead:r.plan.sourceHead,planSha256:r.plan.planSha256,evidenceSha256:r.evidenceSha256,reviewSha256:r.reviewSha256,evidenceCount:8,issueCount:r.report.issues.length,verdict:r.report.verdict,qaAccepted:r.report.verdict==="approved",checkpointAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}function Ce(r,t,e,n){try{const o=E(r),s=G(o,t,e,n),c=ae(s),d=I(o,!0),a=b(d,`review-${c.planSha256}.json`),g={report:s.report,receipt:c},f={schemaVersion:1,kind:"root-qa-scan-review-record",...g,checksumSha256:C(`root-qa-scan-review-record-v1\0${JSON.stringify(g)}`)},l=`${JSON.stringify(f)}
2
- `;if(D(a)){const i=k(a);if(!i.isFile()||i.isSymbolicLink()||i.uid!==process.getuid?.()||(i.mode&63)!==0||j(a,"utf8")!==l)throw new Error("qa-scan-review-unverified");return c}const p=b(d,`review-${Y()}.tmp`),y=z(p,"wx",384);try{ne(y,l),J(y)}finally{x(y)}try{if(G(o,t,e,n).reviewSha256!==c.reviewSha256)throw new Error("qa-scan-review-unverified");ee(p,a);const i=z(d,"r");try{J(i)}finally{x(i)}}finally{D(p)&&te(p)}return c}catch{throw new Error("qa-scan-review-unverified")}}function ce(r,t,e){try{const n=E(r),o=P(n,t,e),s=b(I(n,!1),`review-${o.planSha256}.json`),c=k(s);if(!c.isFile()||c.isSymbolicLink()||c.uid!==process.getuid?.()||(c.mode&63)!==0||c.size<1||c.size>Q+4096)throw new Error("qa-scan-review-unverified");const d=j(s,"utf8"),a=JSON.parse(d);if(!m(a,["schemaVersion","kind","report","receipt","checksumSha256"])||a.schemaVersion!==1||a.kind!=="root-qa-scan-review-record"||typeof a.checksumSha256!="string"||!F.test(a.checksumSha256)||d!==`${JSON.stringify(a)}
3
- `)throw new Error("qa-scan-review-unverified");const g={report:a.report,receipt:a.receipt};if(a.checksumSha256!==C(`root-qa-scan-review-record-v1\0${JSON.stringify(g)}`))throw new Error("qa-scan-review-unverified");const f=G(n,t,e,a.report),l=ae(f);if(!m(a.receipt,Object.keys(l))||JSON.stringify(a.receipt)!==JSON.stringify(l))throw new Error("qa-scan-review-unverified");return l}catch{throw new Error("qa-scan-review-unverified")}}function U(r,t){try{if(!m(t,["schemaVersion","targets"])||t.schemaVersion!==1||!Array.isArray(t.targets)||t.targets.length<2||t.targets.length>16)throw new Error("qa-scan-review-aggregate-unverified");const e=E(r),n=L(e),o=new Set,s=new Set;let c;const d=t.targets.map(i=>{if(!m(i,["surface","targetUrl"])||typeof i.surface!="string"||typeof i.targetUrl!="string"||o.has(i.surface))throw new Error("qa-scan-review-aggregate-unverified");const v=P(e,i.surface,i.targetUrl),w=new URL(i.targetUrl).origin;if(v.sourceHead!==n||s.has(v.targetSha256)||c!==void 0&&c!==w)throw new Error("qa-scan-review-aggregate-unverified");return c=w,o.add(i.surface),s.add(v.targetSha256),{surface:i.surface,targetUrl:i.targetUrl,plan:v}}).sort((i,v)=>i.surface<v.surface?-1:i.surface>v.surface?1:0),a=()=>d.map(({surface:i,targetUrl:v,plan:w})=>{const S=ce(e,i,v);if(S.sourceHead!==n||S.planSha256!==w.planSha256)throw new Error("qa-scan-review-aggregate-unverified");return S}),g=a(),f=a();if(L(e)!==n||JSON.stringify(g)!==JSON.stringify(f))throw new Error("qa-scan-review-aggregate-unverified");if(new Set(f.map(i=>i.evidenceSha256)).size!==f.length)throw new Error("qa-scan-review-aggregate-unverified");const l=f.reduce((i,v)=>i+v.issueCount,0),p=f.every(i=>i.qaAccepted&&i.verdict==="approved"),y=C(`root-qa-scan-aggregate-v2\0${n}\0${f.map(i=>[i.planSha256,i.evidenceSha256,i.reviewSha256,i.verdict,i.issueCount].join(":")).join("\0")}`);return Object.freeze({schemaVersion:1,evidenceContractVersion:V,kind:"root-qa-scan-aggregate",sourceHead:n,aggregateSha256:y,surfaceCount:f.length,evidenceCount:f.reduce((i,v)=>i+v.evidenceCount,0),issueCount:l,verdict:p?"approved":"issues-found",qaAccepted:p,checkpointAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}catch{throw new Error("qa-scan-review-aggregate-unverified")}}function K(r){if(r.qaAccepted&&(r.verdict!=="approved"||r.issueCount!==0)||!r.qaAccepted&&(r.verdict!=="issues-found"||r.issueCount<1))throw new Error("qa-scan-review-phase-unverified");return Object.freeze({schemaVersion:1,evidenceContractVersion:V,kind:"root-qa-scan-phase-checkpoint",sourceHead:r.sourceHead,aggregateSha256:r.aggregateSha256,surfaceCount:r.surfaceCount,q01DiscoveryEvidenceValidated:!0,q02InteractionEvidenceValidated:!0,q03RootTriageValidated:!0,qaAccepted:r.qaAccepted,rootPhaseCheckpointAccepted:!0,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}function Oe(r,t){try{const e=E(r),n=U(e,t),o=K(n),s=I(e,!0),c=b(s,`phase-${o.aggregateSha256}.json`),d={receipt:o},a={schemaVersion:1,kind:"root-qa-scan-phase-record",...d,checksumSha256:C(`root-qa-scan-phase-record-v1\0${JSON.stringify(d)}`)},g=`${JSON.stringify(a)}
4
- `;if(D(c)){if(de(e,t).aggregateSha256!==o.aggregateSha256)throw new Error("qa-scan-review-phase-unverified");return o}const f=b(s,`phase-${Y()}.tmp`),l=z(f,"wx",384);try{ne(l,g),J(l)}finally{x(l)}try{const p=K(U(e,t));if(JSON.stringify(p)!==JSON.stringify(o))throw new Error("qa-scan-review-phase-unverified");ee(f,c);const y=z(s,"r");try{J(y)}finally{x(y)}}finally{D(f)&&te(f)}return o}catch{throw new Error("qa-scan-review-phase-unverified")}}function de(r,t){try{const e=E(r),n=K(U(e,t)),o=b(I(e,!1),`phase-${n.aggregateSha256}.json`),s=k(o);if(!s.isFile()||s.isSymbolicLink()||s.uid!==process.getuid?.()||(s.mode&63)!==0||s.size<1||s.size>4096)throw new Error("qa-scan-review-phase-unverified");const c=j(o,"utf8"),d=JSON.parse(c);if(!m(d,["schemaVersion","kind","receipt","checksumSha256"])||d.schemaVersion!==1||d.kind!=="root-qa-scan-phase-record"||d.checksumSha256!==C(`root-qa-scan-phase-record-v1\0${JSON.stringify({receipt:d.receipt})}`)||!m(d.receipt,Object.keys(n))||JSON.stringify(d.receipt)!==JSON.stringify(n)||c!==`${JSON.stringify(d)}
5
- `)throw new Error("qa-scan-review-phase-unverified");return n}catch{throw new Error("qa-scan-review-phase-unverified")}}async function Ve(r){const t=r[0];let e=process.cwd(),n="",o="",s="plain",c=!1,d=!1,a=!1,g=!1,f=!1,l="",p="",y="";const i=new Set;try{if(t!=="plan"&&t!=="capture"&&t!=="interact"&&t!=="accept"&&t!=="status"&&t!=="aggregate"&&t!=="phase-checkpoint"&&t!=="phase-status"&&t!=="discover"&&t!=="discovery-status"&&t!=="discovered-phase-checkpoint"&&t!=="discovered-phase-status")throw new Error("qa-scan-review-action-invalid");for(let u=1;u<r.length;u++){const h=r[u];if(i.has(h))throw new Error("qa-scan-review-argument-duplicate");i.add(h);const q=()=>r[++u]??"";if(h==="--project-root")e=q();else if(h==="--surface")n=q();else if(h==="--target-url")o=q();else if(h==="--action-id")p=q();else if(h==="--expected-status")y=q();else if(h==="--discovery-sha")l=q();else if(h==="--format"){const O=q();if(O!=="json"&&O!=="plain")throw new Error("qa-scan-review-format-invalid");s=O}else if(h==="--authorize-review")c=!0;else if(h==="--authorize-capture")d=!0;else if(h==="--authorize-interaction")a=!0;else if(h==="--authorize-discovery")g=!0;else if(h==="--authorize-root-checkpoint")f=!0;else throw new Error("qa-scan-review-argument-invalid")}const v=t==="discovered-phase-checkpoint"||t==="discovered-phase-status",w=t==="aggregate"||t==="phase-checkpoint"||t==="phase-status"||v,S=t==="discover"||t==="discovery-status",$=t==="phase-checkpoint"||t==="discovered-phase-checkpoint";if(!e||(w||t==="discovery-status"?i.has("--surface")||i.has("--target-url"):t==="discover"?i.has("--surface")||!o:!n||!o)||c!==(t==="accept")||d!==(t==="capture")||a!==(t==="interact")||g!==(t==="discover")||f!==$||(v||t==="discovery-status"?!F.test(l):i.has("--discovery-sha"))||(t==="interact"?!p||!y:i.has("--action-id")||i.has("--expected-status")))throw new Error("qa-scan-review-authorization-or-target-required");if(w){let u="";for await(const le of process.stdin)if(u+=String(le),Buffer.byteLength(u)>Q)throw new Error("qa-scan-review-too-large");const{parseDocument:h}=await import("yaml"),q=h(u,{strict:!0,uniqueKeys:!0,schema:"core",customTags:[]});if(q.errors.length||q.warnings.length)throw new Error(t==="aggregate"?"qa-scan-review-aggregate-unverified":"qa-scan-review-phase-unverified");const O=JSON.parse(u),N=v?t==="discovered-phase-checkpoint"?(await import("./qa-scan-discovery.js")).acceptRootQaScanDiscoveredPhase(e,l,O):(await import("./qa-scan-discovery.js")).readRootQaScanDiscoveredPhase(e,l,O):t==="aggregate"?U(e,O):t==="phase-checkpoint"?Oe(e,O):de(e,O);return s==="json"?console.log(JSON.stringify(N)):"verdict"in N?console.log(`Root QA scan aggregate ${N.verdict}; surfaces=${N.surfaceCount}, issues=${N.issueCount}, workflow-continuation=unauthorized`):console.log(`Root QA scan bounded local evidence checkpoint; surfaces=${N.surfaceCount}, qa-accepted=${N.qaAccepted}, workflow-continuation=unauthorized`),N.qaAccepted?0:2}if(S){const{discoverRootQaScanLinks:u,readRootQaScanDiscovery:h}=await import("./qa-scan-discovery.js"),q=t==="discover"?await u({projectRoot:e,seedUrl:o,authorizeDiscovery:g}):h(e,l);return console.log(s==="json"?JSON.stringify(q):`Root QA scan link inventory; targets=${q.targetCount}, QA=not-accepted`),0}if(t==="capture"){const{captureRootQaScan:u}=await import("./qa-scan-capture.js"),h=await u({projectRoot:e,surface:n,targetUrl:o,authorizeCapture:d});return console.log(s==="json"?JSON.stringify(h):`Root QA scan captured ${h.evidenceCount} private artifacts; independent review required`),0}if(t==="interact"){const{captureRootQaScanInteraction:u}=await import("./qa-scan-capture.js"),h=await u({projectRoot:e,surface:n,targetUrl:o,actionId:p,expectedStatus:y,authorizeInteraction:a});return console.log(s==="json"?JSON.stringify(h):"Root QA scan synthetic interaction captured; independent review required"),0}if(t==="plan"){const u=P(e,n,o),h=H(E(e),!1);return console.log(s==="json"?JSON.stringify({plan:u,evidenceDirectory:h,approvedEvidenceContract:me}):`Root QA scan plan ${u.planSha256}; evidence directory ${h}; no QA accepted; use --format json for approved evidence contract`),0}if(t==="status"){const u=ce(e,n,o);return console.log(s==="json"?JSON.stringify(u):`Root QA scan ${u.verdict}; evidence=${u.evidenceCount}, issues=${u.issueCount}, qaAccepted=${u.qaAccepted}`),u.qaAccepted?0:2}let A="";for await(const u of process.stdin)if(A+=String(u),Buffer.byteLength(A)>Q)throw new Error("qa-scan-review-too-large");const{parseDocument:fe}=await import("yaml"),W=fe(A,{strict:!0,uniqueKeys:!0,schema:"core",customTags:[]});if(W.errors.length||W.warnings.length)throw new Error("qa-scan-review-unverified");const ue=JSON.parse(A),R=Ce(e,n,o,ue);return console.log(s==="json"?JSON.stringify(R):`Root QA scan ${R.verdict}; evidence=${R.evidenceCount}, issues=${R.issueCount}, qaAccepted=${R.qaAccepted}`),R.qaAccepted?0:2}catch(v){const w=v instanceof Error&&/^qa-scan-review-[a-z-]+$/.test(v.message)?v.message:"qa-scan-review-unverified";return s==="json"?console.log(JSON.stringify({ok:!1,reasonCode:w})):console.error(w),1}}export{Oe as acceptRootQaScanPhaseCheckpoint,Ce as acceptRootQaScanReview,U as aggregateRootQaScanReviews,P as planRootQaScan,Ve as qaScanReviewCliHandler,de as readRootQaScanPhaseCheckpoint,ce as readRootQaScanReview,ze as rootQaScanEvidenceDirectory,Ee as validateRootQaScanPng,G as validateRootQaScanReview};
1
+ import{createHash as he,randomUUID as Y}from"node:crypto";import{closeSync as $,constants as X,existsSync as D,fstatSync as Z,fsyncSync as J,linkSync as ee,lstatSync as k,mkdirSync as re,openSync as z,readFileSync as j,realpathSync as B,unlinkSync as te,writeFileSync as ne}from"node:fs";import{isAbsolute as ve,join as b,resolve as E}from"node:path";import{inflateSync as pe}from"node:zlib";import{mutationQaGit as Q}from"./mutation-qa-git.js";const F=/^[a-f0-9]{64}$/,we=/^[a-z][a-z0-9-]{0,63}$/,ge=/^[a-z][a-z0-9-]{0,63}\.(?:png|json)$/,oe=8*1024*1024,T=128*1024,V=2,se=Buffer.from([137,80,78,71,13,10,26,10]),ye=Uint32Array.from({length:256},(r,t)=>{let e=t;for(let n=0;n<8;n++)e=(e&1)!==0?3988292384^e>>>1:e>>>1;return e>>>0}),M=["visual","responsive","responsive-tablet","responsive-desktop","accessibility","interaction","console-network","performance"],Se=Object.freeze({visual:"browser-screenshot",responsive:"browser-screenshot","responsive-tablet":"browser-screenshot","responsive-desktop":"browser-screenshot",accessibility:"axe-scan",interaction:"browser-interaction","console-network":"console-capture",performance:"performance-trace"}),me=Object.freeze({visual:"PNG 8-bit non-interlaced CRC+IDAT verified; width=1440, 600<=height<=4096, pixels<=8000000",responsive:"PNG 8-bit non-interlaced CRC+IDAT verified; width=375, 600<=height<=4096","responsive-tablet":"PNG 8-bit non-interlaced CRC+IDAT verified; width=768, 600<=height<=4096","responsive-desktop":"PNG 8-bit non-interlaced CRC+IDAT verified; width=1024, 600<=height<=4096",accessibility:"JSON: schemaVersion=1 engine=axe-core violationCount=0 incompleteCount=0 passCount>0",interaction:"JSON: schemaVersion=2 syntheticTarget=true networkAttemptsAfterAction=0 checks=[{id:functional-safe-slug,passed:true}] (1..32 distinct)","console-network":"JSON: schemaVersion=1 initialCapture=true consoleErrors=0 pageErrors=0 failedRequests=0 requestsObserved>0",performance:"JSON: schemaVersion=1 metric=load 0<=observedMs<=budgetMs<=30000"});function _(r,t=1e6){return Number.isSafeInteger(r)&&r>=0&&r<=t}function qe(r,t,e){let n=4294967295;for(let o=t;o<e;o++)n=ye[(n^r[o])&255]^n>>>8;return(n^4294967295)>>>0}function Ee(r,t){if(r.length<57||!r.subarray(0,8).equals(se))return!1;let e=8,n=0,o=0,s=0,c=0,d=!1,a=!1,g=!1,f=!1,l=0;const p=[];for(;e<r.length;){if(++l>1024||r.length-e<12)return!1;const w=r.readUInt32BE(e),S=e+8+w,x=S+4;if(w>oe||x>r.length)return!1;const A=r.toString("ascii",e+4,e+8);if(!/^[A-Za-z]{4}$/.test(A)||qe(r,e+4,S)!==r.readUInt32BE(S))return!1;if(A==="IHDR"){if(d||e!==8||w!==13||(d=!0,n=r.readUInt32BE(e+8),o=r.readUInt32BE(e+12),c=r[e+17],s=c===0?1:c===2?3:c===4?2:c===6?4:0,!s||r[e+16]!==8||r[e+18]!==0||r[e+19]!==0||r[e+20]!==0||n<1||o<1||n>4096||o>4096||n*o>8e6))return!1}else if(A==="IDAT"){if(!d||f||w===0)return!1;p.push(r.subarray(e+8,S))}else if(A==="PLTE"){if(!d||g||p.length>0||c!==2&&c!==6||w===0||w>768||w%3!==0)return!1;g=!0}else if(A==="IEND"){if(!d||p.length===0||w!==0||x!==r.length)return!1;a=!0;break}else{if(!d||A[0]===A[0]?.toUpperCase())return!1;p.length&&(f=!0)}e=x}const y={visual:1440,responsive:375,"responsive-tablet":768,"responsive-desktop":1024}[t];if(!a||n!==y||o<600)return!1;const i=n*s,v=o*(i+1);try{const w=pe(Buffer.concat(p),{maxOutputLength:v+1});if(w.length!==v)return!1;for(let S=0;S<o;S++)if(w[S*(i+1)]>4)return!1;return!0}catch{return!1}}function Ae(r,t){if(r==="visual"||r==="responsive"||r==="responsive-tablet"||r==="responsive-desktop")return Ee(t,r);let e;try{e=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(t))}catch{return!1}if(r==="accessibility")return m(e,["schemaVersion","engine","violationCount","incompleteCount","passCount"])&&e.schemaVersion===1&&e.engine==="axe-core"&&e.violationCount===0&&e.incompleteCount===0&&_(e.passCount)&&e.passCount>0;if(r==="interaction"){if(!m(e,["schemaVersion","syntheticTarget","networkAttemptsAfterAction","checks"])||e.schemaVersion!==2||e.syntheticTarget!==!0||e.networkAttemptsAfterAction!==0||!Array.isArray(e.checks)||e.checks.length<1||e.checks.length>32)return!1;const n=new Set;return e.checks.every(o=>!m(o,["id","passed"])||typeof o.id!="string"||!/^functional-[a-z][a-z0-9-]{0,50}$/.test(o.id)||n.has(o.id)||o.passed!==!0?!1:(n.add(o.id),!0))}return r==="console-network"?m(e,["schemaVersion","initialCapture","consoleErrors","pageErrors","failedRequests","requestsObserved"])&&e.schemaVersion===1&&e.initialCapture===!0&&e.consoleErrors===0&&e.pageErrors===0&&e.failedRequests===0&&_(e.requestsObserved)&&e.requestsObserved>0:m(e,["schemaVersion","metric","observedMs","budgetMs"])&&e.schemaVersion===1&&e.metric==="load"&&_(e.observedMs,3e4)&&_(e.budgetMs,3e4)&&e.budgetMs>0&&e.observedMs<=e.budgetMs}function C(r){return he("sha256").update(r).digest("hex")}function m(r,t){return r!==null&&typeof r=="object"&&!Array.isArray(r)&&Object.keys(r).sort().join("\0")===[...t].sort().join("\0")}function I(r,t){const e=Q(r,["rev-parse","--git-common-dir"]).toString("utf8").trim(),n=ve(e)?E(e):E(r,e);if(B(n)!==n||k(n).isSymbolicLink()||k(n).uid!==process.getuid?.())throw new Error("qa-scan-review-unverified");const o=b(n,"neocortex-qa-scan");t&&re(o,{recursive:!0,mode:448});const s=k(o);if(!s.isDirectory()||s.isSymbolicLink()||s.uid!==process.getuid?.()||(s.mode&63)!==0)throw new Error("qa-scan-review-unverified");return o}function H(r,t){const e=b(I(r,t),"evidence");t&&re(e,{recursive:!0,mode:448});const n=k(e);if(!n.isDirectory()||n.isSymbolicLink()||n.uid!==process.getuid?.()||(n.mode&63)!==0)throw new Error("qa-scan-review-unverified");return e}function Ve(r){return H(E(r),!1)}function L(r){if(B(r)!==r||!k(r).isDirectory()||Q(r,["rev-parse","--show-toplevel"]).toString("utf8").trim()!==r)throw new Error("qa-scan-review-unverified");if(Q(r,["status","--porcelain=v1","-z","--untracked-files=all"]).toString("utf8").split("\0").filter(Boolean).some(n=>!/^.{2} \.neocortex\//.test(n)))throw new Error("qa-scan-review-source-dirty");const e=Q(r,["rev-parse","--verify","HEAD"]).toString("utf8").trim();if(!/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(e))throw new Error("qa-scan-review-unverified");return e}function ke(r){let t;try{t=new URL(r)}catch{throw new Error("qa-scan-review-target-invalid")}if(t.protocol!=="http:"||!["127.0.0.1","localhost","[::1]"].includes(t.hostname)||!t.port||t.username||t.password||t.search||t.hash)throw new Error("qa-scan-review-target-invalid");return C(`qa-scan-loopback-target-v1\0${t.href}`)}function P(r,t,e){try{if(!we.test(t))throw new Error("qa-scan-review-surface-invalid");const n=E(r),o=L(n),s=ke(e);H(n,!0);const c={schemaVersion:1,evidenceContractVersion:V,kind:"root-qa-scan-plan",sourceHead:o,surface:t,targetSha256:s,requiredKinds:M,qaAccepted:!1,localOnly:!0};return Object.freeze({...c,planSha256:C(`root-qa-scan-plan-v2\0${JSON.stringify(c)}`)})}catch(n){throw n instanceof Error&&n.message.startsWith("qa-scan-review-")?n:new Error("qa-scan-review-unverified")}}function ie(r,t){const e=new Set,n=[];for(const o of t.observations){if(!m(o,["kind","artifactId","captureMethod","sha256","capturedAt","rationale"])||!M.includes(o.kind)||!ge.test(o.artifactId)||e.has(o.artifactId)||o.captureMethod!==Se[o.kind]||!F.test(o.sha256)||typeof o.rationale!="string"||o.rationale.length<30||o.rationale.length>500||/[\x00-\x1f\x7f]/.test(o.rationale))throw new Error("qa-scan-review-unverified");const s=typeof o.capturedAt=="string"?Date.parse(o.capturedAt):NaN;if(!Number.isFinite(s)||new Date(s).toISOString()!==o.capturedAt||s>Date.now()+5*6e4||Date.now()-s>60*6e4)throw new Error("qa-scan-review-unverified");e.add(o.artifactId);const c=b(H(r,!1),o.artifactId),d=k(c);if(!d.isFile()||d.isSymbolicLink()||d.uid!==process.getuid?.()||(d.mode&63)!==0||d.size<8||d.size>oe||B(c)!==c)throw new Error("qa-scan-review-unverified");const a=z(c,X.O_RDONLY|(X.O_NOFOLLOW??0));let g;try{const f=Z(a);if(!f.isFile()||f.dev!==d.dev||f.ino!==d.ino||f.uid!==d.uid||f.size!==d.size)throw new Error("qa-scan-review-unverified");g=j(a);const l=Z(a),p=k(c);if(l.dev!==f.dev||l.ino!==f.ino||l.size!==f.size||l.mtimeMs!==f.mtimeMs||l.ctimeMs!==f.ctimeMs||p.dev!==f.dev||p.ino!==f.ino)throw new Error("qa-scan-review-unverified")}finally{$(a)}if(C(g)!==o.sha256)throw new Error("qa-scan-review-unverified");if(o.artifactId.endsWith(".png")){if(o.captureMethod!=="browser-screenshot"||!g.subarray(0,8).equals(se))throw new Error("qa-scan-review-unverified")}else{if(o.captureMethod==="browser-screenshot")throw new Error("qa-scan-review-unverified");try{const f=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(g));if(!f||typeof f!="object"||Array.isArray(f))throw new Error}catch{throw new Error("qa-scan-review-unverified")}}if(t.verdict==="approved"&&!Ae(o.kind,g))throw new Error("qa-scan-review-unverified");n.push(`${o.kind}:${o.artifactId}:${o.sha256}`)}return C(`root-qa-scan-evidence-v2\0${n.sort().join("\0")}`)}function G(r,t,e,n){try{const o=P(r,t,e);if(!m(n,["schemaVersion","evidenceContractVersion","kind","sourceHead","planSha256","surface","targetSha256","verdict","observations","issues"])||n.schemaVersion!==1||n.evidenceContractVersion!==V||n.kind!=="root-qa-scan-report"||n.sourceHead!==o.sourceHead||n.planSha256!==o.planSha256||n.surface!==o.surface||n.targetSha256!==o.targetSha256||n.verdict!=="approved"&&n.verdict!=="issues-found"||!Array.isArray(n.observations)||n.observations.length!==M.length||!Array.isArray(n.issues)||n.issues.length>128||Buffer.byteLength(JSON.stringify(n))>T)throw new Error("qa-scan-review-unverified");const s=n;if(s.observations.map(a=>a.kind).sort().join("\0")!==[...M].sort().join("\0"))throw new Error("qa-scan-review-unverified");const c=ie(E(r),s),d=new Set(s.observations.map(a=>a.artifactId));for(const a of s.issues)if(!m(a,["severity","evidenceArtifactId","summary"])||!["critical","high","medium","low"].includes(a.severity)||!d.has(a.evidenceArtifactId)||typeof a.summary!="string"||a.summary.length<20||a.summary.length>500||/[\x00-\x1f\x7f]/.test(a.summary))throw new Error("qa-scan-review-unverified");if(s.verdict==="approved"!=(s.issues.length===0))throw new Error("qa-scan-review-unverified");if(L(E(r))!==o.sourceHead||ie(E(r),s)!==c)throw new Error("qa-scan-review-unverified");return Object.freeze({plan:o,report:s,evidenceSha256:c,reviewSha256:C(`root-qa-scan-review-v2\0${JSON.stringify(s)}`)})}catch{throw new Error("qa-scan-review-unverified")}}function ae(r){return Object.freeze({schemaVersion:1,evidenceContractVersion:V,kind:"root-qa-scan-review",sourceHead:r.plan.sourceHead,planSha256:r.plan.planSha256,evidenceSha256:r.evidenceSha256,reviewSha256:r.reviewSha256,evidenceCount:8,issueCount:r.report.issues.length,verdict:r.report.verdict,qaAccepted:r.report.verdict==="approved",checkpointAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}function Ce(r,t,e,n){try{const o=E(r),s=G(o,t,e,n),c=ae(s),d=I(o,!0),a=b(d,`review-${c.planSha256}.json`),g={report:s.report,receipt:c},f={schemaVersion:1,kind:"root-qa-scan-review-record",...g,checksumSha256:C(`root-qa-scan-review-record-v1\0${JSON.stringify(g)}`)},l=`${JSON.stringify(f)}
2
+ `;if(D(a)){const i=k(a);if(!i.isFile()||i.isSymbolicLink()||i.uid!==process.getuid?.()||(i.mode&63)!==0||j(a,"utf8")!==l)throw new Error("qa-scan-review-unverified");return c}const p=b(d,`review-${Y()}.tmp`),y=z(p,"wx",384);try{ne(y,l),J(y)}finally{$(y)}try{if(G(o,t,e,n).reviewSha256!==c.reviewSha256)throw new Error("qa-scan-review-unverified");ee(p,a);const i=z(d,"r");try{J(i)}finally{$(i)}}finally{D(p)&&te(p)}return c}catch{throw new Error("qa-scan-review-unverified")}}function ce(r,t,e){return Oe(r,t,e).receipt}function Oe(r,t,e){try{const n=E(r),o=P(n,t,e),s=b(I(n,!1),`review-${o.planSha256}.json`),c=k(s);if(!c.isFile()||c.isSymbolicLink()||c.uid!==process.getuid?.()||(c.mode&63)!==0||c.size<1||c.size>T+4096)throw new Error("qa-scan-review-unverified");const d=j(s,"utf8"),a=JSON.parse(d);if(!m(a,["schemaVersion","kind","report","receipt","checksumSha256"])||a.schemaVersion!==1||a.kind!=="root-qa-scan-review-record"||typeof a.checksumSha256!="string"||!F.test(a.checksumSha256)||d!==`${JSON.stringify(a)}
3
+ `)throw new Error("qa-scan-review-unverified");const g={report:a.report,receipt:a.receipt};if(a.checksumSha256!==C(`root-qa-scan-review-record-v1\0${JSON.stringify(g)}`))throw new Error("qa-scan-review-unverified");const f=G(n,t,e,a.report),l=ae(f);if(!m(a.receipt,Object.keys(l))||JSON.stringify(a.receipt)!==JSON.stringify(l))throw new Error("qa-scan-review-unverified");return Object.freeze({receipt:l,report:Object.freeze(structuredClone(f.report))})}catch{throw new Error("qa-scan-review-unverified")}}function U(r,t){try{if(!m(t,["schemaVersion","targets"])||t.schemaVersion!==1||!Array.isArray(t.targets)||t.targets.length<1||t.targets.length>16)throw new Error("qa-scan-review-aggregate-unverified");const e=E(r),n=L(e),o=new Set,s=new Set;let c;const d=t.targets.map(i=>{if(!m(i,["surface","targetUrl"])||typeof i.surface!="string"||typeof i.targetUrl!="string"||o.has(i.surface))throw new Error("qa-scan-review-aggregate-unverified");const v=P(e,i.surface,i.targetUrl),w=new URL(i.targetUrl).origin;if(v.sourceHead!==n||s.has(v.targetSha256)||c!==void 0&&c!==w)throw new Error("qa-scan-review-aggregate-unverified");return c=w,o.add(i.surface),s.add(v.targetSha256),{surface:i.surface,targetUrl:i.targetUrl,plan:v}}).sort((i,v)=>i.surface<v.surface?-1:i.surface>v.surface?1:0),a=()=>d.map(({surface:i,targetUrl:v,plan:w})=>{const S=ce(e,i,v);if(S.sourceHead!==n||S.planSha256!==w.planSha256)throw new Error("qa-scan-review-aggregate-unverified");return S}),g=a(),f=a();if(L(e)!==n||JSON.stringify(g)!==JSON.stringify(f))throw new Error("qa-scan-review-aggregate-unverified");if(new Set(f.map(i=>i.evidenceSha256)).size!==f.length)throw new Error("qa-scan-review-aggregate-unverified");const l=f.reduce((i,v)=>i+v.issueCount,0),p=f.every(i=>i.qaAccepted&&i.verdict==="approved"),y=C(`root-qa-scan-aggregate-v2\0${n}\0${f.map(i=>[i.planSha256,i.evidenceSha256,i.reviewSha256,i.verdict,i.issueCount].join(":")).join("\0")}`);return Object.freeze({schemaVersion:1,evidenceContractVersion:V,kind:"root-qa-scan-aggregate",sourceHead:n,aggregateSha256:y,surfaceCount:f.length,evidenceCount:f.reduce((i,v)=>i+v.evidenceCount,0),issueCount:l,verdict:p?"approved":"issues-found",qaAccepted:p,checkpointAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}catch{throw new Error("qa-scan-review-aggregate-unverified")}}function K(r){if(r.qaAccepted&&(r.verdict!=="approved"||r.issueCount!==0)||!r.qaAccepted&&(r.verdict!=="issues-found"||r.issueCount<1))throw new Error("qa-scan-review-phase-unverified");return Object.freeze({schemaVersion:1,evidenceContractVersion:V,kind:"root-qa-scan-phase-checkpoint",sourceHead:r.sourceHead,aggregateSha256:r.aggregateSha256,surfaceCount:r.surfaceCount,q01DiscoveryEvidenceValidated:!0,q02InteractionEvidenceValidated:!0,q03RootTriageValidated:!0,qaAccepted:r.qaAccepted,rootPhaseCheckpointAccepted:!0,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}function be(r,t){try{const e=E(r),n=U(e,t),o=K(n),s=I(e,!0),c=b(s,`phase-${o.aggregateSha256}.json`),d={receipt:o},a={schemaVersion:1,kind:"root-qa-scan-phase-record",...d,checksumSha256:C(`root-qa-scan-phase-record-v1\0${JSON.stringify(d)}`)},g=`${JSON.stringify(a)}
4
+ `;if(D(c)){if(de(e,t).aggregateSha256!==o.aggregateSha256)throw new Error("qa-scan-review-phase-unverified");return o}const f=b(s,`phase-${Y()}.tmp`),l=z(f,"wx",384);try{ne(l,g),J(l)}finally{$(l)}try{const p=K(U(e,t));if(JSON.stringify(p)!==JSON.stringify(o))throw new Error("qa-scan-review-phase-unverified");ee(f,c);const y=z(s,"r");try{J(y)}finally{$(y)}}finally{D(f)&&te(f)}return o}catch{throw new Error("qa-scan-review-phase-unverified")}}function de(r,t){try{const e=E(r),n=K(U(e,t)),o=b(I(e,!1),`phase-${n.aggregateSha256}.json`),s=k(o);if(!s.isFile()||s.isSymbolicLink()||s.uid!==process.getuid?.()||(s.mode&63)!==0||s.size<1||s.size>4096)throw new Error("qa-scan-review-phase-unverified");const c=j(o,"utf8"),d=JSON.parse(c);if(!m(d,["schemaVersion","kind","receipt","checksumSha256"])||d.schemaVersion!==1||d.kind!=="root-qa-scan-phase-record"||d.checksumSha256!==C(`root-qa-scan-phase-record-v1\0${JSON.stringify({receipt:d.receipt})}`)||!m(d.receipt,Object.keys(n))||JSON.stringify(d.receipt)!==JSON.stringify(n)||c!==`${JSON.stringify(d)}
5
+ `)throw new Error("qa-scan-review-phase-unverified");return n}catch{throw new Error("qa-scan-review-phase-unverified")}}async function Ie(r){const t=r[0];let e=process.cwd(),n="",o="",s="plain",c=!1,d=!1,a=!1,g=!1,f=!1,l="",p="",y="";const i=new Set;try{if(t!=="plan"&&t!=="capture"&&t!=="interact"&&t!=="accept"&&t!=="status"&&t!=="aggregate"&&t!=="phase-checkpoint"&&t!=="phase-status"&&t!=="discover"&&t!=="discovery-status"&&t!=="discovered-phase-checkpoint"&&t!=="discovered-phase-status")throw new Error("qa-scan-review-action-invalid");for(let u=1;u<r.length;u++){const h=r[u];if(i.has(h))throw new Error("qa-scan-review-argument-duplicate");i.add(h);const q=()=>r[++u]??"";if(h==="--project-root")e=q();else if(h==="--surface")n=q();else if(h==="--target-url")o=q();else if(h==="--action-id")p=q();else if(h==="--expected-status")y=q();else if(h==="--discovery-sha")l=q();else if(h==="--format"){const O=q();if(O!=="json"&&O!=="plain")throw new Error("qa-scan-review-format-invalid");s=O}else if(h==="--authorize-review")c=!0;else if(h==="--authorize-capture")d=!0;else if(h==="--authorize-interaction")a=!0;else if(h==="--authorize-discovery")g=!0;else if(h==="--authorize-root-checkpoint")f=!0;else throw new Error("qa-scan-review-argument-invalid")}const v=t==="discovered-phase-checkpoint"||t==="discovered-phase-status",w=t==="aggregate"||t==="phase-checkpoint"||t==="phase-status"||v,S=t==="discover"||t==="discovery-status",x=t==="phase-checkpoint"||t==="discovered-phase-checkpoint";if(!e||(w||t==="discovery-status"?i.has("--surface")||i.has("--target-url"):t==="discover"?i.has("--surface")||!o:!n||!o)||c!==(t==="accept")||d!==(t==="capture")||a!==(t==="interact")||g!==(t==="discover")||f!==x||(v||t==="discovery-status"?!F.test(l):i.has("--discovery-sha"))||(t==="interact"?!p||!y:i.has("--action-id")||i.has("--expected-status")))throw new Error("qa-scan-review-authorization-or-target-required");if(w){let u="";for await(const le of process.stdin)if(u+=String(le),Buffer.byteLength(u)>T)throw new Error("qa-scan-review-too-large");const{parseDocument:h}=await import("yaml"),q=h(u,{strict:!0,uniqueKeys:!0,schema:"core",customTags:[]});if(q.errors.length||q.warnings.length)throw new Error(t==="aggregate"?"qa-scan-review-aggregate-unverified":"qa-scan-review-phase-unverified");const O=JSON.parse(u),N=v?t==="discovered-phase-checkpoint"?(await import("./qa-scan-discovery.js")).acceptRootQaScanDiscoveredPhase(e,l,O):(await import("./qa-scan-discovery.js")).readRootQaScanDiscoveredPhase(e,l,O):t==="aggregate"?U(e,O):t==="phase-checkpoint"?be(e,O):de(e,O);return s==="json"?console.log(JSON.stringify(N)):"verdict"in N?console.log(`Root QA scan aggregate ${N.verdict}; surfaces=${N.surfaceCount}, issues=${N.issueCount}, workflow-continuation=unauthorized`):console.log(`Root QA scan bounded local evidence checkpoint; surfaces=${N.surfaceCount}, qa-accepted=${N.qaAccepted}, workflow-continuation=unauthorized`),N.qaAccepted?0:2}if(S){const{discoverRootQaScanLinks:u,readRootQaScanDiscovery:h}=await import("./qa-scan-discovery.js"),q=t==="discover"?await u({projectRoot:e,seedUrl:o,authorizeDiscovery:g}):h(e,l);return console.log(s==="json"?JSON.stringify(q):`Root QA scan link inventory; targets=${q.targetCount}, QA=not-accepted`),0}if(t==="capture"){const{captureRootQaScan:u}=await import("./qa-scan-capture.js"),h=await u({projectRoot:e,surface:n,targetUrl:o,authorizeCapture:d});return console.log(s==="json"?JSON.stringify(h):`Root QA scan captured ${h.evidenceCount} private artifacts; independent review required`),0}if(t==="interact"){const{captureRootQaScanInteraction:u}=await import("./qa-scan-capture.js"),h=await u({projectRoot:e,surface:n,targetUrl:o,actionId:p,expectedStatus:y,authorizeInteraction:a});return console.log(s==="json"?JSON.stringify(h):"Root QA scan synthetic interaction captured; independent review required"),0}if(t==="plan"){const u=P(e,n,o),h=H(E(e),!1);return console.log(s==="json"?JSON.stringify({plan:u,evidenceDirectory:h,approvedEvidenceContract:me}):`Root QA scan plan ${u.planSha256}; evidence directory ${h}; no QA accepted; use --format json for approved evidence contract`),0}if(t==="status"){const u=ce(e,n,o);return console.log(s==="json"?JSON.stringify(u):`Root QA scan ${u.verdict}; evidence=${u.evidenceCount}, issues=${u.issueCount}, qaAccepted=${u.qaAccepted}`),u.qaAccepted?0:2}let A="";for await(const u of process.stdin)if(A+=String(u),Buffer.byteLength(A)>T)throw new Error("qa-scan-review-too-large");const{parseDocument:fe}=await import("yaml"),W=fe(A,{strict:!0,uniqueKeys:!0,schema:"core",customTags:[]});if(W.errors.length||W.warnings.length)throw new Error("qa-scan-review-unverified");const ue=JSON.parse(A),R=Ce(e,n,o,ue);return console.log(s==="json"?JSON.stringify(R):`Root QA scan ${R.verdict}; evidence=${R.evidenceCount}, issues=${R.issueCount}, qaAccepted=${R.qaAccepted}`),R.qaAccepted?0:2}catch(v){const w=v instanceof Error&&/^qa-scan-review-[a-z-]+$/.test(v.message)?v.message:"qa-scan-review-unverified";return s==="json"?console.log(JSON.stringify({ok:!1,reasonCode:w})):console.error(w),1}}export{be as acceptRootQaScanPhaseCheckpoint,Ce as acceptRootQaScanReview,U as aggregateRootQaScanReviews,P as planRootQaScan,Ie as qaScanReviewCliHandler,de as readRootQaScanPhaseCheckpoint,ce as readRootQaScanReview,Oe as readRootQaScanReviewedEvidence,Ve as rootQaScanEvidenceDirectory,Ae as validateRootQaScanArtifact,Ee as validateRootQaScanPng,G as validateRootQaScanReview};
@@ -0,0 +1,62 @@
1
+ import { type RootQaScanScenario } from './qa-scan-capture.js';
2
+ import { type RootQaScanObservationV1 } from './qa-scan-review.js';
3
+ import type { SyntheticBackendCapturePort } from './qa-synthetic-broker-port.js';
4
+ import type { Browser } from 'playwright-core';
5
+ export interface SyntheticDockerCaptureContext {
6
+ readonly kind: 'operator-docker-job';
7
+ readonly imageId: string;
8
+ readonly containerId: string;
9
+ readonly jobBindingSha256: string;
10
+ readonly launch: () => Promise<Browser>;
11
+ }
12
+ export declare function captureSyntheticBackendScenario(options: {
13
+ readonly projectRoot: string;
14
+ readonly surface: string;
15
+ readonly targetUrl: string;
16
+ readonly scenario: RootQaScanScenario;
17
+ readonly generation: string;
18
+ readonly grantId: string;
19
+ }, trustedPort?: SyntheticBackendCapturePort, dockerContext?: SyntheticDockerCaptureContext): Promise<{
20
+ observationSha256: string;
21
+ qaAccepted: false;
22
+ checkpointAccepted: false;
23
+ workflowContinuationAuthorized: false;
24
+ producerJob?: {
25
+ imageId: string;
26
+ containerId: string;
27
+ jobBindingSha256: string;
28
+ } | undefined;
29
+ schemaVersion: 3;
30
+ kind: "root-qa-synthetic-backend-observation";
31
+ sourceHead: string;
32
+ planSha256: string;
33
+ surface: string;
34
+ targetSha256: string;
35
+ generation: string;
36
+ scenarioSha256: string;
37
+ backendContractSha256: string;
38
+ requirementSourceSha256: string;
39
+ requirementIds: readonly string[];
40
+ capturedAt: string;
41
+ checks: {
42
+ id: string;
43
+ passed: true;
44
+ }[];
45
+ scenario: RootQaScanScenario;
46
+ grantId: string;
47
+ evidenceCount: 8;
48
+ observations: RootQaScanObservationV1[];
49
+ initialRequests: number;
50
+ allowedActionRequests: number;
51
+ deniedRequests: number;
52
+ backend: readonly {
53
+ path: string;
54
+ method: string;
55
+ status: number;
56
+ responseSha256: string;
57
+ }[];
58
+ sandboxNetwork: "none";
59
+ browserNetwork: "docker-none" | "unshared";
60
+ browserHostWorkspaceMounted: false;
61
+ browserVersion: string;
62
+ }>;
@@ -0,0 +1 @@
1
+ import{createHash as re,randomUUID as ce}from"node:crypto";import{closeSync as N,constants as c,fsyncSync as B,openSync as D,writeFileSync as ne}from"node:fs";import{join as oe}from"node:path";import le from"axe-core";import{launchSyntheticBackendBrowser as de}from"./qa-synthetic-browser.js";import{scenarioRequirementDigest as V,validateRootQaScanScenario as he}from"./qa-scan-capture.js";import{planRootQaScan as U,rootQaScanEvidenceDirectory as fe}from"./qa-scan-review.js";import{openSyntheticBackendSession as we}from"./qa-synthetic-backend.js";const F=a=>re("sha256").update(a).digest("hex");async function Se(a,f,i){const d=he(a.scenario),g=F(JSON.stringify(d)),y=U(a.projectRoot,a.surface,a.targetUrl),q=V(a.projectRoot,d);if(f&&(Object.keys(f).sort().join(",")!=="contractSha256,observations,request,scopeSha256,verify"||!["observations","request","verify"].every(n=>typeof f[n]=="function")||!/^[a-f0-9]{64}$/.test(f.contractSha256)||f.scopeSha256!==F(JSON.stringify([y.sourceHead,a.surface,a.targetUrl,a.generation,g,a.grantId]))))throw new Error("qa-synthetic-backend-broker-port-unverified");const w=f??await we({...a,scenarioSha256:g});if(i&&(!f||Object.keys(i).sort().join(",")!=="containerId,imageId,jobBindingSha256,kind,launch"||i.kind!=="operator-docker-job"||!/^sha256:[a-f0-9]{64}$/.test(i.imageId)||!/^[a-f0-9]{64}$/.test(i.containerId)||!/^[a-f0-9]{64}$/.test(i.jobBindingSha256)||typeof i.launch!="function"))throw new Error("qa-synthetic-backend-docker-context-invalid");const E=i?await i.launch():await de();let O,W=!1,u="context",R="";try{const n=await E.newContext({serviceWorkers:"block",acceptDownloads:!1,permissions:[],ignoreHTTPSErrors:!1,viewport:{width:1280,height:900}});let l=0,b=0,j=!1,k=0,x=0,H=0,J=0,I=0,M=0;const p=new Set,$=new URL(a.targetUrl);await n.route("**/*",async e=>{try{const t=e.request();j?k++:x++;const s=w.request(t.url(),t.method(),t.postData(),await t.allHeaders());p.add(s);let o;try{o=await s}finally{p.delete(s)}await e.fulfill({status:o.status,body:o.body,headers:{"content-type":o.contentType,"cache-control":"no-store"}})}catch(t){t instanceof Error&&/^qa-[a-z-]+$/.test(t.message)&&(R=t.message),l++,await e.abort("blockedbyclient").catch(()=>{})}}),await n.routeWebSocket(/.*/,async e=>{l++,await e.close({code:1008,reason:"blocked"})});const r=await n.newPage();n.on("page",e=>{e!==r&&(b++,e.close())}),r.on("framenavigated",()=>{j&&b++}),r.on("download",()=>{b++}),r.on("pageerror",()=>{b++,J++}),r.on("console",e=>{e.type()==="error"&&H++}),r.on("request",()=>{M++}),r.on("requestfailed",()=>{I++}),r.on("response",e=>{e.status()>=400&&(I++,b++)}),O=setTimeout(()=>{W=!0,n.close().catch(()=>{})},15e3),u="navigation";const z=await r.goto($.href,{waitUntil:"load",timeout:5e3});if(!z||z.status()>=400||r.url()!==$.href||l||b)throw new Error("qa-synthetic-backend-target-unavailable");j=!0;const h=()=>{if(l)throw new Error("qa-synthetic-backend-effects-denied-network");if(b)throw new Error("qa-synthetic-backend-effects-denied-page");if(W)throw new Error("qa-synthetic-backend-effects-denied-budget");if(r.url()!==$.href)throw new Error("qa-synthetic-backend-effects-denied-navigation")},C=async e=>{const t=r.locator(e);if(await t.count()!==1)throw new Error("qa-synthetic-backend-target-ambiguous");return t};u="scenario";for(const e of d.actions){const t=await C(e.selector);if(!await t.isVisible()||!await t.isEnabled())throw new Error("qa-synthetic-backend-action-unavailable");e.kind==="click"?await t.click({timeout:2e3}):e.kind==="fill"?await t.fill(e.value,{timeout:2e3}):e.kind==="select"?await t.selectOption(e.value,{timeout:2e3}):e.kind==="press"&&await t.press(e.key,{timeout:2e3}),h()}const A=[];for(const e of d.checks){const t=await C(e.selector),s=Date.now()+2e3;for(;h(),(e.kind==="text"?(await t.textContent({timeout:1e3}))?.trim():e.kind==="value"?await t.inputValue({timeout:1e3}):e.kind==="visible"?await t.isVisible():e.kind==="enabled"?await t.isEnabled():await t.isChecked({timeout:1e3}))!==e.expected;){if(Date.now()>=s)throw new Error("qa-synthetic-backend-check-failed");await r.waitForTimeout(25)}A.push({id:`functional-${e.id}`,passed:!0})}if(await r.waitForTimeout(250),h(),await Promise.allSettled([...p]),h(),await w.verify(),!k||!w.observations().length)throw new Error("qa-synthetic-backend-no-backend-observation");if(U(a.projectRoot,a.surface,a.targetUrl).planSha256!==y.planSha256)throw new Error("qa-synthetic-backend-source-drift");if(V(a.projectRoot,d)!==q)throw new Error("qa-synthetic-backend-source-drift");u="artifacts";const T=fe(a.projectRoot),Y=[],Q=new Date().toISOString(),m=(e,t,s,o,ie)=>{if(s.length<8||s.length>8*1024*1024)throw new Error("qa-synthetic-backend-artifact-limit");const se=re("sha256").update(s).digest("hex"),ae=`backend-${e}-${ce().replaceAll("-","").slice(0,20)}.${o}`,L=D(oe(T,ae),c.O_WRONLY|c.O_CREAT|c.O_EXCL|c.O_NOFOLLOW,384);try{ne(L,s),B(L)}finally{N(L)}Y.push({kind:e,artifactId:ae,sha256:se,captureMethod:t,capturedAt:Q,rationale:ie})},v=e=>Buffer.from(JSON.stringify(e));await r.addScriptTag({content:le.source});let X=0,Z=0,G=0;for(const[e,t,s]of[["visual",1440,900],["responsive",375,812],["responsive-tablet",768,1024],["responsive-desktop",1024,768]]){await r.setViewportSize({width:t,height:s}),h(),m(e,"browser-screenshot",await r.screenshot({type:"png",animations:"disabled",timeout:2e3}),"png",`Isolated network-namespace browser screenshot at ${t}x${s} after the source-bound synthetic scenario.`);const o=await r.evaluate("window.axe.run(document)");if(!o||!Array.isArray(o.violations)||!Array.isArray(o.incomplete)||!Array.isArray(o.passes))throw new Error("qa-synthetic-backend-accessibility-unavailable");X+=o.violations.length,Z+=o.incomplete.length,G+=o.passes.length,h()}m("accessibility","axe-scan",v({schemaVersion:1,engine:"axe-core",violationCount:X,incompleteCount:Z,passCount:G}),"json","Independent-review input: axe observations across all four declared viewport widths after the synthetic scenario.");const S=await r.evaluate("performance.getEntriesByType('navigation')[0]?.duration");if(typeof S!="number"||!Number.isFinite(S)||S<0)throw new Error("qa-synthetic-backend-performance-unavailable");if(m("performance","performance-trace",v({schemaVersion:1,metric:"load",observedMs:Math.ceil(S),budgetMs:1e4}),"json","Physical navigation load observation in the isolated browser with a predeclared ten-second load budget."),await Promise.allSettled([...p]),h(),await n.close(),await Promise.allSettled([...p]),h(),await w.verify(),m("interaction","browser-interaction",v({schemaVersion:3,syntheticTarget:!0,allowedActionRequests:k,deniedRequests:l,generation:a.generation,scenarioSha256:g,checks:A,backend:w.observations()}),"json","Source-bound synthetic backend interaction with exact declared transport and physical browser/backend observations."),m("console-network","console-capture",v({schemaVersion:1,initialCapture:!0,consoleErrors:H,pageErrors:J,failedRequests:I,requestsObserved:M}),"json","Console and request listeners were installed before navigation in a fresh network-isolated browser context."),U(a.projectRoot,a.surface,a.targetUrl).planSha256!==y.planSha256||V(a.projectRoot,d)!==q)throw new Error("qa-synthetic-backend-source-drift");const K={schemaVersion:3,kind:"root-qa-synthetic-backend-observation",sourceHead:y.sourceHead,planSha256:y.planSha256,surface:a.surface,targetSha256:y.targetSha256,generation:a.generation,scenarioSha256:g,backendContractSha256:w.contractSha256,requirementSourceSha256:q,requirementIds:d.requirementIds,capturedAt:Q,checks:A,scenario:d,grantId:a.grantId,evidenceCount:8,observations:Y,initialRequests:x,allowedActionRequests:k,deniedRequests:l,backend:w.observations(),sandboxNetwork:"none",browserNetwork:i?"docker-none":"unshared",browserHostWorkspaceMounted:!1,browserVersion:E.version(),...i?{producerJob:{imageId:i.imageId,containerId:i.containerId,jobBindingSha256:i.jobBindingSha256}}:{},qaAccepted:!1,checkpointAccepted:!1,workflowContinuationAuthorized:!1},P=JSON.stringify(K),ee=F(P),_=D(oe(T,`synthetic-backend-${ee}.json`),c.O_WRONLY|c.O_CREAT|c.O_EXCL|c.O_NOFOLLOW,384);try{ne(_,P),B(_)}finally{N(_)}const te=D(T,c.O_RDONLY|c.O_DIRECTORY|c.O_NOFOLLOW);try{B(te)}finally{N(te)}return{...K,observationSha256:ee}}catch(n){if(R)throw new Error(R);if(n instanceof Error&&/^qa-(synthetic-backend|scan-review)-[a-z-]+$/.test(n.message))throw n;if(n instanceof Error){const l=n.message.match(/net::(ERR_[A-Z_]+)/)?.[1];if(l)throw new Error(`qa-synthetic-backend-${u}-${l.toLowerCase().replaceAll("_","-")}`);if(/Target.*closed|browser has been closed/i.test(n.message))throw new Error(`qa-synthetic-backend-${u}-browser-closed`);if(/Timeout/i.test(n.message))throw new Error(`qa-synthetic-backend-${u}-timeout`)}throw new Error(`qa-synthetic-backend-${u}-unavailable`)}finally{O&&clearTimeout(O),await E.close().catch(()=>{})}}export{Se as captureSyntheticBackendScenario};