@ornexus/neocortex 4.60.67 → 4.60.68

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 (230) hide show
  1. package/dist/release-source.json +1 -1
  2. package/dist/sbom.cdx.json +1194 -1194
  3. package/install.sh +1 -1
  4. package/package.json +2 -2
  5. package/packages/client/dist/adapters/adapter-registry.js +108 -1
  6. package/packages/client/dist/adapters/antigravity-adapter.js +77 -2
  7. package/packages/client/dist/adapters/claude-code-adapter.js +79 -3
  8. package/packages/client/dist/adapters/codex-adapter.js +80 -2
  9. package/packages/client/dist/adapters/cursor-adapter.js +115 -4
  10. package/packages/client/dist/adapters/gemini-adapter.js +71 -2
  11. package/packages/client/dist/adapters/grok-adapter.js +42 -2
  12. package/packages/client/dist/adapters/index.js +22 -1
  13. package/packages/client/dist/adapters/platform-detector.js +212 -1
  14. package/packages/client/dist/adapters/target-adapter.js +12 -0
  15. package/packages/client/dist/adapters/vscode-adapter.js +72 -2
  16. package/packages/client/dist/agent/refresh-stubs.js +417 -2
  17. package/packages/client/dist/agent/update-agent-yaml.js +102 -1
  18. package/packages/client/dist/agent/update-description.js +251 -1
  19. package/packages/client/dist/cache/crypto-utils.js +78 -1
  20. package/packages/client/dist/cache/encrypted-cache.js +108 -1
  21. package/packages/client/dist/cache/in-memory-asset-cache.js +73 -1
  22. package/packages/client/dist/cache/index.js +13 -1
  23. package/packages/client/dist/cache/protected-pi-boundary.js +44 -1
  24. package/packages/client/dist/checkpoint/checkpoint-client-reader.js +127 -2
  25. package/packages/client/dist/checkpoint/index.js +11 -1
  26. package/packages/client/dist/checkpoint/shared-checkpoint-types.js +32 -1
  27. package/packages/client/dist/cli.js +290 -3
  28. package/packages/client/dist/commands/activate.js +394 -8
  29. package/packages/client/dist/commands/architecture-answers.js +274 -7
  30. package/packages/client/dist/commands/architecture-memory-terminal.js +253 -4
  31. package/packages/client/dist/commands/architecture-non-ui-continuation.js +100 -1
  32. package/packages/client/dist/commands/architecture-root-binding.js +51 -1
  33. package/packages/client/dist/commands/cache-status.js +112 -2
  34. package/packages/client/dist/commands/config.js +88 -1
  35. package/packages/client/dist/commands/doctor.js +635 -2
  36. package/packages/client/dist/commands/invoke.js +7140 -142
  37. package/packages/client/dist/commands/legacy-epic-terminal.js +71 -1
  38. package/packages/client/dist/commands/legacy-story-branch.js +96 -1
  39. package/packages/client/dist/commands/legacy-story-pr.js +276 -1
  40. package/packages/client/dist/commands/mutation-qa-checkpoint.js +285 -5
  41. package/packages/client/dist/commands/mutation-qa-gate.js +54 -1
  42. package/packages/client/dist/commands/mutation-qa-git.js +41 -1
  43. package/packages/client/dist/commands/mutation-qa-policy.js +102 -1
  44. package/packages/client/dist/commands/mutation-qa-run.js +219 -3
  45. package/packages/client/dist/commands/mutation-qa-verdict.js +149 -1
  46. package/packages/client/dist/commands/mutation-qa.js +220 -1
  47. package/packages/client/dist/commands/native-agent.js +136 -2
  48. package/packages/client/dist/commands/planning-diagnosis.js +43 -1
  49. package/packages/client/dist/commands/planning-generation.js +53 -1
  50. package/packages/client/dist/commands/planning-memory.js +40 -1
  51. package/packages/client/dist/commands/planning-research.js +42 -1
  52. package/packages/client/dist/commands/private-session.js +75 -1
  53. package/packages/client/dist/commands/property-qa.js +139 -1
  54. package/packages/client/dist/commands/qa-control.js +16 -1
  55. package/packages/client/dist/commands/qa-scan-browser-launch.js +55 -1
  56. package/packages/client/dist/commands/qa-scan-browser-route.js +28 -1
  57. package/packages/client/dist/commands/qa-scan-capture.js +663 -5
  58. package/packages/client/dist/commands/qa-scan-discovery.js +376 -3
  59. package/packages/client/dist/commands/qa-scan-review.js +826 -5
  60. package/packages/client/dist/commands/qa-synthetic-backend-capture.js +266 -1
  61. package/packages/client/dist/commands/qa-synthetic-backend-review.js +297 -1
  62. package/packages/client/dist/commands/qa-synthetic-backend.js +340 -2
  63. package/packages/client/dist/commands/qa-synthetic-broker-port.js +85 -1
  64. package/packages/client/dist/commands/qa-synthetic-browser.js +39 -1
  65. package/packages/client/dist/commands/refactor-characterization.js +671 -21
  66. package/packages/client/dist/commands/refactor-commit.js +399 -13
  67. package/packages/client/dist/commands/refactor-control.js +142 -3
  68. package/packages/client/dist/commands/refactor-cpp-assertions.js +93 -3
  69. package/packages/client/dist/commands/refactor-review.js +283 -5
  70. package/packages/client/dist/commands/refactor-root.js +588 -7
  71. package/packages/client/dist/commands/refresh-memory.js +156 -3
  72. package/packages/client/dist/commands/shared-architecture-non-ui.js +24 -1
  73. package/packages/client/dist/commands/story-checkpoint.js +171 -1
  74. package/packages/client/dist/config/profile-directory.js +13 -1
  75. package/packages/client/dist/config/resolver-selection.js +278 -1
  76. package/packages/client/dist/config/secure-config.js +281 -12
  77. package/packages/client/dist/config.js +223 -2
  78. package/packages/client/dist/constants.js +25 -1
  79. package/packages/client/dist/context/context-collector.js +222 -2
  80. package/packages/client/dist/context/context-sanitizer.js +145 -1
  81. package/packages/client/dist/continuity/autonomous-governance.js +285 -1
  82. package/packages/client/dist/continuity/continuity-client-state-store.js +1587 -1
  83. package/packages/client/dist/continuity/invoke-hooks.js +129 -1
  84. package/packages/client/dist/continuity/migrations/001-initial-schema.js +12 -2
  85. package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +12 -2
  86. package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +12 -2
  87. package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +12 -2
  88. package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +12 -2
  89. package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +10 -2
  90. package/packages/client/dist/continuity/native-agent-store.js +69 -1
  91. package/packages/client/dist/continuity/native-agent-types.js +90 -1
  92. package/packages/client/dist/continuity/outbox.js +117 -1
  93. package/packages/client/dist/continuity/runner-bootstrap-policy.js +172 -1
  94. package/packages/client/dist/continuity/sqlite-store.js +2536 -81
  95. package/packages/client/dist/effects/invoke-effect-gateways.js +62 -1
  96. package/packages/client/dist/errors/error-messages.js +228 -2
  97. package/packages/client/dist/errors/frontmatter-diagnostics.js +125 -1
  98. package/packages/client/dist/evidence/collector.js +347 -1
  99. package/packages/client/dist/evidence/index.js +1 -1
  100. package/packages/client/dist/evidence/types.js +2 -0
  101. package/packages/client/dist/fs/atomic-state-replace.js +152 -1
  102. package/packages/client/dist/generated/command-registry.generated.js +4076 -1
  103. package/packages/client/dist/generated/public-projections.generated.js +149 -1
  104. package/packages/client/dist/git/direct-yolo-c01-retry.js +138 -4
  105. package/packages/client/dist/git/direct-yolo-checkpoint.js +1234 -4
  106. package/packages/client/dist/git/git-lifecycle-step-ids.js +14 -1
  107. package/packages/client/dist/git/git-lifecycle-store.js +1938 -9
  108. package/packages/client/dist/git/legacy-story-branch-checkpoint.js +259 -9
  109. package/packages/client/dist/git/legacy-story-branch-setup.js +348 -4
  110. package/packages/client/dist/git/legacy-story-pr.js +3538 -70
  111. package/packages/client/dist/git/secure-filesystem-boundary.js +182 -5
  112. package/packages/client/dist/graph-retrieval/pre-command-hook.js +284 -1
  113. package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +237 -1
  114. package/packages/client/dist/i18n/first-run.js +60 -2
  115. package/packages/client/dist/index.js +47 -1
  116. package/packages/client/dist/invocation/invoke-input.js +24 -1
  117. package/packages/client/dist/license/index.js +5 -1
  118. package/packages/client/dist/license/license-client.js +304 -1
  119. package/packages/client/dist/machine/fingerprint.js +163 -2
  120. package/packages/client/dist/machine/index.js +5 -1
  121. package/packages/client/dist/memory/project-memory-path-scope.js +62 -1
  122. package/packages/client/dist/memory/project-memory-writer.js +1007 -40
  123. package/packages/client/dist/memory/shared-project-memory-types.js +253 -2
  124. package/packages/client/dist/migrations/project-identity-capability.js +50 -1
  125. package/packages/client/dist/migrations/project-identity.js +493 -7
  126. package/packages/client/dist/migrations/state-json.js +344 -4
  127. package/packages/client/dist/policy/architecture-policy.js +184 -2
  128. package/packages/client/dist/policy/index.js +7 -1
  129. package/packages/client/dist/policy/shared-policy-types.js +38 -0
  130. package/packages/client/dist/policy/subagent-approval-policy.js +55 -1
  131. package/packages/client/dist/private-execution/clean-baseline.js +279 -2
  132. package/packages/client/dist/private-execution/codex-executor.js +574 -4
  133. package/packages/client/dist/private-execution/codex-host-runtime.js +137 -1
  134. package/packages/client/dist/private-execution/codex-lifecycle-guard.js +97 -1
  135. package/packages/client/dist/private-execution/codex-scoped-permissions.js +128 -1
  136. package/packages/client/dist/private-execution/delivery.js +410 -13
  137. package/packages/client/dist/private-execution/diagnosis-root-review.js +294 -2
  138. package/packages/client/dist/private-execution/frozen-handoff.js +19 -6
  139. package/packages/client/dist/private-execution/implementation-validation.js +106 -2
  140. package/packages/client/dist/private-execution/isolated-admission.js +1432 -23
  141. package/packages/client/dist/private-execution/isolated-session.js +725 -5
  142. package/packages/client/dist/private-execution/isolated-workspace.js +122 -1
  143. package/packages/client/dist/private-execution/pact-consumer-runtime.js +409 -3
  144. package/packages/client/dist/private-execution/pact-test-source.js +258 -1
  145. package/packages/client/dist/private-execution/pact-toolchain.js +290 -3
  146. package/packages/client/dist/private-execution/pact-write-set.js +172 -1
  147. package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +406 -5
  148. package/packages/client/dist/private-execution/planning-generation-checkpoint.js +462 -5
  149. package/packages/client/dist/private-execution/planning-memory-checkpoint.js +139 -3
  150. package/packages/client/dist/private-execution/planning-research-checkpoint.js +325 -4
  151. package/packages/client/dist/private-execution/private-report-store.js +42 -1
  152. package/packages/client/dist/private-execution/property-test-candidate.js +103 -1
  153. package/packages/client/dist/private-execution/property-test-choices.js +147 -3
  154. package/packages/client/dist/private-execution/property-test-plan.js +271 -7
  155. package/packages/client/dist/private-execution/property-test-review.js +258 -4
  156. package/packages/client/dist/private-execution/property-test-runtime.js +372 -9
  157. package/packages/client/dist/private-execution/property-test-source.js +139 -1
  158. package/packages/client/dist/private-execution/root-checkpoint.js +80 -1
  159. package/packages/client/dist/private-execution/root-memory-git-evidence.js +135 -3
  160. package/packages/client/dist/private-execution/root-memory-recovery.js +93 -1
  161. package/packages/client/dist/private-execution/root-memory-request.js +29 -1
  162. package/packages/client/dist/private-execution/root-memory-scope.js +178 -1
  163. package/packages/client/dist/private-execution/root-memory-writer.js +102 -1
  164. package/packages/client/dist/private-execution/session-lease.js +63 -1
  165. package/packages/client/dist/private-execution/shared-private-admission-types.js +50 -1
  166. package/packages/client/dist/private-execution/shared-project-validation.js +66 -1
  167. package/packages/client/dist/private-execution/shared-root-memory-types.js +61 -1
  168. package/packages/client/dist/private-execution/ux-source-validation.js +372 -1
  169. package/packages/client/dist/private-execution/ux-write-set.js +191 -2
  170. package/packages/client/dist/resilience/circuit-breaker.js +170 -1
  171. package/packages/client/dist/resilience/degradation-manager.js +164 -1
  172. package/packages/client/dist/resilience/freshness-indicator.js +100 -1
  173. package/packages/client/dist/resilience/index.js +8 -1
  174. package/packages/client/dist/resilience/recovery-detector.js +74 -1
  175. package/packages/client/dist/resolvers/asset-resolver.js +13 -0
  176. package/packages/client/dist/resolvers/local-resolver.js +255 -8
  177. package/packages/client/dist/resolvers/remote-resolver.js +233 -1
  178. package/packages/client/dist/runner/binary.js +404 -1
  179. package/packages/client/dist/runner/cli.js +1571 -17
  180. package/packages/client/dist/runner/headless/adapters.js +49 -1
  181. package/packages/client/dist/runner/headless/claude-code-adapter.js +79 -1
  182. package/packages/client/dist/runner/headless/continuous-story-workspace.js +1628 -11
  183. package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +69 -1
  184. package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +110 -2
  185. package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +1236 -8
  186. package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +318 -2
  187. package/packages/client/dist/runner/headless/fake-adapter.js +62 -1
  188. package/packages/client/dist/runner/headless/managed-profile.js +132 -1
  189. package/packages/client/dist/runner/headless/opencode-adapter.js +145 -2
  190. package/packages/client/dist/runner/headless/prepared-story-bundle.js +43 -1
  191. package/packages/client/dist/runner/headless/resolver.js +231 -1
  192. package/packages/client/dist/runner/headless/run-yolo.js +885 -1
  193. package/packages/client/dist/runner/headless/run-yoloop.js +2574 -2
  194. package/packages/client/dist/runner/headless/types.js +574 -2
  195. package/packages/client/dist/runner/local-console.js +396 -13
  196. package/packages/client/dist/runner/portal-sync-worker.js +1487 -1
  197. package/packages/client/dist/runner/project-identity.js +106 -1
  198. package/packages/client/dist/runner/scheduler.js +995 -7
  199. package/packages/client/dist/runner-cli.js +18 -2
  200. package/packages/client/dist/state/invocation-shape.js +13 -1
  201. package/packages/client/dist/state/project-state-snapshot.js +1677 -1
  202. package/packages/client/dist/state/state-json-repair.js +785 -3
  203. package/packages/client/dist/state/structural-dag-projection.js +118 -1
  204. package/packages/client/dist/telemetry/index.js +5 -1
  205. package/packages/client/dist/telemetry/offline-queue.js +131 -1
  206. package/packages/client/dist/tier/index.js +5 -1
  207. package/packages/client/dist/tier/tier-aware-client.js +405 -1
  208. package/packages/client/dist/types/index.js +38 -1
  209. package/packages/client/dist/version.js +27 -1
  210. package/packages/client/dist/yolo/change-evidence-collector.js +126 -1
  211. package/packages/client/dist/yolo/invoke-envelope-capability.js +62 -1
  212. package/packages/client/dist/yolo/spec-envelope-collector.js +185 -5
  213. package/packages/client/dist/yoloop/discovery-hook.js +300 -2
  214. package/packages/client/dist/yoloop/index.js +8 -1
  215. package/packages/client/dist/yoloop/invoke-hooks.js +669 -12
  216. package/packages/client/dist/yoloop/shared-discover-epics.js +1007 -1
  217. package/packages/client/dist/yoloop/shared-yoloop-types.js +33 -1
  218. package/packages/client/dist/yoloop/target-normalization.js +180 -1
  219. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1723 -1
  220. package/targets-stubs/antigravity/gemini.md +1 -1
  221. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  222. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  223. package/targets-stubs/claude-code/neocortex-root.md +2 -2
  224. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  225. package/targets-stubs/claude-code/neocortex.md +2 -2
  226. package/targets-stubs/codex/AGENTS.md +1 -1
  227. package/targets-stubs/cursor/agent.md +2 -2
  228. package/targets-stubs/gemini-cli/agent.md +2 -2
  229. package/targets-stubs/opencode/neocortex-root.md +1 -1
  230. package/targets-stubs/vscode/neocortex.agent.md +2 -2
