@ornexus/neocortex 4.60.66 → 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 (236) hide show
  1. package/dist/release-source.json +1 -1
  2. package/dist/sbom.cdx.json +1598 -1640
  3. package/install.ps1 +1 -1
  4. package/install.sh +2 -1
  5. package/package.json +10 -7
  6. package/packages/client/dist/adapters/adapter-registry.js +108 -1
  7. package/packages/client/dist/adapters/antigravity-adapter.js +77 -2
  8. package/packages/client/dist/adapters/claude-code-adapter.js +79 -3
  9. package/packages/client/dist/adapters/codex-adapter.js +80 -2
  10. package/packages/client/dist/adapters/cursor-adapter.js +115 -4
  11. package/packages/client/dist/adapters/gemini-adapter.js +71 -2
  12. package/packages/client/dist/adapters/grok-adapter.js +42 -2
  13. package/packages/client/dist/adapters/index.js +22 -1
  14. package/packages/client/dist/adapters/platform-detector.js +212 -1
  15. package/packages/client/dist/adapters/target-adapter.js +12 -0
  16. package/packages/client/dist/adapters/vscode-adapter.js +72 -2
  17. package/packages/client/dist/agent/refresh-stubs.js +417 -2
  18. package/packages/client/dist/agent/update-agent-yaml.js +102 -1
  19. package/packages/client/dist/agent/update-description.js +251 -1
  20. package/packages/client/dist/cache/crypto-utils.js +78 -1
  21. package/packages/client/dist/cache/encrypted-cache.js +108 -1
  22. package/packages/client/dist/cache/in-memory-asset-cache.js +73 -1
  23. package/packages/client/dist/cache/index.js +13 -1
  24. package/packages/client/dist/cache/protected-pi-boundary.js +44 -1
  25. package/packages/client/dist/checkpoint/checkpoint-client-reader.js +127 -2
  26. package/packages/client/dist/checkpoint/index.js +11 -1
  27. package/packages/client/dist/checkpoint/shared-checkpoint-types.js +32 -1
  28. package/packages/client/dist/cli.js +303 -3
  29. package/packages/client/dist/commands/activate.js +394 -8
  30. package/packages/client/dist/commands/architecture-answers.js +274 -7
  31. package/packages/client/dist/commands/architecture-memory-terminal.js +253 -4
  32. package/packages/client/dist/commands/architecture-non-ui-continuation.js +100 -1
  33. package/packages/client/dist/commands/architecture-root-binding.js +51 -1
  34. package/packages/client/dist/commands/cache-status.js +112 -2
  35. package/packages/client/dist/commands/config.js +88 -1
  36. package/packages/client/dist/commands/doctor.d.ts +135 -0
  37. package/packages/client/dist/commands/doctor.js +635 -0
  38. package/packages/client/dist/commands/invoke.d.ts +18 -0
  39. package/packages/client/dist/commands/invoke.js +7140 -142
  40. package/packages/client/dist/commands/legacy-epic-terminal.js +71 -1
  41. package/packages/client/dist/commands/legacy-story-branch.js +96 -1
  42. package/packages/client/dist/commands/legacy-story-pr.js +276 -1
  43. package/packages/client/dist/commands/mutation-qa-checkpoint.js +285 -5
  44. package/packages/client/dist/commands/mutation-qa-gate.js +54 -1
  45. package/packages/client/dist/commands/mutation-qa-git.js +41 -1
  46. package/packages/client/dist/commands/mutation-qa-policy.js +102 -1
  47. package/packages/client/dist/commands/mutation-qa-run.js +219 -3
  48. package/packages/client/dist/commands/mutation-qa-verdict.js +149 -1
  49. package/packages/client/dist/commands/mutation-qa.js +220 -1
  50. package/packages/client/dist/commands/native-agent.js +136 -2
  51. package/packages/client/dist/commands/planning-diagnosis.js +43 -1
  52. package/packages/client/dist/commands/planning-generation.js +53 -1
  53. package/packages/client/dist/commands/planning-memory.js +40 -1
  54. package/packages/client/dist/commands/planning-research.js +42 -1
  55. package/packages/client/dist/commands/private-session.js +75 -1
  56. package/packages/client/dist/commands/property-qa.js +139 -1
  57. package/packages/client/dist/commands/qa-control.js +16 -1
  58. package/packages/client/dist/commands/qa-scan-browser-launch.js +55 -1
  59. package/packages/client/dist/commands/qa-scan-browser-route.js +28 -1
  60. package/packages/client/dist/commands/qa-scan-capture.js +663 -5
  61. package/packages/client/dist/commands/qa-scan-discovery.js +376 -3
  62. package/packages/client/dist/commands/qa-scan-review.js +826 -5
  63. package/packages/client/dist/commands/qa-synthetic-backend-capture.js +266 -1
  64. package/packages/client/dist/commands/qa-synthetic-backend-review.js +297 -1
  65. package/packages/client/dist/commands/qa-synthetic-backend.js +340 -2
  66. package/packages/client/dist/commands/qa-synthetic-broker-port.js +85 -1
  67. package/packages/client/dist/commands/qa-synthetic-browser.js +39 -1
  68. package/packages/client/dist/commands/refactor-characterization.js +671 -21
  69. package/packages/client/dist/commands/refactor-commit.js +399 -13
  70. package/packages/client/dist/commands/refactor-control.js +142 -3
  71. package/packages/client/dist/commands/refactor-cpp-assertions.js +93 -3
  72. package/packages/client/dist/commands/refactor-review.js +283 -5
  73. package/packages/client/dist/commands/refactor-root.js +588 -7
  74. package/packages/client/dist/commands/refresh-memory.js +156 -3
  75. package/packages/client/dist/commands/shared-architecture-non-ui.js +24 -1
  76. package/packages/client/dist/commands/story-checkpoint.js +171 -1
  77. package/packages/client/dist/config/profile-directory.js +13 -1
  78. package/packages/client/dist/config/resolver-selection.js +278 -1
  79. package/packages/client/dist/config/secure-config.js +281 -12
  80. package/packages/client/dist/config.js +223 -2
  81. package/packages/client/dist/constants.js +25 -1
  82. package/packages/client/dist/context/context-collector.js +222 -2
  83. package/packages/client/dist/context/context-sanitizer.js +145 -1
  84. package/packages/client/dist/continuity/autonomous-governance.js +285 -1
  85. package/packages/client/dist/continuity/continuity-client-state-store.js +1587 -1
  86. package/packages/client/dist/continuity/invoke-hooks.js +129 -1
  87. package/packages/client/dist/continuity/migrations/001-initial-schema.js +12 -2
  88. package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +12 -2
  89. package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +12 -2
  90. package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +12 -2
  91. package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +12 -2
  92. package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +10 -2
  93. package/packages/client/dist/continuity/native-agent-store.js +69 -1
  94. package/packages/client/dist/continuity/native-agent-types.js +90 -1
  95. package/packages/client/dist/continuity/outbox.js +117 -1
  96. package/packages/client/dist/continuity/runner-bootstrap-policy.js +172 -1
  97. package/packages/client/dist/continuity/sqlite-store.js +2536 -81
  98. package/packages/client/dist/effects/invoke-effect-gateways.js +62 -1
  99. package/packages/client/dist/errors/error-messages.js +228 -2
  100. package/packages/client/dist/errors/frontmatter-diagnostics.js +125 -1
  101. package/packages/client/dist/evidence/collector.js +347 -1
  102. package/packages/client/dist/evidence/index.js +1 -1
  103. package/packages/client/dist/evidence/types.js +2 -0
  104. package/packages/client/dist/fs/atomic-state-replace.js +152 -1
  105. package/packages/client/dist/generated/command-registry.generated.js +4076 -1
  106. package/packages/client/dist/generated/public-projections.generated.js +149 -1
  107. package/packages/client/dist/git/direct-yolo-c01-retry.js +138 -4
  108. package/packages/client/dist/git/direct-yolo-checkpoint.js +1234 -4
  109. package/packages/client/dist/git/git-lifecycle-step-ids.js +14 -1
  110. package/packages/client/dist/git/git-lifecycle-store.js +1938 -9
  111. package/packages/client/dist/git/legacy-story-branch-checkpoint.js +259 -9
  112. package/packages/client/dist/git/legacy-story-branch-setup.js +348 -4
  113. package/packages/client/dist/git/legacy-story-pr.js +3538 -70
  114. package/packages/client/dist/git/secure-filesystem-boundary.js +182 -5
  115. package/packages/client/dist/graph-retrieval/pre-command-hook.js +284 -1
  116. package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +237 -1
  117. package/packages/client/dist/i18n/first-run.js +60 -2
  118. package/packages/client/dist/index.js +47 -1
  119. package/packages/client/dist/invocation/invoke-input.js +24 -1
  120. package/packages/client/dist/license/index.js +5 -1
  121. package/packages/client/dist/license/license-client.js +304 -1
  122. package/packages/client/dist/machine/fingerprint.js +163 -2
  123. package/packages/client/dist/machine/index.js +5 -1
  124. package/packages/client/dist/memory/project-memory-path-scope.js +62 -1
  125. package/packages/client/dist/memory/project-memory-writer.js +1007 -40
  126. package/packages/client/dist/memory/shared-project-memory-types.js +253 -2
  127. package/packages/client/dist/migrations/project-identity-capability.js +50 -1
  128. package/packages/client/dist/migrations/project-identity.js +493 -7
  129. package/packages/client/dist/migrations/state-json.js +344 -4
  130. package/packages/client/dist/policy/architecture-policy.js +184 -2
  131. package/packages/client/dist/policy/index.js +7 -1
  132. package/packages/client/dist/policy/shared-policy-types.js +38 -0
  133. package/packages/client/dist/policy/subagent-approval-policy.js +55 -1
  134. package/packages/client/dist/private-execution/clean-baseline.js +279 -2
  135. package/packages/client/dist/private-execution/codex-executor.js +574 -4
  136. package/packages/client/dist/private-execution/codex-host-runtime.js +137 -1
  137. package/packages/client/dist/private-execution/codex-lifecycle-guard.js +97 -1
  138. package/packages/client/dist/private-execution/codex-scoped-permissions.js +128 -1
  139. package/packages/client/dist/private-execution/delivery.js +410 -13
  140. package/packages/client/dist/private-execution/diagnosis-root-review.js +294 -2
  141. package/packages/client/dist/private-execution/frozen-handoff.js +19 -6
  142. package/packages/client/dist/private-execution/implementation-validation.js +106 -2
  143. package/packages/client/dist/private-execution/isolated-admission.js +1432 -23
  144. package/packages/client/dist/private-execution/isolated-session.js +725 -5
  145. package/packages/client/dist/private-execution/isolated-workspace.js +122 -1
  146. package/packages/client/dist/private-execution/pact-consumer-runtime.js +409 -3
  147. package/packages/client/dist/private-execution/pact-test-source.js +258 -1
  148. package/packages/client/dist/private-execution/pact-toolchain.js +290 -3
  149. package/packages/client/dist/private-execution/pact-write-set.js +172 -1
  150. package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +406 -5
  151. package/packages/client/dist/private-execution/planning-generation-checkpoint.js +462 -5
  152. package/packages/client/dist/private-execution/planning-memory-checkpoint.js +139 -3
  153. package/packages/client/dist/private-execution/planning-research-checkpoint.js +325 -4
  154. package/packages/client/dist/private-execution/private-report-store.js +42 -1
  155. package/packages/client/dist/private-execution/property-test-candidate.js +103 -1
  156. package/packages/client/dist/private-execution/property-test-choices.js +147 -3
  157. package/packages/client/dist/private-execution/property-test-plan.js +271 -7
  158. package/packages/client/dist/private-execution/property-test-review.js +258 -4
  159. package/packages/client/dist/private-execution/property-test-runtime.js +372 -9
  160. package/packages/client/dist/private-execution/property-test-source.js +139 -1
  161. package/packages/client/dist/private-execution/root-checkpoint.js +80 -1
  162. package/packages/client/dist/private-execution/root-memory-git-evidence.js +135 -3
  163. package/packages/client/dist/private-execution/root-memory-recovery.js +93 -1
  164. package/packages/client/dist/private-execution/root-memory-request.js +29 -1
  165. package/packages/client/dist/private-execution/root-memory-scope.js +178 -1
  166. package/packages/client/dist/private-execution/root-memory-writer.js +102 -1
  167. package/packages/client/dist/private-execution/session-lease.js +63 -1
  168. package/packages/client/dist/private-execution/shared-private-admission-types.js +50 -1
  169. package/packages/client/dist/private-execution/shared-project-validation.js +66 -1
  170. package/packages/client/dist/private-execution/shared-root-memory-types.js +61 -1
  171. package/packages/client/dist/private-execution/ux-source-validation.js +372 -1
  172. package/packages/client/dist/private-execution/ux-write-set.js +191 -2
  173. package/packages/client/dist/resilience/circuit-breaker.js +170 -1
  174. package/packages/client/dist/resilience/degradation-manager.js +164 -1
  175. package/packages/client/dist/resilience/freshness-indicator.js +100 -1
  176. package/packages/client/dist/resilience/index.js +8 -1
  177. package/packages/client/dist/resilience/recovery-detector.js +74 -1
  178. package/packages/client/dist/resolvers/asset-resolver.js +13 -0
  179. package/packages/client/dist/resolvers/local-resolver.js +255 -8
  180. package/packages/client/dist/resolvers/remote-resolver.js +233 -1
  181. package/packages/client/dist/runner/binary.js +404 -1
  182. package/packages/client/dist/runner/cli.js +1571 -17
  183. package/packages/client/dist/runner/headless/adapters.js +49 -1
  184. package/packages/client/dist/runner/headless/claude-code-adapter.js +79 -1
  185. package/packages/client/dist/runner/headless/continuous-story-workspace.js +1628 -11
  186. package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +69 -1
  187. package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +110 -2
  188. package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +1236 -8
  189. package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +318 -2
  190. package/packages/client/dist/runner/headless/fake-adapter.js +62 -1
  191. package/packages/client/dist/runner/headless/managed-profile.js +132 -1
  192. package/packages/client/dist/runner/headless/opencode-adapter.js +145 -2
  193. package/packages/client/dist/runner/headless/prepared-story-bundle.js +43 -1
  194. package/packages/client/dist/runner/headless/resolver.js +231 -1
  195. package/packages/client/dist/runner/headless/run-yolo.js +885 -1
  196. package/packages/client/dist/runner/headless/run-yoloop.d.ts +4 -1
  197. package/packages/client/dist/runner/headless/run-yoloop.js +2574 -2
  198. package/packages/client/dist/runner/headless/types.js +574 -2
  199. package/packages/client/dist/runner/local-console.js +396 -13
  200. package/packages/client/dist/runner/portal-sync-worker.js +1487 -1
  201. package/packages/client/dist/runner/project-identity.js +106 -1
  202. package/packages/client/dist/runner/scheduler.js +995 -7
  203. package/packages/client/dist/runner-cli.js +18 -2
  204. package/packages/client/dist/state/invocation-shape.js +13 -1
  205. package/packages/client/dist/state/project-state-snapshot.js +1677 -1
  206. package/packages/client/dist/state/state-json-repair.js +785 -3
  207. package/packages/client/dist/state/structural-dag-projection.js +118 -1
  208. package/packages/client/dist/telemetry/index.js +5 -1
  209. package/packages/client/dist/telemetry/offline-queue.js +131 -1
  210. package/packages/client/dist/tier/index.js +5 -1
  211. package/packages/client/dist/tier/tier-aware-client.js +405 -1
  212. package/packages/client/dist/types/index.js +38 -1
  213. package/packages/client/dist/version.js +27 -1
  214. package/packages/client/dist/yolo/change-evidence-collector.js +126 -1
  215. package/packages/client/dist/yolo/invoke-envelope-capability.js +62 -1
  216. package/packages/client/dist/yolo/spec-envelope-collector.js +185 -5
  217. package/packages/client/dist/yoloop/discovery-hook.js +300 -2
  218. package/packages/client/dist/yoloop/index.js +8 -1
  219. package/packages/client/dist/yoloop/invoke-hooks.js +669 -12
  220. package/packages/client/dist/yoloop/shared-discover-epics.js +1007 -1
  221. package/packages/client/dist/yoloop/shared-yoloop-types.js +33 -1
  222. package/packages/client/dist/yoloop/target-normalization.js +180 -1
  223. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1723 -1
  224. package/packages/shared/dist/compatibility/index.js +476 -0
  225. package/packages/shared/dist/yoloop/release-blocked.js +653 -0
  226. package/targets-stubs/antigravity/gemini.md +1 -1
  227. package/targets-stubs/antigravity/skill/SKILL.md +3 -2
  228. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  229. package/targets-stubs/claude-code/neocortex-root.md +5 -5
  230. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  231. package/targets-stubs/claude-code/neocortex.md +8 -9
  232. package/targets-stubs/codex/AGENTS.md +3 -3
  233. package/targets-stubs/cursor/agent.md +4 -3
  234. package/targets-stubs/gemini-cli/agent.md +5 -4
  235. package/targets-stubs/opencode/neocortex-root.md +3 -3
  236. package/targets-stubs/vscode/neocortex.agent.md +5 -4
