@ornexus/neocortex 4.60.72 → 4.60.73

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 (243) hide show
  1. package/dist/release-source.json +1 -1
  2. package/dist/sbom.cdx.json +24 -24
  3. package/install.ps1 +1 -1
  4. package/install.sh +1 -1
  5. package/package.json +2 -2
  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 +358 -5
  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.js +635 -2
  37. package/packages/client/dist/commands/invoke.js +7200 -143
  38. package/packages/client/dist/commands/legacy-epic-terminal.js +71 -1
  39. package/packages/client/dist/commands/legacy-story-branch.js +96 -1
  40. package/packages/client/dist/commands/legacy-story-pr.js +276 -1
  41. package/packages/client/dist/commands/mutation-qa-checkpoint.js +285 -5
  42. package/packages/client/dist/commands/mutation-qa-gate.js +54 -1
  43. package/packages/client/dist/commands/mutation-qa-git.js +41 -1
  44. package/packages/client/dist/commands/mutation-qa-policy.js +102 -1
  45. package/packages/client/dist/commands/mutation-qa-run.js +219 -3
  46. package/packages/client/dist/commands/mutation-qa-verdict.js +149 -1
  47. package/packages/client/dist/commands/mutation-qa.js +220 -1
  48. package/packages/client/dist/commands/native-agent.js +136 -2
  49. package/packages/client/dist/commands/planning-diagnosis.js +43 -1
  50. package/packages/client/dist/commands/planning-generation.js +53 -1
  51. package/packages/client/dist/commands/planning-memory.js +40 -1
  52. package/packages/client/dist/commands/planning-research.js +42 -1
  53. package/packages/client/dist/commands/private-session.js +75 -1
  54. package/packages/client/dist/commands/property-qa.js +139 -1
  55. package/packages/client/dist/commands/qa-control.js +16 -1
  56. package/packages/client/dist/commands/qa-scan-browser-launch.js +55 -1
  57. package/packages/client/dist/commands/qa-scan-browser-route.js +28 -1
  58. package/packages/client/dist/commands/qa-scan-capture.js +663 -5
  59. package/packages/client/dist/commands/qa-scan-discovery.js +376 -3
  60. package/packages/client/dist/commands/qa-scan-review.js +826 -5
  61. package/packages/client/dist/commands/qa-synthetic-backend-capture.js +266 -1
  62. package/packages/client/dist/commands/qa-synthetic-backend-review.js +297 -1
  63. package/packages/client/dist/commands/qa-synthetic-backend.js +340 -2
  64. package/packages/client/dist/commands/qa-synthetic-broker-port.js +85 -1
  65. package/packages/client/dist/commands/qa-synthetic-browser.js +39 -1
  66. package/packages/client/dist/commands/refactor-characterization.js +671 -21
  67. package/packages/client/dist/commands/refactor-commit.js +399 -13
  68. package/packages/client/dist/commands/refactor-control.js +142 -3
  69. package/packages/client/dist/commands/refactor-cpp-assertions.js +93 -3
  70. package/packages/client/dist/commands/refactor-review.js +283 -5
  71. package/packages/client/dist/commands/refactor-root.js +588 -7
  72. package/packages/client/dist/commands/refresh-memory.js +156 -3
  73. package/packages/client/dist/commands/shared-architecture-non-ui.js +24 -1
  74. package/packages/client/dist/commands/story-checkpoint.js +171 -1
  75. package/packages/client/dist/config/profile-directory.js +13 -1
  76. package/packages/client/dist/config/resolver-selection.js +278 -1
  77. package/packages/client/dist/config/secure-config.js +281 -12
  78. package/packages/client/dist/config.js +223 -2
  79. package/packages/client/dist/constants.js +25 -1
  80. package/packages/client/dist/context/context-collector.js +222 -2
  81. package/packages/client/dist/context/context-sanitizer.js +145 -1
  82. package/packages/client/dist/continuity/autonomous-governance.js +285 -1
  83. package/packages/client/dist/continuity/continuity-client-state-store.js +1587 -1
  84. package/packages/client/dist/continuity/invoke-hooks.js +129 -1
  85. package/packages/client/dist/continuity/migrations/001-initial-schema.js +12 -2
  86. package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +12 -2
  87. package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +12 -2
  88. package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +12 -2
  89. package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +12 -2
  90. package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +10 -2
  91. package/packages/client/dist/continuity/native-agent-store.js +69 -1
  92. package/packages/client/dist/continuity/native-agent-types.js +90 -1
  93. package/packages/client/dist/continuity/outbox.js +117 -1
  94. package/packages/client/dist/continuity/runner-bootstrap-policy.js +172 -1
  95. package/packages/client/dist/continuity/sqlite-store.d.ts +22 -1
  96. package/packages/client/dist/continuity/sqlite-store.js +2612 -81
  97. package/packages/client/dist/effects/invoke-effect-gateways.js +62 -1
  98. package/packages/client/dist/errors/error-messages.js +228 -2
  99. package/packages/client/dist/errors/frontmatter-diagnostics.js +125 -1
  100. package/packages/client/dist/evidence/collector.js +347 -1
  101. package/packages/client/dist/evidence/index.js +1 -1
  102. package/packages/client/dist/evidence/types.js +2 -0
  103. package/packages/client/dist/fs/atomic-state-replace.js +152 -1
  104. package/packages/client/dist/generated/command-registry.generated.js +4076 -1
  105. package/packages/client/dist/generated/public-projections.generated.js +149 -1
  106. package/packages/client/dist/git/direct-yolo-c01-retry.js +138 -4
  107. package/packages/client/dist/git/direct-yolo-checkpoint.js +1234 -4
  108. package/packages/client/dist/git/epic-operational-state.d.ts +198 -0
  109. package/packages/client/dist/git/epic-operational-state.js +672 -0
  110. package/packages/client/dist/git/git-lifecycle-step-ids.js +14 -1
  111. package/packages/client/dist/git/git-lifecycle-store.d.ts +9 -1
  112. package/packages/client/dist/git/git-lifecycle-store.js +1981 -9
  113. package/packages/client/dist/git/legacy-story-branch-checkpoint.js +259 -9
  114. package/packages/client/dist/git/legacy-story-branch-setup.js +348 -4
  115. package/packages/client/dist/git/legacy-story-pr.js +3538 -70
  116. package/packages/client/dist/git/secure-filesystem-boundary.js +182 -5
  117. package/packages/client/dist/graph-retrieval/pre-command-hook.js +284 -1
  118. package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +237 -1
  119. package/packages/client/dist/i18n/first-run.js +60 -2
  120. package/packages/client/dist/index.js +47 -1
  121. package/packages/client/dist/invocation/invoke-input.js +24 -1
  122. package/packages/client/dist/license/index.js +5 -1
  123. package/packages/client/dist/license/license-client.js +304 -1
  124. package/packages/client/dist/machine/fingerprint.js +163 -2
  125. package/packages/client/dist/machine/index.js +5 -1
  126. package/packages/client/dist/memory/project-memory-path-scope.js +62 -1
  127. package/packages/client/dist/memory/project-memory-writer.js +1007 -40
  128. package/packages/client/dist/memory/shared-project-memory-types.js +253 -2
  129. package/packages/client/dist/migrations/epic-lifecycle-backfill.d.ts +97 -0
  130. package/packages/client/dist/migrations/epic-lifecycle-backfill.js +1060 -0
  131. package/packages/client/dist/migrations/project-identity-capability.js +50 -1
  132. package/packages/client/dist/migrations/project-identity.js +493 -7
  133. package/packages/client/dist/migrations/state-json.d.ts +22 -0
  134. package/packages/client/dist/migrations/state-json.js +393 -4
  135. package/packages/client/dist/policy/architecture-policy.js +184 -2
  136. package/packages/client/dist/policy/index.js +7 -1
  137. package/packages/client/dist/policy/shared-policy-types.js +38 -0
  138. package/packages/client/dist/policy/subagent-approval-policy.js +55 -1
  139. package/packages/client/dist/private-execution/clean-baseline.js +279 -2
  140. package/packages/client/dist/private-execution/codex-executor.js +574 -4
  141. package/packages/client/dist/private-execution/codex-host-runtime.js +137 -1
  142. package/packages/client/dist/private-execution/codex-lifecycle-guard.js +97 -1
  143. package/packages/client/dist/private-execution/codex-scoped-permissions.js +128 -1
  144. package/packages/client/dist/private-execution/delivery.js +410 -13
  145. package/packages/client/dist/private-execution/diagnosis-root-review.js +294 -2
  146. package/packages/client/dist/private-execution/frozen-handoff.js +19 -6
  147. package/packages/client/dist/private-execution/implementation-validation.js +106 -2
  148. package/packages/client/dist/private-execution/isolated-admission.js +1432 -23
  149. package/packages/client/dist/private-execution/isolated-session.js +725 -5
  150. package/packages/client/dist/private-execution/isolated-workspace.js +122 -1
  151. package/packages/client/dist/private-execution/pact-consumer-runtime.js +409 -3
  152. package/packages/client/dist/private-execution/pact-test-source.js +258 -1
  153. package/packages/client/dist/private-execution/pact-toolchain.js +290 -3
  154. package/packages/client/dist/private-execution/pact-write-set.js +172 -1
  155. package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +406 -5
  156. package/packages/client/dist/private-execution/planning-generation-checkpoint.js +462 -5
  157. package/packages/client/dist/private-execution/planning-memory-checkpoint.js +139 -3
  158. package/packages/client/dist/private-execution/planning-research-checkpoint.js +325 -4
  159. package/packages/client/dist/private-execution/private-report-store.js +42 -1
  160. package/packages/client/dist/private-execution/property-test-candidate.js +103 -1
  161. package/packages/client/dist/private-execution/property-test-choices.js +147 -3
  162. package/packages/client/dist/private-execution/property-test-plan.js +271 -7
  163. package/packages/client/dist/private-execution/property-test-review.js +258 -4
  164. package/packages/client/dist/private-execution/property-test-runtime.js +372 -9
  165. package/packages/client/dist/private-execution/property-test-source.js +139 -1
  166. package/packages/client/dist/private-execution/root-checkpoint.js +80 -1
  167. package/packages/client/dist/private-execution/root-memory-git-evidence.js +135 -3
  168. package/packages/client/dist/private-execution/root-memory-recovery.js +93 -1
  169. package/packages/client/dist/private-execution/root-memory-request.js +29 -1
  170. package/packages/client/dist/private-execution/root-memory-scope.js +178 -1
  171. package/packages/client/dist/private-execution/root-memory-writer.js +102 -1
  172. package/packages/client/dist/private-execution/session-lease.js +63 -1
  173. package/packages/client/dist/private-execution/shared-private-admission-types.js +50 -1
  174. package/packages/client/dist/private-execution/shared-project-validation.js +66 -1
  175. package/packages/client/dist/private-execution/shared-root-memory-types.js +61 -1
  176. package/packages/client/dist/private-execution/ux-source-validation.js +372 -1
  177. package/packages/client/dist/private-execution/ux-write-set.js +191 -2
  178. package/packages/client/dist/registry/registry-reconciliation.js +234 -1
  179. package/packages/client/dist/resilience/circuit-breaker.js +170 -1
  180. package/packages/client/dist/resilience/degradation-manager.js +164 -1
  181. package/packages/client/dist/resilience/freshness-indicator.js +100 -1
  182. package/packages/client/dist/resilience/index.js +8 -1
  183. package/packages/client/dist/resilience/recovery-detector.js +74 -1
  184. package/packages/client/dist/resolvers/asset-resolver.js +13 -0
  185. package/packages/client/dist/resolvers/local-resolver.js +255 -8
  186. package/packages/client/dist/resolvers/remote-resolver.js +233 -1
  187. package/packages/client/dist/runner/binary.js +404 -1
  188. package/packages/client/dist/runner/cli.js +1571 -17
  189. package/packages/client/dist/runner/headless/adapters.js +49 -1
  190. package/packages/client/dist/runner/headless/claude-code-adapter.js +79 -1
  191. package/packages/client/dist/runner/headless/continuous-story-workspace.js +1628 -11
  192. package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +69 -1
  193. package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +110 -2
  194. package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +1236 -8
  195. package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +318 -2
  196. package/packages/client/dist/runner/headless/fake-adapter.js +62 -1
  197. package/packages/client/dist/runner/headless/managed-profile.js +132 -1
  198. package/packages/client/dist/runner/headless/opencode-adapter.js +145 -2
  199. package/packages/client/dist/runner/headless/prepared-story-bundle.js +43 -1
  200. package/packages/client/dist/runner/headless/resolver.js +231 -1
  201. package/packages/client/dist/runner/headless/run-yolo.js +885 -1
  202. package/packages/client/dist/runner/headless/run-yoloop.js +2574 -2
  203. package/packages/client/dist/runner/headless/types.js +574 -2
  204. package/packages/client/dist/runner/local-console.js +396 -13
  205. package/packages/client/dist/runner/portal-sync-worker.js +1487 -1
  206. package/packages/client/dist/runner/project-identity.js +106 -1
  207. package/packages/client/dist/runner/scheduler.js +995 -7
  208. package/packages/client/dist/runner-cli.js +18 -2
  209. package/packages/client/dist/state/invocation-shape.js +13 -1
  210. package/packages/client/dist/state/project-state-snapshot.d.ts +8 -0
  211. package/packages/client/dist/state/project-state-snapshot.js +1731 -1
  212. package/packages/client/dist/state/state-json-repair.js +785 -3
  213. package/packages/client/dist/state/structural-dag-projection.js +118 -1
  214. package/packages/client/dist/telemetry/index.js +5 -1
  215. package/packages/client/dist/telemetry/offline-queue.js +131 -1
  216. package/packages/client/dist/tier/index.js +5 -1
  217. package/packages/client/dist/tier/tier-aware-client.js +405 -1
  218. package/packages/client/dist/types/index.js +38 -1
  219. package/packages/client/dist/version.js +27 -1
  220. package/packages/client/dist/yolo/change-evidence-collector.js +126 -1
  221. package/packages/client/dist/yolo/invoke-envelope-capability.js +62 -1
  222. package/packages/client/dist/yolo/spec-envelope-collector.js +184 -5
  223. package/packages/client/dist/yoloop/discovery-hook.js +300 -2
  224. package/packages/client/dist/yoloop/index.js +8 -1
  225. package/packages/client/dist/yoloop/invoke-hooks.js +669 -12
  226. package/packages/client/dist/yoloop/shared-discover-epics.js +1007 -1
  227. package/packages/client/dist/yoloop/shared-yoloop-types.d.ts +65 -1
  228. package/packages/client/dist/yoloop/shared-yoloop-types.js +33 -1
  229. package/packages/client/dist/yoloop/target-normalization.js +180 -1
  230. package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +8 -0
  231. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1920 -1
  232. package/packages/shared/dist/yolo/spec-envelope.js +3 -6
  233. package/targets-stubs/antigravity/gemini.md +1 -1
  234. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  235. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  236. package/targets-stubs/claude-code/neocortex-root.md +2 -2
  237. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  238. package/targets-stubs/claude-code/neocortex.md +2 -2
  239. package/targets-stubs/codex/AGENTS.md +1 -1
  240. package/targets-stubs/cursor/agent.md +2 -2
  241. package/targets-stubs/gemini-cli/agent.md +2 -2
  242. package/targets-stubs/opencode/neocortex-root.md +1 -1
  243. package/targets-stubs/vscode/neocortex.agent.md +2 -2