@@ -1,21 +1,671 @@
1
- import{spawnSync as G}from"node:child_process";import{createHash as lt,randomBytes as dt}from"node:crypto";import{closeSync as Q,constants as R,existsSync as C,fsyncSync as K,lstatSync as $,mkdirSync as Z,mkdtempSync as ut,openSync as tt,readFileSync as H,realpathSync as W,rmSync as pt,writeFileSync as z}from"node:fs";import{tmpdir as ht}from"node:os";import{basename as mt,dirname as N,join as y,resolve as wt}from"node:path";import{mutationQaGit as et}from"./mutation-qa-git.js";import{exactCommittedRefactorFile as vt,privateRefactorDirectory as rt,readPrivateRefactorAdmission as yt,verifyRootRefactorForCharacterization as ot}from"./refactor-root.js";const A=/^[a-f0-9]{64}$/,gt=64*1024*1024,st=32,nt=128;function bt(e){const t=e.match(/^(\d+)\.(\d+)\.(\d+)$/);if(!t)return!1;const r=Number(t[1]),n=Number(t[2]);return r>22||r===22&&n>=6}function St(){return bt(process.versions.node)&&process.config.variables.node_use_amaro===!0}function b(e){return lt("sha256").update(e).digest("hex")}function D(e){return e.length>0&&e.length<=512&&/^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*$/.test(e)&&e.split("/").every(t=>t!=="."&&t!==".."&&![".git",".neocortex","node_modules"].includes(t.toLowerCase()))}function V(e){return[...e].sort((t,r)=>Buffer.from(t).compare(Buffer.from(r)))}function Et(e,t,r){if(!t.endsWith(".js")&&!t.endsWith(".ts"))return;let n=N(t);for(;;){const f=n==="."?"package.json":`${n}/package.json`;if(C(y(e,f))){if(!r.includes(f))throw new Error("refactor-root-tests-manifest-unbound");let s;try{s=JSON.parse(L(e,f).toString("utf8"))}catch{throw new Error("refactor-root-tests-manifest-invalid")}if(!s||typeof s!="object"||Array.isArray(s)||!["module","commonjs"].includes(s.type)||t.endsWith(".ts")&&s.type!=="module")throw new Error("refactor-root-tests-manifest-invalid");return}if(n===".")break;n=N(n)}throw new Error("refactor-root-tests-manifest-missing")}function L(e,t){if(!D(t))throw new Error("refactor-root-tests-path-invalid");const r=y(e,t),n=$(r);if(W(r)!==r||!n.isFile()||n.isSymbolicLink()||n.size<1||n.size>8*1024*1024)throw new Error("refactor-root-tests-source-unverified");const f=H(r);if(f.length<1||f.length>8*1024*1024||f.includes(0))throw new Error("refactor-root-tests-source-unverified");return new TextDecoder("utf-8",{fatal:!0}).decode(f),f}function q(e,t){if(!A.test(t))throw new Error("refactor-root-baseline-invalid");return y(rt(e),`characterization-baseline-${t}.json`)}function Y(e,t){const r=$(q(e,t));if(!r.isFile()||r.isSymbolicLink()||r.uid!==process.getuid?.()||(r.mode&63)!==0||r.size<100||r.size>64*1024)throw new Error("refactor-root-baseline-invalid");const n=H(q(e,t));if(n.length>64*1024)throw new Error("refactor-root-baseline-invalid");let f;try{f=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(n))}catch{throw new Error("refactor-root-baseline-invalid")}if(!f||typeof f!="object"||Array.isArray(f))throw new Error("refactor-root-baseline-invalid");const s=f,a=["schemaVersion","kind","admissionSha256","sourceHead","testFiles","dependencies","causalPath","candidateSha256","outputSha256","causalOutputSha256","baselineSha256"],l=o=>Array.isArray(o)&&o.every(p=>p&&typeof p=="object"&&!Array.isArray(p)&&Object.keys(p).sort().join("\0")==="path\0sha256"&&typeof p.path=="string"&&typeof p.sha256=="string"&&D(p.path)&&A.test(p.sha256)),w=s.schemaVersion===2;if(Object.keys(s).sort().join("\0")!==(w?[...a,"runner","toolchainSha256"]:a).sort().join("\0")||s.schemaVersion!==1&&s.schemaVersion!==2||w&&(!["vitest","cpp"].includes(String(s.runner))||!A.test(String(s.toolchainSha256)))||s.kind!=="root-refactor-characterization-baseline"||!A.test(String(s.admissionSha256))||typeof s.sourceHead!="string"||!/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(s.sourceHead)||!l(s.testFiles)||!l(s.dependencies)||s.testFiles.length<1||s.testFiles.length>st||s.dependencies.length>nt||typeof s.causalPath!="string"||!D(s.causalPath)||!A.test(String(s.candidateSha256))||!A.test(String(s.outputSha256))||!A.test(String(s.causalOutputSha256))||s.baselineSha256!==t)throw new Error("refactor-root-baseline-invalid");const c=s.testFiles,d=s.dependencies;if(new Set([...c,...d].map(o=>o.path)).size!==c.length+d.length||c.map(o=>o.path).join("\0")!==V(c.map(o=>o.path)).join("\0")||d.map(o=>o.path).join("\0")!==V(d.map(o=>o.path)).join("\0"))throw new Error("refactor-root-baseline-invalid");const{baselineSha256:i,...u}=s;if(b(JSON.stringify(u))!==t||!n.equals(Buffer.from(`${JSON.stringify(s)}
2
- `)))throw new Error("refactor-root-baseline-invalid");return s}function kt(e,t){const r=b(JSON.stringify(t)),n=q(e,r),f={...t,baselineSha256:r},s=Buffer.from(`${JSON.stringify(f)}
3
- `);if(C(n))Y(e,r);else{const a=tt(n,R.O_WRONLY|R.O_CREAT|R.O_EXCL|(R.O_NOFOLLOW??0),384);try{z(a,s),K(a)}finally{Q(a)}const l=tt(rt(e),R.O_RDONLY|(R.O_DIRECTORY??0));try{K(l)}finally{Q(l)}}return Y(e,r),r}function it(e){return[...e.files.map(t=>t.path),...e.schemaVersion===2?e.newFiles.map(t=>t.path):[]]}function at(e,t,r,n=[]){const f=new Map;let s=0;for(const l of[...it(t),...r]){if(n.includes(l)){try{$(y(e,l))}catch(c){if(c.code==="ENOENT")continue;throw c}throw new Error("refactor-root-tests-source-stale")}const w=L(e,l);if(s+=w.length,s>gt)throw new Error("refactor-root-tests-snapshot-too-large");f.set(l,w)}const a=b(JSON.stringify([...[...f].map(([l,w])=>[l,b(w)]),...n.map(l=>[l,null])]));return{files:f,candidateSha256:a}}function ct(){try{const e=W("/usr/bin/g++"),t=$(e);if(!e.startsWith("/usr/")||!/^[A-Za-z0-9_./+-]+$/.test(e)||!t.isFile()||t.uid!==0||(t.mode&18)!==0||t.size<1||t.size>32*1024*1024)throw new Error("invalid compiler");const r=G(e,["-dumpfullversion","-dumpversion"],{env:{PATH:"/usr/bin:/bin"},encoding:"utf8",timeout:1e4,maxBuffer:4096}),n=r.stdout?.trim();if(r.status!==0||!/^(?:11|12|13|14|15)\.[0-9]+\.[0-9]+$/.test(n))throw new Error("unsupported compiler");return{executable:e,sha256:b(JSON.stringify([e,n,b(H(e))]))}}catch{throw new Error("refactor-root-tests-toolchain-unverified")}}function Ot(e,t,r){let n="";for(const M of t){let J=N(M),F="";for(;;){const I=J==="."?"package.json":`${J}/package.json`;if(C(y(e,I))){F=I;break}if(J===".")break;J=N(J)}if(!F||n&&F!==n)throw new Error("refactor-root-tests-toolchain-unverified");n=F}if(!r.includes(n)||!r.includes("package-lock.json"))throw new Error("refactor-root-tests-toolchain-unbound");const f=L(e,n),s=L(e,"package-lock.json");let a,l;try{a=JSON.parse(f.toString("utf8")),l=JSON.parse(s.toString("utf8"))}catch{throw new Error("refactor-root-tests-toolchain-unverified")}const w=a.devDependencies?.vitest??a.dependencies?.vitest,c=l.packages,d=n==="package.json"?"":N(n),i=c?.[d],u=i?.devDependencies?.vitest??i?.dependencies?.vitest,o=c?.["node_modules/vitest"]?.version,p=c?.["node_modules/vite"]?.version;if(typeof w!="string"||w.length>80||!/^[\^~]?[0-9]+\.[0-9]+\.[0-9]+$/.test(w)||w!==u||typeof o!="string"||!/^[0-9]+\.[0-9]+\.[0-9]+$/.test(o)||!o.startsWith("3.2.")||typeof p!="string"||!/^[0-9]+\.[0-9]+\.[0-9]+$/.test(p)||!_t(p))throw new Error("refactor-root-tests-toolchain-unverified");const v=W(y(e,"node_modules")),g=$(v);if(mt(v)!=="node_modules"||!g.isDirectory()||g.isSymbolicLink()||g.uid!==process.getuid?.())throw new Error("refactor-root-tests-toolchain-unverified");const h=y(v,"vitest","package.json"),S=y(v,"vitest","vitest.mjs"),O=y(v,"vite","package.json"),E=$(h),_=$(S),B=$(O);if(!E.isFile()||E.isSymbolicLink()||!_.isFile()||_.isSymbolicLink()||!B.isFile()||B.isSymbolicLink()||W(h)!==h||W(S)!==S||W(O)!==O||E.size>64*1024||_.size>1024*1024||B.size>64*1024)throw new Error("refactor-root-tests-toolchain-unverified");const P=H(h),k=H(S),x=H(O);let j,T;try{j=JSON.parse(P.toString("utf8")),T=JSON.parse(x.toString("utf8"))}catch{throw new Error("refactor-root-tests-toolchain-unverified")}if(j.name!=="vitest"||j.version!==o||T.name!=="vite"||T.version!==p)throw new Error("refactor-root-tests-toolchain-unverified");return{nodeModules:v,sha256:b(JSON.stringify([b(f),b(s),b(P),b(k),b(x)]))}}function _t(e){const t=e.match(/^(\d+)\.(\d+)\.(\d+)$/);return!!t&&Number(t[1])<=7&&(Number(t[1])>6||Number(t[1])===6&&Number(t[2])>=1)}function ft(e,t,r,n,f,s,a){if(r==="python"){const c=N(t[0]),d=V([...e.keys()].filter(i=>(c==="."||i.startsWith(`${c}/`))&&/(?:^|\/)test_[A-Za-z0-9_]+\.py$/.test(i)));if(JSON.stringify(d)!==JSON.stringify(t))throw new Error("refactor-root-tests-path-invalid")}const l=W(r==="cpp"?"/usr/bin/dash":r==="python"?"/usr/bin/python3":process.execPath);if(r==="node-typescript"&&!St())throw new Error("refactor-root-tests-toolchain-unavailable");if(process.platform!=="linux"||!C("/usr/bin/bwrap")||!$("/usr/bin/bwrap").isFile()||!(l.startsWith("/usr/")||l.startsWith("/home/linuxbrew/.linuxbrew/")))throw new Error("refactor-root-tests-sandbox-unavailable");const w=ut(y(ht(),"p222-refactor-characterization-"));try{const c=y(w,"work");Z(c,{mode:448});for(const[h,S]of e){const O=y(c,h);Z(N(O),{recursive:!0,mode:448}),z(O,S,{mode:384,flag:"wx"})}if(r==="vitest"){if(!f)throw new Error("refactor-root-tests-toolchain-unavailable");Z(y(c,"node_modules"),{mode:448}),z(y(c,"p222-vitest.config.mjs"),`export default ${JSON.stringify({test:{include:t,environment:"node",silent:!0,passWithNoTests:!1,watch:!1,maxWorkers:1,fileParallelism:!1,pool:"forks",coverage:{enabled:!1}}})};
4
- `,{mode:384,flag:"wx"})}if(r==="cpp"){if(!s||!a||!e.has(a))throw new Error("refactor-root-tests-toolchain-unavailable");const h=s.executable,S=V([...e.keys()].filter(E=>E.endsWith(".cpp")&&E!==a&&!t.includes(E))).map(E=>`'/work/${E}'`);n&&z(y(c,"p222-cpp-causal.cpp"),`#include <cstdio>
5
- #include <cstdlib>
6
- extern "C" void __cyg_profile_func_enter(void*,void*) { std::fputs(${JSON.stringify(n+`
7
- `)},stderr); std::_Exit(86); }
8
- extern "C" void __cyg_profile_func_exit(void*,void*) {}
9
- `,{mode:384,flag:"wx"});const O=["set -eu","ulimit -c 0",`'${h}' -std=c++17 -O0 -UNDEBUG -I/work ${n?"-finstrument-functions -finstrument-functions-exclude-file-list=/usr/include ":""}-c '/work/${a}' -o /tmp/p222-cpp-causal.o`,...t.flatMap((E,_)=>[`'${h}' -std=c++17 -O0 -UNDEBUG -I/work '/work/${E}' ${S.join(" ")} /tmp/p222-cpp-causal.o ${n?"/work/p222-cpp-causal.cpp ":""}-o /tmp/p222-cpp-test-${_}`,`/tmp/p222-cpp-test-${_}`,`printf 'P222_CPP_TEST_PASSED_${_}\\n'`])].join(`
10
- `)+`
11
- `;z(y(c,"p222-cpp-runner.sh"),O,{mode:384,flag:"wx"})}const d=y(w,"etc");Z(d,{mode:448}),z(y(d,"passwd"),`nobody:x:65534:65534:nobody:/tmp:/bin/sh
12
- `,{mode:384}),z(y(d,"group"),`nogroup:x:65534:
13
- `,{mode:384}),z(y(d,"hosts"),`127.0.0.1 localhost
14
- `,{mode:384}),z(y(d,"nsswitch.conf"),`hosts: files
15
- `,{mode:384});const i=["--die-with-parent","--new-session","--unshare-all","--clearenv","--setenv","HOME","/tmp","--setenv","TMPDIR","/tmp","--setenv","PATH","/home/linuxbrew/.linuxbrew/bin:/usr/bin:/bin","--setenv","CI","true","--ro-bind","/usr","/usr"];r==="python"&&i.push("--setenv","PYTHONPATH","/work");for(const h of["/lib","/lib64","/bin"])C(h)&&i.push("--ro-bind",h,h);C("/home/linuxbrew/.linuxbrew")&&i.push("--ro-bind","/home/linuxbrew/.linuxbrew","/home/linuxbrew/.linuxbrew"),i.push("--dir","/etc");for(const h of["passwd","group","hosts","nsswitch.conf"])i.push("--ro-bind",y(d,h),`/etc/${h}`);i.push("--dev","/dev","--proc","/proc","--tmpfs","/tmp","--ro-bind",c,"/work"),r==="vitest"&&i.push("--ro-bind",f.nodeModules,"/work/node_modules"),i.push("--chdir","/work","--",l),r==="cpp"?i.push("/work/p222-cpp-runner.sh"):r==="python"?i.push("-B","-S","-m","unittest","discover","-s",N(t[0]),"-p","test_*.py"):r==="vitest"?i.push("/work/node_modules/vitest/vitest.mjs","run","--root","/work","--config","/work/p222-vitest.config.mjs","--configLoader=runner","--reporter=json","--no-cache","--no-file-parallelism","--maxWorkers=1"):(r==="node-typescript"&&i.push("--experimental-strip-types"),i.push("--test","--test-reporter=tap",...t));const u=G("/usr/bin/bwrap",i,{env:{PATH:"/usr/bin:/bin"},encoding:"buffer",timeout:18e4,maxBuffer:4*1024*1024});if(u.error||u.signal||u.status===null)throw new Error("refactor-root-tests-sandbox-unavailable");const o=Buffer.concat([u.stdout??Buffer.alloc(0),u.stderr??Buffer.alloc(0)]),p=r!=="python"||/^Ran [1-9][0-9]* tests? in /m.test(o.toString("utf8"));let v=!0;if(r==="vitest")try{const h=JSON.parse((u.stdout??Buffer.alloc(0)).toString("utf8"));v=Number.isSafeInteger(h.numPassedTests)&&Number(h.numPassedTests)>0&&h.numTotalTests===h.numPassedTests&&h.numFailedTests===0}catch{v=!1}const g=r!=="cpp"||t.every((h,S)=>o.toString("utf8").split(`
16
- `).includes(`P222_CPP_TEST_PASSED_${S}`));return{passed:u.status===0&&p&&v&&g,outputSha256:b(o),canaryObserved:n!==void 0&&(r==="cpp"?o.toString("utf8").split(`
17
- `).includes(n):o.includes(Buffer.from(n)))}}finally{pt(w,{recursive:!0,force:!0})}}function xt(e,t,r,n,f,s,a,l,w="native"){if(!s)throw new Error("refactor-root-run-authorization-required");if(r.length<1||r.length>st||n.length>nt||!["native","vitest","cpp"].includes(w)||f==="baseline"&&l!==void 0||f==="after"&&(!l||!A.test(l)))throw new Error("refactor-root-tests-arguments-invalid");const c=V(r),d=V(n),i=w==="cpp"?"cpp":w==="vitest"?"vitest":c.every(o=>/(?:^|\/)test_[A-Za-z0-9_]+\.py$/.test(o))&&a.endsWith(".py")?"python":c.every(o=>o.endsWith(".test.ts"))&&a.endsWith(".ts")?"node-typescript":"node";if(new Set([...c,...d]).size!==c.length+d.length||c.some(o=>!D(o)||(i==="cpp"?!o.endsWith(".test.cpp"):i==="python"?!/(?:^|\/)test_[A-Za-z0-9_]+\.py$/.test(o)||N(o)!==N(c[0]):i==="vitest"?!/\.test\.(?:ts|js|mjs)$/.test(o):i==="node-typescript"?!o.endsWith(".test.ts"):!/\.test\.(?:mjs|cjs|js)$/.test(o)))||!D(a)||(i==="cpp"?!a.endsWith(".cpp")||a.endsWith(".test.cpp"):i==="python"?!a.endsWith(".py"):i==="vitest"?!/\.(?:ts|js|mjs)$/.test(a)||/\.test\./.test(a):i==="node-typescript"?!a.endsWith(".ts")||a.endsWith(".test.ts"):!/\.(?:mjs|cjs|js)$/.test(a))||d.some(o=>!D(o)||i==="python"&&/(?:^|\/)test_[A-Za-z0-9_]+\.py$/.test(o)))throw new Error("refactor-root-tests-path-invalid");const u=wt(e);try{const o=yt(u,t),{pendingDocumentationPaths:p}=ot(u,t),v=new Set(it(o));if(c.some(m=>!v.has(m))||!o.files.some(m=>m.path===a)||c.includes(a)||d.some(m=>v.has(m)))throw new Error("refactor-root-tests-path-invalid");for(const m of[...c,a,...d])Et(u,m,d);for(const m of d)vt(u,m);const g=i==="vitest"?Ot(u,c,d):void 0,h=i==="cpp"?ct():void 0,S=g?.sha256??h?.sha256,O=et(u,["diff","--name-only","-z","--no-ext-diff","--no-textconv"]).toString("utf8").split("\0").filter(m=>m&&!m.startsWith(".neocortex/")),E=et(u,["ls-files","--others","--exclude-standard","-z"]).toString("utf8").split("\0").filter(m=>m&&!m.startsWith(".neocortex/")),_=[...O,...E].filter(m=>!c.includes(m));if(f==="baseline"&&_.length>0||f==="after"&&_.length===0)throw new Error("refactor-root-tests-phase-invalid");const B=c.map(m=>({path:m,sha256:b(L(u,m))})),P=d.map(m=>({path:m,sha256:b(L(u,m))})),k=f==="after"?Y(u,l):null;if(k&&(k.admissionSha256!==t||k.sourceHead!==o.sourceHead||k.causalPath!==a||(k.schemaVersion===2?w!==k.runner||k.toolchainSha256!==S:w!=="native")||JSON.stringify(k.testFiles)!==JSON.stringify(B)||JSON.stringify(k.dependencies)!==JSON.stringify(P)))throw new Error("refactor-root-baseline-stale");const x=at(u,o,d,p);if(k&&b(x.files.get(a))===o.files.find(m=>m.path===a).sha256)throw new Error("refactor-root-tests-phase-invalid");const j=ft(x.files,c,i,void 0,g,h,a);let T=null,M=!1;if(j.passed){const m=`P222_REFACTOR_CAUSAL_${dt(16).toString("hex")}`,X=new Map(x.files);i!=="cpp"&&X.set(a,Buffer.concat([Buffer.from(i==="python"?`raise RuntimeError(${JSON.stringify(m)})
18
- `:`throw new Error(${JSON.stringify(m)});
19
- `),x.files.get(a)]));const U=ft(X,c,i,m,g,h,a);T=U.outputSha256,M=!U.passed&&U.canaryObserved}const J=ot(u,t);if(JSON.stringify(J.pendingDocumentationPaths)!==JSON.stringify(p))throw new Error("refactor-root-tests-source-stale");const F=at(u,o,d,p);if(h&&ct().sha256!==h.sha256)throw new Error("refactor-root-tests-toolchain-stale");if(x.candidateSha256!==F.candidateSha256)throw new Error("refactor-root-tests-source-stale");let I=l??null;return f==="baseline"&&j.passed&&M&&T&&(I=kt(u,{...i==="vitest"||i==="cpp"?{schemaVersion:2,runner:i,toolchainSha256:S}:{schemaVersion:1},kind:"root-refactor-characterization-baseline",admissionSha256:t,sourceHead:o.sourceHead,testFiles:B,dependencies:P,causalPath:a,candidateSha256:x.candidateSha256,outputSha256:j.outputSha256,causalOutputSha256:T})),Object.freeze({schemaVersion:1,kind:"root-refactor-characterization-observation",phase:f,admissionSha256:t,baselineSha256:I,candidateSha256:x.candidateSha256,outputSha256:j.outputSha256,causalOutputSha256:T,testFileCount:c.length,dependencyCount:d.length,testsPassedObserved:j.passed,causalFailureObserved:M,qaAccepted:!1,gitCommitAuthorized:!1,publicSafe:!0})}catch(o){throw o instanceof Error&&o.message.startsWith("refactor-root-")?o:new Error("refactor-root-tests-unverified")}}function Wt(e){let t="",r="",n,f="",s=!1,a="",l="native",w="plain";const c=[],d=[];try{if(e[0]!=="characterize")throw new Error("refactor-root-tests-arguments-invalid");for(let o=1;o<e.length;o++){const p=e[o],v=()=>{const g=e[++o];if(!g||g.startsWith("--"))throw new Error("refactor-root-tests-arguments-invalid");return g};if(p==="--project-root"&&!t)t=v();else if(p==="--admission-sha"&&!r)r=v();else if(p==="--baseline-sha"&&!n)n=v();else if(p==="--phase"&&!f){const g=v();if(g!=="baseline"&&g!=="after")throw new Error("refactor-root-tests-arguments-invalid");f=g}else if(p==="--test-path")c.push(v());else if(p==="--dependency")d.push(v());else if(p==="--causal-path"&&!a)a=v();else if(p==="--runner"&&l==="native"){const g=v();if(g!=="vitest"&&g!=="cpp")throw new Error("refactor-root-tests-arguments-invalid");l=g}else if(p==="--authorize-run"&&!s)s=!0;else if(p==="--format"&&w==="plain")w=v();else throw new Error("refactor-root-tests-arguments-invalid")}if(!t||!r||!f||!a||!["plain","json"].includes(w))throw new Error("refactor-root-tests-arguments-invalid");const i=xt(t,r,c,d,f,s,a,n,l),u=i.testsPassedObserved&&i.causalFailureObserved;return process.stdout.write(`${JSON.stringify({ok:u,observation:i})}
20
- `),u?0:1}catch(i){const u=i instanceof Error&&/^refactor-root-[a-z-]+$/.test(i.message)?i.message:"refactor-root-tests-unverified";return process.stdout.write(`${JSON.stringify({ok:!1,reasonCode:u})}
21
- `),1}}export{Y as readRootRefactorBaseline,Wt as refactorCharacterizationCliHandler,xt as runRootRefactorCharacterization,St as supportsNativeTypeStrippingRuntime,bt as supportsNativeTypeStrippingVersion};
1
+ /** Root-owned, no-network observation for admitted Node JS/TS, Python or C++ tests. */
2
+ import { spawnSync } from 'node:child_process';
3
+ import { createHash, randomBytes } from 'node:crypto';
4
+ import { closeSync, constants, existsSync, fsyncSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { basename, dirname, join, resolve } from 'node:path';
7
+ import { mutationQaGit } from './mutation-qa-git.js';
8
+ import { exactCommittedRefactorFile, privateRefactorDirectory, readPrivateRefactorAdmission, verifyRootRefactorForCharacterization } from './refactor-root.js';
9
+ const SHA256 = /^[a-f0-9]{64}$/;
10
+ const MAX_RUN_BYTES = 64 * 1024 * 1024;
11
+ const MAX_TESTS = 32;
12
+ const MAX_DEPENDENCIES = 128;
13
+ /** Node's native type-stripping switch first exists in 22.6.0. */
14
+ export function supportsNativeTypeStrippingVersion(version) {
15
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
16
+ if (!match)
17
+ return false;
18
+ const major = Number(match[1]);
19
+ const minor = Number(match[2]);
20
+ return major > 22 || (major === 22 && minor >= 6);
21
+ }
22
+ /** Distribution capability is separate from semver; some Node builds disable Amaro. */
23
+ export function supportsNativeTypeStrippingRuntime() {
24
+ return supportsNativeTypeStrippingVersion(process.versions.node)
25
+ && process.config.variables.node_use_amaro === true;
26
+ }
27
+ function digest(bytes) {
28
+ return createHash('sha256').update(bytes).digest('hex');
29
+ }
30
+ function safePath(path) {
31
+ return path.length > 0 && path.length <= 512
32
+ && /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*$/.test(path)
33
+ && path.split('/').every((part) => part !== '.' && part !== '..'
34
+ && !['.git', '.neocortex', 'node_modules'].includes(part.toLowerCase()));
35
+ }
36
+ function ordered(paths) {
37
+ return [...paths].sort((a, b) => Buffer.from(a).compare(Buffer.from(b)));
38
+ }
39
+ function requireExplicitJsModuleType(root, path, dependencies) {
40
+ if (!path.endsWith('.js') && !path.endsWith('.ts'))
41
+ return;
42
+ let directory = dirname(path);
43
+ while (true) {
44
+ const manifest = directory === '.' ? 'package.json' : `${directory}/package.json`;
45
+ if (existsSync(join(root, manifest))) {
46
+ if (!dependencies.includes(manifest))
47
+ throw new Error('refactor-root-tests-manifest-unbound');
48
+ let parsed;
49
+ try {
50
+ parsed = JSON.parse(physicalBytes(root, manifest).toString('utf8'));
51
+ }
52
+ catch {
53
+ throw new Error('refactor-root-tests-manifest-invalid');
54
+ }
55
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)
56
+ || !['module', 'commonjs'].includes(parsed.type)
57
+ || (path.endsWith('.ts') && parsed.type !== 'module')) {
58
+ throw new Error('refactor-root-tests-manifest-invalid');
59
+ }
60
+ return;
61
+ }
62
+ if (directory === '.')
63
+ break;
64
+ directory = dirname(directory);
65
+ }
66
+ throw new Error('refactor-root-tests-manifest-missing');
67
+ }
68
+ function physicalBytes(root, path) {
69
+ if (!safePath(path))
70
+ throw new Error('refactor-root-tests-path-invalid');
71
+ const physicalPath = join(root, path);
72
+ const stat = lstatSync(physicalPath);
73
+ if (realpathSync(physicalPath) !== physicalPath || !stat.isFile() || stat.isSymbolicLink()
74
+ || stat.size < 1 || stat.size > 8 * 1024 * 1024) {
75
+ throw new Error('refactor-root-tests-source-unverified');
76
+ }
77
+ const bytes = readFileSync(physicalPath);
78
+ if (bytes.length < 1 || bytes.length > 8 * 1024 * 1024 || bytes.includes(0)) {
79
+ throw new Error('refactor-root-tests-source-unverified');
80
+ }
81
+ new TextDecoder('utf-8', { fatal: true }).decode(bytes);
82
+ return bytes;
83
+ }
84
+ function baselinePath(root, sha256) {
85
+ if (!SHA256.test(sha256))
86
+ throw new Error('refactor-root-baseline-invalid');
87
+ return join(privateRefactorDirectory(root), `characterization-baseline-${sha256}.json`);
88
+ }
89
+ export function readRootRefactorBaseline(root, sha256) {
90
+ const stat = lstatSync(baselinePath(root, sha256));
91
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.uid !== process.getuid?.()
92
+ || (stat.mode & 0o077) !== 0 || stat.size < 100 || stat.size > 64 * 1024) {
93
+ throw new Error('refactor-root-baseline-invalid');
94
+ }
95
+ const raw = readFileSync(baselinePath(root, sha256));
96
+ if (raw.length > 64 * 1024)
97
+ throw new Error('refactor-root-baseline-invalid');
98
+ let parsed;
99
+ try {
100
+ parsed = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(raw));
101
+ }
102
+ catch {
103
+ throw new Error('refactor-root-baseline-invalid');
104
+ }
105
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
106
+ throw new Error('refactor-root-baseline-invalid');
107
+ }
108
+ const value = parsed;
109
+ const keys = ['schemaVersion', 'kind', 'admissionSha256', 'sourceHead', 'testFiles',
110
+ 'dependencies', 'causalPath', 'candidateSha256', 'outputSha256',
111
+ 'causalOutputSha256', 'baselineSha256'];
112
+ const filesValid = (files) => Array.isArray(files)
113
+ && files.every((file) => file && typeof file === 'object' && !Array.isArray(file)
114
+ && Object.keys(file).sort().join('\0') === 'path\0sha256'
115
+ && typeof file.path === 'string'
116
+ && typeof file.sha256 === 'string'
117
+ && safePath(file.path) && SHA256.test(file.sha256));
118
+ const boundRunner = value.schemaVersion === 2;
119
+ if (Object.keys(value).sort().join('\0') !== (boundRunner
120
+ ? [...keys, 'runner', 'toolchainSha256'] : keys).sort().join('\0')
121
+ || (value.schemaVersion !== 1 && value.schemaVersion !== 2)
122
+ || (boundRunner && (!['vitest', 'cpp'].includes(String(value.runner)) || !SHA256.test(String(value.toolchainSha256))))
123
+ || value.kind !== 'root-refactor-characterization-baseline'
124
+ || !SHA256.test(String(value.admissionSha256))
125
+ || typeof value.sourceHead !== 'string' || !/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(value.sourceHead)
126
+ || !filesValid(value.testFiles) || !filesValid(value.dependencies)
127
+ || value.testFiles.length < 1 || value.testFiles.length > MAX_TESTS
128
+ || value.dependencies.length > MAX_DEPENDENCIES
129
+ || typeof value.causalPath !== 'string' || !safePath(value.causalPath)
130
+ || !SHA256.test(String(value.candidateSha256)) || !SHA256.test(String(value.outputSha256))
131
+ || !SHA256.test(String(value.causalOutputSha256))
132
+ || value.baselineSha256 !== sha256)
133
+ throw new Error('refactor-root-baseline-invalid');
134
+ const tests = value.testFiles;
135
+ const dependencies = value.dependencies;
136
+ if (new Set([...tests, ...dependencies].map((file) => file.path)).size
137
+ !== tests.length + dependencies.length
138
+ || tests.map((file) => file.path).join('\0') !== ordered(tests.map((file) => file.path)).join('\0')
139
+ || dependencies.map((file) => file.path).join('\0')
140
+ !== ordered(dependencies.map((file) => file.path)).join('\0')) {
141
+ throw new Error('refactor-root-baseline-invalid');
142
+ }
143
+ const { baselineSha256: _digest, ...body } = value;
144
+ if (digest(JSON.stringify(body)) !== sha256
145
+ || !raw.equals(Buffer.from(`${JSON.stringify(value)}\n`))) {
146
+ throw new Error('refactor-root-baseline-invalid');
147
+ }
148
+ return value;
149
+ }
150
+ function persistBaseline(root, body) {
151
+ const baselineSha256 = digest(JSON.stringify(body));
152
+ const path = baselinePath(root, baselineSha256);
153
+ const record = { ...body, baselineSha256 };
154
+ const bytes = Buffer.from(`${JSON.stringify(record)}\n`);
155
+ if (existsSync(path)) {
156
+ readRootRefactorBaseline(root, baselineSha256);
157
+ }
158
+ else {
159
+ const handle = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL
160
+ | (constants.O_NOFOLLOW ?? 0), 0o600);
161
+ try {
162
+ writeFileSync(handle, bytes);
163
+ fsyncSync(handle);
164
+ }
165
+ finally {
166
+ closeSync(handle);
167
+ }
168
+ const directoryHandle = openSync(privateRefactorDirectory(root), constants.O_RDONLY | (constants.O_DIRECTORY ?? 0));
169
+ try {
170
+ fsyncSync(directoryHandle);
171
+ }
172
+ finally {
173
+ closeSync(directoryHandle);
174
+ }
175
+ }
176
+ readRootRefactorBaseline(root, baselineSha256);
177
+ return baselineSha256;
178
+ }
179
+ function admittedPaths(record) {
180
+ return [...record.files.map((file) => file.path),
181
+ ...(record.schemaVersion === 2 ? record.newFiles.map((file) => file.path) : [])];
182
+ }
183
+ function snapshot(root, record, dependencies, pendingDocumentationPaths = []) {
184
+ const files = new Map();
185
+ let totalBytes = 0;
186
+ for (const path of [...admittedPaths(record), ...dependencies]) {
187
+ if (pendingDocumentationPaths.includes(path)) {
188
+ try {
189
+ lstatSync(join(root, path));
190
+ }
191
+ catch (error) {
192
+ if (error.code === 'ENOENT')
193
+ continue;
194
+ throw error;
195
+ }
196
+ throw new Error('refactor-root-tests-source-stale');
197
+ }
198
+ const bytes = physicalBytes(root, path);
199
+ totalBytes += bytes.length;
200
+ if (totalBytes > MAX_RUN_BYTES)
201
+ throw new Error('refactor-root-tests-snapshot-too-large');
202
+ files.set(path, bytes);
203
+ }
204
+ const candidateSha256 = digest(JSON.stringify([
205
+ ...[...files].map(([path, bytes]) => [path, digest(bytes)]),
206
+ ...pendingDocumentationPaths.map(path => [path, null]),
207
+ ]));
208
+ return { files, candidateSha256 };
209
+ }
210
+ function exactCppToolchain() {
211
+ try {
212
+ const executable = realpathSync('/usr/bin/g++');
213
+ const stat = lstatSync(executable);
214
+ if (!executable.startsWith('/usr/') || !/^[A-Za-z0-9_./+-]+$/.test(executable)
215
+ || !stat.isFile() || stat.uid !== 0 || (stat.mode & 0o022) !== 0
216
+ || stat.size < 1 || stat.size > 32 * 1024 * 1024)
217
+ throw new Error('invalid compiler');
218
+ const result = spawnSync(executable, ['-dumpfullversion', '-dumpversion'], {
219
+ env: { PATH: '/usr/bin:/bin' }, encoding: 'utf8', timeout: 10_000, maxBuffer: 4096,
220
+ });
221
+ const version = result.stdout?.trim();
222
+ if (result.status !== 0 || !/^(?:11|12|13|14|15)\.[0-9]+\.[0-9]+$/.test(version)) {
223
+ throw new Error('unsupported compiler');
224
+ }
225
+ return { executable, sha256: digest(JSON.stringify([executable, version,
226
+ digest(readFileSync(executable))])) };
227
+ }
228
+ catch {
229
+ throw new Error('refactor-root-tests-toolchain-unverified');
230
+ }
231
+ }
232
+ /** Bind an installed runner to the committed manifest and lock, never run npm or project scripts. */
233
+ function exactVitestToolchain(root, tests, dependencies) {
234
+ let packagePath = '';
235
+ for (const test of tests) {
236
+ let directory = dirname(test);
237
+ let nearest = '';
238
+ while (true) {
239
+ const candidate = directory === '.' ? 'package.json' : `${directory}/package.json`;
240
+ if (existsSync(join(root, candidate))) {
241
+ nearest = candidate;
242
+ break;
243
+ }
244
+ if (directory === '.')
245
+ break;
246
+ directory = dirname(directory);
247
+ }
248
+ if (!nearest || packagePath && nearest !== packagePath) {
249
+ throw new Error('refactor-root-tests-toolchain-unverified');
250
+ }
251
+ packagePath = nearest;
252
+ }
253
+ if (!dependencies.includes(packagePath) || !dependencies.includes('package-lock.json')) {
254
+ throw new Error('refactor-root-tests-toolchain-unbound');
255
+ }
256
+ const manifestBytes = physicalBytes(root, packagePath);
257
+ const lockBytes = physicalBytes(root, 'package-lock.json');
258
+ let manifest;
259
+ let lock;
260
+ try {
261
+ manifest = JSON.parse(manifestBytes.toString('utf8'));
262
+ lock = JSON.parse(lockBytes.toString('utf8'));
263
+ }
264
+ catch {
265
+ throw new Error('refactor-root-tests-toolchain-unverified');
266
+ }
267
+ const declared = manifest.devDependencies?.vitest
268
+ ?? manifest.dependencies?.vitest;
269
+ const packages = lock.packages;
270
+ const lockWorkspace = packagePath === 'package.json' ? '' : dirname(packagePath);
271
+ const lockedPackage = packages?.[lockWorkspace];
272
+ const lockedRequest = lockedPackage?.devDependencies?.vitest
273
+ ?? lockedPackage?.dependencies?.vitest;
274
+ const lockedVersion = packages?.['node_modules/vitest']?.version;
275
+ const lockedViteVersion = packages?.['node_modules/vite']?.version;
276
+ if (typeof declared !== 'string' || declared.length > 80 || !/^[\^~]?[0-9]+\.[0-9]+\.[0-9]+$/.test(declared)
277
+ || declared !== lockedRequest || typeof lockedVersion !== 'string'
278
+ || !/^[0-9]+\.[0-9]+\.[0-9]+$/.test(lockedVersion)
279
+ || !lockedVersion.startsWith('3.2.')
280
+ || typeof lockedViteVersion !== 'string'
281
+ || !/^[0-9]+\.[0-9]+\.[0-9]+$/.test(lockedViteVersion)
282
+ || !supportsViteRunnerLoader(lockedViteVersion)) {
283
+ throw new Error('refactor-root-tests-toolchain-unverified');
284
+ }
285
+ const nodeModules = realpathSync(join(root, 'node_modules'));
286
+ const modulesStat = lstatSync(nodeModules);
287
+ if (basename(nodeModules) !== 'node_modules' || !modulesStat.isDirectory()
288
+ || modulesStat.isSymbolicLink() || modulesStat.uid !== process.getuid?.()) {
289
+ throw new Error('refactor-root-tests-toolchain-unverified');
290
+ }
291
+ const installedPath = join(nodeModules, 'vitest', 'package.json');
292
+ const entryPath = join(nodeModules, 'vitest', 'vitest.mjs');
293
+ const vitePath = join(nodeModules, 'vite', 'package.json');
294
+ const installedStat = lstatSync(installedPath);
295
+ const entryStat = lstatSync(entryPath);
296
+ const viteStat = lstatSync(vitePath);
297
+ if (!installedStat.isFile() || installedStat.isSymbolicLink()
298
+ || !entryStat.isFile() || entryStat.isSymbolicLink()
299
+ || !viteStat.isFile() || viteStat.isSymbolicLink()
300
+ || realpathSync(installedPath) !== installedPath || realpathSync(entryPath) !== entryPath
301
+ || realpathSync(vitePath) !== vitePath
302
+ || installedStat.size > 64 * 1024 || entryStat.size > 1024 * 1024
303
+ || viteStat.size > 64 * 1024) {
304
+ throw new Error('refactor-root-tests-toolchain-unverified');
305
+ }
306
+ const installedBytes = readFileSync(installedPath);
307
+ const entryBytes = readFileSync(entryPath);
308
+ const viteBytes = readFileSync(vitePath);
309
+ let installed;
310
+ let vite;
311
+ try {
312
+ installed = JSON.parse(installedBytes.toString('utf8'));
313
+ vite = JSON.parse(viteBytes.toString('utf8'));
314
+ }
315
+ catch {
316
+ throw new Error('refactor-root-tests-toolchain-unverified');
317
+ }
318
+ if (installed.name !== 'vitest' || installed.version !== lockedVersion
319
+ || vite.name !== 'vite' || vite.version !== lockedViteVersion) {
320
+ throw new Error('refactor-root-tests-toolchain-unverified');
321
+ }
322
+ return { nodeModules, sha256: digest(JSON.stringify([
323
+ digest(manifestBytes), digest(lockBytes), digest(installedBytes), digest(entryBytes),
324
+ digest(viteBytes),
325
+ ])) };
326
+ }
327
+ function supportsViteRunnerLoader(version) {
328
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
329
+ return !!match && Number(match[1]) <= 7 && (Number(match[1]) > 6
330
+ || Number(match[1]) === 6 && Number(match[2]) >= 1);
331
+ }
332
+ function sandboxCharacterizationTests(files, tests, runtime, canary, toolchain, cppToolchain, causalPath) {
333
+ if (runtime === 'python') {
334
+ const directory = dirname(tests[0]);
335
+ const discoverable = ordered([...files.keys()].filter((path) => (directory === '.' || path.startsWith(`${directory}/`))
336
+ && /(?:^|\/)test_[A-Za-z0-9_]+\.py$/.test(path)));
337
+ if (JSON.stringify(discoverable) !== JSON.stringify(tests)) {
338
+ throw new Error('refactor-root-tests-path-invalid');
339
+ }
340
+ }
341
+ const executable = realpathSync(runtime === 'cpp' ? '/usr/bin/dash'
342
+ : runtime === 'python' ? '/usr/bin/python3' : process.execPath);
343
+ if (runtime === 'node-typescript' && !supportsNativeTypeStrippingRuntime()) {
344
+ throw new Error('refactor-root-tests-toolchain-unavailable');
345
+ }
346
+ if (process.platform !== 'linux' || !existsSync('/usr/bin/bwrap')
347
+ || !lstatSync('/usr/bin/bwrap').isFile()
348
+ || !(executable.startsWith('/usr/') || executable.startsWith('/home/linuxbrew/.linuxbrew/'))) {
349
+ throw new Error('refactor-root-tests-sandbox-unavailable');
350
+ }
351
+ const scratch = mkdtempSync(join(tmpdir(), 'p222-refactor-characterization-'));
352
+ try {
353
+ const work = join(scratch, 'work');
354
+ mkdirSync(work, { mode: 0o700 });
355
+ for (const [path, bytes] of files) {
356
+ const target = join(work, path);
357
+ mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
358
+ writeFileSync(target, bytes, { mode: 0o600, flag: 'wx' });
359
+ }
360
+ if (runtime === 'vitest') {
361
+ if (!toolchain)
362
+ throw new Error('refactor-root-tests-toolchain-unavailable');
363
+ mkdirSync(join(work, 'node_modules'), { mode: 0o700 });
364
+ writeFileSync(join(work, 'p222-vitest.config.mjs'), `export default ${JSON.stringify({ test: { include: tests, environment: 'node',
365
+ silent: true, passWithNoTests: false, watch: false, maxWorkers: 1,
366
+ fileParallelism: false, pool: 'forks', coverage: { enabled: false } } })};\n`, { mode: 0o600, flag: 'wx' });
367
+ }
368
+ if (runtime === 'cpp') {
369
+ if (!cppToolchain || !causalPath || !files.has(causalPath)) {
370
+ throw new Error('refactor-root-tests-toolchain-unavailable');
371
+ }
372
+ const compiler = cppToolchain.executable;
373
+ const sources = ordered([...files.keys()].filter(path => path.endsWith('.cpp')
374
+ && path !== causalPath && !tests.includes(path))).map(path => `'/work/${path}'`);
375
+ if (canary)
376
+ writeFileSync(join(work, 'p222-cpp-causal.cpp'), '#include <cstdio>\n#include <cstdlib>\n'
377
+ + `extern "C" void __cyg_profile_func_enter(void*,void*) { std::fputs(${JSON.stringify(canary + '\n')},stderr); std::_Exit(86); }\n`
378
+ + 'extern "C" void __cyg_profile_func_exit(void*,void*) {}\n', { mode: 0o600, flag: 'wx' });
379
+ const script = ['set -eu', 'ulimit -c 0',
380
+ `'${compiler}' -std=c++17 -O0 -UNDEBUG -I/work ${canary
381
+ ? '-finstrument-functions -finstrument-functions-exclude-file-list=/usr/include ' : ''}`
382
+ + `-c '/work/${causalPath}' -o /tmp/p222-cpp-causal.o`,
383
+ ...tests.flatMap((test, index) => [
384
+ `'${compiler}' -std=c++17 -O0 -UNDEBUG -I/work '/work/${test}' ${sources.join(' ')} `
385
+ + `/tmp/p222-cpp-causal.o ${canary ? '/work/p222-cpp-causal.cpp ' : ''}-o /tmp/p222-cpp-test-${index}`,
386
+ `/tmp/p222-cpp-test-${index}`,
387
+ `printf 'P222_CPP_TEST_PASSED_${index}\\n'`,
388
+ ]),
389
+ ].join('\n') + '\n';
390
+ writeFileSync(join(work, 'p222-cpp-runner.sh'), script, { mode: 0o600, flag: 'wx' });
391
+ }
392
+ const etc = join(scratch, 'etc');
393
+ mkdirSync(etc, { mode: 0o700 });
394
+ writeFileSync(join(etc, 'passwd'), 'nobody:x:65534:65534:nobody:/tmp:/bin/sh\n', { mode: 0o600 });
395
+ writeFileSync(join(etc, 'group'), 'nogroup:x:65534:\n', { mode: 0o600 });
396
+ writeFileSync(join(etc, 'hosts'), '127.0.0.1 localhost\n', { mode: 0o600 });
397
+ writeFileSync(join(etc, 'nsswitch.conf'), 'hosts: files\n', { mode: 0o600 });
398
+ const args = ['--die-with-parent', '--new-session', '--unshare-all', '--clearenv',
399
+ '--setenv', 'HOME', '/tmp', '--setenv', 'TMPDIR', '/tmp',
400
+ '--setenv', 'PATH', '/home/linuxbrew/.linuxbrew/bin:/usr/bin:/bin',
401
+ '--setenv', 'CI', 'true', '--ro-bind', '/usr', '/usr'];
402
+ if (runtime === 'python')
403
+ args.push('--setenv', 'PYTHONPATH', '/work');
404
+ for (const path of ['/lib', '/lib64', '/bin']) {
405
+ if (existsSync(path))
406
+ args.push('--ro-bind', path, path);
407
+ }
408
+ if (existsSync('/home/linuxbrew/.linuxbrew')) {
409
+ args.push('--ro-bind', '/home/linuxbrew/.linuxbrew', '/home/linuxbrew/.linuxbrew');
410
+ }
411
+ args.push('--dir', '/etc');
412
+ for (const file of ['passwd', 'group', 'hosts', 'nsswitch.conf']) {
413
+ args.push('--ro-bind', join(etc, file), `/etc/${file}`);
414
+ }
415
+ args.push('--dev', '/dev', '--proc', '/proc', '--tmpfs', '/tmp', '--ro-bind', work, '/work');
416
+ if (runtime === 'vitest')
417
+ args.push('--ro-bind', toolchain.nodeModules, '/work/node_modules');
418
+ args.push('--chdir', '/work', '--', executable);
419
+ if (runtime === 'cpp') {
420
+ args.push('/work/p222-cpp-runner.sh');
421
+ }
422
+ else if (runtime === 'python') {
423
+ // A literal discovery directory and fixed pattern run only the admitted
424
+ // test files copied into the read-only snapshot. No project runner,
425
+ // site-packages or shell-controlled command line enters this boundary.
426
+ args.push('-B', '-S', '-m', 'unittest', 'discover', '-s', dirname(tests[0]), '-p', 'test_*.py');
427
+ }
428
+ else if (runtime === 'vitest') {
429
+ args.push('/work/node_modules/vitest/vitest.mjs', 'run', '--root', '/work', '--config', '/work/p222-vitest.config.mjs', '--configLoader=runner', '--reporter=json', '--no-cache', '--no-file-parallelism', '--maxWorkers=1');
430
+ }
431
+ else {
432
+ if (runtime === 'node-typescript')
433
+ args.push('--experimental-strip-types');
434
+ args.push('--test', '--test-reporter=tap', ...tests);
435
+ }
436
+ const result = spawnSync('/usr/bin/bwrap', args, {
437
+ env: { PATH: '/usr/bin:/bin' }, encoding: 'buffer',
438
+ timeout: 180_000, maxBuffer: 4 * 1024 * 1024,
439
+ });
440
+ if (result.error || result.signal || result.status === null) {
441
+ throw new Error('refactor-root-tests-sandbox-unavailable');
442
+ }
443
+ const output = Buffer.concat([result.stdout ?? Buffer.alloc(0), result.stderr ?? Buffer.alloc(0)]);
444
+ const pythonTestsObserved = runtime !== 'python'
445
+ || /^Ran [1-9][0-9]* tests? in /m.test(output.toString('utf8'));
446
+ let vitestTestsObserved = true;
447
+ if (runtime === 'vitest') {
448
+ try {
449
+ const report = JSON.parse((result.stdout ?? Buffer.alloc(0)).toString('utf8'));
450
+ vitestTestsObserved = Number.isSafeInteger(report.numPassedTests)
451
+ && Number(report.numPassedTests) > 0
452
+ && report.numTotalTests === report.numPassedTests
453
+ && report.numFailedTests === 0;
454
+ }
455
+ catch {
456
+ vitestTestsObserved = false;
457
+ }
458
+ }
459
+ const cppTestsObserved = runtime !== 'cpp' || tests.every((_, index) => output.toString('utf8').split('\n').includes(`P222_CPP_TEST_PASSED_${index}`));
460
+ return { passed: result.status === 0 && pythonTestsObserved && vitestTestsObserved && cppTestsObserved,
461
+ outputSha256: digest(output),
462
+ canaryObserved: canary !== undefined && (runtime === 'cpp'
463
+ ? output.toString('utf8').split('\n').includes(canary) : output.includes(Buffer.from(canary))) };
464
+ }
465
+ finally {
466
+ rmSync(scratch, { recursive: true, force: true });
467
+ }
468
+ }
469
+ export function runRootRefactorCharacterization(projectRoot, admissionSha256, testPaths, dependencyPaths, phase, authorizeRun, causalPath, baselineSha256, runner = 'native') {
470
+ if (!authorizeRun)
471
+ throw new Error('refactor-root-run-authorization-required');
472
+ if (testPaths.length < 1 || testPaths.length > MAX_TESTS || dependencyPaths.length > MAX_DEPENDENCIES
473
+ || !['native', 'vitest', 'cpp'].includes(runner)
474
+ || (phase === 'baseline' && baselineSha256 !== undefined)
475
+ || (phase === 'after' && (!baselineSha256 || !SHA256.test(baselineSha256)))) {
476
+ throw new Error('refactor-root-tests-arguments-invalid');
477
+ }
478
+ const tests = ordered(testPaths);
479
+ const dependencies = ordered(dependencyPaths);
480
+ const runtime = runner === 'cpp' ? 'cpp' : runner === 'vitest' ? 'vitest'
481
+ : tests.every((path) => /(?:^|\/)test_[A-Za-z0-9_]+\.py$/.test(path))
482
+ && causalPath.endsWith('.py') ? 'python'
483
+ : tests.every((path) => path.endsWith('.test.ts')) && causalPath.endsWith('.ts')
484
+ ? 'node-typescript' : 'node';
485
+ if (new Set([...tests, ...dependencies]).size !== tests.length + dependencies.length
486
+ || tests.some((path) => !safePath(path) || (runtime === 'cpp' ? !path.endsWith('.test.cpp') : runtime === 'python'
487
+ ? !/(?:^|\/)test_[A-Za-z0-9_]+\.py$/.test(path) || dirname(path) !== dirname(tests[0])
488
+ : runtime === 'vitest' ? !/\.test\.(?:ts|js|mjs)$/.test(path)
489
+ : runtime === 'node-typescript' ? !path.endsWith('.test.ts')
490
+ : !/\.test\.(?:mjs|cjs|js)$/.test(path)))
491
+ || !safePath(causalPath) || (runtime === 'cpp'
492
+ ? !causalPath.endsWith('.cpp') || causalPath.endsWith('.test.cpp') : runtime === 'python'
493
+ ? !causalPath.endsWith('.py') : runtime === 'vitest'
494
+ ? !/\.(?:ts|js|mjs)$/.test(causalPath) || /\.test\./.test(causalPath)
495
+ : runtime === 'node-typescript'
496
+ ? !causalPath.endsWith('.ts') || causalPath.endsWith('.test.ts')
497
+ : !/\.(?:mjs|cjs|js)$/.test(causalPath))
498
+ || dependencies.some((path) => !safePath(path) || runtime === 'python'
499
+ && /(?:^|\/)test_[A-Za-z0-9_]+\.py$/.test(path))) {
500
+ throw new Error('refactor-root-tests-path-invalid');
501
+ }
502
+ const root = resolve(projectRoot);
503
+ try {
504
+ const record = readPrivateRefactorAdmission(root, admissionSha256);
505
+ const { pendingDocumentationPaths } = verifyRootRefactorForCharacterization(root, admissionSha256);
506
+ const admitted = new Set(admittedPaths(record));
507
+ if (tests.some((path) => !admitted.has(path))
508
+ || !record.files.some((file) => file.path === causalPath)
509
+ || tests.includes(causalPath)
510
+ || dependencies.some((path) => admitted.has(path))) {
511
+ throw new Error('refactor-root-tests-path-invalid');
512
+ }
513
+ for (const path of [...tests, causalPath, ...dependencies]) {
514
+ requireExplicitJsModuleType(root, path, dependencies);
515
+ }
516
+ for (const path of dependencies)
517
+ exactCommittedRefactorFile(root, path);
518
+ const toolchain = runtime === 'vitest'
519
+ ? exactVitestToolchain(root, tests, dependencies) : undefined;
520
+ const cppToolchain = runtime === 'cpp' ? exactCppToolchain() : undefined;
521
+ const toolchainSha256 = toolchain?.sha256 ?? cppToolchain?.sha256;
522
+ const changed = mutationQaGit(root, ['diff', '--name-only', '-z', '--no-ext-diff', '--no-textconv'])
523
+ .toString('utf8').split('\0').filter((path) => path && !path.startsWith('.neocortex/'));
524
+ const untracked = mutationQaGit(root, ['ls-files', '--others', '--exclude-standard', '-z'])
525
+ .toString('utf8').split('\0').filter((path) => path && !path.startsWith('.neocortex/'));
526
+ const changedOutsideTests = [...changed, ...untracked].filter((path) => !tests.includes(path));
527
+ if ((phase === 'baseline' && changedOutsideTests.length > 0)
528
+ || (phase === 'after' && changedOutsideTests.length === 0)) {
529
+ throw new Error('refactor-root-tests-phase-invalid');
530
+ }
531
+ const testFiles = tests.map((path) => ({ path, sha256: digest(physicalBytes(root, path)) }));
532
+ const dependencyFiles = dependencies.map((path) => ({ path, sha256: digest(physicalBytes(root, path)) }));
533
+ const baseline = phase === 'after' ? readRootRefactorBaseline(root, baselineSha256) : null;
534
+ if (baseline && (baseline.admissionSha256 !== admissionSha256
535
+ || baseline.sourceHead !== record.sourceHead
536
+ || baseline.causalPath !== causalPath
537
+ || (baseline.schemaVersion === 2 ? runner !== baseline.runner
538
+ || baseline.toolchainSha256 !== toolchainSha256 : runner !== 'native')
539
+ || JSON.stringify(baseline.testFiles) !== JSON.stringify(testFiles)
540
+ || JSON.stringify(baseline.dependencies) !== JSON.stringify(dependencyFiles))) {
541
+ throw new Error('refactor-root-baseline-stale');
542
+ }
543
+ const before = snapshot(root, record, dependencies, pendingDocumentationPaths);
544
+ if (baseline && digest(before.files.get(causalPath))
545
+ === record.files.find((file) => file.path === causalPath).sha256) {
546
+ throw new Error('refactor-root-tests-phase-invalid');
547
+ }
548
+ const execution = sandboxCharacterizationTests(before.files, tests, runtime, undefined, toolchain, cppToolchain, causalPath);
549
+ let causalOutputSha256 = null;
550
+ let causalFailureObserved = false;
551
+ if (execution.passed) {
552
+ const canary = `P222_REFACTOR_CAUSAL_${randomBytes(16).toString('hex')}`;
553
+ const causalFiles = new Map(before.files);
554
+ if (runtime !== 'cpp')
555
+ causalFiles.set(causalPath, Buffer.concat([
556
+ Buffer.from(runtime === 'python'
557
+ ? `raise RuntimeError(${JSON.stringify(canary)})\n`
558
+ : `throw new Error(${JSON.stringify(canary)});\n`),
559
+ before.files.get(causalPath),
560
+ ]));
561
+ const causal = sandboxCharacterizationTests(causalFiles, tests, runtime, canary, toolchain, cppToolchain, causalPath);
562
+ causalOutputSha256 = causal.outputSha256;
563
+ causalFailureObserved = !causal.passed && causal.canaryObserved;
564
+ }
565
+ const revalidated = verifyRootRefactorForCharacterization(root, admissionSha256);
566
+ if (JSON.stringify(revalidated.pendingDocumentationPaths) !== JSON.stringify(pendingDocumentationPaths)) {
567
+ throw new Error('refactor-root-tests-source-stale');
568
+ }
569
+ const after = snapshot(root, record, dependencies, pendingDocumentationPaths);
570
+ if (cppToolchain && exactCppToolchain().sha256 !== cppToolchain.sha256) {
571
+ throw new Error('refactor-root-tests-toolchain-stale');
572
+ }
573
+ if (before.candidateSha256 !== after.candidateSha256) {
574
+ throw new Error('refactor-root-tests-source-stale');
575
+ }
576
+ let savedBaseline = baselineSha256 ?? null;
577
+ if (phase === 'baseline' && execution.passed && causalFailureObserved && causalOutputSha256) {
578
+ savedBaseline = persistBaseline(root, {
579
+ ...(runtime === 'vitest' || runtime === 'cpp'
580
+ ? { schemaVersion: 2, runner: runtime,
581
+ toolchainSha256: toolchainSha256 }
582
+ : { schemaVersion: 1 }),
583
+ kind: 'root-refactor-characterization-baseline',
584
+ admissionSha256, sourceHead: record.sourceHead,
585
+ testFiles, dependencies: dependencyFiles, causalPath,
586
+ candidateSha256: before.candidateSha256,
587
+ outputSha256: execution.outputSha256, causalOutputSha256,
588
+ });
589
+ }
590
+ return Object.freeze({ schemaVersion: 1, kind: 'root-refactor-characterization-observation',
591
+ phase, admissionSha256, baselineSha256: savedBaseline,
592
+ candidateSha256: before.candidateSha256, outputSha256: execution.outputSha256,
593
+ causalOutputSha256,
594
+ testFileCount: tests.length, dependencyCount: dependencies.length,
595
+ testsPassedObserved: execution.passed, causalFailureObserved, qaAccepted: false,
596
+ gitCommitAuthorized: false, publicSafe: true });
597
+ }
598
+ catch (error) {
599
+ if (error instanceof Error && error.message.startsWith('refactor-root-'))
600
+ throw error;
601
+ throw new Error('refactor-root-tests-unverified');
602
+ }
603
+ }
604
+ export function refactorCharacterizationCliHandler(args) {
605
+ let projectRoot = '';
606
+ let admissionSha256 = '';
607
+ let baselineSha256;
608
+ let phase = '';
609
+ let authorizeRun = false;
610
+ let causalPath = '';
611
+ let runner = 'native';
612
+ let format = 'plain';
613
+ const tests = [];
614
+ const dependencies = [];
615
+ try {
616
+ if (args[0] !== 'characterize')
617
+ throw new Error('refactor-root-tests-arguments-invalid');
618
+ for (let index = 1; index < args.length; index++) {
619
+ const token = args[index];
620
+ const next = () => {
621
+ const value = args[++index];
622
+ if (!value || value.startsWith('--'))
623
+ throw new Error('refactor-root-tests-arguments-invalid');
624
+ return value;
625
+ };
626
+ if (token === '--project-root' && !projectRoot)
627
+ projectRoot = next();
628
+ else if (token === '--admission-sha' && !admissionSha256)
629
+ admissionSha256 = next();
630
+ else if (token === '--baseline-sha' && !baselineSha256)
631
+ baselineSha256 = next();
632
+ else if (token === '--phase' && !phase) {
633
+ const value = next();
634
+ if (value !== 'baseline' && value !== 'after')
635
+ throw new Error('refactor-root-tests-arguments-invalid');
636
+ phase = value;
637
+ }
638
+ else if (token === '--test-path')
639
+ tests.push(next());
640
+ else if (token === '--dependency')
641
+ dependencies.push(next());
642
+ else if (token === '--causal-path' && !causalPath)
643
+ causalPath = next();
644
+ else if (token === '--runner' && runner === 'native') {
645
+ const value = next();
646
+ if (value !== 'vitest' && value !== 'cpp')
647
+ throw new Error('refactor-root-tests-arguments-invalid');
648
+ runner = value;
649
+ }
650
+ else if (token === '--authorize-run' && !authorizeRun)
651
+ authorizeRun = true;
652
+ else if (token === '--format' && format === 'plain')
653
+ format = next();
654
+ else
655
+ throw new Error('refactor-root-tests-arguments-invalid');
656
+ }
657
+ if (!projectRoot || !admissionSha256 || !phase || !causalPath || !['plain', 'json'].includes(format)) {
658
+ throw new Error('refactor-root-tests-arguments-invalid');
659
+ }
660
+ const observation = runRootRefactorCharacterization(projectRoot, admissionSha256, tests, dependencies, phase, authorizeRun, causalPath, baselineSha256, runner);
661
+ const ok = observation.testsPassedObserved && observation.causalFailureObserved;
662
+ process.stdout.write(`${JSON.stringify({ ok, observation })}\n`);
663
+ return ok ? 0 : 1;
664
+ }
665
+ catch (error) {
666
+ const reasonCode = error instanceof Error && /^refactor-root-[a-z-]+$/.test(error.message)
667
+ ? error.message : 'refactor-root-tests-unverified';
668
+ process.stdout.write(`${JSON.stringify({ ok: false, reasonCode })}\n`);
669
+ return 1;
670
+ }
671
+ }