@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 +1,122 @@
1
- import{execFileSync as v}from"node:child_process";import{createHash as S}from"node:crypto";import{chmodSync as I,existsSync as D,lstatSync as h,mkdtempSync as _,readFileSync as w,realpathSync as l}from"node:fs";import{isAbsolute as E,join as u,resolve as d}from"node:path";const y=".neocortex/pending-epic-claims.json";function p(e,t){const s={...process.env};for(const r of["GIT_DIR","GIT_WORK_TREE","GIT_INDEX_FILE","GIT_OBJECT_DIRECTORY","GIT_ALTERNATE_OBJECT_DIRECTORIES","GIT_COMMON_DIR","GIT_NAMESPACE","GIT_CEILING_DIRECTORIES"])delete s[r];return v("git",[...t],{cwd:e,env:s,stdio:["ignore","pipe","pipe"],timeout:3e4,maxBuffer:16*1024*1024})}function c(e,t){return p(e,t).toString("utf8").trim()}function m(e,t,s,r=!1){const n=p(e,["status","--porcelain=v1","-z","--untracked-files=all"]).toString("utf8");let a=!r;if(r)try{const i=h(u(e,".neocortex")),o=h(u(e,y));a=i.isDirectory()&&!i.isSymbolicLink()&&i.uid===process.getuid?.()&&(i.mode&18)===0&&o.isFile()&&!o.isSymbolicLink()&&o.uid===process.getuid?.()&&(o.mode&63)===0&&o.size>0&&o.size<=128*1024}catch{a=!1}return c(e,["rev-parse","HEAD"])===t&&c(e,["symbolic-ref","--quiet","--short","HEAD"])===s&&a&&(n.length===0||r&&n===`?? ${y}\0`)}function f(e){const t=u(e,".neocortex","state.json"),s=h(t);if(!s.isFile()||s.isSymbolicLink())throw new Error("state-invalid");return S("sha256").update(w(t)).digest("hex")}function g(e){try{const t=c(e,["rev-parse","--verify","HEAD"]);if(!/^[a-f0-9]{40,64}$/.test(t))throw new Error("head-invalid");return Object.freeze({headSha:t,stateSha256:f(e)})}catch{throw new Error("private-execution-project-snapshot-unverified")}}function b(e,t){try{if(e.baseSha!==t.headSha||c(e.sourceRoot,["rev-parse","--verify","HEAD"])!==t.headSha||f(e.sourceRoot)!==t.stateSha256||f(e.workspacePath)!==t.stateSha256)throw new Error("snapshot-mismatch")}catch{throw new Error("private-execution-project-snapshot-unverified")}}function C(e,t,s=!1){let r,n;try{if(process.platform!=="linux"||typeof process.getuid!="function"||!E(e)||!E(t))throw new Error("path-invalid");if(r=d(e),n=d(t),l(r)!==r||!h(r).isDirectory()||l(n)!==n||!h(n).isDirectory()||h(n).uid!==process.getuid()||(h(n).mode&18)!==0||c(r,["rev-parse","--show-toplevel"])!==r)throw new Error("path-invalid");const a=c(r,["rev-parse","HEAD"]),i=c(r,["symbolic-ref","--quiet","--short","HEAD"]);if(!/^[a-f0-9]{40,64}$/.test(a)||!i||!m(r,a,i,s))throw new Error("source-dirty");const o=_(u(n,"p222-private-yolo-"));if(I(o,448),p(o,["init","-q"]),p(o,["fetch","-q","--no-tags","--depth=1",r,a]),p(o,["checkout","-q","-b",i,"FETCH_HEAD"]),!m(r,a,i,s)||c(o,["rev-parse","HEAD"])!==a||c(o,["symbolic-ref","--quiet","--short","HEAD"])!==i||p(o,["status","--porcelain=v1","-z","--untracked-files=all"]).byteLength!==0||D(u(o,".git","objects","info","alternates")))throw new Error("physical-mismatch");return Object.freeze({schemaVersion:1,sourceRoot:r,workspacePath:o,baseSha:a,executionBranch:i})}catch{throw new Error("private-execution-isolated-workspace-unverified")}}export{y as PRIVATE_EPIC_PENDING_SIDECAR,b as assertPrivateProjectStepBaseline,g as capturePrivateProjectStepBaseline,C as preparePrivateYoloIsolatedWorkspace};
1
+ /** Independent checkout for a private YOLO agent; preparation has no root Git effects. */
2
+ import { execFileSync } from 'node:child_process';
3
+ import { createHash } from 'node:crypto';
4
+ import { chmodSync, existsSync, lstatSync, mkdtempSync, readFileSync, realpathSync } from 'node:fs';
5
+ import { isAbsolute, join, resolve } from 'node:path';
6
+ export const PRIVATE_EPIC_PENDING_SIDECAR = '.neocortex/pending-epic-claims.json';
7
+ function git(root, args) {
8
+ const environment = { ...process.env };
9
+ for (const key of [
10
+ 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY',
11
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_COMMON_DIR', 'GIT_NAMESPACE', 'GIT_CEILING_DIRECTORIES',
12
+ ])
13
+ delete environment[key];
14
+ return execFileSync('git', [...args], {
15
+ cwd: root, env: environment, stdio: ['ignore', 'pipe', 'pipe'],
16
+ timeout: 30_000, maxBuffer: 16 * 1024 * 1024,
17
+ });
18
+ }
19
+ function output(root, args) {
20
+ return git(root, args).toString('utf8').trim();
21
+ }
22
+ function cleanSource(root, headSha, branch, allowPendingEpicSidecar = false) {
23
+ const dirty = git(root, ['status', '--porcelain=v1', '-z', '--untracked-files=all']).toString('utf8');
24
+ let pendingValid = !allowPendingEpicSidecar;
25
+ if (allowPendingEpicSidecar) {
26
+ try {
27
+ const directory = lstatSync(join(root, '.neocortex'));
28
+ const entry = lstatSync(join(root, PRIVATE_EPIC_PENDING_SIDECAR));
29
+ pendingValid = directory.isDirectory() && !directory.isSymbolicLink()
30
+ && directory.uid === process.getuid?.() && (directory.mode & 0o022) === 0
31
+ && entry.isFile() && !entry.isSymbolicLink()
32
+ && entry.uid === process.getuid?.() && (entry.mode & 0o077) === 0
33
+ && entry.size > 0 && entry.size <= 128 * 1024;
34
+ }
35
+ catch {
36
+ pendingValid = false;
37
+ }
38
+ }
39
+ return output(root, ['rev-parse', 'HEAD']) === headSha
40
+ && output(root, ['symbolic-ref', '--quiet', '--short', 'HEAD']) === branch
41
+ && pendingValid
42
+ && (dirty.length === 0 || allowPendingEpicSidecar && dirty === `?? ${PRIVATE_EPIC_PENDING_SIDECAR}\0`);
43
+ }
44
+ function projectStateSha256(root) {
45
+ const statePath = join(root, '.neocortex', 'state.json');
46
+ const entry = lstatSync(statePath);
47
+ if (!entry.isFile() || entry.isSymbolicLink())
48
+ throw new Error('state-invalid');
49
+ return createHash('sha256').update(readFileSync(statePath)).digest('hex');
50
+ }
51
+ /** Pin the request's physical source before projecting a private project-step snapshot. */
52
+ export function capturePrivateProjectStepBaseline(projectRoot) {
53
+ try {
54
+ const headSha = output(projectRoot, ['rev-parse', '--verify', 'HEAD']);
55
+ if (!/^[a-f0-9]{40,64}$/.test(headSha))
56
+ throw new Error('head-invalid');
57
+ return Object.freeze({ headSha, stateSha256: projectStateSha256(projectRoot) });
58
+ }
59
+ catch {
60
+ throw new Error('private-execution-project-snapshot-unverified');
61
+ }
62
+ }
63
+ /** A later commit or state rewrite cannot execute against an older server lease. */
64
+ export function assertPrivateProjectStepBaseline(workspace, baseline) {
65
+ try {
66
+ if (workspace.baseSha !== baseline.headSha
67
+ || output(workspace.sourceRoot, ['rev-parse', '--verify', 'HEAD']) !== baseline.headSha
68
+ || projectStateSha256(workspace.sourceRoot) !== baseline.stateSha256
69
+ || projectStateSha256(workspace.workspacePath) !== baseline.stateSha256) {
70
+ throw new Error('snapshot-mismatch');
71
+ }
72
+ }
73
+ catch {
74
+ throw new Error('private-execution-project-snapshot-unverified');
75
+ }
76
+ }
77
+ /**
78
+ * Build a separate object store and index from a clean, committed checkout.
79
+ * The caller must retain the workspace until its bytes are independently
80
+ * admitted into the root; this function never stages, commits, or deletes it.
81
+ */
82
+ export function preparePrivateYoloIsolatedWorkspace(projectRoot, workspaceParent, allowPendingEpicSidecar = false) {
83
+ let root;
84
+ let parent;
85
+ try {
86
+ if (process.platform !== 'linux' || typeof process.getuid !== 'function'
87
+ || !isAbsolute(projectRoot) || !isAbsolute(workspaceParent)) {
88
+ throw new Error('path-invalid');
89
+ }
90
+ root = resolve(projectRoot);
91
+ parent = resolve(workspaceParent);
92
+ if (realpathSync(root) !== root || !lstatSync(root).isDirectory()
93
+ || realpathSync(parent) !== parent || !lstatSync(parent).isDirectory()
94
+ || lstatSync(parent).uid !== process.getuid()
95
+ || (lstatSync(parent).mode & 0o022) !== 0
96
+ || output(root, ['rev-parse', '--show-toplevel']) !== root) {
97
+ throw new Error('path-invalid');
98
+ }
99
+ const baseSha = output(root, ['rev-parse', 'HEAD']);
100
+ const executionBranch = output(root, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
101
+ if (!/^[a-f0-9]{40,64}$/.test(baseSha)
102
+ || !executionBranch || !cleanSource(root, baseSha, executionBranch, allowPendingEpicSidecar)) {
103
+ throw new Error('source-dirty');
104
+ }
105
+ const workspacePath = mkdtempSync(join(parent, 'p222-private-yolo-'));
106
+ chmodSync(workspacePath, 0o700);
107
+ git(workspacePath, ['init', '-q']);
108
+ git(workspacePath, ['fetch', '-q', '--no-tags', '--depth=1', root, baseSha]);
109
+ git(workspacePath, ['checkout', '-q', '-b', executionBranch, 'FETCH_HEAD']);
110
+ if (!cleanSource(root, baseSha, executionBranch, allowPendingEpicSidecar)
111
+ || output(workspacePath, ['rev-parse', 'HEAD']) !== baseSha
112
+ || output(workspacePath, ['symbolic-ref', '--quiet', '--short', 'HEAD']) !== executionBranch
113
+ || git(workspacePath, ['status', '--porcelain=v1', '-z', '--untracked-files=all']).byteLength !== 0
114
+ || existsSync(join(workspacePath, '.git', 'objects', 'info', 'alternates'))) {
115
+ throw new Error('physical-mismatch');
116
+ }
117
+ return Object.freeze({ schemaVersion: 1, sourceRoot: root, workspacePath, baseSha, executionBranch });
118
+ }
119
+ catch {
120
+ throw new Error('private-execution-isolated-workspace-unverified');
121
+ }
122
+ }
@@ -1,3 +1,409 @@
1
- import{spawnSync as O}from"node:child_process";import{createHash as Z}from"node:crypto";import{constants as z}from"node:fs";import{closeSync as J,existsSync as w,fstatSync as W,lstatSync as R,mkdirSync as G,mkdtempSync as K,openSync as Q,readFileSync as ee,readdirSync as te,realpathSync as se,rmSync as ne,writeFileSync as P}from"node:fs";import{tmpdir as oe}from"node:os";import{dirname as re,join as m,posix as T,resolve as ie}from"node:path";import{isDeepStrictEqual as ae}from"node:util";import{parse as F}from"@babel/parser";import{derivePactWriteSet as ce}from"./pact-write-set.js";import{validatePactTestSource as ue}from"./pact-test-source.js";import{inspectPactToolchain as pe,planPactToolchainManifest as le}from"./pact-toolchain.js";const fe=1024,de=16*1024*1024,me=2*1024*1024,I="P222_APP_CONSUMER_CAUSAL_CANARY",B=/^(?:src|app|lib|packages|clients)\//,ye=/\.(?:ts|tsx|js|jsx|mjs|cjs|json)$/;class U extends Error{reason;constructor(c){super(c),this.reason=c}}function n(p){throw new U(p)}function L(p,c,i,o,l=0,u=0){const s=p==="consumer-proven";return Object.freeze({schemaVersion:1,outcome:s?"consumer-proven":"unverified",reasonCode:p,sourceRevision:c,filesSha256:i,expectedInteractions:o,passedTests:l,mutationFailedTests:u,appConsumerProven:s,providerVerified:!1,qaAccepted:!1,publicSafe:!0})}function A(p,c,i){let o=p;for(const s of c.split("/").slice(0,-1)){o=m(o,s);const e=R(o);(!e.isDirectory()||e.isSymbolicLink())&&n("source-unsupported")}const l=m(p,c),u=Q(l,z.O_RDONLY|z.O_NOFOLLOW);try{const s=W(u);return(!s.isFile()||s.size<1||s.size>i)&&n("source-unsupported"),ee(u)}finally{J(u)}}function _(p,c){const i={PATH:process.env.PATH,HOME:process.env.HOME},o=O("git",c,{cwd:p,env:i,timeout:3e4,maxBuffer:2*1024*1024,stdio:["ignore","pipe","ignore"]});return(o.error||o.status!==0||!Buffer.isBuffer(o.stdout))&&n("source-unsupported"),o.stdout}function ge(p,c,i){const o=new Map,l=F(c,{sourceType:"module",plugins:["typescript"]});for(const u of l.program.body){if(u.type!=="ImportDeclaration"||u.source.value==="@pact-foundation/pact"||u.source.value==="vitest")continue;const s=u.source.value;/^\.\.?\/[A-Za-z0-9_./-]{1,240}$/.test(s)||n("source-unsupported");const e=T.normalize(T.join(T.dirname(p),s));(!B.test(e)||e.split("/").includes(".."))&&n("source-unsupported");const a=(/\.(?:ts|tsx|js|mjs)$/.test(e)?[e,...e.endsWith(".js")?[`${e.slice(0,-3)}.ts`,`${e.slice(0,-3)}.tsx`]:[]]:[`${e}.ts`,`${e}.tsx`,`${e}.js`,`${e}.mjs`,`${e}/index.ts`,`${e}/index.tsx`,`${e}/index.js`]).filter(f=>i.has(f));a.length!==1&&n("source-unsupported");const y=o.get(a[0])??new Set;for(const f of u.specifiers){f.type==="ImportNamespaceSpecifier"&&n("source-unsupported");const g=f.type==="ImportDefaultSpecifier"?"default":f.imported.type==="Identifier"?f.imported.name:f.imported.value;/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(g)||n("source-unsupported"),y.add(g)}y.size===0&&n("source-unsupported"),o.set(a[0],y)}return o}function he(p){const i=[F(p,{sourceType:"module",plugins:["typescript"]}).program];let o=null,l=0;for(;i.length>0;){const u=i.pop();if(!u||typeof u!="object"||Array.isArray(u))continue;++l>1e5&&n("source-unsupported");const s=u;if(s.type==="NewExpression"&&s.callee&&typeof s.callee=="object"&&s.callee.type==="Identifier"&&s.callee.name==="PactV3"){(o!==null||!Array.isArray(s.arguments)||s.arguments.length!==1)&&n("source-unsupported");const e=s.arguments[0];(!e||e.type!=="ObjectExpression"||!Array.isArray(e.properties))&&n("source-unsupported");let t="pacts",a=!1;for(const y of e.properties){const f=y;(f.type!=="ObjectProperty"||f.computed===!0)&&n("source-unsupported");const g=f.key;if((g.type==="Identifier"?g.name:g.type==="StringLiteral"?g.value:void 0)!=="dir")continue;a&&n("source-unsupported"),a=!0;const b=f.value;(b.type!=="StringLiteral"||typeof b.value!="string")&&n("source-unsupported"),b.value==="./pact-files"||b.value==="pact-files"?t="pact-files":b.value!=="./pacts"&&b.value!=="pacts"&&n("source-unsupported")}o=t}for(const[e,t]of Object.entries(s))["loc","extra","comments","leadingComments","trailingComments","innerComments"].includes(e)||(Array.isArray(t)?i.push(...t):t&&typeof t=="object"&&i.push(t))}return o===null&&n("source-unsupported"),o}function be(p,c){const i=["--die-with-parent","--new-session","--unshare-all","--clearenv","--setenv","HOME","/work","--setenv","TMPDIR","/tmp","--setenv","PATH","/home/linuxbrew/.linuxbrew/bin:/usr/bin:/bin","--setenv","CI","true","--setenv","NODE_ENV","test","--ro-bind","/usr","/usr","--ro-bind","/lib","/lib","--ro-bind","/lib64","/lib64","--ro-bind","/bin","/bin","--ro-bind","/etc","/etc"];return w("/home/linuxbrew/.linuxbrew")&&i.push("--ro-bind","/home/linuxbrew/.linuxbrew","/home/linuxbrew/.linuxbrew"),[...i,"--dev","/dev","--proc","/proc","--tmpfs","/tmp","--bind",p,"/work","--chdir","/work","--",process.execPath,"node_modules/vitest/vitest.mjs","run","--maxWorkers=1","tests/pact/consumer/","--reporter=json",`--outputFile=/work/${c}`]}function q(p,c){const i=O("/usr/bin/bwrap",be(p,c),{env:{PATH:"/usr/bin:/bin"},timeout:18e4,stdio:"ignore"});return(i.error||i.signal||i.status===null)&&n("sandbox-unavailable"),i.status}function X(p,c,i){w(m(p,c))||n("sandbox-unavailable");let o;try{o=JSON.parse(A(p,c,me).toString("utf8"))}catch{n("consumer-failed")}(!o||typeof o!="object"||Array.isArray(o))&&n("consumer-failed");const l=o.testResults;(!Array.isArray(l)||l.length!==i.length)&&n("consumer-failed");const u=new Map;for(const s of l){(!s||typeof s!="object"||Array.isArray(s))&&n("consumer-failed");const e=s;(typeof e.name!="string"||!i.some(t=>e.name===`/work/${t}`)||u.has(e.name)||!Array.isArray(e.assertionResults)||e.assertionResults.length<1)&&n("consumer-failed"),u.set(e.name,e)}return i.flatMap(s=>u.get(`/work/${s}`).assertionResults)}function ve(p,c){const i=p.split("/"),o=c.split("/");return i.length===o.length&&i.every((l,u)=>{const s=o[u];if(!l.includes("{"))return l===s;const e=/^([^{}]*)\{[^{}]+\}([^{}]*)$/.exec(l);return!!e&&s.startsWith(e[1])&&s.endsWith(e[2])&&s.length>e[1].length+e[2].length})}function Ae(p,c,i){const o=m(p,c);if(!w(o)||!R(o).isDirectory())return!1;const l=te(o).filter(e=>/^[A-Za-z0-9_.-]+\.json$/.test(e));if(l.length<1||l.length>16)return!1;const u=[];for(const e of l){let t;try{t=JSON.parse(A(o,e,1024*1024).toString("utf8"))}catch{return!1}if(!t||typeof t!="object"||Array.isArray(t))return!1;const a=t.interactions;if(!Array.isArray(a))return!1;for(const y of a){if(!y||typeof y!="object"||Array.isArray(y))return!1;const f=y.request;if(!f||typeof f!="object"||Array.isArray(f))return!1;const{method:g,path:h}=f;if(typeof g!="string"||typeof h!="string")return!1;u.push({method:g,path:h})}}const s=[...u];for(const e of i){const t=s.findIndex(a=>a.method.toUpperCase()===e.method&&ve(e.endpointPath,a.path));if(t===-1)return!1;s.splice(t,1)}return s.length===0}function Me(p,c,i){let o=null,l=null,u=0,s=0;const e=c.interactions.length;let t;try{(process.platform!=="linux"||!w("/usr/bin/bwrap"))&&n("sandbox-unavailable");const a=ie(p);(se(a)!==a||!R(a).isDirectory())&&n("source-unsupported"),o=_(a,["rev-parse","HEAD"]).toString("utf8").trim(),(!/^[a-f0-9]{40,64}$/.test(o)||_(a,["rev-parse","--show-toplevel"]).toString("utf8").trim()!==a)&&n("source-unsupported");const y=ce(a,i);(y.outcome!=="ready"||!ae(y.writeSet,c))&&n("source-unsupported"),pe(a,i).outcome!=="manifest-aligned"&&n("toolchain-unavailable"),le(a,i);const f=new Set(_(a,["ls-files","-z","--","src","app","lib","packages","clients"]).toString("utf8").split("\0").filter(Boolean)),g=[...f].filter(r=>B.test(r)&&ye.test(r));(g.length>fe||[...f].some(r=>r.includes("\\")||r.split("/").some(d=>!d||d==="."||d==="..")))&&n("source-unsupported");const h=c.interactions.map(r=>r.filePath);(new Set(h).size!==h.length||h.length<1)&&n("source-unsupported");const b=new Map,k=new Map;let x=null;for(const r of c.interactions){const d=A(a,r.filePath,262144),v=new TextDecoder("utf-8",{fatal:!0}).decode(d);ue(r.filePath,v,null,r,a).consumerBinding!=="app-import-candidate"&&n("app-binding-missing");const C=he(v);x!==null&&x!==C&&n("source-unsupported"),x=C;for(const[H,Y]of ge(r.filePath,v,f)){const N=k.get(H)??new Set;for(const V of Y)N.add(V);k.set(H,N)}b.set(r.filePath,d)}(k.size<1||x===null)&&n("app-binding-missing");const j=new Map([["package.json",A(a,"package.json",1024*1024)],["package-lock.json",A(a,"package-lock.json",8*1024*1024)],...b.entries()]);for(const r of g)j.set(r,A(a,r,1024*1024));[...j.values()].reduce((r,d)=>r+d.length,0)>de&&n("source-unsupported");const M=Z("sha256");for(const[r,d]of[...j.entries()].sort(([v],[S])=>v<S?-1:v>S?1:0))M.update(r).update("\0").update(d).update("\0");l=M.digest("hex"),t=K(m(oe(),"p222-pact-runtime-"));for(const r of["package.json","package-lock.json"])P(m(t,r),j.get(r),{mode:384});P(m(t,"user.npmrc"),"",{mode:384}),P(m(t,"global.npmrc"),"",{mode:384});const D=O("npm",["ci","--ignore-scripts","--no-audit","--no-fund","--registry=https://registry.npmjs.org/",`--userconfig=${m(t,"user.npmrc")}`,`--globalconfig=${m(t,"global.npmrc")}`],{cwd:t,env:{PATH:process.env.PATH,HOME:t,TMPDIR:t,npm_config_cache:m(t,".npm-cache"),CI:"true",NO_UPDATE_NOTIFIER:"1"},timeout:18e4,stdio:"ignore"});(D.error||D.status!==0)&&n("install-failed");for(const[r,d]of j)r==="package.json"||r==="package-lock.json"||(G(m(t,re(r)),{recursive:!0,mode:448}),P(m(t,r),d,{mode:384}));q(t,"positive.json")!==0&&n(w(m(t,"positive.json"))?"consumer-failed":"sandbox-unavailable");const $=X(t,"positive.json",h);($.some(r=>r.status!=="passed")||!Ae(t,x,c.interactions))&&n("pact-artifact-missing"),u=$.length;for(const[r,d]of k){const v=[...d].map(S=>S==="default"?`export default function(){throw new Error('${I}')}`:`export const ${S}=function(){throw new Error('${I}')}`).join(`
2
- `);P(m(t,r),`${v}
3
- `,{mode:384})}(q(t,"mutation.json")===0||!w(m(t,"mutation.json")))&&n("mutation-not-causal");const E=X(t,"mutation.json",h);return(E.length!==$.length||E.some(r=>r.status!=="failed"||!Array.isArray(r.failureMessages)||!r.failureMessages.some(d=>typeof d=="string"&&d.includes(I))))&&n("mutation-not-causal"),s=E.length,(_(a,["rev-parse","HEAD"]).toString("utf8").trim()!==o||[...j].some(([r,d])=>!A(a,r,8*1024*1024).equals(d)))&&n("source-unsupported"),L("consumer-proven",o,l,e,u,s)}catch(a){const y=a instanceof U?a.reason:"source-unsupported";return L(y,o,l,e,u,s)}finally{t&&ne(t,{recursive:!0,force:!0})}}export{Me as verifyPactConsumerRuntime};
1
+ /**
2
+ * Root-owned, local-only Pact consumer proof. Untrusted generated tests run in a
3
+ * Bubblewrap namespace containing copied, bounded project files and no host
4
+ * credentials or external network. No test text or process output is public.
5
+ */
6
+ import { spawnSync } from 'node:child_process';
7
+ import { createHash } from 'node:crypto';
8
+ import { constants } from 'node:fs';
9
+ import { closeSync, existsSync, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
10
+ import { tmpdir } from 'node:os';
11
+ import { dirname, join, posix, resolve } from 'node:path';
12
+ import { isDeepStrictEqual } from 'node:util';
13
+ import { parse } from '@babel/parser';
14
+ import { derivePactWriteSet } from './pact-write-set.js';
15
+ import { validatePactTestSource } from './pact-test-source.js';
16
+ import { inspectPactToolchain, planPactToolchainManifest } from './pact-toolchain.js';
17
+ const MAX_SOURCE_FILES = 1024;
18
+ const MAX_SOURCE_BYTES = 16 * 1024 * 1024;
19
+ const MAX_REPORT_BYTES = 2 * 1024 * 1024;
20
+ const CANARY = 'P222_APP_CONSUMER_CAUSAL_CANARY';
21
+ const SOURCE_PREFIX = /^(?:src|app|lib|packages|clients)\//;
22
+ const SOURCE_SUFFIX = /\.(?:ts|tsx|js|jsx|mjs|cjs|json)$/;
23
+ class RuntimeFailure extends Error {
24
+ reason;
25
+ constructor(reason) {
26
+ super(reason);
27
+ this.reason = reason;
28
+ }
29
+ }
30
+ function fail(reason) { throw new RuntimeFailure(reason); }
31
+ function result(reasonCode, revision, digest, expectedInteractions, passedTests = 0, mutationFailedTests = 0) {
32
+ const proven = reasonCode === 'consumer-proven';
33
+ return Object.freeze({ schemaVersion: 1, outcome: proven ? 'consumer-proven' : 'unverified',
34
+ reasonCode, sourceRevision: revision, filesSha256: digest, expectedInteractions,
35
+ passedTests, mutationFailedTests, appConsumerProven: proven,
36
+ providerVerified: false, qaAccepted: false, publicSafe: true });
37
+ }
38
+ function regularBytes(root, relative, cap) {
39
+ let current = root;
40
+ for (const part of relative.split('/').slice(0, -1)) {
41
+ current = join(current, part);
42
+ const entry = lstatSync(current);
43
+ if (!entry.isDirectory() || entry.isSymbolicLink())
44
+ fail('source-unsupported');
45
+ }
46
+ const path = join(root, relative);
47
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
48
+ try {
49
+ const entry = fstatSync(fd);
50
+ if (!entry.isFile() || entry.size < 1 || entry.size > cap)
51
+ fail('source-unsupported');
52
+ return readFileSync(fd);
53
+ }
54
+ finally {
55
+ closeSync(fd);
56
+ }
57
+ }
58
+ function git(root, args) {
59
+ const env = { PATH: process.env.PATH, HOME: process.env.HOME };
60
+ const command = spawnSync('git', args, { cwd: root, env, timeout: 30_000,
61
+ maxBuffer: 2 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] });
62
+ if (command.error || command.status !== 0 || !Buffer.isBuffer(command.stdout))
63
+ fail('source-unsupported');
64
+ return command.stdout;
65
+ }
66
+ function appImportBindings(path, source, tracked) {
67
+ const modules = new Map();
68
+ const ast = parse(source, { sourceType: 'module', plugins: ['typescript'] });
69
+ for (const statement of ast.program.body) {
70
+ if (statement.type !== 'ImportDeclaration'
71
+ || statement.source.value === '@pact-foundation/pact'
72
+ || statement.source.value === 'vitest')
73
+ continue;
74
+ const specifier = statement.source.value;
75
+ if (!/^\.\.?\/[A-Za-z0-9_./-]{1,240}$/.test(specifier))
76
+ fail('source-unsupported');
77
+ const relative = posix.normalize(posix.join(posix.dirname(path), specifier));
78
+ if (!SOURCE_PREFIX.test(relative) || relative.split('/').includes('..'))
79
+ fail('source-unsupported');
80
+ const candidates = /\.(?:ts|tsx|js|mjs)$/.test(relative)
81
+ ? [relative, ...(relative.endsWith('.js') ? [`${relative.slice(0, -3)}.ts`,
82
+ `${relative.slice(0, -3)}.tsx`] : [])]
83
+ : [`${relative}.ts`, `${relative}.tsx`, `${relative}.js`, `${relative}.mjs`,
84
+ `${relative}/index.ts`, `${relative}/index.tsx`, `${relative}/index.js`];
85
+ const matches = candidates.filter((candidate) => tracked.has(candidate));
86
+ if (matches.length !== 1)
87
+ fail('source-unsupported');
88
+ const names = modules.get(matches[0]) ?? new Set();
89
+ for (const imported of statement.specifiers) {
90
+ if (imported.type === 'ImportNamespaceSpecifier')
91
+ fail('source-unsupported');
92
+ const name = imported.type === 'ImportDefaultSpecifier' ? 'default'
93
+ : imported.imported.type === 'Identifier' ? imported.imported.name : imported.imported.value;
94
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name))
95
+ fail('source-unsupported');
96
+ names.add(name);
97
+ }
98
+ if (names.size === 0)
99
+ fail('source-unsupported');
100
+ modules.set(matches[0], names);
101
+ }
102
+ return modules;
103
+ }
104
+ /** Pact's default output or the generator's explicit local output, never a dynamic path. */
105
+ function pactArtifactDirectory(source) {
106
+ const ast = parse(source, { sourceType: 'module', plugins: ['typescript'] });
107
+ const stack = [ast.program];
108
+ let directory = null;
109
+ let visited = 0;
110
+ while (stack.length > 0) {
111
+ const node = stack.pop();
112
+ if (!node || typeof node !== 'object' || Array.isArray(node))
113
+ continue;
114
+ if (++visited > 100_000)
115
+ fail('source-unsupported');
116
+ const value = node;
117
+ if (value.type === 'NewExpression' && value.callee && typeof value.callee === 'object'
118
+ && value.callee.type === 'Identifier'
119
+ && value.callee.name === 'PactV3') {
120
+ if (directory !== null || !Array.isArray(value.arguments) || value.arguments.length !== 1) {
121
+ fail('source-unsupported');
122
+ }
123
+ const options = value.arguments[0];
124
+ if (!options || options.type !== 'ObjectExpression' || !Array.isArray(options.properties)) {
125
+ fail('source-unsupported');
126
+ }
127
+ let selected = 'pacts';
128
+ let seenDir = false;
129
+ for (const entry of options.properties) {
130
+ const property = entry;
131
+ if (property.type !== 'ObjectProperty' || property.computed === true) {
132
+ fail('source-unsupported');
133
+ }
134
+ const key = property.key;
135
+ const name = key.type === 'Identifier' ? key.name
136
+ : key.type === 'StringLiteral' ? key.value : undefined;
137
+ if (name !== 'dir')
138
+ continue;
139
+ if (seenDir)
140
+ fail('source-unsupported');
141
+ seenDir = true;
142
+ const literal = property.value;
143
+ if (literal.type !== 'StringLiteral' || typeof literal.value !== 'string') {
144
+ fail('source-unsupported');
145
+ }
146
+ if (literal.value === './pact-files' || literal.value === 'pact-files')
147
+ selected = 'pact-files';
148
+ else if (literal.value !== './pacts' && literal.value !== 'pacts')
149
+ fail('source-unsupported');
150
+ }
151
+ directory = selected;
152
+ }
153
+ for (const [key, child] of Object.entries(value)) {
154
+ if (['loc', 'extra', 'comments', 'leadingComments', 'trailingComments', 'innerComments'].includes(key))
155
+ continue;
156
+ if (Array.isArray(child))
157
+ stack.push(...child);
158
+ else if (child && typeof child === 'object')
159
+ stack.push(child);
160
+ }
161
+ }
162
+ if (directory === null)
163
+ fail('source-unsupported');
164
+ return directory;
165
+ }
166
+ function sandboxArgs(scratch, report) {
167
+ const args = ['--die-with-parent', '--new-session', '--unshare-all', '--clearenv',
168
+ '--setenv', 'HOME', '/work', '--setenv', 'TMPDIR', '/tmp',
169
+ '--setenv', 'PATH', '/home/linuxbrew/.linuxbrew/bin:/usr/bin:/bin',
170
+ '--setenv', 'CI', 'true', '--setenv', 'NODE_ENV', 'test',
171
+ '--ro-bind', '/usr', '/usr', '--ro-bind', '/lib', '/lib',
172
+ '--ro-bind', '/lib64', '/lib64', '--ro-bind', '/bin', '/bin',
173
+ '--ro-bind', '/etc', '/etc'];
174
+ if (existsSync('/home/linuxbrew/.linuxbrew')) {
175
+ args.push('--ro-bind', '/home/linuxbrew/.linuxbrew', '/home/linuxbrew/.linuxbrew');
176
+ }
177
+ return [...args, '--dev', '/dev', '--proc', '/proc', '--tmpfs', '/tmp',
178
+ '--bind', scratch, '/work', '--chdir', '/work', '--', process.execPath,
179
+ 'node_modules/vitest/vitest.mjs', 'run', '--maxWorkers=1',
180
+ 'tests/pact/consumer/', '--reporter=json', `--outputFile=/work/${report}`];
181
+ }
182
+ function runSandbox(scratch, report) {
183
+ const command = spawnSync('/usr/bin/bwrap', sandboxArgs(scratch, report), {
184
+ env: { PATH: '/usr/bin:/bin' }, timeout: 180_000,
185
+ stdio: 'ignore',
186
+ });
187
+ if (command.error || command.signal || command.status === null)
188
+ fail('sandbox-unavailable');
189
+ return command.status;
190
+ }
191
+ function reportAssertions(scratch, name, paths) {
192
+ if (!existsSync(join(scratch, name)))
193
+ fail('sandbox-unavailable');
194
+ let parsed;
195
+ try {
196
+ parsed = JSON.parse(regularBytes(scratch, name, MAX_REPORT_BYTES).toString('utf8'));
197
+ }
198
+ catch {
199
+ fail('consumer-failed');
200
+ }
201
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
202
+ fail('consumer-failed');
203
+ const files = parsed.testResults;
204
+ if (!Array.isArray(files) || files.length !== paths.length)
205
+ fail('consumer-failed');
206
+ const byPath = new Map();
207
+ for (const value of files) {
208
+ if (!value || typeof value !== 'object' || Array.isArray(value))
209
+ fail('consumer-failed');
210
+ const file = value;
211
+ if (typeof file.name !== 'string' || !paths.some((path) => file.name === `/work/${path}`)
212
+ || byPath.has(file.name) || !Array.isArray(file.assertionResults)
213
+ || file.assertionResults.length < 1)
214
+ fail('consumer-failed');
215
+ byPath.set(file.name, file);
216
+ }
217
+ return paths.flatMap((path) => byPath.get(`/work/${path}`).assertionResults);
218
+ }
219
+ function matchesPath(template, actual) {
220
+ const expected = template.split('/');
221
+ const candidate = actual.split('/');
222
+ return expected.length === candidate.length && expected.every((segment, index) => {
223
+ const value = candidate[index];
224
+ if (!segment.includes('{'))
225
+ return segment === value;
226
+ const parameter = /^([^{}]*)\{[^{}]+\}([^{}]*)$/.exec(segment);
227
+ return !!parameter && value.startsWith(parameter[1]) && value.endsWith(parameter[2])
228
+ && value.length > parameter[1].length + parameter[2].length;
229
+ });
230
+ }
231
+ function pactInteractions(scratch, artifactDirectory, expected) {
232
+ const dir = join(scratch, artifactDirectory);
233
+ if (!existsSync(dir) || !lstatSync(dir).isDirectory())
234
+ return false;
235
+ const files = readdirSync(dir).filter((path) => /^[A-Za-z0-9_.-]+\.json$/.test(path));
236
+ if (files.length < 1 || files.length > 16)
237
+ return false;
238
+ const actual = [];
239
+ for (const file of files) {
240
+ let parsed;
241
+ try {
242
+ parsed = JSON.parse(regularBytes(dir, file, 1024 * 1024).toString('utf8'));
243
+ }
244
+ catch {
245
+ return false;
246
+ }
247
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
248
+ return false;
249
+ const interactions = parsed.interactions;
250
+ if (!Array.isArray(interactions))
251
+ return false;
252
+ for (const interaction of interactions) {
253
+ if (!interaction || typeof interaction !== 'object' || Array.isArray(interaction))
254
+ return false;
255
+ const request = interaction.request;
256
+ if (!request || typeof request !== 'object' || Array.isArray(request))
257
+ return false;
258
+ const { method, path } = request;
259
+ if (typeof method !== 'string' || typeof path !== 'string')
260
+ return false;
261
+ actual.push({ method, path });
262
+ }
263
+ }
264
+ const remaining = [...actual];
265
+ for (const row of expected) {
266
+ const match = remaining.findIndex((item) => item.method.toUpperCase() === row.method
267
+ && matchesPath(row.endpointPath, item.path));
268
+ if (match === -1)
269
+ return false;
270
+ remaining.splice(match, 1);
271
+ }
272
+ return remaining.length === 0;
273
+ }
274
+ /** Only aggregate causal evidence leaves this process; private reports are removed. */
275
+ export function verifyPactConsumerRuntime(projectRoot, expectedWriteSet, generateProvider) {
276
+ let revision = null;
277
+ let filesSha256 = null;
278
+ let passedTests = 0;
279
+ let mutationFailedTests = 0;
280
+ const expectedInteractions = expectedWriteSet.interactions.length;
281
+ let scratch;
282
+ try {
283
+ if (process.platform !== 'linux' || !existsSync('/usr/bin/bwrap'))
284
+ fail('sandbox-unavailable');
285
+ const root = resolve(projectRoot);
286
+ if (realpathSync(root) !== root || !lstatSync(root).isDirectory())
287
+ fail('source-unsupported');
288
+ revision = git(root, ['rev-parse', 'HEAD']).toString('utf8').trim();
289
+ if (!/^[a-f0-9]{40,64}$/.test(revision)
290
+ || git(root, ['rev-parse', '--show-toplevel']).toString('utf8').trim() !== root)
291
+ fail('source-unsupported');
292
+ const projection = derivePactWriteSet(root, generateProvider);
293
+ if (projection.outcome !== 'ready' || !isDeepStrictEqual(projection.writeSet, expectedWriteSet)) {
294
+ fail('source-unsupported');
295
+ }
296
+ if (inspectPactToolchain(root, generateProvider).outcome !== 'manifest-aligned') {
297
+ fail('toolchain-unavailable');
298
+ }
299
+ planPactToolchainManifest(root, generateProvider);
300
+ const tracked = new Set(git(root, ['ls-files', '-z', '--', 'src', 'app', 'lib', 'packages', 'clients'])
301
+ .toString('utf8').split('\0').filter(Boolean));
302
+ const sourcePaths = [...tracked].filter((path) => SOURCE_PREFIX.test(path) && SOURCE_SUFFIX.test(path));
303
+ if (sourcePaths.length > MAX_SOURCE_FILES || [...tracked].some((path) => path.includes('\\') || path.split('/').some((part) => !part || part === '.' || part === '..'))) {
304
+ fail('source-unsupported');
305
+ }
306
+ const consumerPaths = expectedWriteSet.interactions.map((row) => row.filePath);
307
+ if (new Set(consumerPaths).size !== consumerPaths.length || consumerPaths.length < 1) {
308
+ fail('source-unsupported');
309
+ }
310
+ const testBytes = new Map();
311
+ const mutation = new Map();
312
+ let artifactDirectory = null;
313
+ for (const row of expectedWriteSet.interactions) {
314
+ const bytes = regularBytes(root, row.filePath, 256 * 1024);
315
+ const source = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
316
+ const inspected = validatePactTestSource(row.filePath, source, null, row, root);
317
+ if (inspected.consumerBinding !== 'app-import-candidate')
318
+ fail('app-binding-missing');
319
+ const testDirectory = pactArtifactDirectory(source);
320
+ if (artifactDirectory !== null && artifactDirectory !== testDirectory)
321
+ fail('source-unsupported');
322
+ artifactDirectory = testDirectory;
323
+ for (const [path, names] of appImportBindings(row.filePath, source, tracked)) {
324
+ const set = mutation.get(path) ?? new Set();
325
+ for (const name of names)
326
+ set.add(name);
327
+ mutation.set(path, set);
328
+ }
329
+ testBytes.set(row.filePath, bytes);
330
+ }
331
+ if (mutation.size < 1 || artifactDirectory === null)
332
+ fail('app-binding-missing');
333
+ const files = new Map([
334
+ ['package.json', regularBytes(root, 'package.json', 1024 * 1024)],
335
+ ['package-lock.json', regularBytes(root, 'package-lock.json', 8 * 1024 * 1024)],
336
+ ...[...testBytes.entries()],
337
+ ]);
338
+ for (const path of sourcePaths)
339
+ files.set(path, regularBytes(root, path, 1024 * 1024));
340
+ if ([...files.values()].reduce((sum, bytes) => sum + bytes.length, 0) > MAX_SOURCE_BYTES) {
341
+ fail('source-unsupported');
342
+ }
343
+ const hash = createHash('sha256');
344
+ for (const [path, bytes] of [...files.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
345
+ hash.update(path).update('\0').update(bytes).update('\0');
346
+ }
347
+ filesSha256 = hash.digest('hex');
348
+ scratch = mkdtempSync(join(tmpdir(), 'p222-pact-runtime-'));
349
+ for (const path of ['package.json', 'package-lock.json']) {
350
+ writeFileSync(join(scratch, path), files.get(path), { mode: 0o600 });
351
+ }
352
+ writeFileSync(join(scratch, 'user.npmrc'), '', { mode: 0o600 });
353
+ writeFileSync(join(scratch, 'global.npmrc'), '', { mode: 0o600 });
354
+ const installed = spawnSync('npm', ['ci', '--ignore-scripts', '--no-audit', '--no-fund',
355
+ '--registry=https://registry.npmjs.org/', `--userconfig=${join(scratch, 'user.npmrc')}`,
356
+ `--globalconfig=${join(scratch, 'global.npmrc')}`], {
357
+ cwd: scratch, env: { PATH: process.env.PATH, HOME: scratch, TMPDIR: scratch,
358
+ npm_config_cache: join(scratch, '.npm-cache'), CI: 'true', NO_UPDATE_NOTIFIER: '1' },
359
+ timeout: 180_000, stdio: 'ignore',
360
+ });
361
+ if (installed.error || installed.status !== 0)
362
+ fail('install-failed');
363
+ for (const [path, bytes] of files) {
364
+ if (path === 'package.json' || path === 'package-lock.json')
365
+ continue;
366
+ mkdirSync(join(scratch, dirname(path)), { recursive: true, mode: 0o700 });
367
+ writeFileSync(join(scratch, path), bytes, { mode: 0o600 });
368
+ }
369
+ const positive = runSandbox(scratch, 'positive.json');
370
+ if (positive !== 0)
371
+ fail(existsSync(join(scratch, 'positive.json'))
372
+ ? 'consumer-failed' : 'sandbox-unavailable');
373
+ const assertions = reportAssertions(scratch, 'positive.json', consumerPaths);
374
+ if (assertions.some((row) => row.status !== 'passed')
375
+ || !pactInteractions(scratch, artifactDirectory, expectedWriteSet.interactions)) {
376
+ fail('pact-artifact-missing');
377
+ }
378
+ passedTests = assertions.length;
379
+ for (const [path, names] of mutation) {
380
+ const stub = [...names].map((name) => name === 'default'
381
+ ? `export default function(){throw new Error('${CANARY}')}`
382
+ : `export const ${name}=function(){throw new Error('${CANARY}')}`).join('\n');
383
+ writeFileSync(join(scratch, path), `${stub}\n`, { mode: 0o600 });
384
+ }
385
+ const negative = runSandbox(scratch, 'mutation.json');
386
+ if (negative === 0 || !existsSync(join(scratch, 'mutation.json')))
387
+ fail('mutation-not-causal');
388
+ const mutated = reportAssertions(scratch, 'mutation.json', consumerPaths);
389
+ if (mutated.length !== assertions.length || mutated.some((row) => row.status !== 'failed'
390
+ || !Array.isArray(row.failureMessages)
391
+ || !row.failureMessages.some((message) => typeof message === 'string' && message.includes(CANARY)))) {
392
+ fail('mutation-not-causal');
393
+ }
394
+ mutationFailedTests = mutated.length;
395
+ if (git(root, ['rev-parse', 'HEAD']).toString('utf8').trim() !== revision
396
+ || [...files].some(([path, bytes]) => !regularBytes(root, path, 8 * 1024 * 1024).equals(bytes))) {
397
+ fail('source-unsupported');
398
+ }
399
+ return result('consumer-proven', revision, filesSha256, expectedInteractions, passedTests, mutationFailedTests);
400
+ }
401
+ catch (error) {
402
+ const reason = error instanceof RuntimeFailure ? error.reason : 'source-unsupported';
403
+ return result(reason, revision, filesSha256, expectedInteractions, passedTests, mutationFailedTests);
404
+ }
405
+ finally {
406
+ if (scratch)
407
+ rmSync(scratch, { recursive: true, force: true });
408
+ }
409
+ }