@@ -1,3 +1,139 @@
1
- import{execFileSync as q,spawnSync as x}from"node:child_process";import{createHash as A}from"node:crypto";import{closeSync as h,existsSync as $,fsyncSync as H,lstatSync as g,openSync as S,readFileSync as I,readlinkSync as M,realpathSync as D,unlinkSync as F,writeFileSync as N}from"node:fs";import{join as d,resolve as b}from"node:path";import{isDeepStrictEqual as w}from"node:util";import{canonicalPlanningIntake as B}from"../../../shared/dist/planning/diagnosis-state.js";import{parseRefreshMemoryArgs as O,refreshProjectMemoryTargets as v,verifyProjectMemoryTargets as J}from"../commands/refresh-memory.js";import{planningPendingSidecarDigest as P}from"./planning-diagnosis-checkpoint.js";import{verifyCommittedPlanningGeneration as k}from"./planning-generation-checkpoint.js";const L=e=>A("sha256").update(e).digest("hex"),r=()=>new Error("planning-memory-physical-evidence-invalid"),E=e=>!!e&&typeof e=="object"&&!Array.isArray(e),y=(e,...t)=>q("git",t,{cwd:e,encoding:"utf8",stdio:["ignore","pipe","ignore"],maxBuffer:1024*1024}).trim(),R=e=>`.neocortex/planning/epic-${e}/memory-acceptance.json`;function C(e,t){const i=x("git",["show",`HEAD:${t}`],{cwd:e,encoding:"buffer",maxBuffer:1048576});if(i.status!==0||!Buffer.isBuffer(i.stdout))throw r();return i.stdout}function V(e,t,i){if(D(e)!==e||y(e,"rev-parse","--show-toplevel")!==e)throw r();const o=k(e,t,i),c=O(["--project-root",e,"--verify"]),m=J(c),l=[...new Set(v(c).flatMap(n=>n.files.map(s=>s.relativeFile)))].sort();for(const n of l){const s=d(e,n),a=g(s);if(!(a.isSymbolicLink()?Buffer.from(M(s)):I(s)).equals(C(e,n)))throw r();const f=y(e,"ls-tree","HEAD","--",n).split(" ")[0];if(a.isSymbolicLink()?f!=="120000":!a.isFile()||!["100644","100755"].includes(f))throw r()}if(y(e,"rev-parse","HEAD")!==o.verifiedHead)throw r();return{generation:o,memory:m,files:l}}function u(e){if(!E(e)||Object.keys(e).sort().join("|")!==["schemaVersion","verdict","claimId","epicId","sourceHead","generationReceiptId","canonicalSha256","projectionSha256","fileCount","qaEvidenceId","artifactSummary"].sort().join("|")||e.schemaVersion!=="PlanningMemoryRootReview/1"||e.verdict!=="accepted"||typeof e.claimId!="string"||!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(e.claimId)||typeof e.epicId!="string"||!/^P[1-9][0-9]{0,11}$/.test(e.epicId)||typeof e.sourceHead!="string"||!/^[a-f0-9]{40}$/.test(e.sourceHead)||[e.generationReceiptId,e.canonicalSha256,e.projectionSha256].some(t=>typeof t!="string"||!/^[a-f0-9]{64}$/.test(t))||!Number.isInteger(e.fileCount)||Number(e.fileCount)<1||Number(e.fileCount)>64||typeof e.qaEvidenceId!="string"||!/^qa-[a-z0-9-]{8,80}$/.test(e.qaEvidenceId)||typeof e.artifactSummary!="string"||!e.artifactSummary.trim())throw r();B({goal:e.artifactSummary})}function j(e,t){u(t);const{generation:i,memory:o}=V(e,t.claimId,t.epicId);if(i.physicalAcceptanceReceiptId!==t.generationReceiptId||o.canonicalSha256!==t.canonicalSha256||o.projectionSha256!==t.projectionSha256||o.fileCount!==t.fileCount||x("git",["merge-base","--is-ancestor",t.sourceHead,"HEAD"],{cwd:e,stdio:"ignore"}).status!==0)throw r();const c={schemaVersion:"PlanningMemoryPhysicalAcceptance/1",claimId:t.claimId,epicId:t.epicId,sourceHead:t.sourceHead,generationReceiptId:t.generationReceiptId,stateSha256:i.stateSha256,canonicalSha256:t.canonicalSha256,projectionSha256:t.projectionSha256,fileCount:t.fileCount,qaEvidenceId:t.qaEvidenceId,artifactSummary:t.artifactSummary};return{...c,receiptId:L(JSON.stringify(c)),publicSafe:!0}}function Z(e,t){u(t);const i=b(e),o=R(t.epicId);k(i,t.claimId,t.epicId);const c=d(i,".neocortex/state.json.planning.lock"),m=S(c,"wx",384);try{const l=P(i),n=j(i,t);if($(d(i,o))){const f=g(d(i,o));if(!f.isFile()||f.isSymbolicLink()||f.size>16384||!w(JSON.parse(I(d(i,o),"utf8")),n))throw r();return n}if(y(i,"rev-parse","HEAD")!==t.sourceHead||y(i,"diff","--name-only")||y(i,"diff","--cached","--name-only")||y(i,"ls-files","--others","--exclude-standard").split(`
2
- `).filter(Boolean).some(f=>f!==".neocortex/pending-epic-claims.json"&&f!==".neocortex/state.json.planning.lock")||P(i)!==l||!w(j(i,t),n))throw r();const a=S(d(i,o),"wx",384);try{N(a,`${JSON.stringify(n,null,2)}
3
- `),H(a)}finally{h(a)}const p=S(d(i,".neocortex/planning",`epic-${t.epicId}`),"r");try{H(p)}finally{h(p)}return n}finally{h(m),F(c)}}function _(e,t,i){if(!/^P[1-9][0-9]{0,11}$/.test(i))throw r();const o=b(e),c=R(i),m=g(d(o,c));if(!m.isFile()||m.isSymbolicLink()||m.size>16384)throw r();const l=I(d(o,c));if(!l.equals(C(o,c)))throw r();const n=JSON.parse(l.toString("utf8"));if(!E(n)||n.claimId!==t||n.epicId!==i)throw r();const s={schemaVersion:"PlanningMemoryRootReview/1",verdict:"accepted",claimId:t,epicId:i,sourceHead:n.sourceHead,generationReceiptId:n.generationReceiptId,canonicalSha256:n.canonicalSha256,projectionSha256:n.projectionSha256,fileCount:n.fileCount,qaEvidenceId:n.qaEvidenceId,artifactSummary:n.artifactSummary};u(s);const a=j(o,s);if(!w(n,a))throw r();return a}export{Z as sealPlanningMemory,_ as verifyCommittedPlanningMemory};
1
+ /** Root P04 accepts committed canonical memory and every physical projection. */
2
+ import { execFileSync, spawnSync } from 'node:child_process';
3
+ import { createHash } from 'node:crypto';
4
+ import { closeSync, existsSync, fsyncSync, lstatSync, openSync, readFileSync, readlinkSync, realpathSync, unlinkSync, writeFileSync } from 'node:fs';
5
+ import { join, resolve } from 'node:path';
6
+ import { isDeepStrictEqual } from 'node:util';
7
+ import { canonicalPlanningIntake } from '../../../shared/dist/planning/diagnosis-state.js';
8
+ import { parseRefreshMemoryArgs, refreshProjectMemoryTargets, verifyProjectMemoryTargets } from '../commands/refresh-memory.js';
9
+ import { planningPendingSidecarDigest } from './planning-diagnosis-checkpoint.js';
10
+ import { verifyCommittedPlanningGeneration } from './planning-generation-checkpoint.js';
11
+ const digest = (bytes) => createHash('sha256').update(bytes).digest('hex');
12
+ const fail = () => new Error('planning-memory-physical-evidence-invalid');
13
+ const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
14
+ const git = (root, ...args) => execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 1024 * 1024 }).trim();
15
+ const receiptPath = (epicId) => `.neocortex/planning/epic-${epicId}/memory-acceptance.json`;
16
+ function committed(root, path) {
17
+ const result = spawnSync('git', ['show', `HEAD:${path}`], { cwd: root, encoding: 'buffer', maxBuffer: 1024 * 1024 });
18
+ if (result.status !== 0 || !Buffer.isBuffer(result.stdout))
19
+ throw fail();
20
+ return result.stdout;
21
+ }
22
+ function physical(root, claimId, epicId) {
23
+ if (realpathSync(root) !== root || git(root, 'rev-parse', '--show-toplevel') !== root)
24
+ throw fail();
25
+ const generation = verifyCommittedPlanningGeneration(root, claimId, epicId);
26
+ const options = parseRefreshMemoryArgs(['--project-root', root, '--verify']);
27
+ const memory = verifyProjectMemoryTargets(options);
28
+ const files = [...new Set(refreshProjectMemoryTargets(options).flatMap(r => r.files.map(f => f.relativeFile)))].sort();
29
+ for (const path of files) {
30
+ const file = join(root, path), entry = lstatSync(file);
31
+ const bytes = entry.isSymbolicLink() ? Buffer.from(readlinkSync(file)) : readFileSync(file);
32
+ if (!bytes.equals(committed(root, path)))
33
+ throw fail();
34
+ const mode = git(root, 'ls-tree', 'HEAD', '--', path).split(' ')[0];
35
+ if (entry.isSymbolicLink() ? mode !== '120000' : !entry.isFile() || !['100644', '100755'].includes(mode))
36
+ throw fail();
37
+ }
38
+ if (git(root, 'rev-parse', 'HEAD') !== generation.verifiedHead)
39
+ throw fail();
40
+ return { generation, memory, files };
41
+ }
42
+ function review(value) {
43
+ if (!record(value) || Object.keys(value).sort().join('|') !== [
44
+ 'schemaVersion', 'verdict', 'claimId', 'epicId', 'sourceHead', 'generationReceiptId',
45
+ 'canonicalSha256', 'projectionSha256', 'fileCount', 'qaEvidenceId', 'artifactSummary',
46
+ ].sort().join('|') || value.schemaVersion !== 'PlanningMemoryRootReview/1' || value.verdict !== 'accepted'
47
+ || typeof value.claimId !== 'string' || !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(value.claimId)
48
+ || typeof value.epicId !== 'string' || !/^P[1-9][0-9]{0,11}$/.test(value.epicId)
49
+ || typeof value.sourceHead !== 'string' || !/^[a-f0-9]{40}$/.test(value.sourceHead)
50
+ || [value.generationReceiptId, value.canonicalSha256, value.projectionSha256].some(v => typeof v !== 'string' || !/^[a-f0-9]{64}$/.test(v))
51
+ || !Number.isInteger(value.fileCount) || Number(value.fileCount) < 1 || Number(value.fileCount) > 64
52
+ || typeof value.qaEvidenceId !== 'string' || !/^qa-[a-z0-9-]{8,80}$/.test(value.qaEvidenceId)
53
+ || typeof value.artifactSummary !== 'string' || !value.artifactSummary.trim())
54
+ throw fail();
55
+ canonicalPlanningIntake({ goal: value.artifactSummary });
56
+ }
57
+ function derive(root, input) {
58
+ review(input);
59
+ const { generation, memory } = physical(root, input.claimId, input.epicId);
60
+ if (generation.physicalAcceptanceReceiptId !== input.generationReceiptId
61
+ || memory.canonicalSha256 !== input.canonicalSha256 || memory.projectionSha256 !== input.projectionSha256
62
+ || memory.fileCount !== input.fileCount
63
+ || spawnSync('git', ['merge-base', '--is-ancestor', input.sourceHead, 'HEAD'], { cwd: root, stdio: 'ignore' }).status !== 0)
64
+ throw fail();
65
+ const fields = { schemaVersion: 'PlanningMemoryPhysicalAcceptance/1',
66
+ claimId: input.claimId, epicId: input.epicId, sourceHead: input.sourceHead,
67
+ generationReceiptId: input.generationReceiptId, stateSha256: generation.stateSha256,
68
+ canonicalSha256: input.canonicalSha256, projectionSha256: input.projectionSha256,
69
+ fileCount: input.fileCount, qaEvidenceId: input.qaEvidenceId, artifactSummary: input.artifactSummary };
70
+ return { ...fields, receiptId: digest(JSON.stringify(fields)), publicSafe: true };
71
+ }
72
+ /** Only a typed independent root review can create this receipt. No canonical state is rewritten. */
73
+ export function sealPlanningMemory(projectRoot, input) {
74
+ review(input);
75
+ const root = resolve(projectRoot), path = receiptPath(input.epicId);
76
+ verifyCommittedPlanningGeneration(root, input.claimId, input.epicId);
77
+ const lockPath = join(root, '.neocortex/state.json.planning.lock'), lock = openSync(lockPath, 'wx', 0o600);
78
+ try {
79
+ const pending = planningPendingSidecarDigest(root), result = derive(root, input);
80
+ if (existsSync(join(root, path))) {
81
+ const entry = lstatSync(join(root, path));
82
+ if (!entry.isFile() || entry.isSymbolicLink() || entry.size > 16384
83
+ || !isDeepStrictEqual(JSON.parse(readFileSync(join(root, path), 'utf8')), result))
84
+ throw fail();
85
+ return result;
86
+ }
87
+ if (git(root, 'rev-parse', 'HEAD') !== input.sourceHead || git(root, 'diff', '--name-only')
88
+ || git(root, 'diff', '--cached', '--name-only'))
89
+ throw fail();
90
+ const untracked = git(root, 'ls-files', '--others', '--exclude-standard').split('\n').filter(Boolean);
91
+ if (untracked.some(path => path !== '.neocortex/pending-epic-claims.json' && path !== '.neocortex/state.json.planning.lock'))
92
+ throw fail();
93
+ if (planningPendingSidecarDigest(root) !== pending || !isDeepStrictEqual(derive(root, input), result))
94
+ throw fail();
95
+ const fd = openSync(join(root, path), 'wx', 0o600);
96
+ try {
97
+ writeFileSync(fd, `${JSON.stringify(result, null, 2)}\n`);
98
+ fsyncSync(fd);
99
+ }
100
+ finally {
101
+ closeSync(fd);
102
+ }
103
+ const directory = openSync(join(root, '.neocortex/planning', `epic-${input.epicId}`), 'r');
104
+ try {
105
+ fsyncSync(directory);
106
+ }
107
+ finally {
108
+ closeSync(directory);
109
+ }
110
+ return result;
111
+ }
112
+ finally {
113
+ closeSync(lock);
114
+ unlinkSync(lockPath);
115
+ }
116
+ }
117
+ /** Fresh read-only physical verification; metadata, a digest or an old receipt is insufficient. */
118
+ export function verifyCommittedPlanningMemory(projectRoot, claimId, epicId) {
119
+ if (!/^P[1-9][0-9]{0,11}$/.test(epicId))
120
+ throw fail();
121
+ const root = resolve(projectRoot), path = receiptPath(epicId), entry = lstatSync(join(root, path));
122
+ if (!entry.isFile() || entry.isSymbolicLink() || entry.size > 16384)
123
+ throw fail();
124
+ const bytes = readFileSync(join(root, path));
125
+ if (!bytes.equals(committed(root, path)))
126
+ throw fail();
127
+ const receipt = JSON.parse(bytes.toString('utf8'));
128
+ if (!record(receipt) || receipt.claimId !== claimId || receipt.epicId !== epicId)
129
+ throw fail();
130
+ const input = { schemaVersion: 'PlanningMemoryRootReview/1', verdict: 'accepted',
131
+ claimId, epicId, sourceHead: receipt.sourceHead, generationReceiptId: receipt.generationReceiptId,
132
+ canonicalSha256: receipt.canonicalSha256, projectionSha256: receipt.projectionSha256,
133
+ fileCount: receipt.fileCount, qaEvidenceId: receipt.qaEvidenceId, artifactSummary: receipt.artifactSummary };
134
+ review(input);
135
+ const expected = derive(root, input);
136
+ if (!isDeepStrictEqual(receipt, expected))
137
+ throw fail();
138
+ return expected;
139
+ }
@@ -1,4 +1,325 @@
1
- import{execFileSync as W,spawnSync as X}from"node:child_process";import{createHash as K,randomUUID as Q}from"node:crypto";import{closeSync as D,fsyncSync as q,lstatSync as A,mkdirSync as Y,openSync as b,readFileSync as v,realpathSync as J,renameSync as ee,unlinkSync as F,writeFileSync as L}from"node:fs";import{isAbsolute as te,join as y,resolve as B}from"node:path";import{isDeepStrictEqual as P}from"node:util";import{createPlanningReceiptV1 as V}from"../../../shared/dist/planning/diagnosis-state.js";import{acceptedPlanningDiagnosisPredecessor as T,planningPendingSidecarDigest as C,planningPhysicalPatchDigest as ie}from"./planning-diagnosis-checkpoint.js";const l=/^[a-f0-9]{64}$/,ne=/^P[1-9][0-9]{0,11}$/,se=/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/,ce="acd455ed0e39d98b90fb57391db75e366e4c5604db043e03a89a20e33d643c3c",re="fa2194f89c01f07e225fd6f39e98e679f628e072239b6cd0af3ee527f0722518";function p(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function u(e){return K("sha256").update(e).digest("hex")}function _(e,t,i=128*1024){return W("git",[...t],{cwd:e,encoding:"utf8",stdio:["ignore","pipe","ignore"],maxBuffer:i}).trim()}function w(e,t,i){const n=A(y(e,t));if(!n.isFile()||n.isSymbolicLink()||n.size<1||n.size>i)throw new Error("planning-research-physical-artifact-invalid");return v(y(e,t))}function G(e,t){for(const i of[".neocortex",".neocortex/planning",`.neocortex/planning/epic-${t}`]){const n=A(y(e,i));if(!n.isDirectory()||n.isSymbolicLink()||n.uid!==process.getuid?.()||i===".neocortex"&&(n.mode&18)!==0)throw new Error("planning-research-directory-boundary-invalid")}}function M(e,t){return typeof e=="string"&&e.length<=128&&u(e)===t}function ae(e,t){const i=e.replace(/\r\n/g,`
2
- `),n=/^---\n([\s\S]{1,8192}?)\n---\n/.exec(i)?.[1];if(!n)return;const a=n.split(`
3
- `),r=c=>{const s=a.filter(o=>o.startsWith(`${c}:`));return s.length===1?s[0].slice(c.length+1).trim().replace(/^(?:"(.*)"|'(.*)')$/,"$1$2"):void 0},g=r("step");return r("epic_id")===t&&r("status")==="completed"&&M(g,re)&&i.slice(n.length+9).trim().length>0?g:void 0}function oe(e){if(!p(e)||Object.keys(e).sort().join("|")!==["schemaVersion","verdict","claimId","epicId","diagnosisReceiptId","intakeRevision","sourceHead","sourceBranch","sourceStateSha256","admission","artifactDigests","qaEvidenceId","artifactSummary"].sort().join("|")||e.schemaVersion!=="PlanningResearchRootReview/1"||e.verdict!=="accepted"||typeof e.claimId!="string"||!se.test(e.claimId)||typeof e.epicId!="string"||!ne.test(e.epicId)||typeof e.diagnosisReceiptId!="string"||!l.test(e.diagnosisReceiptId)||typeof e.intakeRevision!="string"||!l.test(e.intakeRevision)||typeof e.sourceHead!="string"||!/^[a-f0-9]{40}$/.test(e.sourceHead)||typeof e.sourceBranch!="string"||!/^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,127}$/.test(e.sourceBranch)||typeof e.sourceStateSha256!="string"||!l.test(e.sourceStateSha256)||!p(e.admission)||Object.keys(e.admission).sort().join("|")!==["schemaVersion","outcome","baseSha","executionBranch","changedPaths","patchSha256"].sort().join("|")||e.admission.schemaVersion!==1||e.admission.outcome!=="admitted-unverified"||e.admission.baseSha!==e.sourceHead||e.admission.executionBranch!==e.sourceBranch||typeof e.admission.patchSha256!="string"||!l.test(e.admission.patchSha256)||!p(e.artifactDigests)||Object.keys(e.artifactDigests).sort().join("|")!=="index|research"||typeof e.artifactDigests.index!="string"||!l.test(e.artifactDigests.index)||typeof e.artifactDigests.research!="string"||!l.test(e.artifactDigests.research)||typeof e.qaEvidenceId!="string"||!/^qa-[a-z0-9-]{8,80}$/.test(e.qaEvidenceId)||typeof e.artifactSummary!="string")throw new Error("planning-research-review-invalid");V({kind:"research",epicId:e.epicId,intakeRevision:e.intakeRevision,predecessorReceiptId:e.diagnosisReceiptId,artifactSummary:e.artifactSummary,accepted:!0})}function U(e,t){const i={schemaVersion:"PlanningResearchPhysicalAcceptance/1",claimId:e.claimId,epicId:e.epicId,diagnosisReceiptId:e.diagnosisReceiptId,researchReceiptId:t,intakeRevision:e.intakeRevision,qaEvidenceId:e.qaEvidenceId,sourceHead:e.sourceHead,sourceStateSha256:e.sourceStateSha256,admissionPatchSha256:e.admission.patchSha256,artifactDigests:e.artifactDigests};return{...i,receiptId:u(JSON.stringify(i)),publicSafe:!0}}function z(e,t,i,n){if(G(e,t.epicId),_(e,["rev-parse","HEAD"])!==t.sourceHead||_(e,["symbolic-ref","--quiet","--short","HEAD"])!==t.sourceBranch)throw new Error("planning-research-head-conflict");const a={research:`.neocortex/planning/epic-${t.epicId}/research.md`,index:".neocortex/planning/planning.json"};if(!Array.isArray(t.admission.changedPaths)||[...t.admission.changedPaths].sort().join("|")!==Object.values(a).sort().join("|")||i&&ie(e,a,n,!0)!==t.admission.patchSha256)throw new Error("planning-research-admission-patch-conflict");const r={research:w(e,a.research,256*1024),index:w(e,a.index,128*1024)},g=ae(r.research.toString("utf8"),t.epicId);if(u(r.research)!==t.artifactDigests.research||u(r.index)!==t.artifactDigests.index||!g)throw new Error("planning-research-artifact-conflict");const c=w(e,".neocortex/state.json",8*1024*1024),s=JSON.parse(c.toString("utf8")),o=T(s,t.claimId,t.epicId,!i);if(o.diagnosisReceiptId!==t.diagnosisReceiptId||o.intakeRevision!==t.intakeRevision||i&&u(c)!==t.sourceStateSha256)throw new Error("planning-research-state-conflict");const j=s.epics[t.epicId].createEpicPlanningPhysical.artifactDigests,I=`.neocortex/planning/epic-${t.epicId}`;for(const[x,$]of Object.entries({idea:`${I}/idea.json`,diagnosis:`${I}/diagnosis.md`,enrichment:`${I}/enriched-prompt.md`}))if(u(w(e,$,x==="idea"?32*1024:256*1024))!==j[x])throw new Error("planning-research-predecessor-artifact-drift");const R=X("git",["show",`HEAD:${a.index}`],{cwd:e,encoding:"buffer",maxBuffer:128*1024});if(R.status!==0||!Buffer.isBuffer(R.stdout)||u(R.stdout)!==j.index)throw new Error("planning-research-predecessor-index-drift");const m=JSON.parse(R.stdout.toString("utf8")),f=JSON.parse(r.index.toString("utf8"));if(!p(m)||!p(m.epics)||!p(f)||!p(f.epics))throw new Error("planning-research-index-invalid");const d=m.epics[t.epicId];if(!p(d)||d.id!==t.epicId||d.idea!==o.rawIntent||d.status!=="diagnosed"||!Array.isArray(d.steps_completed)||d.steps_completed.length!==1||!M(d.steps_completed[0],ce)||!P(d.output_files,{diagnosis:`${I}/diagnosis.md`}))throw new Error("planning-research-index-predecessor-invalid");const E={...m,last_scan:f.last_scan,epics:{...m.epics,[t.epicId]:{...d,status:"researched",steps_completed:[...d.steps_completed,g],output_files:{diagnosis:`${I}/diagnosis.md`,research:a.research}}}};if(typeof f.last_scan!="string"||!Number.isFinite(Date.parse(f.last_scan))||!P(f,E))throw new Error("planning-research-index-transition-invalid");const k=V({kind:"research",epicId:t.epicId,intakeRevision:t.intakeRevision,predecessorReceiptId:t.diagnosisReceiptId,artifactSummary:t.artifactSummary,accepted:!0});return{stateRaw:c,state:s,receipt:k}}function de(e,t){const i=z(e,t,!1),a=i.state.epics[t.epicId],r=a.createEpicPlanning;if(a.status!=="researched"||!P(r.researchReceipt,i.receipt)||!P(a.createEpicPlanningResearchPhysical,U(t,i.receipt.receiptId)))throw new Error("planning-research-replay-conflict");return i.receipt.receiptId}function Ie(e,t,i){const n=T(e,t,i,!0),r=e.epics[i],g=p(r?.createEpicPlanning)?r.createEpicPlanning:void 0,c=p(g?.researchReceipt)?g.researchReceipt:void 0,s=p(r?.createEpicPlanningResearchPhysical)?r.createEpicPlanningResearchPhysical:void 0,o=p(s?.artifactDigests)?s.artifactDigests:void 0;if(r?.status!=="researched"||!c||!s||!o||typeof c.artifactSummary!="string"||!P(c,V({kind:"research",epicId:i,intakeRevision:n.intakeRevision,predecessorReceiptId:n.diagnosisReceiptId,artifactSummary:c.artifactSummary,accepted:!0}))||s.schemaVersion!=="PlanningResearchPhysicalAcceptance/1"||s.claimId!==t||s.epicId!==i||s.diagnosisReceiptId!==n.diagnosisReceiptId||s.researchReceiptId!==c.receiptId||s.intakeRevision!==n.intakeRevision||typeof s.qaEvidenceId!="string"||!/^qa-[a-z0-9-]{8,80}$/.test(s.qaEvidenceId)||typeof s.sourceHead!="string"||!/^[a-f0-9]{40}$/.test(s.sourceHead)||typeof s.sourceStateSha256!="string"||!l.test(s.sourceStateSha256)||typeof s.admissionPatchSha256!="string"||!l.test(s.admissionPatchSha256)||Object.keys(o).sort().join("|")!=="index|research"||typeof o.index!="string"||!l.test(o.index)||typeof o.research!="string"||!l.test(o.research))throw new Error("planning-generation-predecessor-invalid");const h={schemaVersion:s.schemaVersion,claimId:t,epicId:i,diagnosisReceiptId:n.diagnosisReceiptId,researchReceiptId:c.receiptId,intakeRevision:n.intakeRevision,qaEvidenceId:s.qaEvidenceId,sourceHead:s.sourceHead,sourceStateSha256:s.sourceStateSha256,admissionPatchSha256:s.admissionPatchSha256,artifactDigests:o},S={...h,receiptId:u(JSON.stringify(h)),publicSafe:!0};if(!P(s,S))throw new Error("planning-generation-predecessor-invalid");return{intakeRevision:n.intakeRevision,diagnosisReceiptId:n.diagnosisReceiptId,researchReceiptId:c.receiptId,physicalAcceptanceReceiptId:S.receiptId}}function Se(e,t){oe(t);const i=t,n=B(e);if(J(n)!==n||_(n,["rev-parse","--show-toplevel"])!==n)throw new Error("planning-research-root-invalid");G(n,i.epicId);const a=y(n,".neocortex"),r=y(a,"state.json.planning.lock"),g=b(r,"wx",384);let c;try{const s=C(n),o=w(n,".neocortex/state.json",8*1024*1024);if(u(o)!==i.sourceStateSha256){const Z=de(n,i);return{epicId:i.epicId,researchReceiptId:Z,qaEvidenceId:i.qaEvidenceId}}const h=z(n,i,!0),S=h.state.epics,H=S[i.epicId],j=H.createEpicPlanning,I=U(i,h.receipt.receiptId),R={...h.state,epics:{...S,[i.epicId]:{...H,status:"researched",createEpicPlanning:{...j,researchReceipt:h.receipt},createEpicPlanningResearchPhysical:I}}},m=_(n,["rev-parse","--git-common-dir"]),f=te(m)?B(m):B(n,m);if(J(f)!==f||A(f).isSymbolicLink())throw new Error("planning-research-backup-dir-invalid");const d=y(f,"neocortex-planning");Y(d,{recursive:!0,mode:448});const E=A(d);if(!E.isDirectory()||E.isSymbolicLink()||E.uid!==process.getuid?.()||(E.mode&63)!==0)throw new Error("planning-research-backup-dir-invalid");const k=Q(),x=b(y(d,`state-backup-${k}.json`),"wx",384);try{L(x,h.stateRaw),q(x)}finally{D(x)}const $=b(d,"r");try{q($)}finally{D($)}c=y(a,`state.json.planning-tmp-${k}`);const O=b(c,"wx",384);try{L(O,`${JSON.stringify(R,null,2)}
4
- `),q(O)}finally{D(O)}if(!z(n,i,!0,`.neocortex/state.json.planning-tmp-${k}`).stateRaw.equals(h.stateRaw))throw new Error("planning-research-state-conflict");if(C(n)!==s)throw new Error("planning-research-pending-conflict");ee(c,y(a,"state.json")),c=void 0;const N=b(a,"r");try{q(N)}finally{D(N)}return{epicId:i.epicId,researchReceiptId:h.receipt.receiptId,qaEvidenceId:i.qaEvidenceId}}finally{if(c)try{F(c)}catch{}D(g),F(r)}}export{Ie as acceptedPlanningResearchPredecessor,Se as sealPlanningResearch};
1
+ /** Root-owned P02 transition after an independently reviewed isolated research candidate. */
2
+ import { execFileSync, spawnSync } from 'node:child_process';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
+ import { closeSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
5
+ import { isAbsolute, join, resolve } from 'node:path';
6
+ import { isDeepStrictEqual } from 'node:util';
7
+ import { createPlanningReceiptV1 } from '../../../shared/dist/planning/diagnosis-state.js';
8
+ import { acceptedPlanningDiagnosisPredecessor, planningPendingSidecarDigest, planningPhysicalPatchDigest } from './planning-diagnosis-checkpoint.js';
9
+ const HEX = /^[a-f0-9]{64}$/;
10
+ const EPIC = /^P[1-9][0-9]{0,11}$/;
11
+ const CLAIM = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
12
+ const P01_STEP_DIGEST = 'acd455ed0e39d98b90fb57391db75e366e4c5604db043e03a89a20e33d643c3c';
13
+ const P02_STEP_DIGEST = 'fa2194f89c01f07e225fd6f39e98e679f628e072239b6cd0af3ee527f0722518';
14
+ function record(value) {
15
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
16
+ }
17
+ function digest(value) {
18
+ return createHash('sha256').update(value).digest('hex');
19
+ }
20
+ function git(root, args, maxBuffer = 128 * 1024) {
21
+ return execFileSync('git', [...args], { cwd: root, encoding: 'utf8',
22
+ stdio: ['ignore', 'pipe', 'ignore'], maxBuffer }).trim();
23
+ }
24
+ function regularBytes(root, path, maxBytes) {
25
+ const entry = lstatSync(join(root, path));
26
+ if (!entry.isFile() || entry.isSymbolicLink() || entry.size < 1 || entry.size > maxBytes) {
27
+ throw new Error('planning-research-physical-artifact-invalid');
28
+ }
29
+ return readFileSync(join(root, path));
30
+ }
31
+ function safePlanningParents(root, epicId) {
32
+ for (const relativePath of ['.neocortex', '.neocortex/planning',
33
+ `.neocortex/planning/epic-${epicId}`]) {
34
+ const entry = lstatSync(join(root, relativePath));
35
+ if (!entry.isDirectory() || entry.isSymbolicLink()
36
+ || entry.uid !== process.getuid?.()
37
+ || relativePath === '.neocortex' && (entry.mode & 0o022) !== 0) {
38
+ throw new Error('planning-research-directory-boundary-invalid');
39
+ }
40
+ }
41
+ }
42
+ function stepMatches(value, expected) {
43
+ return typeof value === 'string' && value.length <= 128 && digest(value) === expected;
44
+ }
45
+ function researchDocumentStep(content, epicId) {
46
+ const normalized = content.replace(/\r\n/g, '\n');
47
+ const header = /^---\n([\s\S]{1,8192}?)\n---\n/.exec(normalized)?.[1];
48
+ if (!header)
49
+ return undefined;
50
+ const lines = header.split('\n');
51
+ const scalar = (key) => {
52
+ const found = lines.filter((line) => line.startsWith(`${key}:`));
53
+ return found.length === 1
54
+ ? found[0].slice(key.length + 1).trim().replace(/^(?:"(.*)"|'(.*)')$/, '$1$2')
55
+ : undefined;
56
+ };
57
+ const step = scalar('step');
58
+ return scalar('epic_id') === epicId && scalar('status') === 'completed'
59
+ && stepMatches(step, P02_STEP_DIGEST)
60
+ && normalized.slice(header.length + 9).trim().length > 0 ? step : undefined;
61
+ }
62
+ function validateReview(value) {
63
+ if (!record(value) || Object.keys(value).sort().join('|') !== [
64
+ 'schemaVersion', 'verdict', 'claimId', 'epicId', 'diagnosisReceiptId',
65
+ 'intakeRevision', 'sourceHead', 'sourceBranch', 'sourceStateSha256',
66
+ 'admission', 'artifactDigests', 'qaEvidenceId', 'artifactSummary',
67
+ ].sort().join('|') || value.schemaVersion !== 'PlanningResearchRootReview/1'
68
+ || value.verdict !== 'accepted' || typeof value.claimId !== 'string' || !CLAIM.test(value.claimId)
69
+ || typeof value.epicId !== 'string' || !EPIC.test(value.epicId)
70
+ || typeof value.diagnosisReceiptId !== 'string' || !HEX.test(value.diagnosisReceiptId)
71
+ || typeof value.intakeRevision !== 'string' || !HEX.test(value.intakeRevision)
72
+ || typeof value.sourceHead !== 'string' || !/^[a-f0-9]{40}$/.test(value.sourceHead)
73
+ || typeof value.sourceBranch !== 'string'
74
+ || !/^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,127}$/.test(value.sourceBranch)
75
+ || typeof value.sourceStateSha256 !== 'string' || !HEX.test(value.sourceStateSha256)
76
+ || !record(value.admission) || Object.keys(value.admission).sort().join('|') !== [
77
+ 'schemaVersion', 'outcome', 'baseSha', 'executionBranch', 'changedPaths', 'patchSha256',
78
+ ].sort().join('|')
79
+ || value.admission.schemaVersion !== 1 || value.admission.outcome !== 'admitted-unverified'
80
+ || value.admission.baseSha !== value.sourceHead
81
+ || value.admission.executionBranch !== value.sourceBranch
82
+ || typeof value.admission.patchSha256 !== 'string' || !HEX.test(value.admission.patchSha256)
83
+ || !record(value.artifactDigests)
84
+ || Object.keys(value.artifactDigests).sort().join('|') !== 'index|research'
85
+ || typeof value.artifactDigests.index !== 'string' || !HEX.test(value.artifactDigests.index)
86
+ || typeof value.artifactDigests.research !== 'string' || !HEX.test(value.artifactDigests.research)
87
+ || typeof value.qaEvidenceId !== 'string' || !/^qa-[a-z0-9-]{8,80}$/.test(value.qaEvidenceId)
88
+ || typeof value.artifactSummary !== 'string') {
89
+ throw new Error('planning-research-review-invalid');
90
+ }
91
+ createPlanningReceiptV1({ kind: 'research', epicId: value.epicId,
92
+ intakeRevision: value.intakeRevision, predecessorReceiptId: value.diagnosisReceiptId,
93
+ artifactSummary: value.artifactSummary, accepted: true });
94
+ }
95
+ function physicalAcceptance(review, researchReceiptId) {
96
+ const fields = { schemaVersion: 'PlanningResearchPhysicalAcceptance/1',
97
+ claimId: review.claimId, epicId: review.epicId,
98
+ diagnosisReceiptId: review.diagnosisReceiptId, researchReceiptId,
99
+ intakeRevision: review.intakeRevision, qaEvidenceId: review.qaEvidenceId,
100
+ sourceHead: review.sourceHead, sourceStateSha256: review.sourceStateSha256,
101
+ admissionPatchSha256: review.admission.patchSha256,
102
+ artifactDigests: review.artifactDigests };
103
+ return { ...fields, receiptId: digest(JSON.stringify(fields)), publicSafe: true };
104
+ }
105
+ function evidence(root, review, checkPatch, ownTemporaryPath) {
106
+ safePlanningParents(root, review.epicId);
107
+ if (git(root, ['rev-parse', 'HEAD']) !== review.sourceHead
108
+ || git(root, ['symbolic-ref', '--quiet', '--short', 'HEAD']) !== review.sourceBranch) {
109
+ throw new Error('planning-research-head-conflict');
110
+ }
111
+ const paths = { research: `.neocortex/planning/epic-${review.epicId}/research.md`,
112
+ index: '.neocortex/planning/planning.json' };
113
+ if (!Array.isArray(review.admission.changedPaths)
114
+ || [...review.admission.changedPaths].sort().join('|') !== Object.values(paths).sort().join('|')
115
+ || checkPatch && planningPhysicalPatchDigest(root, paths, ownTemporaryPath, true)
116
+ !== review.admission.patchSha256) {
117
+ throw new Error('planning-research-admission-patch-conflict');
118
+ }
119
+ const current = { research: regularBytes(root, paths.research, 256 * 1024),
120
+ index: regularBytes(root, paths.index, 128 * 1024) };
121
+ const researchStep = researchDocumentStep(current.research.toString('utf8'), review.epicId);
122
+ if (digest(current.research) !== review.artifactDigests.research
123
+ || digest(current.index) !== review.artifactDigests.index
124
+ || !researchStep) {
125
+ throw new Error('planning-research-artifact-conflict');
126
+ }
127
+ const stateRaw = regularBytes(root, '.neocortex/state.json', 8 * 1024 * 1024);
128
+ const state = JSON.parse(stateRaw.toString('utf8'));
129
+ const predecessor = acceptedPlanningDiagnosisPredecessor(state, review.claimId, review.epicId, !checkPatch);
130
+ if (predecessor.diagnosisReceiptId !== review.diagnosisReceiptId
131
+ || predecessor.intakeRevision !== review.intakeRevision
132
+ || checkPatch && digest(stateRaw) !== review.sourceStateSha256) {
133
+ throw new Error('planning-research-state-conflict');
134
+ }
135
+ const epics = state.epics;
136
+ const epic = epics[review.epicId];
137
+ const physical = epic.createEpicPlanningPhysical;
138
+ const acceptedDigests = physical.artifactDigests;
139
+ const prefix = `.neocortex/planning/epic-${review.epicId}`;
140
+ for (const [name, path] of Object.entries({ idea: `${prefix}/idea.json`,
141
+ diagnosis: `${prefix}/diagnosis.md`, enrichment: `${prefix}/enriched-prompt.md` })) {
142
+ if (digest(regularBytes(root, path, name === 'idea' ? 32 * 1024 : 256 * 1024))
143
+ !== acceptedDigests[name])
144
+ throw new Error('planning-research-predecessor-artifact-drift');
145
+ }
146
+ const baseline = spawnSync('git', ['show', `HEAD:${paths.index}`], { cwd: root,
147
+ encoding: 'buffer', maxBuffer: 128 * 1024 });
148
+ if (baseline.status !== 0 || !Buffer.isBuffer(baseline.stdout)
149
+ || digest(baseline.stdout) !== acceptedDigests.index) {
150
+ throw new Error('planning-research-predecessor-index-drift');
151
+ }
152
+ const prior = JSON.parse(baseline.stdout.toString('utf8'));
153
+ const next = JSON.parse(current.index.toString('utf8'));
154
+ if (!record(prior) || !record(prior.epics) || !record(next) || !record(next.epics)) {
155
+ throw new Error('planning-research-index-invalid');
156
+ }
157
+ const priorEpic = prior.epics[review.epicId];
158
+ if (!record(priorEpic) || priorEpic.id !== review.epicId
159
+ || priorEpic.idea !== predecessor.rawIntent || priorEpic.status !== 'diagnosed'
160
+ || !Array.isArray(priorEpic.steps_completed) || priorEpic.steps_completed.length !== 1
161
+ || !stepMatches(priorEpic.steps_completed[0], P01_STEP_DIGEST)
162
+ || !isDeepStrictEqual(priorEpic.output_files, { diagnosis: `${prefix}/diagnosis.md` })) {
163
+ throw new Error('planning-research-index-predecessor-invalid');
164
+ }
165
+ const expected = { ...prior, last_scan: next.last_scan, epics: { ...prior.epics,
166
+ [review.epicId]: { ...priorEpic, status: 'researched',
167
+ steps_completed: [...priorEpic.steps_completed, researchStep],
168
+ output_files: { diagnosis: `${prefix}/diagnosis.md`, research: paths.research } } } };
169
+ if (typeof next.last_scan !== 'string' || !Number.isFinite(Date.parse(next.last_scan))
170
+ || !isDeepStrictEqual(next, expected)) {
171
+ throw new Error('planning-research-index-transition-invalid');
172
+ }
173
+ const receipt = createPlanningReceiptV1({ kind: 'research', epicId: review.epicId,
174
+ intakeRevision: review.intakeRevision, predecessorReceiptId: review.diagnosisReceiptId,
175
+ artifactSummary: review.artifactSummary, accepted: true });
176
+ return { stateRaw, state, receipt };
177
+ }
178
+ function acceptedReplay(root, review) {
179
+ const current = evidence(root, review, false);
180
+ const epics = current.state.epics;
181
+ const epic = epics[review.epicId];
182
+ const checkpoint = epic.createEpicPlanning;
183
+ if (epic.status !== 'researched' || !isDeepStrictEqual(checkpoint.researchReceipt, current.receipt)
184
+ || !isDeepStrictEqual(epic.createEpicPlanningResearchPhysical, physicalAcceptance(review, current.receipt.receiptId))) {
185
+ throw new Error('planning-research-replay-conflict');
186
+ }
187
+ return current.receipt.receiptId;
188
+ }
189
+ /** State-consistent P02 seal for a future P03 lease; physical files remain a separate check. */
190
+ export function acceptedPlanningResearchPredecessor(stateInput, claimId, epicId) {
191
+ const diagnosis = acceptedPlanningDiagnosisPredecessor(stateInput, claimId, epicId, true);
192
+ const state = stateInput;
193
+ const epic = state.epics[epicId];
194
+ const checkpoint = record(epic?.createEpicPlanning) ? epic.createEpicPlanning : undefined;
195
+ const receipt = record(checkpoint?.researchReceipt) ? checkpoint.researchReceipt : undefined;
196
+ const physical = record(epic?.createEpicPlanningResearchPhysical)
197
+ ? epic.createEpicPlanningResearchPhysical : undefined;
198
+ const digests = record(physical?.artifactDigests) ? physical.artifactDigests : undefined;
199
+ if (epic?.status !== 'researched' || !receipt || !physical || !digests
200
+ || typeof receipt.artifactSummary !== 'string'
201
+ || !isDeepStrictEqual(receipt, createPlanningReceiptV1({ kind: 'research', epicId,
202
+ intakeRevision: diagnosis.intakeRevision,
203
+ predecessorReceiptId: diagnosis.diagnosisReceiptId,
204
+ artifactSummary: receipt.artifactSummary, accepted: true }))
205
+ || physical.schemaVersion !== 'PlanningResearchPhysicalAcceptance/1'
206
+ || physical.claimId !== claimId || physical.epicId !== epicId
207
+ || physical.diagnosisReceiptId !== diagnosis.diagnosisReceiptId
208
+ || physical.researchReceiptId !== receipt.receiptId
209
+ || physical.intakeRevision !== diagnosis.intakeRevision
210
+ || typeof physical.qaEvidenceId !== 'string'
211
+ || !/^qa-[a-z0-9-]{8,80}$/.test(physical.qaEvidenceId)
212
+ || typeof physical.sourceHead !== 'string' || !/^[a-f0-9]{40}$/.test(physical.sourceHead)
213
+ || typeof physical.sourceStateSha256 !== 'string' || !HEX.test(physical.sourceStateSha256)
214
+ || typeof physical.admissionPatchSha256 !== 'string' || !HEX.test(physical.admissionPatchSha256)
215
+ || Object.keys(digests).sort().join('|') !== 'index|research'
216
+ || typeof digests.index !== 'string' || !HEX.test(digests.index)
217
+ || typeof digests.research !== 'string' || !HEX.test(digests.research)) {
218
+ throw new Error('planning-generation-predecessor-invalid');
219
+ }
220
+ const fields = { schemaVersion: physical.schemaVersion, claimId, epicId,
221
+ diagnosisReceiptId: diagnosis.diagnosisReceiptId, researchReceiptId: receipt.receiptId,
222
+ intakeRevision: diagnosis.intakeRevision, qaEvidenceId: physical.qaEvidenceId,
223
+ sourceHead: physical.sourceHead, sourceStateSha256: physical.sourceStateSha256,
224
+ admissionPatchSha256: physical.admissionPatchSha256, artifactDigests: digests };
225
+ const expected = { ...fields, receiptId: digest(JSON.stringify(fields)), publicSafe: true };
226
+ if (!isDeepStrictEqual(physical, expected))
227
+ throw new Error('planning-generation-predecessor-invalid');
228
+ return { intakeRevision: diagnosis.intakeRevision,
229
+ diagnosisReceiptId: diagnosis.diagnosisReceiptId, researchReceiptId: receipt.receiptId,
230
+ physicalAcceptanceReceiptId: expected.receiptId };
231
+ }
232
+ /** A root reviewer accepts exact P02 files, then this CAS persists only the research checkpoint. */
233
+ export function sealPlanningResearch(projectRoot, reviewInput) {
234
+ validateReview(reviewInput);
235
+ const review = reviewInput;
236
+ const root = resolve(projectRoot);
237
+ if (realpathSync(root) !== root || git(root, ['rev-parse', '--show-toplevel']) !== root) {
238
+ throw new Error('planning-research-root-invalid');
239
+ }
240
+ safePlanningParents(root, review.epicId);
241
+ const stateDir = join(root, '.neocortex');
242
+ const lockPath = join(stateDir, 'state.json.planning.lock');
243
+ const lock = openSync(lockPath, 'wx', 0o600);
244
+ let temporaryPath;
245
+ try {
246
+ const pendingDigest = planningPendingSidecarDigest(root);
247
+ const currentRaw = regularBytes(root, '.neocortex/state.json', 8 * 1024 * 1024);
248
+ if (digest(currentRaw) !== review.sourceStateSha256) {
249
+ const researchReceiptId = acceptedReplay(root, review);
250
+ return { epicId: review.epicId, researchReceiptId, qaEvidenceId: review.qaEvidenceId };
251
+ }
252
+ const before = evidence(root, review, true);
253
+ const epics = before.state.epics;
254
+ const epic = epics[review.epicId];
255
+ const checkpoint = epic.createEpicPlanning;
256
+ const acceptance = physicalAcceptance(review, before.receipt.receiptId);
257
+ const next = { ...before.state, epics: { ...epics,
258
+ [review.epicId]: { ...epic, status: 'researched',
259
+ createEpicPlanning: { ...checkpoint, researchReceipt: before.receipt },
260
+ createEpicPlanningResearchPhysical: acceptance } } };
261
+ const commonRaw = git(root, ['rev-parse', '--git-common-dir']);
262
+ const commonDir = isAbsolute(commonRaw) ? resolve(commonRaw) : resolve(root, commonRaw);
263
+ if (realpathSync(commonDir) !== commonDir || lstatSync(commonDir).isSymbolicLink()) {
264
+ throw new Error('planning-research-backup-dir-invalid');
265
+ }
266
+ const backupDir = join(commonDir, 'neocortex-planning');
267
+ mkdirSync(backupDir, { recursive: true, mode: 0o700 });
268
+ const backupEntry = lstatSync(backupDir);
269
+ if (!backupEntry.isDirectory() || backupEntry.isSymbolicLink()
270
+ || backupEntry.uid !== process.getuid?.() || (backupEntry.mode & 0o077) !== 0) {
271
+ throw new Error('planning-research-backup-dir-invalid');
272
+ }
273
+ const suffix = randomUUID();
274
+ const backup = openSync(join(backupDir, `state-backup-${suffix}.json`), 'wx', 0o600);
275
+ try {
276
+ writeFileSync(backup, before.stateRaw);
277
+ fsyncSync(backup);
278
+ }
279
+ finally {
280
+ closeSync(backup);
281
+ }
282
+ const backupDirectory = openSync(backupDir, 'r');
283
+ try {
284
+ fsyncSync(backupDirectory);
285
+ }
286
+ finally {
287
+ closeSync(backupDirectory);
288
+ }
289
+ temporaryPath = join(stateDir, `state.json.planning-tmp-${suffix}`);
290
+ const temporary = openSync(temporaryPath, 'wx', 0o600);
291
+ try {
292
+ writeFileSync(temporary, `${JSON.stringify(next, null, 2)}\n`);
293
+ fsyncSync(temporary);
294
+ }
295
+ finally {
296
+ closeSync(temporary);
297
+ }
298
+ const recheck = evidence(root, review, true, `.neocortex/state.json.planning-tmp-${suffix}`);
299
+ if (!recheck.stateRaw.equals(before.stateRaw))
300
+ throw new Error('planning-research-state-conflict');
301
+ if (planningPendingSidecarDigest(root) !== pendingDigest) {
302
+ throw new Error('planning-research-pending-conflict');
303
+ }
304
+ renameSync(temporaryPath, join(stateDir, 'state.json'));
305
+ temporaryPath = undefined;
306
+ const stateDirectory = openSync(stateDir, 'r');
307
+ try {
308
+ fsyncSync(stateDirectory);
309
+ }
310
+ finally {
311
+ closeSync(stateDirectory);
312
+ }
313
+ return { epicId: review.epicId, researchReceiptId: before.receipt.receiptId,
314
+ qaEvidenceId: review.qaEvidenceId };
315
+ }
316
+ finally {
317
+ if (temporaryPath)
318
+ try {
319
+ unlinkSync(temporaryPath);
320
+ }
321
+ catch { /* preserve primary failure */ }
322
+ closeSync(lock);
323
+ unlinkSync(lockPath);
324
+ }
325
+ }
@@ -1 +1,42 @@
1
- import{createHash as f}from"node:crypto";import{existsSync as m,readFileSync as s}from"node:fs";import{join as h}from"node:path";import{assertGitLifecyclePrivateFile as l,ensureGitLifecyclePrivateChildDirectory as u,gitLifecycleRuntimeDirectory as p}from"../git/git-lifecycle-store.js";import{anchoredAtomicWriteFile as v}from"../git/secure-filesystem-boundary.js";async function x(r){try{if(r.format!=="markdown"&&r.format!=="json"||typeof r.material!="string"||!/^[a-f0-9]{64}$/.test(r.sha256))throw new Error("private-report-input-invalid");const e=Buffer.from(r.material,"utf8");if(e.length===0||e.length>1024*1024||f("sha256").update(e).digest("hex")!==r.sha256)throw new Error("private-report-digest-invalid");if(r.format==="json"){const i=JSON.parse(r.material);if(!i||typeof i!="object"||Array.isArray(i))throw new Error("private-report-json-invalid")}const c=await p(r.projectRoot),a=await u(c,"private-ui-ux-reports"),o=`${r.sha256}.${r.format==="json"?"json":"md"}`,t=h(a,o),n=m(t);if(n||await v(a,o,e),await l(t,a),!s(t).equals(e))throw new Error("private-report-readback-invalid");return Object.freeze({schemaVersion:1,kind:"private-ui-ux-review-report",format:r.format,sha256:r.sha256,byteLength:e.length,path:t,reused:n,publicSafe:!0})}catch{throw new Error("private-execution-report-store-unverified")}}export{x as storePrivateUiUxReviewReport};
1
+ /** Owner-private, content-addressed delivery for a root-owned review report. */
2
+ import { createHash } from 'node:crypto';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { assertGitLifecyclePrivateFile, ensureGitLifecyclePrivateChildDirectory, gitLifecycleRuntimeDirectory } from '../git/git-lifecycle-store.js';
6
+ import { anchoredAtomicWriteFile } from '../git/secure-filesystem-boundary.js';
7
+ /** The returned path is local-only; callers must never place report bytes in public metadata. */
8
+ export async function storePrivateUiUxReviewReport(options) {
9
+ try {
10
+ if (options.format !== 'markdown' && options.format !== 'json'
11
+ || typeof options.material !== 'string'
12
+ || !/^[a-f0-9]{64}$/.test(options.sha256))
13
+ throw new Error('private-report-input-invalid');
14
+ const bytes = Buffer.from(options.material, 'utf8');
15
+ if (bytes.length === 0 || bytes.length > 1024 * 1024
16
+ || createHash('sha256').update(bytes).digest('hex') !== options.sha256) {
17
+ throw new Error('private-report-digest-invalid');
18
+ }
19
+ if (options.format === 'json') {
20
+ const value = JSON.parse(options.material);
21
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
22
+ throw new Error('private-report-json-invalid');
23
+ }
24
+ }
25
+ const runtime = await gitLifecycleRuntimeDirectory(options.projectRoot);
26
+ const parent = await ensureGitLifecyclePrivateChildDirectory(runtime, 'private-ui-ux-reports');
27
+ const name = `${options.sha256}.${options.format === 'json' ? 'json' : 'md'}`;
28
+ const path = join(parent, name);
29
+ const reused = existsSync(path);
30
+ if (!reused)
31
+ await anchoredAtomicWriteFile(parent, name, bytes);
32
+ await assertGitLifecyclePrivateFile(path, parent);
33
+ if (!readFileSync(path).equals(bytes))
34
+ throw new Error('private-report-readback-invalid');
35
+ return Object.freeze({ schemaVersion: 1, kind: 'private-ui-ux-review-report',
36
+ format: options.format, sha256: options.sha256, byteLength: bytes.length,
37
+ path, reused, publicSafe: true });
38
+ }
39
+ catch {
40
+ throw new Error('private-execution-report-store-unverified');
41
+ }
42
+ }