@@ -1,23 +1,1432 @@
1
- import{execFileSync as Nt,spawnSync as wt}from"node:child_process";import{createHash as F}from"node:crypto";import{existsSync as nt,lstatSync as x,readFileSync as m,realpathSync as ht,writeFileSync as mt}from"node:fs";import{isAbsolute as ot,join as a,relative as dt,sep as vt}from"node:path";import{isDeepStrictEqual as S}from"node:util";import{readPrivateYoloFrozenWriteSet as Dt,verifyPrivateYoloCandidateWriteSet as Ft,verifyPrivateYoloCleanBaseline as Bt}from"./clean-baseline.js";import{PRIVATE_EPIC_PENDING_SIDECAR as et}from"./isolated-workspace.js";import{loadPactPrivateAnswers as Ct}from"../commands/architecture-answers.js";import{derivePactWriteSet as _t}from"./pact-write-set.js";import{deriveUxCandidateWriteSet as zt,deriveUxWriteSet as Lt}from"./ux-write-set.js";import{validateUxCandidateSource as Ht}from"./ux-source-validation.js";import{validatePactTestSource as Jt}from"./pact-test-source.js";import{classifyProjectMemoryUpdate as Mt}from"../memory/project-memory-writer.js";import{privateRootMemoryEffect as yt,privateRootMemorySourceMatches as qt,privateRootMemorySourceMatchReason as Gt}from"./root-memory-scope.js";import{inspectPactToolchain as Yt,planPactToolchainManifest as Ut}from"./pact-toolchain.js";import{acceptedPlanningDiagnosisPredecessor as Wt,planningPendingSidecarDigest as bt}from"./planning-diagnosis-checkpoint.js";import{acceptedPlanningResearchPredecessor as Vt}from"./planning-research-checkpoint.js";import{confirmPropertyTestPlan as xt,renderPropertyTestConfiguration as Xt}from"./property-test-plan.js";import{validatePropertyTestTypeScriptSource as Kt}from"./property-test-source.js";const Z=16*1024*1024,st=1024,ut="acd455ed0e39d98b90fb57391db75e366e4c5604db043e03a89a20e33d643c3c",Zt="81f50aaa801c1660eabe46e645501acbfb3e9873410c991e1496c014ca75c369",Rt="fa2194f89c01f07e225fd6f39e98e679f628e072239b6cd0af3ee527f0722518",Qt="8636f22ec5950410902448ee1e9cb4947359ac98df518742bddbbb367468e3ae";function tt(t,r){return typeof t=="string"&&t.length<=128&&F("sha256").update(t).digest("hex")===r}function Et(){const t={...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 t[r];return t}function G(t,r){return Nt("git",[...r],{cwd:t,env:Et(),stdio:["ignore","pipe","pipe"],timeout:3e4,maxBuffer:Z})}function J(t,r){return G(t,r).toString("utf8").trim()}function B(t,r=!1){return G(t,["status","--porcelain=v1","-z","--untracked-files=all",...r?["--ignored=matching"]:[]])}function rt(t){return!!t&&!ot(t)&&!t.includes("\\")&&!t.includes("\0")&&!t.split("/").some(r=>!r||r==="."||r===".."||r===".git")}function P(t,r){const e=r.split("/");let n=t;for(const o of e.slice(0,-1)){n=a(n,o);try{const i=x(n);if(!i.isDirectory()||i.isSymbolicLink())throw new Error("path-boundary")}catch(i){if(i.code==="ENOENT")return;throw i}}try{const o=x(a(t,r));if(!o.isFile()||o.isSymbolicLink()||o.size>Z)throw new Error("path-boundary")}catch(o){if(o.code!=="ENOENT")throw o}}function At(t){const r=[...t.matchAll(/\/\/\s*arch:keep-start[^\n]*\n[\s\S]*?\/\/\s*arch:keep-end[^\n]*/g)].map(o=>o[0]),e=(t.match(/arch:keep-start/g)??[]).length,n=(t.match(/arch:keep-end/g)??[]).length;if(e!==n||e!==r.length)throw new Error("architecture-keep-marker-invalid");return r}function M(t,r=!1,e=!1,n=[]){const o=[],i=[];for(const s of t.toString("utf8").split("\0").filter(Boolean)){if(s.length<4||s[2]!==" ")throw new Error("status-invalid");const d=s.slice(0,2),l=s.slice(3);if(!rt(l)||d!=="??"&&d!==" M"&&d!==" D"&&!(d===" T"&&n.includes(l))&&!(e&&d==="!!"&&l===et))throw new Error("status-invalid");o.push(l),d==="??"&&i.push(l)}if(!r&&o.length===0||o.length>st||new Set(o).size!==o.length)throw new Error("status-invalid");return{paths:o.sort(),untracked:i.sort()}}function lt(t,r,e,n){const o=G(r,["ls-tree","-z",e,"--",n]).toString("utf8");if(!o)try{return x(a(t,n)),!1}catch(s){return s.code==="ENOENT"}const i=/^([0-7]{6}) blob [a-f0-9]{40,64}\t([^\0]+)\0$/.exec(o);if(!i||i[2]!==n||!["100644","100755"].includes(i[1]))return!1;try{const s=x(a(t,n));return!s.isFile()||s.isSymbolicLink()||s.size>Z||!!(s.mode&73)!=(i[1]==="100755")?!1:m(a(t,n)).equals(G(r,["show",`${e}:${n}`]))}catch{return!1}}function ve(t,r,e,n){return rt(n)&&/^[a-f0-9]{40,64}$/.test(e)&&lt(t,r,e,n)}function it(t,r){try{const e=a(t,et),n=x(a(t,".neocortex")),o=x(e);return n.isDirectory()&&!n.isSymbolicLink()&&n.uid===process.getuid?.()&&(n.mode&18)===0&&o.isFile()&&!o.isSymbolicLink()&&o.uid===process.getuid?.()&&(o.mode&63)===0&&o.size>0&&o.size<=128*1024&&F("sha256").update(m(e)).digest("hex")===r}catch{return!1}}function ft(t,r,e,n,o,i){const s=new Set(n),d=i?yt(i).files.map(f=>f.path):[],l=M(B(t),!0,!!o,d),c=i?Gt(i,t,r,e):"matched";if(c!=="matched")throw new Error(`root-memory-${c}`);if(!l.paths.every(f=>s.has(f)||d.includes(f)||f===et&&!!o&&it(t,o)))throw new Error("source-unclaimed-delta");if(!n.every(f=>lt(t,r,e,f)))throw new Error("source-frozen-mismatch");return!o||it(t,o)}function kt(t,r){const e=wt("git",["diff","--no-index","--binary","--no-ext-diff","--","/dev/null",r],{cwd:t,env:Et(),encoding:"buffer",timeout:3e4,maxBuffer:Z});if(e.error||e.status!==1||!Buffer.isBuffer(e.stdout)||e.stdout.byteLength===0)throw new Error("patch-invalid");return e.stdout}function St(t,r,e){const n=wt("git",["apply",...e?["--check"]:[],"--binary","-"],{cwd:t,env:Et(),input:r,encoding:"buffer",timeout:3e4,maxBuffer:Z});if(n.error||n.status!==0)throw new Error("apply-failed")}function pt(t,r,e){const n=a(t,e),o=a(r,e);try{const i=x(n),s=x(o);return i.isFile()&&s.isFile()&&(i.mode&73)===(s.mode&73)&&m(n).equals(m(o))}catch(i){if(i.code!=="ENOENT")return!1;try{return x(n),!1}catch(s){if(s.code!=="ENOENT")return!1}try{return x(o),!1}catch(s){return s.code==="ENOENT"}}}function _e(t,r,e,n){$t(t,r,e,!0,n)}function be(t,r,e,n){if($t(t,r,e,!1,n),G(t.workspacePath,["diff","--cached","--name-only"]).byteLength!==0)throw new Error("private-execution-isolated-snapshot-unverified")}function $t(t,r,e,n,o){try{if(process.platform!=="linux"||t.schemaVersion!==1||!ot(t.sourceRoot)||!ot(t.workspacePath)||ht(t.sourceRoot)!==t.sourceRoot||ht(t.workspacePath)!==t.workspacePath||!x(t.workspacePath).isDirectory()||(x(t.workspacePath).mode&63)!==0||dt(t.sourceRoot,t.workspacePath)===""||dt(t.sourceRoot,t.workspacePath).split(vt)[0]!==".."||!/^[a-f0-9]{40,64}$/.test(t.baseSha)||!/^[a-f0-9]{40,64}$/.test(e)||J(t.sourceRoot,["rev-parse","--show-toplevel"])!==t.sourceRoot||J(t.workspacePath,["rev-parse","--show-toplevel"])!==t.workspacePath||J(t.sourceRoot,["rev-parse","HEAD"])!==t.baseSha||J(t.workspacePath,["rev-parse","HEAD"])!==e||J(t.sourceRoot,["symbolic-ref","--short","HEAD"])!==t.executionBranch||J(t.workspacePath,["symbolic-ref","--short","HEAD"])!==t.executionBranch||nt(a(t.workspacePath,".git","objects","info","alternates"))||n&&B(t.workspacePath,!0).byteLength!==0||r.length===0||r.length>st||r.some(i=>!rt(i))||new Set(r).size!==r.length)throw new Error("boundary-invalid");if(G(t.workspacePath,["merge-base","--is-ancestor",t.baseSha,e]),!ft(t.sourceRoot,t.workspacePath,e,r,void 0,o))throw new Error("boundary-raced")}catch(i){const s=i instanceof Error&&(["boundary-invalid","boundary-raced","status-invalid","source-unclaimed-delta","source-frozen-mismatch"].includes(i.message)||/^root-memory-(scope-binding|snapshot-ancestry|snapshot-content|source-content|source-projection)(-unsafe)?$/.test(i.message))?i.message:"physical-inspection-failed";throw new Error("private-execution-isolated-snapshot-unverified",{cause:new Error(s)})}}function Y(t,r,e,n,o,i=[],s){try{if(process.platform!=="linux"||t.schemaVersion!==1||!ot(t.sourceRoot)||!ot(t.workspacePath)||ht(t.sourceRoot)!==t.sourceRoot||ht(t.workspacePath)!==t.workspacePath||!x(t.workspacePath).isDirectory()||(x(t.workspacePath).mode&63)!==0||dt(t.sourceRoot,t.workspacePath)===""||dt(t.sourceRoot,t.workspacePath).split(vt)[0]!==".."||J(t.sourceRoot,["rev-parse","--show-toplevel"])!==t.sourceRoot||J(t.workspacePath,["rev-parse","--show-toplevel"])!==t.workspacePath||J(t.sourceRoot,["rev-parse","HEAD"])!==t.baseSha||!/^[a-f0-9]{40,64}$/.test(e)||J(t.workspacePath,["rev-parse","HEAD"])!==e||J(t.sourceRoot,["symbolic-ref","--short","HEAD"])!==t.executionBranch||J(t.workspacePath,["symbolic-ref","--short","HEAD"])!==t.executionBranch||r.length===0||r.length>st||r.some(g=>!rt(g))||new Set(r).size!==r.length)throw new Error("authority-invalid");if(G(t.workspacePath,["merge-base","--is-ancestor",t.baseSha,e]),!ft(t.sourceRoot,t.workspacePath,e,r,n,s))throw new Error("source-snapshot-mismatch");const d=B(t.workspacePath,!0),l=M(d),c=new Set(r);if(l.paths.some(g=>!c.has(g)||i.includes(g)))throw new Error("path-unclaimed");if(o&&!o())throw new Error("candidate-raced");for(const g of l.paths)P(t.workspacePath,g),P(t.sourceRoot,g);const h=[G(t.workspacePath,["diff","--binary","--no-ext-diff","--no-textconv","HEAD","--"]),...l.untracked.map(g=>kt(t.workspacePath,g))],p=Buffer.concat(h.map(g=>Buffer.concat([g,Buffer.from(`
2
- `)])));if(p.byteLength===0||p.byteLength>Z||!B(t.workspacePath,!0).equals(d)||o&&!o()||!ft(t.sourceRoot,t.workspacePath,e,r,n,s))throw new Error("patch-raced");if(St(t.sourceRoot,p,!0),!B(t.workspacePath,!0).equals(d)||o&&!o()||!ft(t.sourceRoot,t.workspacePath,e,r,n,s))throw new Error("patch-raced");St(t.sourceRoot,p,!1);const v=s?yt(s).files.map(g=>g.path):[];if(M(B(t.sourceRoot),!0,!!n,v).paths.some(g=>!c.has(g)&&!v.includes(g)&&g!==et)||s&&!qt(s,t.sourceRoot)||n&&!it(t.sourceRoot,n)||r.some(g=>!pt(t.workspacePath,t.sourceRoot,g))||J(t.sourceRoot,["rev-parse","HEAD"])!==t.baseSha||J(t.sourceRoot,["symbolic-ref","--short","HEAD"])!==t.executionBranch)throw new Error("admission-raced");return Object.freeze({schemaVersion:1,outcome:"admitted-unverified",baseSha:t.baseSha,executionBranch:t.executionBranch,changedPaths:Object.freeze([...l.paths]),patchSha256:F("sha256").update(p).digest("hex")})}catch{throw new Error("private-execution-isolated-admission-unverified")}}async function xe(t,r,e,n=t.baseSha,o=!1,i){try{const s=await Dt(t.sourceRoot,r,t.executionBranch,e);if(i){const f=yt(i);if(f.storyId!==r||f.childClaimRevision!==F("sha256").update([...s].sort().join("\0")).digest("hex"))throw new Error("root-memory-claim-mismatch")}const d=a(t.workspacePath,".neocortex","state.json"),l=x(d);if(!l.isFile()||l.isSymbolicLink()||l.size>Z)throw new Error("candidate-state-invalid");Ft(m(d,"utf8"),r,s);const c=Y(t,s,n,void 0,void 0,o?[".neocortex/state.json"]:[],i);return await Bt(t.sourceRoot,r,t.executionBranch,e),c}catch{throw new Error("private-execution-isolated-admission-unverified")}}const It=new Set(["id","epic_id","title","description","status","steps_completed","last_step","branch_name","estimated_hours","complexity","priority","type","depends_on","created_at","updated_at","acceptance_criteria"]),te=new Set(["id","title","description","status","stories","total_stories","completed_stories","estimated_hours","complexity","priority","created_at","updated_at","last_updated","source","tags"]);function Ot(t){return["title","description","complexity","priority","type","created_at","updated_at","last_updated","source"].some(e=>t[e]!==void 0&&typeof t[e]!="string")||t.estimated_hours!==void 0&&(typeof t.estimated_hours!="number"||!Number.isFinite(t.estimated_hours)||t.estimated_hours<0)?!1:["depends_on","acceptance_criteria","tags"].every(e=>t[e]===void 0||Array.isArray(t[e])&&t[e].length<=256&&t[e].every(n=>typeof n=="string"))}function ee(t){return Object.keys(t).every(r=>It.has(r))&&t.status==="backlog"&&Ot(t)&&(t.steps_completed===void 0||Array.isArray(t.steps_completed)&&t.steps_completed.length===0)&&(t.last_step===void 0||t.last_step===null)&&(t.branch_name===void 0||t.branch_name===null)}function Re(t,r,e,n,o){try{if(!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(r)||!/^P[1-9][0-9]*$/.test(e)||!/^[a-f0-9]{64}$/.test(n)||!/^[a-f0-9]{64}$/.test(o)||!it(t.sourceRoot,o))throw new Error("claim-invalid");const i=JSON.parse(m(a(t.sourceRoot,et),"utf8")),s=i&&typeof i=="object"&&!Array.isArray(i)?i.operations?.[n]:void 0;if(!s||typeof s!="object"||Array.isArray(s)||s.claimId!==r||s.epicId!==e)throw new Error("claim-sidecar-mismatch");const d=M(B(t.workspacePath,!0)),l=`docs/epics/epic-${e}.md`,c=".neocortex/state.json",f=new RegExp(`^docs/stories/${e}\\.[0-9]{2}\\.story\\.md$`),h=d.paths.filter(E=>f.test(E));if(!d.paths.includes(l)||!d.paths.includes(c)||h.length===0||h.length>99||d.paths.some(E=>E!==l&&E!==c&&E!=="NEOCORTEX.md"&&!f.test(E)))throw new Error("artifact-scope-invalid");const p=h.map(E=>E.slice(13,-9)).sort();if(p.some((E,T)=>E!==`${e}.${String(T+1).padStart(2,"0")}`))throw new Error("story-sequence-invalid");for(const E of[l,...h])try{throw x(a(t.sourceRoot,E)),new Error("artifact-already-exists")}catch(T){if(T.code!=="ENOENT")throw T}const v=(E,T)=>{P(t.workspacePath,E);const K=m(a(t.workspacePath,E),"utf8");return Buffer.byteLength(K)>Z?null:/^---\r?\n([\s\S]{1,8192}?)\r?\n---(?:\r?\n|$)/.exec(K)?.[1]?.split(/\r?\n/).find(ct=>ct.startsWith(`${T}:`))?.slice(T.length+1).trim()?.replace(/^(["'])(.*)\1$/,"$2")??null};if(v(l,"id")!==e||h.some((E,T)=>v(E,"id")!==p[T]||v(E,"epic_id")!==e))throw new Error("artifact-id-invalid");P(t.workspacePath,c);const u=JSON.parse(m(a(t.workspacePath,c),"utf8"));if(!u||typeof u!="object"||Array.isArray(u))throw new Error("state-invalid");const g=u,_=g.epics,j=g.stories,b=_?.[e],N=g.epic_id_ledger;if(g.schema_version!==1||!b||b.id!==e||b.status!=="memory-updated"||Object.keys(b).some(E=>!te.has(E))||!Ot(b)||b.completed_stories!==void 0&&b.completed_stories!==0||b.total_stories!==void 0&&b.total_stories!==p.length||!Array.isArray(b.stories)||b.stories.length!==p.length||b.stories.some((E,T)=>E!==p[T])||!j||p.some(E=>{const T=j[E];return!T||T.id!==E||T.epic_id!==e||!ee(T)})||!N||!Array.isArray(N.reserved_ids)||!N.reserved_ids.includes(e))throw new Error("state-claim-mismatch");P(t.sourceRoot,c);const y=JSON.parse(m(a(t.sourceRoot,c),"utf8")),A=y.epics,R=y.stories;if(!A||!R||A[e]!==void 0||p.some(E=>R[E]!==void 0))throw new Error("state-claim-conflict");const{[e]:U,...L}=_,$={...j};for(const E of p)delete $[E];const{epic_id_ledger:q,last_updated:C,...z}=y,{epic_id_ledger:V,last_updated:X,...Q}=g;if(!S(L,A)||!S($,R)||!S({...Q,epics:void 0,stories:void 0},{...z,epics:void 0,stories:void 0}))throw new Error("state-unrelated-drift");const O=y.epic_id_ledger&&typeof y.epic_id_ledger=="object"?y.epic_id_ledger.reserved_ids:void 0,{reserved_ids:W,...w}=N,D=y.epic_id_ledger&&typeof y.epic_id_ledger=="object"?y.epic_id_ledger:{schema_version:1},{reserved_ids:H,...k}=D;if(O!==void 0&&(!Array.isArray(O)||O.some(E=>typeof E!="string"||!/^P[1-9][0-9]*$/.test(E))||new Set(O).size!==O.length))throw new Error("ledger-drift");const I=[...Array.isArray(O)?O:[]];if(I.includes(e)||I.push(e),!S(N.reserved_ids,I)||!S(w,k))throw new Error("ledger-drift");return Y(t,d.paths,t.baseSha,o)}catch{throw new Error("private-execution-epic-claim-admission-unverified")}}function Ae(t,r,e,n,o,i){try{if(!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(r)||!/^P[1-9][0-9]{0,11}$/.test(e)||!/^[a-f0-9]{64}$/.test(n)||!/^[a-f0-9]{64}$/.test(o)||typeof i!="string"||!i.trim()||i.length>16384||!it(t.sourceRoot,o))throw new Error("planning-claim-invalid");const s=JSON.parse(m(a(t.sourceRoot,et),"utf8")),d=s&&typeof s=="object"&&!Array.isArray(s)?s.operations?.[n]:void 0;if(!d||typeof d!="object"||Array.isArray(d)||d.claimId!==r||d.epicId!==e)throw new Error("planning-claim-sidecar-mismatch");const l=`.neocortex/planning/epic-${e}`,c=`${l}/idea.json`,f=`${l}/diagnosis.md`,h=`${l}/enriched-prompt.md`,p=".neocortex/planning/planning.json",v=[c,f,h,p],u=M(B(t.workspacePath,!0));if(u.paths.length!==v.length||u.paths.some(E=>!v.includes(E)))throw new Error("planning-artifact-scope-invalid");for(const E of[c,f,h]){P(t.workspacePath,E);try{throw x(a(t.sourceRoot,E)),new Error("planning-artifact-already-exists")}catch(T){if(T.code!=="ENOENT")throw T}}const g=(E,T)=>{const K=x(a(t.workspacePath,E));if(!K.isFile()||K.isSymbolicLink()||K.size<1||K.size>T)throw new Error("planning-artifact-invalid");return m(a(t.workspacePath,E),"utf8")},_=JSON.parse(g(c,32*1024));if(!_||typeof _!="object"||Array.isArray(_)||_.epic_id!==e||_.status!=="diagnosed"||typeof _.raw_input!="string"||_.raw_input!==i)throw new Error("planning-idea-invalid");const j=g(f,256*1024),b=g(h,256*1024),N=(E,T)=>{const K=/^---\n([\s\S]{1,8192}?)\n---\n/.exec(E.replace(/\r\n/g,`
3
- `))?.[1];if(!K)return!1;const Pt=K.split(`
4
- `),at=gt=>{const ct=Pt.filter(jt=>jt.startsWith(`${gt}:`));return ct.length===1?ct[0].slice(gt.length+1).trim().replace(/^(?:"(.*)"|'(.*)')$/,"$1$2"):void 0};return at("epic_id")===e&&tt(at("step"),T)&&at("status")==="completed"};if(!N(j,ut)||!N(b,Zt))throw new Error("planning-document-identity-invalid");P(t.workspacePath,p),P(t.sourceRoot,p);const y=JSON.parse(g(p,128*1024)),A=nt(a(t.sourceRoot,p))?JSON.parse(m(a(t.sourceRoot,p),"utf8")):void 0;if(!y||typeof y!="object"||Array.isArray(y)||!("epics"in y)||!y.epics||typeof y.epics!="object"||Array.isArray(y.epics)||A!==void 0&&(!A||typeof A!="object"||Array.isArray(A)||!("epics"in A)||!A.epics||typeof A.epics!="object"||Array.isArray(A.epics)))throw new Error("planning-index-invalid");const R=y,U=R.epics,L=U[e],$=L,q=$?.output_files;if(!L||typeof L!="object"||Array.isArray(L)||$?.id!==e||$.status!=="diagnosed"||$.idea!==_.raw_input||!Array.isArray($.steps_completed)||$.steps_completed.length!==1||!tt($.steps_completed[0],ut)||typeof $.created_at!="string"||!Number.isFinite(Date.parse($.created_at))||$.completed_at!==null||!q||typeof q!="object"||Array.isArray(q)||!S(q,{diagnosis:`${l}/diagnosis.md`})||!S(Object.keys($).sort(),["id","idea","status","steps_completed","created_at","completed_at","output_files"].sort()))throw new Error("planning-index-epic-invalid");const{last_scan:C,epics:z,...V}=R,X=A,Q=X?.epics,{last_scan:O,epics:W,...w}=X??{},{[e]:D,...H}=U;if(Q?.[e]!==void 0||!S(H,Q??{})||X!==void 0&&!S(V,w)||X===void 0&&(Object.keys(U).length!==1||!S(Object.keys(R).sort(),["version","next_epic_id","last_scan","existing_epics_discovered","epics"].sort())||R.version!=="1.0.0"||R.next_epic_id!==1||!S(R.existing_epics_discovered,[]))||typeof R.last_scan!="string"||!Number.isFinite(Date.parse(R.last_scan)))throw new Error("planning-index-drift");const k=v.map(E=>F("sha256").update(m(a(t.workspacePath,E))).digest("hex")),I=()=>v.every((E,T)=>{try{return P(t.workspacePath,E),F("sha256").update(m(a(t.workspacePath,E))).digest("hex")===k[T]}catch{return!1}});return Y(t,v,t.baseSha,o,I)}catch{throw new Error("private-execution-planning-diagnosis-admission-unverified")}}function Se(t,r,e,n,o,i){try{if(!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(r)||!/^P[1-9][0-9]{0,11}$/.test(e)||!/^[a-f0-9]{64}$/.test(n)||!/^[a-f0-9]{64}$/.test(o)||!/^[a-f0-9]{64}$/.test(i))throw new Error("planning-research-binding-invalid");const s=".neocortex/state.json",d=".neocortex/planning/planning.json",l=`.neocortex/planning/epic-${e}`,c=`${l}/research.md`;if(P(t.sourceRoot,s),P(t.workspacePath,s),P(t.sourceRoot,d),P(t.workspacePath,d),P(t.sourceRoot,c),P(t.workspacePath,c),x(a(t.sourceRoot,s)).size>8*1024*1024||x(a(t.workspacePath,s)).size>8*1024*1024||x(a(t.sourceRoot,d)).size>128*1024||x(a(t.workspacePath,d)).size>128*1024)throw new Error("planning-research-source-size-invalid");const f=m(a(t.sourceRoot,s));if(F("sha256").update(f).digest("hex")!==i||!f.equals(m(a(t.workspacePath,s))))throw new Error("planning-research-state-drift");const h=JSON.parse(f.toString("utf8")),p=Wt(h,r,e);if(p.diagnosisReceiptId!==n||p.intakeRevision!==o)throw new Error("planning-research-predecessor-mismatch");const u=h.epics[e].createEpicPlanningPhysical.artifactDigests;for(const[O,W]of Object.entries({idea:`${l}/idea.json`,diagnosis:`${l}/diagnosis.md`,enrichment:`${l}/enriched-prompt.md`,index:d}))if(P(t.sourceRoot,W),F("sha256").update(m(a(t.sourceRoot,W))).digest("hex")!==u[O])throw new Error("planning-research-source-artifact-drift");try{throw x(a(t.sourceRoot,c)),new Error("planning-research-already-exists")}catch(O){if(O.code!=="ENOENT")throw O}const g=[c,d],_=M(B(t.workspacePath,!0));if(_.paths.length!==g.length||_.paths.some(O=>!g.includes(O)))throw new Error("planning-research-write-scope-invalid");const j=x(a(t.workspacePath,c));if(!j.isFile()||j.isSymbolicLink()||j.size<64||j.size>256*1024)throw new Error("planning-research-document-invalid");const b=m(a(t.workspacePath,c),"utf8"),N=/^---\n([\s\S]{1,8192}?)\n---\n/.exec(b.replace(/\r\n/g,`
5
- `))?.[1],y=O=>{const W=N?.split(`
6
- `).filter(w=>w.startsWith(`${O}:`))??[];return W.length===1?W[0].slice(O.length+1).trim().replace(/^(?:"(.*)"|'(.*)')$/,"$1$2"):void 0};if(!N||y("epic_id")!==e||y("status")!=="completed"||!tt(y("step"),Rt)||!b.slice(N.length+8).trim())throw new Error("planning-research-document-identity-invalid");const A=JSON.parse(m(a(t.sourceRoot,d),"utf8")),R=JSON.parse(m(a(t.workspacePath,d),"utf8"));if(!A||typeof A!="object"||Array.isArray(A)||!R||typeof R!="object"||Array.isArray(R))throw new Error("planning-research-index-invalid");const U=A,L=R,$=U.epics,q=L.epics,C=$?.[e];if(!$||Array.isArray($)||!q||Array.isArray(q)||!C||Array.isArray(C)||C.id!==e||C.idea!==p.rawIntent||C.status!=="diagnosed"||!Array.isArray(C.steps_completed)||C.steps_completed.length!==1||!tt(C.steps_completed[0],ut)||!S(C.output_files,{diagnosis:`${l}/diagnosis.md`})||typeof L.last_scan!="string"||!Number.isFinite(Date.parse(L.last_scan)))throw new Error("planning-research-index-predecessor-invalid");const z={...C,status:"researched",steps_completed:[...C.steps_completed,y("step")],output_files:{diagnosis:`${l}/diagnosis.md`,research:c}},V={...U,last_scan:L.last_scan,epics:{...$,[e]:z}};if(!S(L,V))throw new Error("planning-research-index-drift");const X=g.map(O=>F("sha256").update(m(a(t.workspacePath,O))).digest("hex")),Q=()=>g.every((O,W)=>{try{return P(t.workspacePath,O),F("sha256").update(m(a(t.workspacePath,O))).digest("hex")===X[W]}catch{return!1}});return Y(t,g,t.baseSha,bt(t.sourceRoot),Q)}catch{throw new Error("private-execution-planning-research-admission-unverified")}}function $e(t,r,e,n,o,i,s){try{if(!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(r)||!/^P[1-9][0-9]{0,11}$/.test(e)||[n,o,i,s].some(w=>!/^[a-f0-9]{64}$/.test(w)))throw new Error("planning-generation-binding-invalid");const d=".neocortex/state.json",l=".neocortex/planning/planning.json",c=`.neocortex/planning/epic-${e}`,f=`docs/epics/epic-${e}.md`;for(const w of[d,l,`${c}/idea.json`,`${c}/diagnosis.md`,`${c}/enriched-prompt.md`,`${c}/research.md`]){P(t.sourceRoot,w),P(t.workspacePath,w);const D=m(a(t.sourceRoot,w));if(w!==l&&!D.equals(m(a(t.workspacePath,w)))||!D.equals(G(t.sourceRoot,["show",`HEAD:${w}`])))throw new Error("planning-generation-predecessor-uncommitted")}const h=m(a(t.sourceRoot,d));if(h.byteLength>8*1024*1024||F("sha256").update(h).digest("hex")!==s)throw new Error("planning-generation-state-drift");const p=JSON.parse(h.toString("utf8")),v=Vt(p,r,e);if(v.diagnosisReceiptId!==n||v.researchReceiptId!==o||v.intakeRevision!==i)throw new Error("planning-generation-predecessor-mismatch");const u=p.epics[e],g=u.createEpicPlanningPhysical.artifactDigests,_=u.createEpicPlanningResearchPhysical.artifactDigests,j={idea:`${c}/idea.json`,diagnosis:`${c}/diagnosis.md`,enrichment:`${c}/enriched-prompt.md`,research:`${c}/research.md`};for(const[w,D]of Object.entries(j)){const H=w==="research"?_.research:g[w];if(F("sha256").update(m(a(t.sourceRoot,D))).digest("hex")!==H)throw new Error("planning-generation-artifact-drift")}if(F("sha256").update(m(a(t.sourceRoot,l))).digest("hex")!==_.index)throw new Error("planning-generation-index-drift");const b=M(B(t.workspacePath,!0)),N=new RegExp(`^docs/stories/${e}\\.([0-9]{2})\\.story\\.md$`),y=b.paths.filter(w=>N.test(w)).sort((w,D)=>Number(N.exec(w)[1])-Number(N.exec(D)[1]));if(y.length<1||y.length>24||y.some((w,D)=>N.exec(w)[1]!==String(D+1).padStart(2,"0"))||b.paths.length!==y.length+2||b.paths.some(w=>w!==f&&w!==l&&!N.test(w)))throw new Error("planning-generation-write-scope-invalid");const A=[f,l,...y];for(const w of[f,...y]){P(t.sourceRoot,w),P(t.workspacePath,w);try{throw x(a(t.sourceRoot,w)),new Error("planning-generation-path-collision")}catch(H){if(H.code!=="ENOENT")throw H}const D=x(a(t.workspacePath,w));if(!D.isFile()||D.isSymbolicLink()||D.size<128||D.size>(w===f?512:128)*1024)throw new Error("planning-generation-document-invalid")}const R=(w,D)=>{const k=/^---\n([\s\S]{1,8192}?)\n---\n/.exec(w.replace(/\r\n/g,`
7
- `))?.[1]?.split(`
8
- `).filter(I=>I.startsWith(`${D}:`))??[];return k.length===1?k[0].slice(D.length+1).trim().replace(/^(?:"(.*)"|'(.*)')$/,"$1$2"):void 0},U=m(a(t.workspacePath,f),"utf8");if(R(U,"id")!==e||R(U,"stories_count")!==String(y.length)||!U.includes("## Stories"))throw new Error("planning-generation-epic-invalid");for(const[w,D]of y.entries()){const H=m(a(t.workspacePath,D),"utf8"),k=/^---\n([\s\S]{1,8192}?)\n---\n/.exec(H.replace(/\r\n/g,`
9
- `))?.[1]??"";if(R(H,"id")!==`${e}.${String(w+1).padStart(2,"0")}`||R(H,"epic_id")!==e||/^\s*epic\s*:/m.test(k)||R(H,"status")!=="backlog"||!H.includes("## Acceptance Criteria")||!/\bGiven\b[\s\S]*\bWhen\b[\s\S]*\bThen\b/.test(H))throw new Error("planning-generation-story-invalid")}const L=JSON.parse(m(a(t.sourceRoot,l),"utf8")),$=JSON.parse(m(a(t.workspacePath,l),"utf8")),q=L.epics,C=$.epics,z=q?.[e],V=C?.[e];if(!q||!C||!z||!V||z.id!==e||z.status!=="researched"||!Array.isArray(z.steps_completed)||z.steps_completed.length!==2||!tt(z.steps_completed[0],ut)||!tt(z.steps_completed[1],Rt)||!S(z.output_files,{diagnosis:`${c}/diagnosis.md`,research:`${c}/research.md`})||typeof $.last_scan!="string"||!Number.isFinite(Date.parse($.last_scan))||typeof V.completed_at!="string"||!Number.isFinite(Date.parse(V.completed_at)))throw new Error("planning-generation-index-predecessor-invalid");const X=V.steps_completed?.[2];if(!tt(X,Qt))throw new Error("planning-generation-index-transition-invalid");const Q={...L,last_scan:$.last_scan,epics:{...q,[e]:{...z,status:"generated",steps_completed:[...z.steps_completed,X],completed_at:V.completed_at,output_files:{...z.output_files,epic:f,stories:y}}}};if(!S($,Q))throw new Error("planning-generation-index-transition-invalid");const O=A.map(w=>F("sha256").update(m(a(t.workspacePath,w))).digest("hex")),W=()=>A.every((w,D)=>{try{return P(t.workspacePath,w),F("sha256").update(m(a(t.workspacePath,w))).digest("hex")===O[D]}catch{return!1}});return Y(t,A,t.baseSha,bt(t.sourceRoot),W)}catch{throw new Error("private-execution-planning-generation-admission-unverified")}}function Oe(t,r,e){try{if(!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(e))throw new Error("story-id-invalid");const n=".neocortex/state.json";P(r.sourceRoot,n),P(r.workspacePath,n);const o=m(a(r.sourceRoot,n));if(!o.equals(m(a(r.workspacePath,n))))throw new Error("state-drift");const i=JSON.parse(o.toString("utf8")),s=i?.stories?.[e],d=s?.files_to_modify;if(i?.schema_version!==1||s?.id!==e||!Array.isArray(d)||d.length<1||d.length>st||d.some(f=>typeof f!="string"||!rt(f)||f.length>1024||/[\r\n]/.test(f))||new Set(d).size!==d.length)throw new Error("write-set-invalid");const l=d,c=l.filter(f=>{if(!f.startsWith("docs/stories/")||!f.endsWith(".story.md"))return!1;P(r.sourceRoot,f),P(r.workspacePath,f);const h=m(a(r.sourceRoot,f));if(!h.equals(m(a(r.workspacePath,f))))throw new Error("story-drift");const v=/^---\r?\n([\s\S]{1,8192}?)\r?\n---(?:\r?\n|$)/.exec(h.toString("utf8"))?.[1]?.split(/\r?\n/).filter(u=>u.startsWith("id:"))??[];return v.length===1&&v[0].slice(3).trim().replace(/^(["'])(.*)\1$/,"$2")===e});if(c.length!==1)throw new Error("story-target-ambiguous");return`${t}
10
-
11
- Private story handoff (local only):
12
- ${JSON.stringify({storyId:e,storyPath:c[0],allowedPaths:[...l].sort()})}
13
- Work from this isolated checkout's current directory. Use the exact storyPath above for the delivered current step; do not use an unresolved story-file placeholder or infer a filename. Persist the current step's actual result in the declared files; a final chat response alone is not a saved candidate. Modify only allowedPaths and preserve story identity and the write set. Do not operate Git, change unrelated stories, grant root QA/checkpoint authority, or declare the story, epic or pipeline complete. If required inputs are unavailable, report failure without inventing evidence or completion.
14
- `}catch{throw new Error("private-execution-state-step-handoff-unverified")}}function Te(t,r){try{if(!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(r))throw new Error("story-id-invalid");const e=".neocortex/state.json";P(t.sourceRoot,e),P(t.workspacePath,e);const n=JSON.parse(m(a(t.sourceRoot,e),"utf8")),o=JSON.parse(m(a(t.workspacePath,e),"utf8"));if(!n||typeof n!="object"||Array.isArray(n)||!o||typeof o!="object"||Array.isArray(o))throw new Error("state-invalid");const i=n,s=o,d=i.stories,l=s.stories,c=d?.[r],f=l?.[r],h=c?.files_to_modify;if(i.schema_version!==1||s.schema_version!==1||!d||!l||!c||!f||c.id!==r||f.id!==r||f.epic_id!==c.epic_id||!Array.isArray(h)||h.length===0||h.length>st||h.some(R=>typeof R!="string"||!rt(R)||R.length>1024)||new Set(h).size!==h.length||!S(f.files_to_modify,h))throw new Error("write-set-invalid");const{[r]:p,...v}=d,{[r]:u,...g}=l,{stories:_,last_updated:j,...b}=i,{stories:N,last_updated:y,...A}=s;if(!S(g,v)||!S(A,b))throw new Error("state-unrelated-drift");return Y(t,[...h].sort(),t.baseSha)}catch{throw new Error("private-execution-state-step-admission-unverified")}}function je(t,r,e){if(r==="chaos-plan"&&e==="chaos-quarterly-v1")try{const o=new Date,i=`Q${Math.floor(o.getUTCMonth()/3)+1}-${o.getUTCFullYear()}`,s=M(B(t.workspacePath,!0)).paths,d=`tools/chaos/${i}-plan.md`,l=`.neocortex/chaos-history/${i}.md`,c=s.filter(u=>u.startsWith("tools/chaos/experiments/")),f=c.map(u=>/^tools\/chaos\/experiments\/(chaos-mesh|litmus|aws-fis)-(?:PodKill|NetworkChaos|IOChaos|StressChaos|TimeChaos|pod-kill|network-chaos|io-chaos|stress-chaos|time-chaos)-([a-z0-9]+(?:-[a-z0-9]+)*)\.yaml$/.exec(u));if(!s.includes(d)||!s.includes(l)||c.length<5||c.length>10||s.length!==c.length+2||f.some(u=>!u)||new Set(f.map(u=>u[1])).size!==1)throw new Error("chaos-write-set-invalid");for(const u of s)if(P(t.workspacePath,u),x(a(t.workspacePath,u)).size===0)throw new Error("chaos-empty-artifact");const h=m(a(t.workspacePath,l),"utf8");if(!h.includes(i)||!/\bPLANNED\b/.test(h))throw new Error("chaos-history-invalid");const v=G(t.workspacePath,["ls-tree","-z","HEAD","--",l]).length>0?G(t.workspacePath,["show",`HEAD:${l}`]).toString("utf8"):"";if(v&&!h.startsWith(v))throw new Error("chaos-history-not-append-only");return Y(t,s,t.baseSha)}catch{throw new Error("private-execution-project-step-admission-unverified")}const n=r==="sync-openapi"&&e==="sync-openapi-contract-v1"?["docs/architecture/api-contracts.md","docs/api/openapi.yaml"]:r==="tech-debt"&&e==="tech-debt-registry-v1"?[".neocortex/tech-debt-registry.json"]:void 0;if(!n)throw new Error("private-execution-project-step-admission-unverified");try{if(r==="tech-debt"){const o=".neocortex/tech-debt-registry.json";P(t.workspacePath,o);const i=JSON.parse(m(a(t.workspacePath,o),"utf8"));if(!i||typeof i!="object"||Array.isArray(i))throw new Error("registry-invalid");const s=i;if(s.schema_version!==2||s.version!==void 0&&s.version!==2||!Array.isArray(s.items))throw new Error("registry-invalid")}return Y(t,n,t.baseSha)}catch{throw new Error("private-execution-project-step-admission-unverified")}}function Tt(t,r){const e=t==="arch-prd"&&r==="arch-prd-docs-v1"?["docs/architecture/prd.md","docs/architecture/product-experience.md"]:t==="arch-architecture"&&r==="arch-architecture-doc-v1"?["docs/architecture/architecture.md"]:t==="arch-fitness-functions"&&r==="arch-fitness-tools-v1"?["tools/architecture/dependency-cruiser.config.js","tools/architecture/archunit-test.ts","tools/architecture/.eslintrc.boundaries.js"]:t==="arch-api-contracts"&&r==="arch-api-contracts-docs-v1"?["docs/architecture/api-contracts.md","docs/api/openapi.yaml"]:t==="arch-api-integrations"&&r==="arch-api-integrations-doc-v1"?["docs/architecture/api-integrations.md"]:t==="arch-database"&&r==="arch-database-doc-v1"?["docs/architecture/database-schema.md"]:t==="arch-security"&&r==="arch-security-doc-v1"?["docs/architecture/cyber-security.md"]:t==="arch-design-system"&&r==="arch-design-system-doc-v1"?["docs/architecture/atomic-design-system.md"]:t==="arch-performance"&&r==="arch-performance-doc-v1"?["docs/architecture/performance-strategy.md"]:t==="arch-testing"&&r==="arch-testing-doc-v1"?["docs/architecture/testing-strategy.md"]:t==="arch-infrastructure"&&r==="arch-infrastructure-doc-v1"?["docs/architecture/infrastructure-deploy.md"]:t==="arch-review"&&r==="arch-review-doc-v1"?["docs/architecture/review-consolidation.md"]:t==="update-memory"&&r==="arch-memory-canonical-v1"?["NEOCORTEX.md"]:null;return e===null?null:Object.freeze(e)}function Ne(t,r,e,n="all"){const o=Tt(r,e);if(!o)throw new Error("private-execution-architecture-admission-unverified");return`${t}
15
-
16
- Private architecture handoff (local only):
17
- ${JSON.stringify({allowedPaths:o})}
18
- ${n==="non-ui"?`The server admitted a signed non-UI scope for this snapshot. Preserve the backend-only scope: design-system and UX phases are not applicable. Do not require or generate interface artifacts.
19
- `:""}Write exactly the listed nonempty artifacts inside this isolated clone. Existing compliant listed artifacts may remain unchanged; all listed artifacts must exist after execution. Place any necessary scope notes inside those artifacts. Do not edit README, package files, other documents, state, Git, QA records or configuration outside the listed paths. Do not install dependencies, use network, deploy or continue to another stage. Preserve existing arch:keep blocks byte-for-byte. Do not add new arch:keep markers. For managed memory, preserve all bytes outside its existing managed block. Root independently reviews artifacts and owns continuation, Git and acceptance.
20
- `}function De(t,r,e,n,o){const i=Tt(r,e);if(!i||!/^P[1-9][0-9]{0,11}$/.test(n)||!/^[a-f0-9]{64}$/.test(o))throw new Error("private-execution-architecture-admission-unverified");try{P(t.workspacePath,".neocortex/state.json");const s=JSON.parse(m(a(t.workspacePath,".neocortex/state.json"),"utf8"));if(!s||typeof s!="object"||Array.isArray(s))throw new Error("state-invalid");const d=s,c=d.archPlan?.execution,f=d.epics;if(!c||c.currentStageCommand!==r||c.targetEpicId!==n||c.graphRevision!==o||!f||!Object.hasOwn(f,n))throw new Error("state-binding-mismatch");if(r==="update-memory"){if(c.continuationState!=="delegate"||c.reviewDecision?.decision!=="approved")throw new Error("memory-root-review-missing");const h="NEOCORTEX.md";if(!M(B(t.workspacePath,!0)).paths.includes(h))throw new Error("memory-effect-missing");P(t.sourceRoot,h),P(t.workspacePath,h);const p=m(a(t.sourceRoot,h),"utf8"),v=m(a(t.workspacePath,h),"utf8"),u="<!-- NEOCORTEX-MEMORY-START -->",g="<!-- NEOCORTEX-MEMORY-END -->",_=y=>{if(y.split(u).length!==2||y.split(g).length!==2||y.indexOf(u)>=y.indexOf(g))throw new Error("memory-markers-invalid");const A=y.indexOf(u)+u.length,R=y.indexOf(g);return{before:y.slice(0,A),body:y.slice(A,R),after:y.slice(R)}},j=_(p),b=_(v);if(j.before!==b.before||j.after!==b.after||j.body===b.body||Buffer.byteLength(v,"utf8")>32768||v.split(`
21
- `).length>240)throw new Error("memory-managed-block-invalid");const N=new Set(j.body.split(`
22
- `));for(const y of b.body.split(`
23
- `))N.has(y)||Mt(y)}if(r==="arch-prd"){const h="docs/architecture/prd.md";if(!M(B(t.workspacePath,!0)).paths.includes(h))throw new Error("prd-effect-missing");if(P(t.workspacePath,h),x(a(t.workspacePath,h)).size===0)throw new Error("prd-empty")}if(r==="arch-api-contracts"||r==="arch-fitness-functions"||r==="arch-api-integrations"||r==="arch-database"||r==="arch-security"||r==="arch-design-system"||r==="arch-performance"||r==="arch-testing"||r==="arch-infrastructure"||r==="arch-review"){const h=M(B(t.workspacePath,!0)).paths;for(const p of i){if(!h.includes(p)&&G(t.workspacePath,["cat-file","-t",`${t.baseSha}:${p}`]).toString("utf8").trim()!=="blob")throw new Error("architecture-required-effect-missing");if(P(t.workspacePath,p),x(a(t.workspacePath,p)).size===0)throw new Error("architecture-required-effect-empty");if(r==="arch-fitness-functions"){P(t.sourceRoot,p);const v=nt(a(t.sourceRoot,p))?m(a(t.sourceRoot,p),"utf8"):"",u=m(a(t.workspacePath,p),"utf8"),g=At(v),_=At(u);if(!S(_,g))throw new Error("architecture-keep-marker-changed")}}}return Y(t,i,t.baseSha)}catch{throw new Error("private-execution-architecture-admission-unverified")}}function Fe(t,r,e,n){try{if(!/^P[1-9][0-9]{0,11}$/.test(r)||!/^[a-f0-9]{64}$/.test(e)||n.schemaVersion!==1||n.targetEpicId!==r)throw new Error("binding-invalid");const o=Lt(t.sourceRoot,r),i=zt(t.workspacePath,r);if(!S(o,n)||!S(i,n))throw new Error("write-set-invalid");P(t.workspacePath,".neocortex/state.json");const s=JSON.parse(m(a(t.workspacePath,".neocortex/state.json"),"utf8"));if(!s||typeof s!="object"||Array.isArray(s))throw new Error("state-invalid");const d=s,c=d.archPlan?.execution,f=d.epics;if(!c||c.currentStageCommand!=="arch-ux-design"||c.continuationState!=="delegate"||c.targetEpicId!==r||c.graphRevision!==e||!f||!Object.hasOwn(f,r))throw new Error("stage-invalid");const h=M(B(t.workspacePath,!0)).paths;if(h.length!==n.pathCount||h.some(p=>!n.paths.includes(p)))throw new Error("agent-path-unclaimed");for(const p of n.paths){P(t.sourceRoot,p),P(t.workspacePath,p);const v=x(a(t.workspacePath,p));if(!v.isFile()||v.isSymbolicLink()||v.size<1)throw new Error("effect-empty");const u=new TextDecoder("utf-8",{fatal:!0}).decode(m(a(t.workspacePath,p)));if(!u.trim()||u.includes("\0"))throw new Error("effect-invalid")}return Ht(t.workspacePath,n),Y(t,n.paths,t.baseSha)}catch{throw new Error("private-execution-ux-admission-unverified")}}function Be(t,r,e,n,o,i){try{if(!/^P[1-9][0-9]{0,11}$/.test(r)||!/^[a-f0-9]{64}$/.test(e)||n.targetEpicId!==r||n.graphRevision!==e||o.schemaVersion!==1)throw new Error("binding-invalid");const s=Ct(t.sourceRoot);if(!s||!S(s.receipt,n))throw new Error("answers-invalid");const d=_t(t.sourceRoot,s.answers.generateProvider),l=_t(t.workspacePath,s.answers.generateProvider);if(d.outcome!=="ready"||l.outcome!=="ready"||!S(d.writeSet,o)||!S(l.writeSet,o))throw new Error("write-set-invalid");const c=M(B(t.workspacePath,!0)).paths;if(c.some(u=>!o.paths.includes(u)))throw new Error("agent-path-unclaimed");let f=0,h=0;for(const u of o.paths){if(P(t.sourceRoot,u),nt(a(t.sourceRoot,u))||!c.includes(u))throw new Error("effect-missing-or-overwrite");P(t.workspacePath,u);const g=x(a(t.workspacePath,u));if(!g.isFile()||g.size<1)throw new Error("effect-empty");const _=new TextDecoder("utf-8",{fatal:!0}).decode(m(a(t.workspacePath,u))),j=o.interactions.find(N=>N.filePath===u);if(u!==o.providerPath&&!j)throw new Error("private-execution-pact-interaction-missing");const b=Jt(u,_,s.answers.brokerUrl,j,t.workspacePath,s.answers.matchersAggressive);b.consumerBinding==="app-import-candidate"&&f++,b.consumerBinding==="mock-only-scaffold"&&h++}let p=o.paths;if(i){const u=Ut(t.sourceRoot,s.answers.generateProvider),g=_=>F("sha256").update(_).digest("hex");if(i.schemaVersion!==1||i.outcome!=="candidate-unverified"||i.executionVerified!==!1||i.sourceManifestSha256!==g(u.sourceText)||i.sourceLockSha256!==(u.sourceLockText===null?null:g(u.sourceLockText))||i.manifestText!==u.candidateText||!lt(t.sourceRoot,t.workspacePath,t.baseSha,"package.json")||!lt(t.sourceRoot,t.workspacePath,t.baseSha,"package-lock.json"))throw new Error("toolchain-binding-invalid");for(const _ of["package.json","package-lock.json"]){P(t.sourceRoot,_),P(t.workspacePath,_);const j=_==="package.json"?u.sourceText:u.sourceLockText,b=nt(a(t.workspacePath,_))?m(a(t.workspacePath,_),"utf8"):null;if(j!==b)throw new Error("toolchain-clone-raced")}if(mt(a(t.workspacePath,"package.json"),i.manifestText),mt(a(t.workspacePath,"package-lock.json"),i.lockText),Yt(t.workspacePath,s.answers.generateProvider).outcome!=="manifest-aligned")throw new Error("toolchain-candidate-invalid");p=[...o.paths,"package.json","package-lock.json"]}const v=Y(t,p,t.baseSha);return Object.freeze({...v,consumerBindingCandidateCount:f,consumerScaffoldCount:h})}catch{throw new Error("private-execution-pact-admission-unverified")}}function Ce(t,r,e,n=new Date().toISOString()){try{const o=xt(t.sourceRoot,r);if(!S(o,e)||!pt(t.sourceRoot,t.workspacePath,e.prdPath)||e.aggregates.some(h=>!pt(t.sourceRoot,t.workspacePath,h.implementationFile))||e.writeSet.length===0)throw new Error("plan-drift");const i=M(B(t.workspacePath,!0)).paths;if(!S(i,e.writeSet))throw new Error("agent-path-unclaimed");const s=new Map;for(const h of e.aggregates){P(t.sourceRoot,h.testFile),P(t.workspacePath,h.testFile);const p=x(a(t.workspacePath,h.testFile));if(!p.isFile()||p.isSymbolicLink()||p.size<1||p.size>Z)throw new Error("test-invalid");const v=m(a(t.workspacePath,h.testFile)),u=new TextDecoder("utf-8",{fatal:!0}).decode(v),g=e.language==="typescript"?(u.match(/\b(?:it|test)\s*\(/g)??[]).length:(u.match(/^def\s+test_[A-Za-z0-9_]+\s*\(/gm)??[]).length;if(!u.trim()||u.includes("\0")||g<h.invariantCount||/\b(?:it|test|describe)\s*\.\s*(?:skip|todo|only)\s*\(/.test(u)||!u.includes(String(e.numRuns)))throw new Error("test-invalid");e.language==="typescript"&&Kt({source:u,testFile:h.testFile,implementationFile:h.implementationFile,invariantCount:h.invariantCount,numRuns:e.numRuns}),s.set(h.testFile,F("sha256").update(v).digest("hex"))}P(t.sourceRoot,e.prdPath),P(t.workspacePath,e.prdPath);const d=m(a(t.workspacePath,e.prdPath)),l=new TextDecoder("utf-8",{fatal:!0}).decode(d),c=Xt(l,e,n);mt(a(t.workspacePath,e.prdPath),c);const f=F("sha256").update(c).digest("hex");return Y(t,[...e.writeSet,e.prdPath],t.baseSha,void 0,()=>{try{return!S(xt(t.sourceRoot,r),e)||e.aggregates.some(h=>!pt(t.sourceRoot,t.workspacePath,h.implementationFile))||F("sha256").update(m(a(t.workspacePath,e.prdPath))).digest("hex")!==f?!1:[...s].every(([h,p])=>F("sha256").update(m(a(t.workspacePath,h))).digest("hex")===p)}catch{return!1}})}catch{throw new Error("private-execution-property-admission-unverified")}}export{De as admitPrivateArchitectureStageWorkspace,Re as admitPrivateEpicClaimWorkspace,$e as admitPrivateGenerateEpicWorkspace,Ae as admitPrivateIdeaDiagnosisWorkspace,Se as admitPrivateIdeaResearchWorkspace,Be as admitPrivatePactWorkspace,je as admitPrivateProjectStepWorkspace,Ce as admitPrivatePropertyTestWorkspace,Te as admitPrivateStateBoundStepWorkspace,Fe as admitPrivateUxWorkspace,xe as admitPrivateYoloFrozenWorkspace,Ne as appendPrivateArchitectureHandoff,Oe as appendPrivateStateBoundStepHandoff,Tt as privateArchitectureStagePaths,ve as privateYoloSourceFileMatchesSnapshot,be as verifyPrivateYoloRejectedCandidateOrigin,_e as verifyPrivateYoloSnapshotBoundary};
1
+ /** Root-owned, fail-closed patch admission from a private agent checkout. */
2
+ import { execFileSync, spawnSync } from 'node:child_process';
3
+ import { createHash } from 'node:crypto';
4
+ import { existsSync, lstatSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
5
+ import { isAbsolute, join, relative, sep } from 'node:path';
6
+ import { isDeepStrictEqual } from 'node:util';
7
+ import { readPrivateYoloFrozenWriteSet, verifyPrivateYoloCandidateWriteSet, verifyPrivateYoloCleanBaseline, } from './clean-baseline.js';
8
+ import { PRIVATE_EPIC_PENDING_SIDECAR } from './isolated-workspace.js';
9
+ import { loadPactPrivateAnswers } from '../commands/architecture-answers.js';
10
+ import { derivePactWriteSet } from './pact-write-set.js';
11
+ import { deriveUxCandidateWriteSet, deriveUxWriteSet } from './ux-write-set.js';
12
+ import { validateUxCandidateSource } from './ux-source-validation.js';
13
+ import { validatePactTestSource } from './pact-test-source.js';
14
+ import { classifyProjectMemoryUpdate } from '../memory/project-memory-writer.js';
15
+ import { privateRootMemoryEffect, privateRootMemorySourceMatches, privateRootMemorySourceMatchReason } from './root-memory-scope.js';
16
+ import { inspectPactToolchain, planPactToolchainManifest } from './pact-toolchain.js';
17
+ import { acceptedPlanningDiagnosisPredecessor, planningPendingSidecarDigest } from './planning-diagnosis-checkpoint.js';
18
+ import { acceptedPlanningResearchPredecessor } from './planning-research-checkpoint.js';
19
+ import { confirmPropertyTestPlan, renderPropertyTestConfiguration } from './property-test-plan.js';
20
+ import { validatePropertyTestTypeScriptSource } from './property-test-source.js';
21
+ const MAX_PATCH_BYTES = 16 * 1024 * 1024;
22
+ const MAX_PATHS = 1024;
23
+ // Full digest of the server-owned P01 step ID; do not ship that identifier in client dist.
24
+ const P01_DIAGNOSIS_STEP_SHA256 = 'acd455ed0e39d98b90fb57391db75e366e4c5604db043e03a89a20e33d643c3c';
25
+ const P01_ENRICHMENT_STEP_SHA256 = '81f50aaa801c1660eabe46e645501acbfb3e9873410c991e1496c014ca75c369';
26
+ // Full digest of the server-owned P02 step ID; do not ship its plaintext in client dist.
27
+ const P02_RESEARCH_STEP_SHA256 = 'fa2194f89c01f07e225fd6f39e98e679f628e072239b6cd0af3ee527f0722518';
28
+ const P03_GENERATION_STEP_SHA256 = '8636f22ec5950410902448ee1e9cb4947359ac98df518742bddbbb367468e3ae';
29
+ function matchesStepDigest(value, digest) {
30
+ return typeof value === 'string' && value.length <= 128
31
+ && createHash('sha256').update(value).digest('hex') === digest;
32
+ }
33
+ function environment() {
34
+ const env = { ...process.env };
35
+ for (const key of [
36
+ 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY',
37
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_COMMON_DIR', 'GIT_NAMESPACE', 'GIT_CEILING_DIRECTORIES',
38
+ ])
39
+ delete env[key];
40
+ return env;
41
+ }
42
+ function git(root, args) {
43
+ return execFileSync('git', [...args], {
44
+ cwd: root, env: environment(), stdio: ['ignore', 'pipe', 'pipe'],
45
+ timeout: 30_000, maxBuffer: MAX_PATCH_BYTES,
46
+ });
47
+ }
48
+ function output(root, args) {
49
+ return git(root, args).toString('utf8').trim();
50
+ }
51
+ function status(root, includeIgnored = false) {
52
+ return git(root, ['status', '--porcelain=v1', '-z', '--untracked-files=all',
53
+ ...(includeIgnored ? ['--ignored=matching'] : [])]);
54
+ }
55
+ function safePath(path) {
56
+ return Boolean(path) && !isAbsolute(path) && !path.includes('\\') && !path.includes('\0')
57
+ && !path.split('/').some((part) => !part || part === '.' || part === '..' || part === '.git');
58
+ }
59
+ function regularOrAbsent(root, path) {
60
+ const parts = path.split('/');
61
+ let current = root;
62
+ for (const part of parts.slice(0, -1)) {
63
+ current = join(current, part);
64
+ try {
65
+ const entry = lstatSync(current);
66
+ if (!entry.isDirectory() || entry.isSymbolicLink())
67
+ throw new Error('path-boundary');
68
+ }
69
+ catch (error) {
70
+ if (error.code === 'ENOENT')
71
+ return;
72
+ throw error;
73
+ }
74
+ }
75
+ try {
76
+ const entry = lstatSync(join(root, path));
77
+ if (!entry.isFile() || entry.isSymbolicLink() || entry.size > MAX_PATCH_BYTES) {
78
+ throw new Error('path-boundary');
79
+ }
80
+ }
81
+ catch (error) {
82
+ if (error.code !== 'ENOENT')
83
+ throw error;
84
+ }
85
+ }
86
+ /** Fitness regeneration may replace managed rules, but never a user's marked custom blocks. */
87
+ function architectureKeepBlocks(value) {
88
+ const blocks = [...value.matchAll(/\/\/\s*arch:keep-start[^\n]*\n[\s\S]*?\/\/\s*arch:keep-end[^\n]*/g)]
89
+ .map((match) => match[0]);
90
+ const starts = (value.match(/arch:keep-start/g) ?? []).length;
91
+ const ends = (value.match(/arch:keep-end/g) ?? []).length;
92
+ if (starts !== ends || starts !== blocks.length)
93
+ throw new Error('architecture-keep-marker-invalid');
94
+ return blocks;
95
+ }
96
+ function changedPaths(raw, allowEmpty = false, allowIgnoredPendingSidecar = false, rootMemoryTypeChanges = []) {
97
+ const paths = [];
98
+ const untracked = [];
99
+ for (const record of raw.toString('utf8').split('\0').filter(Boolean)) {
100
+ if (record.length < 4 || record[2] !== ' ')
101
+ throw new Error('status-invalid');
102
+ const code = record.slice(0, 2);
103
+ const path = record.slice(3);
104
+ if (!safePath(path) || (code !== '??' && code !== ' M' && code !== ' D'
105
+ && !(code === ' T' && rootMemoryTypeChanges.includes(path))
106
+ && !(allowIgnoredPendingSidecar && code === '!!' && path === PRIVATE_EPIC_PENDING_SIDECAR))) {
107
+ throw new Error('status-invalid');
108
+ }
109
+ paths.push(path);
110
+ if (code === '??')
111
+ untracked.push(path);
112
+ }
113
+ if ((!allowEmpty && paths.length === 0) || paths.length > MAX_PATHS
114
+ || new Set(paths).size !== paths.length) {
115
+ throw new Error('status-invalid');
116
+ }
117
+ return { paths: paths.sort(), untracked: untracked.sort() };
118
+ }
119
+ function sameAsSnapshot(root, snapshotRoot, snapshotSha, path) {
120
+ const tree = git(snapshotRoot, ['ls-tree', '-z', snapshotSha, '--', path]).toString('utf8');
121
+ if (!tree) {
122
+ try {
123
+ lstatSync(join(root, path));
124
+ return false;
125
+ }
126
+ catch (error) {
127
+ return error.code === 'ENOENT';
128
+ }
129
+ }
130
+ const entry = /^([0-7]{6}) blob [a-f0-9]{40,64}\t([^\0]+)\0$/.exec(tree);
131
+ if (!entry || entry[2] !== path || !['100644', '100755'].includes(entry[1]))
132
+ return false;
133
+ try {
134
+ const file = lstatSync(join(root, path));
135
+ if (!file.isFile() || file.isSymbolicLink() || file.size > MAX_PATCH_BYTES
136
+ || Boolean(file.mode & 0o111) !== (entry[1] === '100755'))
137
+ return false;
138
+ return readFileSync(join(root, path)).equals(git(snapshotRoot, ['show', `${snapshotSha}:${path}`]));
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ }
144
+ /** Read-only regular-file comparison; it grants no additional write scope. */
145
+ export function privateYoloSourceFileMatchesSnapshot(root, snapshotRoot, snapshotSha, path) {
146
+ return safePath(path) && /^[a-f0-9]{40,64}$/.test(snapshotSha) && sameAsSnapshot(root, snapshotRoot, snapshotSha, path);
147
+ }
148
+ function pendingSidecarMatches(root, digest) {
149
+ try {
150
+ const path = join(root, PRIVATE_EPIC_PENDING_SIDECAR);
151
+ const directory = lstatSync(join(root, '.neocortex'));
152
+ const entry = lstatSync(path);
153
+ return directory.isDirectory() && !directory.isSymbolicLink()
154
+ && directory.uid === process.getuid?.() && (directory.mode & 0o022) === 0
155
+ && entry.isFile() && !entry.isSymbolicLink()
156
+ && entry.uid === process.getuid?.() && (entry.mode & 0o077) === 0
157
+ && entry.size > 0 && entry.size <= 128 * 1024
158
+ && createHash('sha256').update(readFileSync(path)).digest('hex') === digest;
159
+ }
160
+ catch {
161
+ return false;
162
+ }
163
+ }
164
+ function sourceMatchesSnapshot(root, snapshotRoot, snapshotSha, frozenPaths, pendingSidecarSha256, rootMemoryScope) {
165
+ const claimed = new Set(frozenPaths);
166
+ const memory = rootMemoryScope ? privateRootMemoryEffect(rootMemoryScope).files.map(file => file.path) : [];
167
+ const dirty = changedPaths(status(root), true, !!pendingSidecarSha256, memory);
168
+ const memoryMatch = rootMemoryScope ? privateRootMemorySourceMatchReason(rootMemoryScope, root, snapshotRoot, snapshotSha) : 'matched';
169
+ if (memoryMatch !== 'matched') {
170
+ throw new Error(`root-memory-${memoryMatch}`);
171
+ }
172
+ if (!dirty.paths.every((path) => claimed.has(path) || memory.includes(path)
173
+ || path === PRIVATE_EPIC_PENDING_SIDECAR && !!pendingSidecarSha256
174
+ && pendingSidecarMatches(root, pendingSidecarSha256)))
175
+ throw new Error('source-unclaimed-delta');
176
+ if (!frozenPaths.every((path) => sameAsSnapshot(root, snapshotRoot, snapshotSha, path)))
177
+ throw new Error('source-frozen-mismatch');
178
+ return !pendingSidecarSha256 || pendingSidecarMatches(root, pendingSidecarSha256);
179
+ }
180
+ function patchForNewFile(root, path) {
181
+ const result = spawnSync('git', ['diff', '--no-index', '--binary', '--no-ext-diff', '--', '/dev/null', path], {
182
+ cwd: root, env: environment(), encoding: 'buffer', timeout: 30_000, maxBuffer: MAX_PATCH_BYTES,
183
+ });
184
+ if (result.error || result.status !== 1 || !Buffer.isBuffer(result.stdout)
185
+ || result.stdout.byteLength === 0)
186
+ throw new Error('patch-invalid');
187
+ return result.stdout;
188
+ }
189
+ function apply(root, patch, check) {
190
+ const result = spawnSync('git', ['apply', ...(check ? ['--check'] : []), '--binary', '-'], {
191
+ cwd: root, env: environment(), input: patch, encoding: 'buffer', timeout: 30_000,
192
+ maxBuffer: MAX_PATCH_BYTES,
193
+ });
194
+ if (result.error || result.status !== 0)
195
+ throw new Error('apply-failed');
196
+ }
197
+ function sameFile(source, target, path) {
198
+ const left = join(source, path);
199
+ const right = join(target, path);
200
+ try {
201
+ const sourceEntry = lstatSync(left);
202
+ const targetEntry = lstatSync(right);
203
+ return sourceEntry.isFile() && targetEntry.isFile()
204
+ && (sourceEntry.mode & 0o111) === (targetEntry.mode & 0o111)
205
+ && readFileSync(left).equals(readFileSync(right));
206
+ }
207
+ catch (error) {
208
+ if (error.code !== 'ENOENT')
209
+ return false;
210
+ try {
211
+ lstatSync(left);
212
+ return false;
213
+ }
214
+ catch (leftError) {
215
+ if (leftError.code !== 'ENOENT')
216
+ return false;
217
+ }
218
+ try {
219
+ lstatSync(right);
220
+ return false;
221
+ }
222
+ catch (rightError) {
223
+ return rightError.code === 'ENOENT';
224
+ }
225
+ }
226
+ }
227
+ /** A persisted snapshot must still match both physical checkouts before reuse. */
228
+ export function verifyPrivateYoloSnapshotBoundary(workspace, frozenPaths, snapshotSha, rootMemoryScope) {
229
+ verifySnapshotBoundary(workspace, frozenPaths, snapshotSha, true, rootMemoryScope);
230
+ }
231
+ /** Recovery may inspect unaccepted working files, never a changed index or pin. */
232
+ export function verifyPrivateYoloRejectedCandidateOrigin(workspace, frozenPaths, snapshotSha, rootMemoryScope) {
233
+ verifySnapshotBoundary(workspace, frozenPaths, snapshotSha, false, rootMemoryScope);
234
+ if (git(workspace.workspacePath, ['diff', '--cached', '--name-only']).byteLength !== 0) {
235
+ throw new Error('private-execution-isolated-snapshot-unverified');
236
+ }
237
+ }
238
+ function verifySnapshotBoundary(workspace, frozenPaths, snapshotSha, requireCleanCandidate, rootMemoryScope) {
239
+ try {
240
+ if (process.platform !== 'linux' || workspace.schemaVersion !== 1
241
+ || !isAbsolute(workspace.sourceRoot) || !isAbsolute(workspace.workspacePath)
242
+ || realpathSync(workspace.sourceRoot) !== workspace.sourceRoot
243
+ || realpathSync(workspace.workspacePath) !== workspace.workspacePath
244
+ || !lstatSync(workspace.workspacePath).isDirectory()
245
+ || (lstatSync(workspace.workspacePath).mode & 0o077) !== 0
246
+ || relative(workspace.sourceRoot, workspace.workspacePath) === ''
247
+ || relative(workspace.sourceRoot, workspace.workspacePath).split(sep)[0] !== '..'
248
+ || !/^[a-f0-9]{40,64}$/.test(workspace.baseSha)
249
+ || !/^[a-f0-9]{40,64}$/.test(snapshotSha)
250
+ || output(workspace.sourceRoot, ['rev-parse', '--show-toplevel']) !== workspace.sourceRoot
251
+ || output(workspace.workspacePath, ['rev-parse', '--show-toplevel']) !== workspace.workspacePath
252
+ || output(workspace.sourceRoot, ['rev-parse', 'HEAD']) !== workspace.baseSha
253
+ || output(workspace.workspacePath, ['rev-parse', 'HEAD']) !== snapshotSha
254
+ || output(workspace.sourceRoot, ['symbolic-ref', '--short', 'HEAD']) !== workspace.executionBranch
255
+ || output(workspace.workspacePath, ['symbolic-ref', '--short', 'HEAD']) !== workspace.executionBranch
256
+ || existsSync(join(workspace.workspacePath, '.git', 'objects', 'info', 'alternates'))
257
+ || requireCleanCandidate && status(workspace.workspacePath, true).byteLength !== 0
258
+ || frozenPaths.length === 0 || frozenPaths.length > MAX_PATHS
259
+ || frozenPaths.some((path) => !safePath(path))
260
+ || new Set(frozenPaths).size !== frozenPaths.length)
261
+ throw new Error('boundary-invalid');
262
+ git(workspace.workspacePath, ['merge-base', '--is-ancestor', workspace.baseSha, snapshotSha]);
263
+ if (!sourceMatchesSnapshot(workspace.sourceRoot, workspace.workspacePath, snapshotSha, frozenPaths, undefined, rootMemoryScope)) {
264
+ throw new Error('boundary-raced');
265
+ }
266
+ }
267
+ catch (error) {
268
+ const reason = error instanceof Error && (['boundary-invalid', 'boundary-raced', 'status-invalid',
269
+ 'source-unclaimed-delta', 'source-frozen-mismatch'].includes(error.message) || /^root-memory-(scope-binding|snapshot-ancestry|snapshot-content|source-content|source-projection)(-unsafe)?$/.test(error.message))
270
+ ? error.message : 'physical-inspection-failed';
271
+ throw new Error('private-execution-isolated-snapshot-unverified', { cause: new Error(reason) });
272
+ }
273
+ }
274
+ /**
275
+ * Admit one uncommitted agent patch into a still-clean root. This deliberately
276
+ * does not stage, commit, emit QA, or claim that a YOLO step passed.
277
+ * @internal The frozen-claim entrypoint below is required for C01 ownership.
278
+ */
279
+ function admitPrivateYoloIsolatedWorkspace(workspace, authorizedPaths, snapshotSha, pendingSidecarSha256, candidateIntegrity, forbiddenPaths = [], rootMemoryScope) {
280
+ try {
281
+ if (process.platform !== 'linux' || workspace.schemaVersion !== 1
282
+ || !isAbsolute(workspace.sourceRoot) || !isAbsolute(workspace.workspacePath)
283
+ || realpathSync(workspace.sourceRoot) !== workspace.sourceRoot
284
+ || realpathSync(workspace.workspacePath) !== workspace.workspacePath
285
+ || !lstatSync(workspace.workspacePath).isDirectory()
286
+ || (lstatSync(workspace.workspacePath).mode & 0o077) !== 0
287
+ || relative(workspace.sourceRoot, workspace.workspacePath) === ''
288
+ || relative(workspace.sourceRoot, workspace.workspacePath).split(sep)[0] !== '..'
289
+ || output(workspace.sourceRoot, ['rev-parse', '--show-toplevel']) !== workspace.sourceRoot
290
+ || output(workspace.workspacePath, ['rev-parse', '--show-toplevel']) !== workspace.workspacePath
291
+ || output(workspace.sourceRoot, ['rev-parse', 'HEAD']) !== workspace.baseSha
292
+ || !/^[a-f0-9]{40,64}$/.test(snapshotSha)
293
+ || output(workspace.workspacePath, ['rev-parse', 'HEAD']) !== snapshotSha
294
+ || output(workspace.sourceRoot, ['symbolic-ref', '--short', 'HEAD']) !== workspace.executionBranch
295
+ || output(workspace.workspacePath, ['symbolic-ref', '--short', 'HEAD']) !== workspace.executionBranch
296
+ || authorizedPaths.length === 0 || authorizedPaths.length > MAX_PATHS
297
+ || authorizedPaths.some((path) => !safePath(path))
298
+ || new Set(authorizedPaths).size !== authorizedPaths.length)
299
+ throw new Error('authority-invalid');
300
+ git(workspace.workspacePath, ['merge-base', '--is-ancestor', workspace.baseSha, snapshotSha]);
301
+ if (!sourceMatchesSnapshot(workspace.sourceRoot, workspace.workspacePath, snapshotSha, authorizedPaths, pendingSidecarSha256, rootMemoryScope)) {
302
+ throw new Error('source-snapshot-mismatch');
303
+ }
304
+ const before = status(workspace.workspacePath, true);
305
+ const changes = changedPaths(before);
306
+ const claim = new Set(authorizedPaths);
307
+ if (changes.paths.some((path) => !claim.has(path) || forbiddenPaths.includes(path))) {
308
+ throw new Error('path-unclaimed');
309
+ }
310
+ if (candidateIntegrity && !candidateIntegrity())
311
+ throw new Error('candidate-raced');
312
+ for (const path of changes.paths) {
313
+ regularOrAbsent(workspace.workspacePath, path);
314
+ regularOrAbsent(workspace.sourceRoot, path);
315
+ }
316
+ const trackedPatch = git(workspace.workspacePath, ['diff', '--binary', '--no-ext-diff', '--no-textconv', 'HEAD', '--']);
317
+ const patches = [trackedPatch, ...changes.untracked.map((path) => patchForNewFile(workspace.workspacePath, path))];
318
+ const patch = Buffer.concat(patches.map((item) => Buffer.concat([item, Buffer.from('\n')])));
319
+ if (patch.byteLength === 0 || patch.byteLength > MAX_PATCH_BYTES
320
+ || !status(workspace.workspacePath, true).equals(before)
321
+ || candidateIntegrity && !candidateIntegrity()
322
+ || !sourceMatchesSnapshot(workspace.sourceRoot, workspace.workspacePath, snapshotSha, authorizedPaths, pendingSidecarSha256, rootMemoryScope)) {
323
+ throw new Error('patch-raced');
324
+ }
325
+ apply(workspace.sourceRoot, patch, true);
326
+ if (!status(workspace.workspacePath, true).equals(before)
327
+ || candidateIntegrity && !candidateIntegrity()
328
+ || !sourceMatchesSnapshot(workspace.sourceRoot, workspace.workspacePath, snapshotSha, authorizedPaths, pendingSidecarSha256, rootMemoryScope)) {
329
+ throw new Error('patch-raced');
330
+ }
331
+ apply(workspace.sourceRoot, patch, false);
332
+ const memory = rootMemoryScope ? privateRootMemoryEffect(rootMemoryScope).files.map(file => file.path) : [];
333
+ const admitted = changedPaths(status(workspace.sourceRoot), true, !!pendingSidecarSha256, memory);
334
+ if (admitted.paths.some((path) => !claim.has(path) && !memory.includes(path) && path !== PRIVATE_EPIC_PENDING_SIDECAR)
335
+ || rootMemoryScope && !privateRootMemorySourceMatches(rootMemoryScope, workspace.sourceRoot)
336
+ || pendingSidecarSha256 && !pendingSidecarMatches(workspace.sourceRoot, pendingSidecarSha256)
337
+ || authorizedPaths.some((path) => !sameFile(workspace.workspacePath, workspace.sourceRoot, path))
338
+ || output(workspace.sourceRoot, ['rev-parse', 'HEAD']) !== workspace.baseSha
339
+ || output(workspace.sourceRoot, ['symbolic-ref', '--short', 'HEAD']) !== workspace.executionBranch) {
340
+ throw new Error('admission-raced');
341
+ }
342
+ return Object.freeze({ schemaVersion: 1, outcome: 'admitted-unverified',
343
+ baseSha: workspace.baseSha, executionBranch: workspace.executionBranch,
344
+ changedPaths: Object.freeze([...changes.paths]),
345
+ patchSha256: createHash('sha256').update(patch).digest('hex') });
346
+ }
347
+ catch {
348
+ throw new Error('private-execution-isolated-admission-unverified');
349
+ }
350
+ }
351
+ /** Bind one-shot admission to the physically verified C01 claim and graph. */
352
+ export async function admitPrivateYoloFrozenWorkspace(workspace, storyId, graphRevision, snapshotSha = workspace.baseSha, rootOwnedSetup = false, rootMemoryScope) {
353
+ try {
354
+ const frozen = await readPrivateYoloFrozenWriteSet(workspace.sourceRoot, storyId, workspace.executionBranch, graphRevision);
355
+ if (rootMemoryScope) {
356
+ const effect = privateRootMemoryEffect(rootMemoryScope);
357
+ if (effect.storyId !== storyId || effect.childClaimRevision !== createHash('sha256').update([...frozen].sort().join('\0')).digest('hex')) {
358
+ throw new Error('root-memory-claim-mismatch');
359
+ }
360
+ }
361
+ const statePath = join(workspace.workspacePath, '.neocortex', 'state.json');
362
+ const stat = lstatSync(statePath);
363
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_PATCH_BYTES) {
364
+ throw new Error('candidate-state-invalid');
365
+ }
366
+ verifyPrivateYoloCandidateWriteSet(readFileSync(statePath, 'utf8'), storyId, frozen);
367
+ const receipt = admitPrivateYoloIsolatedWorkspace(workspace, frozen, snapshotSha, undefined, undefined, rootOwnedSetup ? ['.neocortex/state.json'] : [], rootMemoryScope);
368
+ await verifyPrivateYoloCleanBaseline(workspace.sourceRoot, storyId, workspace.executionBranch, graphRevision);
369
+ return receipt;
370
+ }
371
+ catch {
372
+ throw new Error('private-execution-isolated-admission-unverified');
373
+ }
374
+ }
375
+ /**
376
+ * Admit only a generated epic whose physical artifacts agree with the reserved
377
+ * epic ID. The claim is still unverified: this checks structure and write scope,
378
+ * not the quality of the agent's planning or story acceptance criteria.
379
+ */
380
+ // A newly generated queue entry cannot carry a private agent's workflow authority.
381
+ const PRIVATE_EPIC_STORY_DRAFT_KEYS = new Set([
382
+ 'id', 'epic_id', 'title', 'description', 'status', 'steps_completed', 'last_step',
383
+ 'branch_name', 'estimated_hours', 'complexity', 'priority', 'type', 'depends_on',
384
+ 'created_at', 'updated_at', 'acceptance_criteria',
385
+ ]);
386
+ const PRIVATE_EPIC_DRAFT_KEYS = new Set([
387
+ 'id', 'title', 'description', 'status', 'stories', 'total_stories', 'completed_stories',
388
+ 'estimated_hours', 'complexity', 'priority', 'created_at', 'updated_at', 'last_updated', 'source', 'tags',
389
+ ]);
390
+ function validPrivateEpicDraftMetadata(value) {
391
+ const strings = ['title', 'description', 'complexity', 'priority', 'type', 'created_at', 'updated_at', 'last_updated', 'source'];
392
+ if (strings.some(key => value[key] !== undefined && typeof value[key] !== 'string'))
393
+ return false;
394
+ if (value.estimated_hours !== undefined && (typeof value.estimated_hours !== 'number'
395
+ || !Number.isFinite(value.estimated_hours) || value.estimated_hours < 0))
396
+ return false;
397
+ return ['depends_on', 'acceptance_criteria', 'tags'].every(key => value[key] === undefined
398
+ || Array.isArray(value[key]) && value[key].length <= 256
399
+ && value[key].every(item => typeof item === 'string'));
400
+ }
401
+ function validPrivateEpicStoryDraft(story) {
402
+ return Object.keys(story).every(key => PRIVATE_EPIC_STORY_DRAFT_KEYS.has(key))
403
+ && story.status === 'backlog' && validPrivateEpicDraftMetadata(story)
404
+ && (story.steps_completed === undefined || Array.isArray(story.steps_completed) && story.steps_completed.length === 0)
405
+ && (story.last_step === undefined || story.last_step === null)
406
+ && (story.branch_name === undefined || story.branch_name === null);
407
+ }
408
+ export function admitPrivateEpicClaimWorkspace(workspace, claimId, epicId, logicalFingerprint, pendingSidecarSha256) {
409
+ try {
410
+ if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(claimId)
411
+ || !/^P[1-9][0-9]*$/.test(epicId) || !/^[a-f0-9]{64}$/.test(logicalFingerprint)
412
+ || !/^[a-f0-9]{64}$/.test(pendingSidecarSha256)
413
+ || !pendingSidecarMatches(workspace.sourceRoot, pendingSidecarSha256)) {
414
+ throw new Error('claim-invalid');
415
+ }
416
+ const pending = JSON.parse(readFileSync(join(workspace.sourceRoot, PRIVATE_EPIC_PENDING_SIDECAR), 'utf8'));
417
+ const operation = pending && typeof pending === 'object' && !Array.isArray(pending)
418
+ ? pending.operations?.[logicalFingerprint]
419
+ : undefined;
420
+ if (!operation || typeof operation !== 'object' || Array.isArray(operation)
421
+ || operation.claimId !== claimId
422
+ || operation.epicId !== epicId) {
423
+ throw new Error('claim-sidecar-mismatch');
424
+ }
425
+ const changes = changedPaths(status(workspace.workspacePath, true));
426
+ const epicPath = `docs/epics/epic-${epicId}.md`;
427
+ const statePath = '.neocortex/state.json';
428
+ const storyPattern = new RegExp(`^docs/stories/${epicId}\\.[0-9]{2}\\.story\\.md$`);
429
+ const storyPaths = changes.paths.filter((path) => storyPattern.test(path));
430
+ if (!changes.paths.includes(epicPath) || !changes.paths.includes(statePath)
431
+ || storyPaths.length === 0 || storyPaths.length > 99
432
+ || changes.paths.some((path) => path !== epicPath && path !== statePath
433
+ && path !== 'NEOCORTEX.md' && !storyPattern.test(path))) {
434
+ throw new Error('artifact-scope-invalid');
435
+ }
436
+ const storyIds = storyPaths.map((path) => path.slice('docs/stories/'.length, -'.story.md'.length)).sort();
437
+ if (storyIds.some((id, index) => id !== `${epicId}.${String(index + 1).padStart(2, '0')}`)) {
438
+ throw new Error('story-sequence-invalid');
439
+ }
440
+ for (const path of [epicPath, ...storyPaths]) {
441
+ try {
442
+ lstatSync(join(workspace.sourceRoot, path));
443
+ throw new Error('artifact-already-exists');
444
+ }
445
+ catch (error) {
446
+ if (error.code !== 'ENOENT')
447
+ throw error;
448
+ }
449
+ }
450
+ const scalar = (path, key) => {
451
+ regularOrAbsent(workspace.workspacePath, path);
452
+ const content = readFileSync(join(workspace.workspacePath, path), 'utf8');
453
+ if (Buffer.byteLength(content) > MAX_PATCH_BYTES)
454
+ return null;
455
+ const frontmatter = /^---\r?\n([\s\S]{1,8192}?)\r?\n---(?:\r?\n|$)/.exec(content)?.[1];
456
+ const line = frontmatter?.split(/\r?\n/).find((item) => item.startsWith(`${key}:`));
457
+ const raw = line?.slice(key.length + 1).trim();
458
+ return raw?.replace(/^(["'])(.*)\1$/, '$2') ?? null;
459
+ };
460
+ if (scalar(epicPath, 'id') !== epicId
461
+ || storyPaths.some((path, index) => scalar(path, 'id') !== storyIds[index]
462
+ || scalar(path, 'epic_id') !== epicId)) {
463
+ throw new Error('artifact-id-invalid');
464
+ }
465
+ regularOrAbsent(workspace.workspacePath, statePath);
466
+ const state = JSON.parse(readFileSync(join(workspace.workspacePath, statePath), 'utf8'));
467
+ if (!state || typeof state !== 'object' || Array.isArray(state))
468
+ throw new Error('state-invalid');
469
+ const record = state;
470
+ const epics = record.epics;
471
+ const stories = record.stories;
472
+ const epic = epics?.[epicId];
473
+ const ledger = record.epic_id_ledger;
474
+ if (record.schema_version !== 1 || !epic || epic.id !== epicId || epic.status !== 'memory-updated'
475
+ || Object.keys(epic).some(key => !PRIVATE_EPIC_DRAFT_KEYS.has(key))
476
+ || !validPrivateEpicDraftMetadata(epic)
477
+ || epic.completed_stories !== undefined && epic.completed_stories !== 0
478
+ || epic.total_stories !== undefined && epic.total_stories !== storyIds.length
479
+ || !Array.isArray(epic.stories) || epic.stories.length !== storyIds.length
480
+ || epic.stories.some((id, index) => id !== storyIds[index])
481
+ || !stories || storyIds.some((id) => {
482
+ const story = stories[id];
483
+ return !story || story.id !== id || story.epic_id !== epicId || !validPrivateEpicStoryDraft(story);
484
+ })
485
+ || !ledger || !Array.isArray(ledger.reserved_ids)
486
+ || !ledger.reserved_ids.includes(epicId)) {
487
+ throw new Error('state-claim-mismatch');
488
+ }
489
+ regularOrAbsent(workspace.sourceRoot, statePath);
490
+ const original = JSON.parse(readFileSync(join(workspace.sourceRoot, statePath), 'utf8'));
491
+ const originalEpics = original.epics;
492
+ const originalStories = original.stories;
493
+ if (!originalEpics || !originalStories || originalEpics[epicId] !== undefined
494
+ || storyIds.some((id) => originalStories[id] !== undefined)) {
495
+ throw new Error('state-claim-conflict');
496
+ }
497
+ const { [epicId]: _newEpic, ...priorEpics } = epics;
498
+ const priorStories = { ...stories };
499
+ for (const id of storyIds)
500
+ delete priorStories[id];
501
+ const { epic_id_ledger: _oldLedger, last_updated: _oldUpdated, ...originalOther } = original;
502
+ const { epic_id_ledger: _newLedger, last_updated: _newUpdated, ...candidateOther } = record;
503
+ if (!isDeepStrictEqual(priorEpics, originalEpics)
504
+ || !isDeepStrictEqual(priorStories, originalStories)
505
+ || !isDeepStrictEqual({ ...candidateOther, epics: undefined, stories: undefined }, { ...originalOther, epics: undefined, stories: undefined })) {
506
+ throw new Error('state-unrelated-drift');
507
+ }
508
+ const priorReserved = original.epic_id_ledger && typeof original.epic_id_ledger === 'object'
509
+ ? original.epic_id_ledger.reserved_ids : undefined;
510
+ const { reserved_ids: _candidateReserved, ...candidateLedgerOther } = ledger;
511
+ const originalLedger = original.epic_id_ledger && typeof original.epic_id_ledger === 'object'
512
+ ? original.epic_id_ledger : { schema_version: 1 };
513
+ const { reserved_ids: _originalReserved, ...originalLedgerOther } = originalLedger;
514
+ if (priorReserved !== undefined && (!Array.isArray(priorReserved)
515
+ || priorReserved.some((id) => typeof id !== 'string' || !/^P[1-9][0-9]*$/.test(id))
516
+ || new Set(priorReserved).size !== priorReserved.length))
517
+ throw new Error('ledger-drift');
518
+ const expectedReserved = [...(Array.isArray(priorReserved) ? priorReserved : [])];
519
+ if (!expectedReserved.includes(epicId))
520
+ expectedReserved.push(epicId);
521
+ if (!isDeepStrictEqual(ledger.reserved_ids, expectedReserved)
522
+ || !isDeepStrictEqual(candidateLedgerOther, originalLedgerOther)) {
523
+ throw new Error('ledger-drift');
524
+ }
525
+ return admitPrivateYoloIsolatedWorkspace(workspace, changes.paths, workspace.baseSha, pendingSidecarSha256);
526
+ }
527
+ catch {
528
+ throw new Error('private-execution-epic-claim-admission-unverified');
529
+ }
530
+ }
531
+ /** P01 may admit planning artifacts only. Canonical state and QA remain root-owned. */
532
+ export function admitPrivateIdeaDiagnosisWorkspace(workspace, claimId, epicId, logicalFingerprint, pendingSidecarSha256, expectedIdea) {
533
+ try {
534
+ if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(claimId)
535
+ || !/^P[1-9][0-9]{0,11}$/.test(epicId)
536
+ || !/^[a-f0-9]{64}$/.test(logicalFingerprint)
537
+ || !/^[a-f0-9]{64}$/.test(pendingSidecarSha256)
538
+ || typeof expectedIdea !== 'string' || !expectedIdea.trim() || expectedIdea.length > 16_384
539
+ || !pendingSidecarMatches(workspace.sourceRoot, pendingSidecarSha256)) {
540
+ throw new Error('planning-claim-invalid');
541
+ }
542
+ const pending = JSON.parse(readFileSync(join(workspace.sourceRoot, PRIVATE_EPIC_PENDING_SIDECAR), 'utf8'));
543
+ const operation = pending && typeof pending === 'object' && !Array.isArray(pending)
544
+ ? pending.operations?.[logicalFingerprint]
545
+ : undefined;
546
+ if (!operation || typeof operation !== 'object' || Array.isArray(operation)
547
+ || operation.claimId !== claimId
548
+ || operation.epicId !== epicId) {
549
+ throw new Error('planning-claim-sidecar-mismatch');
550
+ }
551
+ const prefix = `.neocortex/planning/epic-${epicId}`;
552
+ const ideaPath = `${prefix}/idea.json`;
553
+ const diagnosisPath = `${prefix}/diagnosis.md`;
554
+ const enrichedPath = `${prefix}/enriched-prompt.md`;
555
+ const indexPath = '.neocortex/planning/planning.json';
556
+ const authorized = [ideaPath, diagnosisPath, enrichedPath, indexPath];
557
+ const changes = changedPaths(status(workspace.workspacePath, true));
558
+ if (changes.paths.length !== authorized.length
559
+ || changes.paths.some((path) => !authorized.includes(path))) {
560
+ throw new Error('planning-artifact-scope-invalid');
561
+ }
562
+ for (const path of [ideaPath, diagnosisPath, enrichedPath]) {
563
+ regularOrAbsent(workspace.workspacePath, path);
564
+ try {
565
+ lstatSync(join(workspace.sourceRoot, path));
566
+ throw new Error('planning-artifact-already-exists');
567
+ }
568
+ catch (error) {
569
+ if (error.code !== 'ENOENT')
570
+ throw error;
571
+ }
572
+ }
573
+ const readCandidate = (path, maxBytes) => {
574
+ const entry = lstatSync(join(workspace.workspacePath, path));
575
+ if (!entry.isFile() || entry.isSymbolicLink() || entry.size < 1 || entry.size > maxBytes) {
576
+ throw new Error('planning-artifact-invalid');
577
+ }
578
+ return readFileSync(join(workspace.workspacePath, path), 'utf8');
579
+ };
580
+ const idea = JSON.parse(readCandidate(ideaPath, 32 * 1024));
581
+ if (!idea || typeof idea !== 'object' || Array.isArray(idea)
582
+ || idea.epic_id !== epicId
583
+ || idea.status !== 'diagnosed'
584
+ || typeof idea.raw_input !== 'string'
585
+ || idea.raw_input !== expectedIdea) {
586
+ throw new Error('planning-idea-invalid');
587
+ }
588
+ const diagnosis = readCandidate(diagnosisPath, 256 * 1024);
589
+ const enriched = readCandidate(enrichedPath, 256 * 1024);
590
+ const documentIdentity = (content, stepDigest) => {
591
+ const header = /^---\n([\s\S]{1,8192}?)\n---\n/.exec(content.replace(/\r\n/g, '\n'))?.[1];
592
+ if (!header)
593
+ return false;
594
+ const lines = header.split('\n');
595
+ const scalar = (key) => {
596
+ const matches = lines.filter((line) => line.startsWith(`${key}:`));
597
+ return matches.length === 1 ? matches[0].slice(key.length + 1).trim().replace(/^(?:"(.*)"|'(.*)')$/, '$1$2') : undefined;
598
+ };
599
+ return scalar('epic_id') === epicId
600
+ && matchesStepDigest(scalar('step'), stepDigest) && scalar('status') === 'completed';
601
+ };
602
+ if (!documentIdentity(diagnosis, P01_DIAGNOSIS_STEP_SHA256)
603
+ || !documentIdentity(enriched, P01_ENRICHMENT_STEP_SHA256)) {
604
+ throw new Error('planning-document-identity-invalid');
605
+ }
606
+ regularOrAbsent(workspace.workspacePath, indexPath);
607
+ regularOrAbsent(workspace.sourceRoot, indexPath);
608
+ const candidate = JSON.parse(readCandidate(indexPath, 128 * 1024));
609
+ const original = existsSync(join(workspace.sourceRoot, indexPath))
610
+ ? JSON.parse(readFileSync(join(workspace.sourceRoot, indexPath), 'utf8')) : undefined;
611
+ if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)
612
+ || !('epics' in candidate) || !candidate.epics || typeof candidate.epics !== 'object'
613
+ || Array.isArray(candidate.epics)
614
+ || original !== undefined && (!original || typeof original !== 'object' || Array.isArray(original)
615
+ || !('epics' in original) || !original.epics || typeof original.epics !== 'object'
616
+ || Array.isArray(original.epics))) {
617
+ throw new Error('planning-index-invalid');
618
+ }
619
+ const index = candidate;
620
+ const entries = index.epics;
621
+ const recorded = entries[epicId];
622
+ const record = recorded;
623
+ const outputs = record?.output_files;
624
+ if (!recorded || typeof recorded !== 'object' || Array.isArray(recorded)
625
+ || record?.id !== epicId || record.status !== 'diagnosed'
626
+ || record.idea !== idea.raw_input
627
+ || !Array.isArray(record.steps_completed) || record.steps_completed.length !== 1
628
+ || !matchesStepDigest(record.steps_completed[0], P01_DIAGNOSIS_STEP_SHA256)
629
+ || typeof record.created_at !== 'string' || !Number.isFinite(Date.parse(record.created_at))
630
+ || record.completed_at !== null || !outputs || typeof outputs !== 'object' || Array.isArray(outputs)
631
+ || !isDeepStrictEqual(outputs, { diagnosis: `${prefix}/diagnosis.md` })
632
+ || !isDeepStrictEqual(Object.keys(record).sort(), ['id', 'idea', 'status', 'steps_completed', 'created_at', 'completed_at', 'output_files'].sort())) {
633
+ throw new Error('planning-index-epic-invalid');
634
+ }
635
+ const { last_scan: _newScan, epics: _newEpics, ...candidateOther } = index;
636
+ const baseline = original;
637
+ const prior = baseline?.epics;
638
+ const { last_scan: _oldScan, epics: _oldEpics, ...originalOther } = baseline ?? {};
639
+ const { [epicId]: _newEntry, ...unrelatedEntries } = entries;
640
+ if (prior?.[epicId] !== undefined
641
+ || !isDeepStrictEqual(unrelatedEntries, prior ?? {})
642
+ || baseline !== undefined && !isDeepStrictEqual(candidateOther, originalOther)
643
+ || baseline === undefined && (Object.keys(entries).length !== 1
644
+ || !isDeepStrictEqual(Object.keys(index).sort(), ['version', 'next_epic_id', 'last_scan', 'existing_epics_discovered', 'epics'].sort())
645
+ || index.version !== '1.0.0' || index.next_epic_id !== 1
646
+ || !isDeepStrictEqual(index.existing_epics_discovered, []))
647
+ || typeof index.last_scan !== 'string' || !Number.isFinite(Date.parse(index.last_scan))) {
648
+ throw new Error('planning-index-drift');
649
+ }
650
+ const digests = authorized.map((path) => createHash('sha256')
651
+ .update(readFileSync(join(workspace.workspacePath, path))).digest('hex'));
652
+ const candidateIntegrity = () => authorized.every((path, index) => {
653
+ try {
654
+ regularOrAbsent(workspace.workspacePath, path);
655
+ return createHash('sha256').update(readFileSync(join(workspace.workspacePath, path))).digest('hex')
656
+ === digests[index];
657
+ }
658
+ catch {
659
+ return false;
660
+ }
661
+ });
662
+ return admitPrivateYoloIsolatedWorkspace(workspace, authorized, workspace.baseSha, pendingSidecarSha256, candidateIntegrity);
663
+ }
664
+ catch {
665
+ throw new Error('private-execution-planning-diagnosis-admission-unverified');
666
+ }
667
+ }
668
+ /** P02 admits only a research candidate on a committed, physically accepted P01. */
669
+ export function admitPrivateIdeaResearchWorkspace(workspace, claimId, epicId, diagnosisReceiptId, intakeRevision, stateSnapshotSha256) {
670
+ try {
671
+ if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(claimId)
672
+ || !/^P[1-9][0-9]{0,11}$/.test(epicId)
673
+ || !/^[a-f0-9]{64}$/.test(diagnosisReceiptId)
674
+ || !/^[a-f0-9]{64}$/.test(intakeRevision)
675
+ || !/^[a-f0-9]{64}$/.test(stateSnapshotSha256)) {
676
+ throw new Error('planning-research-binding-invalid');
677
+ }
678
+ const statePath = '.neocortex/state.json';
679
+ const indexPath = '.neocortex/planning/planning.json';
680
+ const prefix = `.neocortex/planning/epic-${epicId}`;
681
+ const researchPath = `${prefix}/research.md`;
682
+ regularOrAbsent(workspace.sourceRoot, statePath);
683
+ regularOrAbsent(workspace.workspacePath, statePath);
684
+ regularOrAbsent(workspace.sourceRoot, indexPath);
685
+ regularOrAbsent(workspace.workspacePath, indexPath);
686
+ regularOrAbsent(workspace.sourceRoot, researchPath);
687
+ regularOrAbsent(workspace.workspacePath, researchPath);
688
+ if (lstatSync(join(workspace.sourceRoot, statePath)).size > 8 * 1024 * 1024
689
+ || lstatSync(join(workspace.workspacePath, statePath)).size > 8 * 1024 * 1024
690
+ || lstatSync(join(workspace.sourceRoot, indexPath)).size > 128 * 1024
691
+ || lstatSync(join(workspace.workspacePath, indexPath)).size > 128 * 1024) {
692
+ throw new Error('planning-research-source-size-invalid');
693
+ }
694
+ const stateBytes = readFileSync(join(workspace.sourceRoot, statePath));
695
+ if (createHash('sha256').update(stateBytes).digest('hex') !== stateSnapshotSha256
696
+ || !stateBytes.equals(readFileSync(join(workspace.workspacePath, statePath)))) {
697
+ throw new Error('planning-research-state-drift');
698
+ }
699
+ const state = JSON.parse(stateBytes.toString('utf8'));
700
+ const predecessor = acceptedPlanningDiagnosisPredecessor(state, claimId, epicId);
701
+ if (predecessor.diagnosisReceiptId !== diagnosisReceiptId
702
+ || predecessor.intakeRevision !== intakeRevision) {
703
+ throw new Error('planning-research-predecessor-mismatch');
704
+ }
705
+ const epicState = state.epics[epicId];
706
+ const physicalDigests = epicState.createEpicPlanningPhysical.artifactDigests;
707
+ for (const [name, path] of Object.entries({ idea: `${prefix}/idea.json`,
708
+ diagnosis: `${prefix}/diagnosis.md`, enrichment: `${prefix}/enriched-prompt.md`,
709
+ index: indexPath })) {
710
+ regularOrAbsent(workspace.sourceRoot, path);
711
+ if (createHash('sha256').update(readFileSync(join(workspace.sourceRoot, path))).digest('hex')
712
+ !== physicalDigests[name])
713
+ throw new Error('planning-research-source-artifact-drift');
714
+ }
715
+ try {
716
+ lstatSync(join(workspace.sourceRoot, researchPath));
717
+ throw new Error('planning-research-already-exists');
718
+ }
719
+ catch (error) {
720
+ if (error.code !== 'ENOENT')
721
+ throw error;
722
+ }
723
+ const authorized = [researchPath, indexPath];
724
+ const changes = changedPaths(status(workspace.workspacePath, true));
725
+ if (changes.paths.length !== authorized.length
726
+ || changes.paths.some((path) => !authorized.includes(path))) {
727
+ throw new Error('planning-research-write-scope-invalid');
728
+ }
729
+ const researchEntry = lstatSync(join(workspace.workspacePath, researchPath));
730
+ if (!researchEntry.isFile() || researchEntry.isSymbolicLink()
731
+ || researchEntry.size < 64 || researchEntry.size > 256 * 1024) {
732
+ throw new Error('planning-research-document-invalid');
733
+ }
734
+ const research = readFileSync(join(workspace.workspacePath, researchPath), 'utf8');
735
+ const header = /^---\n([\s\S]{1,8192}?)\n---\n/.exec(research.replace(/\r\n/g, '\n'))?.[1];
736
+ const scalar = (key) => {
737
+ const found = header?.split('\n').filter((line) => line.startsWith(`${key}:`)) ?? [];
738
+ return found.length === 1
739
+ ? found[0].slice(key.length + 1).trim().replace(/^(?:"(.*)"|'(.*)')$/, '$1$2')
740
+ : undefined;
741
+ };
742
+ if (!header || scalar('epic_id') !== epicId || scalar('status') !== 'completed'
743
+ || !matchesStepDigest(scalar('step'), P02_RESEARCH_STEP_SHA256)
744
+ || !research.slice(header.length + 8).trim()) {
745
+ throw new Error('planning-research-document-identity-invalid');
746
+ }
747
+ const sourceIndex = JSON.parse(readFileSync(join(workspace.sourceRoot, indexPath), 'utf8'));
748
+ const candidateIndex = JSON.parse(readFileSync(join(workspace.workspacePath, indexPath), 'utf8'));
749
+ if (!sourceIndex || typeof sourceIndex !== 'object' || Array.isArray(sourceIndex)
750
+ || !candidateIndex || typeof candidateIndex !== 'object' || Array.isArray(candidateIndex)) {
751
+ throw new Error('planning-research-index-invalid');
752
+ }
753
+ const source = sourceIndex;
754
+ const candidate = candidateIndex;
755
+ const sourceEpics = source.epics;
756
+ const candidateEpics = candidate.epics;
757
+ const oldEpic = sourceEpics?.[epicId];
758
+ if (!sourceEpics || Array.isArray(sourceEpics) || !candidateEpics || Array.isArray(candidateEpics)
759
+ || !oldEpic || Array.isArray(oldEpic) || oldEpic.id !== epicId
760
+ || oldEpic.idea !== predecessor.rawIntent || oldEpic.status !== 'diagnosed'
761
+ || !Array.isArray(oldEpic.steps_completed) || oldEpic.steps_completed.length !== 1
762
+ || !matchesStepDigest(oldEpic.steps_completed[0], P01_DIAGNOSIS_STEP_SHA256)
763
+ || !isDeepStrictEqual(oldEpic.output_files, { diagnosis: `${prefix}/diagnosis.md` })
764
+ || typeof candidate.last_scan !== 'string'
765
+ || !Number.isFinite(Date.parse(candidate.last_scan))) {
766
+ throw new Error('planning-research-index-predecessor-invalid');
767
+ }
768
+ const expectedEpic = { ...oldEpic, status: 'researched',
769
+ steps_completed: [...oldEpic.steps_completed, scalar('step')],
770
+ output_files: { diagnosis: `${prefix}/diagnosis.md`, research: researchPath } };
771
+ const expectedIndex = { ...source, last_scan: candidate.last_scan,
772
+ epics: { ...sourceEpics, [epicId]: expectedEpic } };
773
+ if (!isDeepStrictEqual(candidate, expectedIndex)) {
774
+ throw new Error('planning-research-index-drift');
775
+ }
776
+ const digests = authorized.map((path) => createHash('sha256')
777
+ .update(readFileSync(join(workspace.workspacePath, path))).digest('hex'));
778
+ const candidateIntegrity = () => authorized.every((path, index) => {
779
+ try {
780
+ regularOrAbsent(workspace.workspacePath, path);
781
+ return createHash('sha256').update(readFileSync(join(workspace.workspacePath, path))).digest('hex')
782
+ === digests[index];
783
+ }
784
+ catch {
785
+ return false;
786
+ }
787
+ });
788
+ return admitPrivateYoloIsolatedWorkspace(workspace, authorized, workspace.baseSha, planningPendingSidecarDigest(workspace.sourceRoot), candidateIntegrity);
789
+ }
790
+ catch {
791
+ throw new Error('private-execution-planning-research-admission-unverified');
792
+ }
793
+ }
794
+ /** Admit only a bounded P03 document/index candidate; root owns QA and state integration. */
795
+ export function admitPrivateGenerateEpicWorkspace(workspace, claimId, epicId, diagnosisReceiptId, researchReceiptId, intakeRevision, sourceStateSha256) {
796
+ try {
797
+ if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(claimId)
798
+ || !/^P[1-9][0-9]{0,11}$/.test(epicId)
799
+ || [diagnosisReceiptId, researchReceiptId, intakeRevision, sourceStateSha256]
800
+ .some((value) => !/^[a-f0-9]{64}$/.test(value)))
801
+ throw new Error('planning-generation-binding-invalid');
802
+ const statePath = '.neocortex/state.json';
803
+ const indexPath = '.neocortex/planning/planning.json';
804
+ const prefix = `.neocortex/planning/epic-${epicId}`;
805
+ const epicPath = `docs/epics/epic-${epicId}.md`;
806
+ for (const path of [statePath, indexPath, `${prefix}/idea.json`, `${prefix}/diagnosis.md`,
807
+ `${prefix}/enriched-prompt.md`, `${prefix}/research.md`]) {
808
+ regularOrAbsent(workspace.sourceRoot, path);
809
+ regularOrAbsent(workspace.workspacePath, path);
810
+ const source = readFileSync(join(workspace.sourceRoot, path));
811
+ if (path !== indexPath && !source.equals(readFileSync(join(workspace.workspacePath, path)))
812
+ || !source.equals(git(workspace.sourceRoot, ['show', `HEAD:${path}`]))) {
813
+ throw new Error('planning-generation-predecessor-uncommitted');
814
+ }
815
+ }
816
+ const stateBytes = readFileSync(join(workspace.sourceRoot, statePath));
817
+ if (stateBytes.byteLength > 8 * 1024 * 1024
818
+ || createHash('sha256').update(stateBytes).digest('hex') !== sourceStateSha256) {
819
+ throw new Error('planning-generation-state-drift');
820
+ }
821
+ const state = JSON.parse(stateBytes.toString('utf8'));
822
+ const predecessor = acceptedPlanningResearchPredecessor(state, claimId, epicId);
823
+ if (predecessor.diagnosisReceiptId !== diagnosisReceiptId
824
+ || predecessor.researchReceiptId !== researchReceiptId
825
+ || predecessor.intakeRevision !== intakeRevision)
826
+ throw new Error('planning-generation-predecessor-mismatch');
827
+ const epicState = (state.epics[epicId]);
828
+ const p01 = epicState.createEpicPlanningPhysical.artifactDigests;
829
+ const p02 = epicState.createEpicPlanningResearchPhysical.artifactDigests;
830
+ const acceptedFiles = { idea: `${prefix}/idea.json`, diagnosis: `${prefix}/diagnosis.md`,
831
+ enrichment: `${prefix}/enriched-prompt.md`, research: `${prefix}/research.md` };
832
+ for (const [name, path] of Object.entries(acceptedFiles)) {
833
+ const expected = name === 'research' ? p02.research : p01[name];
834
+ if (createHash('sha256').update(readFileSync(join(workspace.sourceRoot, path))).digest('hex') !== expected) {
835
+ throw new Error('planning-generation-artifact-drift');
836
+ }
837
+ }
838
+ if (createHash('sha256').update(readFileSync(join(workspace.sourceRoot, indexPath))).digest('hex')
839
+ !== p02.index)
840
+ throw new Error('planning-generation-index-drift');
841
+ const changes = changedPaths(status(workspace.workspacePath, true));
842
+ const storyPattern = new RegExp(`^docs/stories/${epicId}\\.([0-9]{2})\\.story\\.md$`);
843
+ const storyPaths = changes.paths.filter((path) => storyPattern.test(path)).sort((left, right) => Number(storyPattern.exec(left)[1]) - Number(storyPattern.exec(right)[1]));
844
+ if (storyPaths.length < 1 || storyPaths.length > 24
845
+ || storyPaths.some((path, index) => storyPattern.exec(path)[1] !== String(index + 1).padStart(2, '0'))
846
+ || changes.paths.length !== storyPaths.length + 2
847
+ || changes.paths.some((path) => path !== epicPath && path !== indexPath && !storyPattern.test(path))) {
848
+ throw new Error('planning-generation-write-scope-invalid');
849
+ }
850
+ const authorized = [epicPath, indexPath, ...storyPaths];
851
+ for (const path of [epicPath, ...storyPaths]) {
852
+ regularOrAbsent(workspace.sourceRoot, path);
853
+ regularOrAbsent(workspace.workspacePath, path);
854
+ try {
855
+ lstatSync(join(workspace.sourceRoot, path));
856
+ throw new Error('planning-generation-path-collision');
857
+ }
858
+ catch (error) {
859
+ if (error.code !== 'ENOENT')
860
+ throw error;
861
+ }
862
+ const entry = lstatSync(join(workspace.workspacePath, path));
863
+ if (!entry.isFile() || entry.isSymbolicLink() || entry.size < 128
864
+ || entry.size > (path === epicPath ? 512 : 128) * 1024) {
865
+ throw new Error('planning-generation-document-invalid');
866
+ }
867
+ }
868
+ const scalar = (content, key) => {
869
+ const header = /^---\n([\s\S]{1,8192}?)\n---\n/.exec(content.replace(/\r\n/g, '\n'))?.[1];
870
+ const fields = header?.split('\n').filter((line) => line.startsWith(`${key}:`)) ?? [];
871
+ return fields.length === 1 ? fields[0].slice(key.length + 1).trim()
872
+ .replace(/^(?:"(.*)"|'(.*)')$/, '$1$2') : undefined;
873
+ };
874
+ const epicDocument = readFileSync(join(workspace.workspacePath, epicPath), 'utf8');
875
+ if (scalar(epicDocument, 'id') !== epicId
876
+ || scalar(epicDocument, 'stories_count') !== String(storyPaths.length)
877
+ || !epicDocument.includes('## Stories'))
878
+ throw new Error('planning-generation-epic-invalid');
879
+ for (const [index, path] of storyPaths.entries()) {
880
+ const story = readFileSync(join(workspace.workspacePath, path), 'utf8');
881
+ const header = /^---\n([\s\S]{1,8192}?)\n---\n/.exec(story.replace(/\r\n/g, '\n'))?.[1] ?? '';
882
+ if (scalar(story, 'id') !== `${epicId}.${String(index + 1).padStart(2, '0')}`
883
+ || scalar(story, 'epic_id') !== epicId || /^\s*epic\s*:/m.test(header)
884
+ || scalar(story, 'status') !== 'backlog'
885
+ || !story.includes('## Acceptance Criteria')
886
+ || !/\bGiven\b[\s\S]*\bWhen\b[\s\S]*\bThen\b/.test(story)) {
887
+ throw new Error('planning-generation-story-invalid');
888
+ }
889
+ }
890
+ const before = JSON.parse(readFileSync(join(workspace.sourceRoot, indexPath), 'utf8'));
891
+ const after = JSON.parse(readFileSync(join(workspace.workspacePath, indexPath), 'utf8'));
892
+ const sourceEpics = before.epics;
893
+ const candidateEpics = after.epics;
894
+ const oldEpic = sourceEpics?.[epicId];
895
+ const candidateEpic = candidateEpics?.[epicId];
896
+ if (!sourceEpics || !candidateEpics || !oldEpic || !candidateEpic
897
+ || oldEpic.id !== epicId || oldEpic.status !== 'researched'
898
+ || !Array.isArray(oldEpic.steps_completed) || oldEpic.steps_completed.length !== 2
899
+ || !matchesStepDigest(oldEpic.steps_completed[0], P01_DIAGNOSIS_STEP_SHA256)
900
+ || !matchesStepDigest(oldEpic.steps_completed[1], P02_RESEARCH_STEP_SHA256)
901
+ || !isDeepStrictEqual(oldEpic.output_files, {
902
+ diagnosis: `${prefix}/diagnosis.md`, research: `${prefix}/research.md`,
903
+ }) || typeof after.last_scan !== 'string' || !Number.isFinite(Date.parse(after.last_scan))
904
+ || typeof candidateEpic.completed_at !== 'string'
905
+ || !Number.isFinite(Date.parse(candidateEpic.completed_at))) {
906
+ throw new Error('planning-generation-index-predecessor-invalid');
907
+ }
908
+ const generatedStep = candidateEpic.steps_completed?.[2];
909
+ if (!matchesStepDigest(generatedStep, P03_GENERATION_STEP_SHA256)) {
910
+ throw new Error('planning-generation-index-transition-invalid');
911
+ }
912
+ const expected = { ...before, last_scan: after.last_scan, epics: { ...sourceEpics,
913
+ [epicId]: { ...oldEpic, status: 'generated',
914
+ steps_completed: [...oldEpic.steps_completed, generatedStep],
915
+ completed_at: candidateEpic.completed_at,
916
+ output_files: { ...oldEpic.output_files,
917
+ epic: epicPath, stories: storyPaths } } } };
918
+ if (!isDeepStrictEqual(after, expected)) {
919
+ throw new Error('planning-generation-index-transition-invalid');
920
+ }
921
+ const digests = authorized.map((path) => createHash('sha256')
922
+ .update(readFileSync(join(workspace.workspacePath, path))).digest('hex'));
923
+ const candidateIntegrity = () => authorized.every((path, index) => {
924
+ try {
925
+ regularOrAbsent(workspace.workspacePath, path);
926
+ return createHash('sha256').update(readFileSync(join(workspace.workspacePath, path))).digest('hex')
927
+ === digests[index];
928
+ }
929
+ catch {
930
+ return false;
931
+ }
932
+ });
933
+ return admitPrivateYoloIsolatedWorkspace(workspace, authorized, workspace.baseSha, planningPendingSidecarDigest(workspace.sourceRoot), candidateIntegrity);
934
+ }
935
+ catch {
936
+ throw new Error('private-execution-planning-generation-admission-unverified');
937
+ }
938
+ }
939
+ /** Admit a state-bound step without granting the agent direct source writes. */
940
+ /** Bind a standalone private step to its actual local story and existing write set. */
941
+ export function appendPrivateStateBoundStepHandoff(material, workspace, storyId) {
942
+ try {
943
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(storyId))
944
+ throw new Error('story-id-invalid');
945
+ const statePath = '.neocortex/state.json';
946
+ regularOrAbsent(workspace.sourceRoot, statePath);
947
+ regularOrAbsent(workspace.workspacePath, statePath);
948
+ const sourceBytes = readFileSync(join(workspace.sourceRoot, statePath));
949
+ if (!sourceBytes.equals(readFileSync(join(workspace.workspacePath, statePath))))
950
+ throw new Error('state-drift');
951
+ const state = JSON.parse(sourceBytes.toString('utf8'));
952
+ const story = state?.stories?.[storyId];
953
+ const paths = story?.files_to_modify;
954
+ if (state?.schema_version !== 1 || story?.id !== storyId
955
+ || !Array.isArray(paths) || paths.length < 1 || paths.length > MAX_PATHS
956
+ || paths.some((path) => typeof path !== 'string' || !safePath(path)
957
+ || path.length > 1024 || /[\r\n]/.test(path))
958
+ || new Set(paths).size !== paths.length)
959
+ throw new Error('write-set-invalid');
960
+ const allowedPaths = paths;
961
+ const targets = allowedPaths.filter((path) => {
962
+ if (!path.startsWith('docs/stories/') || !path.endsWith('.story.md'))
963
+ return false;
964
+ regularOrAbsent(workspace.sourceRoot, path);
965
+ regularOrAbsent(workspace.workspacePath, path);
966
+ const source = readFileSync(join(workspace.sourceRoot, path));
967
+ if (!source.equals(readFileSync(join(workspace.workspacePath, path))))
968
+ throw new Error('story-drift');
969
+ const header = /^---\r?\n([\s\S]{1,8192}?)\r?\n---(?:\r?\n|$)/.exec(source.toString('utf8'))?.[1];
970
+ const ids = header?.split(/\r?\n/).filter((line) => line.startsWith('id:')) ?? [];
971
+ return ids.length === 1 && ids[0].slice(3).trim().replace(/^(["'])(.*)\1$/, '$2') === storyId;
972
+ });
973
+ if (targets.length !== 1)
974
+ throw new Error('story-target-ambiguous');
975
+ return `${material}\n\nPrivate story handoff (local only):\n${JSON.stringify({
976
+ storyId, storyPath: targets[0], allowedPaths: [...allowedPaths].sort(),
977
+ })}\nWork from this isolated checkout's current directory. Use the exact storyPath above for the delivered current step; do not use an unresolved story-file placeholder or infer a filename. Persist the current step's actual result in the declared files; a final chat response alone is not a saved candidate. Modify only allowedPaths and preserve story identity and the write set. Do not operate Git, change unrelated stories, grant root QA/checkpoint authority, or declare the story, epic or pipeline complete. If required inputs are unavailable, report failure without inventing evidence or completion.\n`;
978
+ }
979
+ catch {
980
+ throw new Error('private-execution-state-step-handoff-unverified');
981
+ }
982
+ }
983
+ export function admitPrivateStateBoundStepWorkspace(workspace, storyId) {
984
+ try {
985
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(storyId))
986
+ throw new Error('story-id-invalid');
987
+ const statePath = '.neocortex/state.json';
988
+ regularOrAbsent(workspace.sourceRoot, statePath);
989
+ regularOrAbsent(workspace.workspacePath, statePath);
990
+ const original = JSON.parse(readFileSync(join(workspace.sourceRoot, statePath), 'utf8'));
991
+ const candidate = JSON.parse(readFileSync(join(workspace.workspacePath, statePath), 'utf8'));
992
+ if (!original || typeof original !== 'object' || Array.isArray(original)
993
+ || !candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
994
+ throw new Error('state-invalid');
995
+ }
996
+ const sourceState = original;
997
+ const candidateState = candidate;
998
+ const sourceStories = sourceState.stories;
999
+ const candidateStories = candidateState.stories;
1000
+ const sourceStory = sourceStories?.[storyId];
1001
+ const candidateStory = candidateStories?.[storyId];
1002
+ const paths = sourceStory?.files_to_modify;
1003
+ if (sourceState.schema_version !== 1 || candidateState.schema_version !== 1
1004
+ || !sourceStories || !candidateStories || !sourceStory || !candidateStory
1005
+ || sourceStory.id !== storyId || candidateStory.id !== storyId
1006
+ || candidateStory.epic_id !== sourceStory.epic_id
1007
+ || !Array.isArray(paths) || paths.length === 0 || paths.length > MAX_PATHS
1008
+ || paths.some((path) => typeof path !== 'string' || !safePath(path) || path.length > 1024)
1009
+ || new Set(paths).size !== paths.length
1010
+ || !isDeepStrictEqual(candidateStory.files_to_modify, paths)) {
1011
+ throw new Error('write-set-invalid');
1012
+ }
1013
+ const { [storyId]: _sourceStory, ...otherSourceStories } = sourceStories;
1014
+ const { [storyId]: _candidateStory, ...otherCandidateStories } = candidateStories;
1015
+ const { stories: _sourceStories, last_updated: _sourceUpdated, ...otherSourceState } = sourceState;
1016
+ const { stories: _candidateStories, last_updated: _candidateUpdated, ...otherCandidateState } = candidateState;
1017
+ if (!isDeepStrictEqual(otherCandidateStories, otherSourceStories)
1018
+ || !isDeepStrictEqual(otherCandidateState, otherSourceState)) {
1019
+ throw new Error('state-unrelated-drift');
1020
+ }
1021
+ return admitPrivateYoloIsolatedWorkspace(workspace, [...paths].sort(), workspace.baseSha);
1022
+ }
1023
+ catch {
1024
+ throw new Error('private-execution-state-step-admission-unverified');
1025
+ }
1026
+ }
1027
+ /** One project-level utility contract, with no state/Git/other-file write grant. */
1028
+ export function admitPrivateProjectStepWorkspace(workspace, commandId, writeSetId) {
1029
+ if (commandId === 'chaos-plan' && writeSetId === 'chaos-quarterly-v1') {
1030
+ try {
1031
+ const now = new Date();
1032
+ const quarter = `Q${Math.floor(now.getUTCMonth() / 3) + 1}-${now.getUTCFullYear()}`;
1033
+ const paths = changedPaths(status(workspace.workspacePath, true)).paths;
1034
+ const planPath = `tools/chaos/${quarter}-plan.md`;
1035
+ const historyPath = `.neocortex/chaos-history/${quarter}.md`;
1036
+ const experimentPaths = paths.filter((path) => path.startsWith('tools/chaos/experiments/'));
1037
+ const match = experimentPaths.map((path) => /^tools\/chaos\/experiments\/(chaos-mesh|litmus|aws-fis)-(?:PodKill|NetworkChaos|IOChaos|StressChaos|TimeChaos|pod-kill|network-chaos|io-chaos|stress-chaos|time-chaos)-([a-z0-9]+(?:-[a-z0-9]+)*)\.yaml$/.exec(path));
1038
+ if (!paths.includes(planPath) || !paths.includes(historyPath)
1039
+ || experimentPaths.length < 5 || experimentPaths.length > 10
1040
+ || paths.length !== experimentPaths.length + 2
1041
+ || match.some((item) => !item)
1042
+ || new Set(match.map((item) => item[1])).size !== 1) {
1043
+ throw new Error('chaos-write-set-invalid');
1044
+ }
1045
+ for (const path of paths) {
1046
+ regularOrAbsent(workspace.workspacePath, path);
1047
+ const entry = lstatSync(join(workspace.workspacePath, path));
1048
+ if (entry.size === 0)
1049
+ throw new Error('chaos-empty-artifact');
1050
+ }
1051
+ const history = readFileSync(join(workspace.workspacePath, historyPath), 'utf8');
1052
+ if (!history.includes(quarter) || !/\bPLANNED\b/.test(history)) {
1053
+ throw new Error('chaos-history-invalid');
1054
+ }
1055
+ const historyInHead = git(workspace.workspacePath, ['ls-tree', '-z', 'HEAD', '--', historyPath]).length > 0;
1056
+ const originalHistory = historyInHead
1057
+ ? git(workspace.workspacePath, ['show', `HEAD:${historyPath}`]).toString('utf8') : '';
1058
+ if (originalHistory && !history.startsWith(originalHistory)) {
1059
+ throw new Error('chaos-history-not-append-only');
1060
+ }
1061
+ return admitPrivateYoloIsolatedWorkspace(workspace, paths, workspace.baseSha);
1062
+ }
1063
+ catch {
1064
+ throw new Error('private-execution-project-step-admission-unverified');
1065
+ }
1066
+ }
1067
+ const allowedPaths = commandId === 'sync-openapi' && writeSetId === 'sync-openapi-contract-v1'
1068
+ ? ['docs/architecture/api-contracts.md', 'docs/api/openapi.yaml']
1069
+ : commandId === 'tech-debt' && writeSetId === 'tech-debt-registry-v1'
1070
+ ? ['.neocortex/tech-debt-registry.json'] : undefined;
1071
+ if (!allowedPaths) {
1072
+ throw new Error('private-execution-project-step-admission-unverified');
1073
+ }
1074
+ try {
1075
+ if (commandId === 'tech-debt') {
1076
+ const registryPath = '.neocortex/tech-debt-registry.json';
1077
+ regularOrAbsent(workspace.workspacePath, registryPath);
1078
+ const raw = JSON.parse(readFileSync(join(workspace.workspacePath, registryPath), 'utf8'));
1079
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
1080
+ throw new Error('registry-invalid');
1081
+ const registry = raw;
1082
+ if (registry.schema_version !== 2 || registry.version !== undefined && registry.version !== 2
1083
+ || !Array.isArray(registry.items))
1084
+ throw new Error('registry-invalid');
1085
+ }
1086
+ return admitPrivateYoloIsolatedWorkspace(workspace, allowedPaths, workspace.baseSha);
1087
+ }
1088
+ catch {
1089
+ throw new Error('private-execution-project-step-admission-unverified');
1090
+ }
1091
+ }
1092
+ /** Closed artifact authority shared by model handoff and physical admission. */
1093
+ export function privateArchitectureStagePaths(commandId, writeSetId) {
1094
+ const allowedPaths = commandId === 'arch-prd' && writeSetId === 'arch-prd-docs-v1'
1095
+ ? ['docs/architecture/prd.md', 'docs/architecture/product-experience.md']
1096
+ : commandId === 'arch-architecture' && writeSetId === 'arch-architecture-doc-v1'
1097
+ ? ['docs/architecture/architecture.md']
1098
+ : commandId === 'arch-fitness-functions' && writeSetId === 'arch-fitness-tools-v1'
1099
+ ? ['tools/architecture/dependency-cruiser.config.js', 'tools/architecture/archunit-test.ts',
1100
+ 'tools/architecture/.eslintrc.boundaries.js']
1101
+ : commandId === 'arch-api-contracts' && writeSetId === 'arch-api-contracts-docs-v1'
1102
+ ? ['docs/architecture/api-contracts.md', 'docs/api/openapi.yaml']
1103
+ : commandId === 'arch-api-integrations' && writeSetId === 'arch-api-integrations-doc-v1'
1104
+ ? ['docs/architecture/api-integrations.md']
1105
+ : commandId === 'arch-database' && writeSetId === 'arch-database-doc-v1'
1106
+ ? ['docs/architecture/database-schema.md']
1107
+ : commandId === 'arch-security' && writeSetId === 'arch-security-doc-v1'
1108
+ ? ['docs/architecture/cyber-security.md']
1109
+ : commandId === 'arch-design-system' && writeSetId === 'arch-design-system-doc-v1'
1110
+ ? ['docs/architecture/atomic-design-system.md']
1111
+ : commandId === 'arch-performance' && writeSetId === 'arch-performance-doc-v1'
1112
+ ? ['docs/architecture/performance-strategy.md']
1113
+ : commandId === 'arch-testing' && writeSetId === 'arch-testing-doc-v1'
1114
+ ? ['docs/architecture/testing-strategy.md']
1115
+ : commandId === 'arch-infrastructure' && writeSetId === 'arch-infrastructure-doc-v1'
1116
+ ? ['docs/architecture/infrastructure-deploy.md']
1117
+ : commandId === 'arch-review' && writeSetId === 'arch-review-doc-v1'
1118
+ ? ['docs/architecture/review-consolidation.md']
1119
+ : commandId === 'update-memory' && writeSetId === 'arch-memory-canonical-v1'
1120
+ ? ['NEOCORTEX.md'] : null;
1121
+ return allowedPaths === null ? null : Object.freeze(allowedPaths);
1122
+ }
1123
+ /** Mechanical scope only; protected workflow material remains server supplied. */
1124
+ export function appendPrivateArchitectureHandoff(material, commandId, writeSetId, applicability = 'all') {
1125
+ const paths = privateArchitectureStagePaths(commandId, writeSetId);
1126
+ if (!paths)
1127
+ throw new Error('private-execution-architecture-admission-unverified');
1128
+ return `${material}\n\nPrivate architecture handoff (local only):\n${JSON.stringify({ allowedPaths: paths })}\n${applicability === 'non-ui' ? 'The server admitted a signed non-UI scope for this snapshot. Preserve the backend-only scope: design-system and UX phases are not applicable. Do not require or generate interface artifacts.\n' : ''}Write exactly the listed nonempty artifacts inside this isolated clone. Existing compliant listed artifacts may remain unchanged; all listed artifacts must exist after execution. Place any necessary scope notes inside those artifacts. Do not edit README, package files, other documents, state, Git, QA records or configuration outside the listed paths. Do not install dependencies, use network, deploy or continue to another stage. Preserve existing arch:keep blocks byte-for-byte. Do not add new arch:keep markers. For managed memory, preserve all bytes outside its existing managed block. Root independently reviews artifacts and owns continuation, Git and acceptance.\n`;
1129
+ }
1130
+ /** Architecture agent writes only its closed documents; stage/graph progression remains root-owned. */
1131
+ export function admitPrivateArchitectureStageWorkspace(workspace, commandId, writeSetId, targetEpicId, graphRevision) {
1132
+ const allowedPaths = privateArchitectureStagePaths(commandId, writeSetId);
1133
+ if (!allowedPaths
1134
+ || !/^P[1-9][0-9]{0,11}$/.test(targetEpicId)
1135
+ || !/^[a-f0-9]{64}$/.test(graphRevision)) {
1136
+ throw new Error('private-execution-architecture-admission-unverified');
1137
+ }
1138
+ try {
1139
+ regularOrAbsent(workspace.workspacePath, '.neocortex/state.json');
1140
+ const state = JSON.parse(readFileSync(join(workspace.workspacePath, '.neocortex/state.json'), 'utf8'));
1141
+ if (!state || typeof state !== 'object' || Array.isArray(state))
1142
+ throw new Error('state-invalid');
1143
+ const record = state;
1144
+ const archPlan = record.archPlan;
1145
+ const execution = archPlan?.execution;
1146
+ const epics = record.epics;
1147
+ if (!execution || execution.currentStageCommand !== commandId
1148
+ || execution.targetEpicId !== targetEpicId || execution.graphRevision !== graphRevision
1149
+ || !epics || !Object.hasOwn(epics, targetEpicId))
1150
+ throw new Error('state-binding-mismatch');
1151
+ if (commandId === 'update-memory') {
1152
+ if (execution.continuationState !== 'delegate'
1153
+ || execution.reviewDecision?.decision !== 'approved') {
1154
+ throw new Error('memory-root-review-missing');
1155
+ }
1156
+ const path = 'NEOCORTEX.md';
1157
+ if (!changedPaths(status(workspace.workspacePath, true)).paths.includes(path)) {
1158
+ throw new Error('memory-effect-missing');
1159
+ }
1160
+ regularOrAbsent(workspace.sourceRoot, path);
1161
+ regularOrAbsent(workspace.workspacePath, path);
1162
+ const before = readFileSync(join(workspace.sourceRoot, path), 'utf8');
1163
+ const after = readFileSync(join(workspace.workspacePath, path), 'utf8');
1164
+ const start = '<!-- NEOCORTEX-MEMORY-START -->';
1165
+ const end = '<!-- NEOCORTEX-MEMORY-END -->';
1166
+ const split = (content) => {
1167
+ if (content.split(start).length !== 2 || content.split(end).length !== 2
1168
+ || content.indexOf(start) >= content.indexOf(end))
1169
+ throw new Error('memory-markers-invalid');
1170
+ const bodyStart = content.indexOf(start) + start.length;
1171
+ const bodyEnd = content.indexOf(end);
1172
+ return { before: content.slice(0, bodyStart), body: content.slice(bodyStart, bodyEnd),
1173
+ after: content.slice(bodyEnd) };
1174
+ };
1175
+ const original = split(before);
1176
+ const candidate = split(after);
1177
+ if (original.before !== candidate.before || original.after !== candidate.after
1178
+ || original.body === candidate.body || Buffer.byteLength(after, 'utf8') > 32_768
1179
+ || after.split('\n').length > 240)
1180
+ throw new Error('memory-managed-block-invalid');
1181
+ const originalLines = new Set(original.body.split('\n'));
1182
+ for (const line of candidate.body.split('\n')) {
1183
+ if (!originalLines.has(line))
1184
+ classifyProjectMemoryUpdate(line);
1185
+ }
1186
+ }
1187
+ if (commandId === 'arch-prd') {
1188
+ const prdPath = 'docs/architecture/prd.md';
1189
+ if (!changedPaths(status(workspace.workspacePath, true)).paths.includes(prdPath)) {
1190
+ throw new Error('prd-effect-missing');
1191
+ }
1192
+ regularOrAbsent(workspace.workspacePath, prdPath);
1193
+ if (lstatSync(join(workspace.workspacePath, prdPath)).size === 0)
1194
+ throw new Error('prd-empty');
1195
+ }
1196
+ if (commandId === 'arch-api-contracts' || commandId === 'arch-fitness-functions'
1197
+ || commandId === 'arch-api-integrations' || commandId === 'arch-database'
1198
+ || commandId === 'arch-security' || commandId === 'arch-design-system'
1199
+ || commandId === 'arch-performance' || commandId === 'arch-testing'
1200
+ || commandId === 'arch-infrastructure' || commandId === 'arch-review') {
1201
+ const changed = changedPaths(status(workspace.workspacePath, true)).paths;
1202
+ for (const path of allowedPaths) {
1203
+ // A repair need not rewrite a correct artifact already in the admitted base.
1204
+ // New/missing outputs still refuse; containment and a real delta remain mandatory below.
1205
+ if (!changed.includes(path) && git(workspace.workspacePath, ['cat-file', '-t', `${workspace.baseSha}:${path}`]).toString('utf8').trim() !== 'blob') {
1206
+ throw new Error('architecture-required-effect-missing');
1207
+ }
1208
+ regularOrAbsent(workspace.workspacePath, path);
1209
+ if (lstatSync(join(workspace.workspacePath, path)).size === 0)
1210
+ throw new Error('architecture-required-effect-empty');
1211
+ if (commandId === 'arch-fitness-functions') {
1212
+ regularOrAbsent(workspace.sourceRoot, path);
1213
+ const source = existsSync(join(workspace.sourceRoot, path))
1214
+ ? readFileSync(join(workspace.sourceRoot, path), 'utf8') : '';
1215
+ const generated = readFileSync(join(workspace.workspacePath, path), 'utf8');
1216
+ const priorBlocks = architectureKeepBlocks(source);
1217
+ const generatedBlocks = architectureKeepBlocks(generated);
1218
+ if (!isDeepStrictEqual(generatedBlocks, priorBlocks)) {
1219
+ throw new Error('architecture-keep-marker-changed');
1220
+ }
1221
+ }
1222
+ }
1223
+ }
1224
+ return admitPrivateYoloIsolatedWorkspace(workspace, allowedPaths, workspace.baseSha);
1225
+ }
1226
+ catch {
1227
+ throw new Error('private-execution-architecture-admission-unverified');
1228
+ }
1229
+ }
1230
+ /** A08 paths come only from a committed root approval, never the child result. */
1231
+ export function admitPrivateUxWorkspace(workspace, targetEpicId, graphRevision, expectedWriteSet) {
1232
+ try {
1233
+ if (!/^P[1-9][0-9]{0,11}$/.test(targetEpicId)
1234
+ || !/^[a-f0-9]{64}$/.test(graphRevision)
1235
+ || expectedWriteSet.schemaVersion !== 1
1236
+ || expectedWriteSet.targetEpicId !== targetEpicId)
1237
+ throw new Error('binding-invalid');
1238
+ const source = deriveUxWriteSet(workspace.sourceRoot, targetEpicId);
1239
+ const candidate = deriveUxCandidateWriteSet(workspace.workspacePath, targetEpicId);
1240
+ if (!isDeepStrictEqual(source, expectedWriteSet)
1241
+ || !isDeepStrictEqual(candidate, expectedWriteSet))
1242
+ throw new Error('write-set-invalid');
1243
+ regularOrAbsent(workspace.workspacePath, '.neocortex/state.json');
1244
+ const state = JSON.parse(readFileSync(join(workspace.workspacePath, '.neocortex/state.json'), 'utf8'));
1245
+ if (!state || typeof state !== 'object' || Array.isArray(state))
1246
+ throw new Error('state-invalid');
1247
+ const record = state;
1248
+ const archPlan = record.archPlan;
1249
+ const execution = archPlan?.execution;
1250
+ const epics = record.epics;
1251
+ if (!execution || execution.currentStageCommand !== 'arch-ux-design'
1252
+ || execution.continuationState !== 'delegate'
1253
+ || execution.targetEpicId !== targetEpicId || execution.graphRevision !== graphRevision
1254
+ || !epics || !Object.hasOwn(epics, targetEpicId))
1255
+ throw new Error('stage-invalid');
1256
+ const changed = changedPaths(status(workspace.workspacePath, true)).paths;
1257
+ if (changed.length !== expectedWriteSet.pathCount
1258
+ || changed.some((path) => !expectedWriteSet.paths.includes(path))) {
1259
+ throw new Error('agent-path-unclaimed');
1260
+ }
1261
+ for (const path of expectedWriteSet.paths) {
1262
+ regularOrAbsent(workspace.sourceRoot, path);
1263
+ regularOrAbsent(workspace.workspacePath, path);
1264
+ const entry = lstatSync(join(workspace.workspacePath, path));
1265
+ if (!entry.isFile() || entry.isSymbolicLink() || entry.size < 1)
1266
+ throw new Error('effect-empty');
1267
+ const content = new TextDecoder('utf-8', { fatal: true })
1268
+ .decode(readFileSync(join(workspace.workspacePath, path)));
1269
+ if (!content.trim() || content.includes('\0'))
1270
+ throw new Error('effect-invalid');
1271
+ }
1272
+ validateUxCandidateSource(workspace.workspacePath, expectedWriteSet);
1273
+ return admitPrivateYoloIsolatedWorkspace(workspace, expectedWriteSet.paths, workspace.baseSha);
1274
+ }
1275
+ catch {
1276
+ throw new Error('private-execution-ux-admission-unverified');
1277
+ }
1278
+ }
1279
+ /**
1280
+ * The private agent writes only the exact OpenAPI-derived test set. Optional
1281
+ * package/lockfile integration is staged by the root after validating that set,
1282
+ * then admitted in the same exact patch. This still proves no test execution.
1283
+ */
1284
+ export function admitPrivatePactWorkspace(workspace, targetEpicId, graphRevision, expectedAnswers, expectedWriteSet, toolchainCandidate) {
1285
+ try {
1286
+ if (!/^P[1-9][0-9]{0,11}$/.test(targetEpicId)
1287
+ || !/^[a-f0-9]{64}$/.test(graphRevision)
1288
+ || expectedAnswers.targetEpicId !== targetEpicId
1289
+ || expectedAnswers.graphRevision !== graphRevision
1290
+ || expectedWriteSet.schemaVersion !== 1)
1291
+ throw new Error('binding-invalid');
1292
+ const privateAnswers = loadPactPrivateAnswers(workspace.sourceRoot);
1293
+ if (!privateAnswers || !isDeepStrictEqual(privateAnswers.receipt, expectedAnswers)) {
1294
+ throw new Error('answers-invalid');
1295
+ }
1296
+ const source = derivePactWriteSet(workspace.sourceRoot, privateAnswers.answers.generateProvider);
1297
+ const candidate = derivePactWriteSet(workspace.workspacePath, privateAnswers.answers.generateProvider);
1298
+ if (source.outcome !== 'ready' || candidate.outcome !== 'ready'
1299
+ || !isDeepStrictEqual(source.writeSet, expectedWriteSet)
1300
+ || !isDeepStrictEqual(candidate.writeSet, expectedWriteSet)) {
1301
+ throw new Error('write-set-invalid');
1302
+ }
1303
+ const changed = changedPaths(status(workspace.workspacePath, true)).paths;
1304
+ if (changed.some((path) => !expectedWriteSet.paths.includes(path))) {
1305
+ throw new Error('agent-path-unclaimed');
1306
+ }
1307
+ let consumerBindingCandidateCount = 0;
1308
+ let consumerScaffoldCount = 0;
1309
+ for (const path of expectedWriteSet.paths) {
1310
+ regularOrAbsent(workspace.sourceRoot, path);
1311
+ if (existsSync(join(workspace.sourceRoot, path)) || !changed.includes(path)) {
1312
+ throw new Error('effect-missing-or-overwrite');
1313
+ }
1314
+ regularOrAbsent(workspace.workspacePath, path);
1315
+ const entry = lstatSync(join(workspace.workspacePath, path));
1316
+ if (!entry.isFile() || entry.size < 1)
1317
+ throw new Error('effect-empty');
1318
+ const content = new TextDecoder('utf-8', { fatal: true })
1319
+ .decode(readFileSync(join(workspace.workspacePath, path)));
1320
+ const expectedInteraction = expectedWriteSet.interactions
1321
+ .find((interaction) => interaction.filePath === path);
1322
+ if (path !== expectedWriteSet.providerPath && !expectedInteraction) {
1323
+ throw new Error('private-execution-pact-interaction-missing');
1324
+ }
1325
+ const inspected = validatePactTestSource(path, content, privateAnswers.answers.brokerUrl, expectedInteraction, workspace.workspacePath, privateAnswers.answers.matchersAggressive);
1326
+ if (inspected.consumerBinding === 'app-import-candidate')
1327
+ consumerBindingCandidateCount++;
1328
+ if (inspected.consumerBinding === 'mock-only-scaffold')
1329
+ consumerScaffoldCount++;
1330
+ }
1331
+ let authorizedPaths = expectedWriteSet.paths;
1332
+ if (toolchainCandidate) {
1333
+ const planned = planPactToolchainManifest(workspace.sourceRoot, privateAnswers.answers.generateProvider);
1334
+ const sha = (value) => createHash('sha256').update(value).digest('hex');
1335
+ if (toolchainCandidate.schemaVersion !== 1
1336
+ || toolchainCandidate.outcome !== 'candidate-unverified'
1337
+ || toolchainCandidate.executionVerified !== false
1338
+ || toolchainCandidate.sourceManifestSha256 !== sha(planned.sourceText)
1339
+ || toolchainCandidate.sourceLockSha256 !== (planned.sourceLockText === null
1340
+ ? null : sha(planned.sourceLockText))
1341
+ || toolchainCandidate.manifestText !== planned.candidateText
1342
+ || !sameAsSnapshot(workspace.sourceRoot, workspace.workspacePath, workspace.baseSha, 'package.json')
1343
+ || !sameAsSnapshot(workspace.sourceRoot, workspace.workspacePath, workspace.baseSha, 'package-lock.json'))
1344
+ throw new Error('toolchain-binding-invalid');
1345
+ for (const path of ['package.json', 'package-lock.json']) {
1346
+ regularOrAbsent(workspace.sourceRoot, path);
1347
+ regularOrAbsent(workspace.workspacePath, path);
1348
+ const sourceText = path === 'package.json' ? planned.sourceText : planned.sourceLockText;
1349
+ const cloneText = existsSync(join(workspace.workspacePath, path))
1350
+ ? readFileSync(join(workspace.workspacePath, path), 'utf8') : null;
1351
+ if (sourceText !== cloneText)
1352
+ throw new Error('toolchain-clone-raced');
1353
+ }
1354
+ writeFileSync(join(workspace.workspacePath, 'package.json'), toolchainCandidate.manifestText);
1355
+ writeFileSync(join(workspace.workspacePath, 'package-lock.json'), toolchainCandidate.lockText);
1356
+ if (inspectPactToolchain(workspace.workspacePath, privateAnswers.answers.generateProvider).outcome !== 'manifest-aligned') {
1357
+ throw new Error('toolchain-candidate-invalid');
1358
+ }
1359
+ authorizedPaths = [...expectedWriteSet.paths, 'package.json', 'package-lock.json'];
1360
+ }
1361
+ const admitted = admitPrivateYoloIsolatedWorkspace(workspace, authorizedPaths, workspace.baseSha);
1362
+ return Object.freeze({ ...admitted, consumerBindingCandidateCount, consumerScaffoldCount });
1363
+ }
1364
+ catch {
1365
+ throw new Error('private-execution-pact-admission-unverified');
1366
+ }
1367
+ }
1368
+ /**
1369
+ * Admit only plan-bound property-test candidates. The agent cannot edit the
1370
+ * PRD: the root appends the operator-confirmed configuration in the clone only
1371
+ * after checking the complete test write set. This is not a QA verdict.
1372
+ */
1373
+ export function admitPrivatePropertyTestWorkspace(workspace, choices, expectedPlan, generatedAt = new Date().toISOString()) {
1374
+ try {
1375
+ const sourcePlan = confirmPropertyTestPlan(workspace.sourceRoot, choices);
1376
+ if (!isDeepStrictEqual(sourcePlan, expectedPlan)
1377
+ || !sameFile(workspace.sourceRoot, workspace.workspacePath, expectedPlan.prdPath)
1378
+ || expectedPlan.aggregates.some((item) => !sameFile(workspace.sourceRoot, workspace.workspacePath, item.implementationFile))
1379
+ || expectedPlan.writeSet.length === 0)
1380
+ throw new Error('plan-drift');
1381
+ const changed = changedPaths(status(workspace.workspacePath, true)).paths;
1382
+ if (!isDeepStrictEqual(changed, expectedPlan.writeSet))
1383
+ throw new Error('agent-path-unclaimed');
1384
+ const candidateDigests = new Map();
1385
+ for (const item of expectedPlan.aggregates) {
1386
+ regularOrAbsent(workspace.sourceRoot, item.testFile);
1387
+ regularOrAbsent(workspace.workspacePath, item.testFile);
1388
+ const entry = lstatSync(join(workspace.workspacePath, item.testFile));
1389
+ if (!entry.isFile() || entry.isSymbolicLink() || entry.size < 1 || entry.size > MAX_PATCH_BYTES) {
1390
+ throw new Error('test-invalid');
1391
+ }
1392
+ const bytes = readFileSync(join(workspace.workspacePath, item.testFile));
1393
+ const content = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
1394
+ const testCount = expectedPlan.language === 'typescript'
1395
+ ? (content.match(/\b(?:it|test)\s*\(/g) ?? []).length
1396
+ : (content.match(/^def\s+test_[A-Za-z0-9_]+\s*\(/gm) ?? []).length;
1397
+ if (!content.trim() || content.includes('\0') || testCount < item.invariantCount
1398
+ || /\b(?:it|test|describe)\s*\.\s*(?:skip|todo|only)\s*\(/.test(content)
1399
+ || !content.includes(String(expectedPlan.numRuns)))
1400
+ throw new Error('test-invalid');
1401
+ if (expectedPlan.language === 'typescript') {
1402
+ validatePropertyTestTypeScriptSource({ source: content, testFile: item.testFile,
1403
+ implementationFile: item.implementationFile, invariantCount: item.invariantCount,
1404
+ numRuns: expectedPlan.numRuns });
1405
+ }
1406
+ candidateDigests.set(item.testFile, createHash('sha256').update(bytes).digest('hex'));
1407
+ }
1408
+ regularOrAbsent(workspace.sourceRoot, expectedPlan.prdPath);
1409
+ regularOrAbsent(workspace.workspacePath, expectedPlan.prdPath);
1410
+ const prdBytes = readFileSync(join(workspace.workspacePath, expectedPlan.prdPath));
1411
+ const prd = new TextDecoder('utf-8', { fatal: true }).decode(prdBytes);
1412
+ const configured = renderPropertyTestConfiguration(prd, expectedPlan, generatedAt);
1413
+ writeFileSync(join(workspace.workspacePath, expectedPlan.prdPath), configured);
1414
+ const configuredDigest = createHash('sha256').update(configured).digest('hex');
1415
+ return admitPrivateYoloIsolatedWorkspace(workspace, [...expectedPlan.writeSet, expectedPlan.prdPath], workspace.baseSha, undefined, () => {
1416
+ try {
1417
+ if (!isDeepStrictEqual(confirmPropertyTestPlan(workspace.sourceRoot, choices), expectedPlan)
1418
+ || expectedPlan.aggregates.some((item) => !sameFile(workspace.sourceRoot, workspace.workspacePath, item.implementationFile))
1419
+ || createHash('sha256').update(readFileSync(join(workspace.workspacePath, expectedPlan.prdPath))).digest('hex') !== configuredDigest)
1420
+ return false;
1421
+ return [...candidateDigests].every(([path, sha]) => createHash('sha256').update(readFileSync(join(workspace.workspacePath, path)))
1422
+ .digest('hex') === sha);
1423
+ }
1424
+ catch {
1425
+ return false;
1426
+ }
1427
+ });
1428
+ }
1429
+ catch {
1430
+ throw new Error('private-execution-property-admission-unverified');
1431
+ }
1432
+ }