@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,9 +1,1981 @@
1
- import{execFile as pi,execFileSync as M}from"node:child_process";import{createHash as w,randomBytes as De}from"node:crypto";import{lstat as Re,open as mi,readFile as F,realpath as X,stat as yi}from"node:fs/promises";import{existsSync as H,lstatSync as ze,readFileSync as He,realpathSync as _e}from"node:fs";import{basename as C,dirname as K,isAbsolute as ae,join as S,relative as bi,resolve as v}from"node:path";import{promisify as wi}from"node:util";import{publicWriteSet as vi}from"../../../shared/dist/yoloop/public-story-write-set.js";import{isRootProjectMemoryEffect as Si}from"../private-execution/shared-root-memory-types.js";import{verifyRootMemoryPhysicalWriteSet as Di}from"../private-execution/root-memory-git-evidence.js";import{resolveBranchUnit as Ri}from"../../../shared/dist/yoloop/branch-unit.js";import{anchoredAtomicWriteFile as _i,anchoredCreateOwnedDirectory as je,anchoredEnsureDirectory as Ge,anchoredExecuteGit as Ei,anchoredExecuteNode as Te,anchoredRemoveDirectory as xi,anchoredRenameDirectory as re}from"./secure-filesystem-boundary.js";import{GIT_LIFECYCLE_COMMIT_PUSH_STEP_ID as Ii,GIT_LIFECYCLE_REVIEW_PR_STEP_ID as Ai,GIT_LIFECYCLE_SETUP_BRANCH_STEP_ID as Ee}from"./git-lifecycle-step-ids.js";const V=wi(pi),le="neocortex-yoloop",he="runtime.json",q="node scripts/test-root.mjs --gate complete",Me=Object.freeze({"P174.03.029264":"339141be7339a0edf55710cbccb6e6558f514792"}),ki=Object.freeze({"P174.03.811759":Object.freeze({baseSha:"749c8fc732deaf26c81a8e08cc94b83c8cf0f5ba",headSha:"84162456d9cbe5000ffd0ed3eb3d0e88cfeba499",graphRevision:"60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445",effectiveClaimRevision:"11f54704f5007f6139159419cfd7781f908b1502e117365c6d84011384579ed2",commitBodyDigest:"b4fc759a49765fd8c0b3d0f5d1c333c2a4bf28d324e83eedabb12f9530df3738",changedPathsDigest:"11f54704f5007f6139159419cfd7781f908b1502e117365c6d84011384579ed2",qaDigest:"f1100775e21f5d0d990f77303a2a06d936908f016983e32a63316d81aee6b9fc",reviewDigest:"50400bbcddb6f02408837cf99077de5d3af8c84822ab23a8b5499c338af7103a"}),"P174.03.685003":Object.freeze({baseSha:"84162456d9cbe5000ffd0ed3eb3d0e88cfeba499",headSha:"272a7fee89e4b9324d35701df1c83b671fa96299",graphRevision:"60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445",effectiveClaimRevision:"6cc3c9033f1c4711dcbe3c676d1d7f9906614c7d6e95228e843d1c63cefbc641",commitBodyDigest:"e97966e26ce3ba31d5951b9fdac67808c88c1fd8be7ec1e73982f3f1cc7ffd81",changedPathsDigest:"6cc3c9033f1c4711dcbe3c676d1d7f9906614c7d6e95228e843d1c63cefbc641",qaDigest:"7439329a93321f39fb6b0de0a41c22e58373a399f9048e362dff4ffa6f1a7d89",reviewDigest:"1b2d12262e84acf9da7d06149a0effb11274ebdc986affbfd47635b855ec7135"}),"P174.03.029264":Object.freeze({baseSha:"272a7fee89e4b9324d35701df1c83b671fa96299",headSha:"339141be7339a0edf55710cbccb6e6558f514792",graphRevision:"60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445",effectiveClaimRevision:"1053bb2baf487e38fa28d7e8cfb872c5c4da66802534ee226bbc2c7455a6a5ab",commitBodyDigest:"0f269a00dcd168a6f548ff10dfa667b193c948ac9c7211f5cb11a0561757c371",changedPathsDigest:"1053bb2baf487e38fa28d7e8cfb872c5c4da66802534ee226bbc2c7455a6a5ab",qaDigest:"ef0fd2c6e216557825cd8521ccb2f9960a3af48f18e7a5ada441bee8bfda400b",reviewDigest:"372d0f4a2f32978c8b39f20167637b3917187edb75d49875e93200c4c5a19d0d"}),"P174.03.913359":Object.freeze({baseSha:"339141be7339a0edf55710cbccb6e6558f514792",headSha:"4ccc90b85c49b27f718090bf25bc452f1b84e5e3",graphRevision:"60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445",effectiveClaimRevision:"12b5cfba2667e8064309807776c1b323561dc8d853f9afe5f676007b8494b2b5",commitBodyDigest:"89a6653197509e75246a758d2706876b337770571a1cac47d9d85ec96c196459",changedPathsDigest:"12b5cfba2667e8064309807776c1b323561dc8d853f9afe5f676007b8494b2b5",qaDigest:"c7d3762ad30e4f9356243df17a1dd7123cbf1b660808d68176f44d1cd5da6449",reviewDigest:"e4ecf577e174fd66f31aa4a0113ab672ced16b12ee531f6831aa533795145ba1"}),"P174.03.724350":Object.freeze({baseSha:"4ccc90b85c49b27f718090bf25bc452f1b84e5e3",headSha:"568e9464fb086304146b9eb4594281f91302aef1",graphRevision:"60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445",effectiveClaimRevision:"c8b8b6b96edf9cea78925e2f961bd74069d77af03783a5368b8ac8025bf1fefe",commitBodyDigest:"b3032338bfcd6c9dc460e9426220a1966dedeff9968c4064fdd36a315560721b",changedPathsDigest:"c8b8b6b96edf9cea78925e2f961bd74069d77af03783a5368b8ac8025bf1fefe",qaDigest:"91f2f5b00935b1b04bb5d400df500cbc97c412072078b713c6e5ba8fda669bd0",reviewDigest:"9bc92069d3b3a76cc8b02d339d432690258be1376464b69e152ed8fd3d682f05"}),"P174.03.011976":Object.freeze({baseSha:"568e9464fb086304146b9eb4594281f91302aef1",headSha:"195599b1a99a99a8416c4c26c9ab9990847338a4",graphRevision:"60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445",effectiveClaimRevision:"49b7f2a0a967b2ba4249615c6d08834445a60df95045352838808eb4d6743b21",commitBodyDigest:"b8a28392fac663dd614b695a49c894b7cab116d45722a42850e3b24e2aaf36f4",changedPathsDigest:"49b7f2a0a967b2ba4249615c6d08834445a60df95045352838808eb4d6743b21",qaDigest:"6eb4459a7d80c4a268fc931297e8156ed2a320584f8775f0787d91b42c0929d4",reviewDigest:"98b21de29f7cceeec73da19000c6c144395cce6eb699c6a82dfab505d4ab7b44"}),"P174.03.686659":Object.freeze({baseSha:"195599b1a99a99a8416c4c26c9ab9990847338a4",headSha:"0db3681a5dad4e25ce37d967c636eb9d54915556",graphRevision:"60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445",effectiveClaimRevision:"5c0fdbf820d677c288981b6f73f7d2d9a04ce7f4713687f12a2e984705244b8e",commitBodyDigest:"143257f4018ad773afc0c7280bd8aab8786851a9c20b65f8110881ff54656839",changedPathsDigest:"5c0fdbf820d677c288981b6f73f7d2d9a04ce7f4713687f12a2e984705244b8e",qaDigest:"6303dbf473e2815a3671aeb2c25528d8daba484b92a5a40d1eea5215e1f6763e",reviewDigest:"1bd6695846ed9340c8677cfa9b573880532187aeee487efaaa70577a0c94e7f2"})}),xe="neocortex-yoloop-runtime.json",Ci="runtime.lock",Fe=new WeakMap;async function Pt(i,e){return(await Oi(i,{headSha:e.headSha,graphRevisions:[e.graphRevision],...e.nowIso!==void 0?{nowIso:e.nowIso}:{},...e.anchoredWorkspace!==void 0?{anchoredWorkspace:e.anchoredWorkspace}:{}})).get(e.graphRevision)}async function Oi(i,e){const t=[...new Set(e.graphRevisions)];if(!D(e.headSha)||t.length===0||t.some(x=>!p(x)))throw new Error("p205-transient-binding-invalid");const r=e.anchoredWorkspace;if(r&&v(i)!==S(v(r.parent),r.name))throw new Error("p205-transient-anchor-binding-invalid");const n=async x=>r?(await Ei(r.parent,r.name,r.identity,x)).toString("utf8"):String((await V("git",[...x],{cwd:i,encoding:"utf8",maxBuffer:8*1024*1024})).stdout),a=async()=>r?JSON.parse((await Te(r.parent,r.name,r.identity,["-e","const fs=require('node:fs');process.stdout.write(JSON.stringify({manifest:fs.readFileSync('config/release-gates.v2.json','utf8'),runner:fs.readFileSync('scripts/test-root.mjs','utf8')}));"])).toString("utf8")):{manifest:await F(S(i,"config","release-gates.v2.json"),"utf8"),runner:await F(S(i,"scripts","test-root.mjs"),"utf8")};if((await n(["rev-parse","HEAD"])).trim()!==e.headSha)throw new Error("p205-transient-head-mismatch");const{manifest:o,runner:u}=await a(),[c,d]=await Promise.all([n(["show",`${e.headSha}:config/release-gates.v2.json`]),n(["show",`${e.headSha}:scripts/test-root.mjs`])]);if(o!==c||u!==d)throw new Error("p205-transient-producer-dirty");const l=JSON.parse(o),g=Array.isArray(l.requiredCompleteClasses)?l.requiredCompleteClasses.filter(x=>typeof x=="string"&&/^[a-z0-9-]+$/.test(x)):[];if(l.contract!=="P205-CI-02"||g.length===0)throw new Error("p205-transient-manifest-invalid");const h={...process.env};delete h.NEOCORTEX_PROFILE_DIR;const m=r?(await Te(r.parent,r.name,r.identity,["scripts/test-root.mjs","--gate","complete"])).toString("utf8"):String((await V(process.execPath,["scripts/test-root.mjs","--gate","complete"],{cwd:i,encoding:"utf8",maxBuffer:64*1024*1024,env:h})).stdout),{manifest:R,runner:k}=await a();if(R!==o||k!==u)throw new Error("p205-transient-producer-raced");if((await n(["rev-parse","HEAD"])).trim()!==e.headSha)throw new Error("p205-transient-head-raced");const G=w("sha256").update(o).digest("hex"),y=w("sha256").update(m).digest("hex"),b=oe(e.nowIso??new Date().toISOString()),T=new Map,Z=Object.freeze(g);for(const x of t){const U=w("sha256").update(["p205-transient-execution-v1",q,e.headSha,x,G,g.join(","),y,b].join("\0")).digest("hex"),ge=Object.freeze({schemaVersion:1,kind:"p205_transient_execution_authority",contract:"P205-CI-02",command:q,executionHeadSha:e.headSha,graphRevision:x,manifestDigest:G,gateIds:Z,resultDigest:y,executedAt:b,executionEvidenceDigest:U}),ye=Object.freeze(Object.create(null));Fe.set(ye,ge),T.set(x,ye)}return T}async function Ie(i,e={}){const t=await Wi(i);return ni(t,e)}function Pi(i,e={}){let t;try{const r=M("git",["rev-parse","--git-common-dir"],{cwd:i,encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim(),n=_e(i),a=ae(r)?v(r):v(n,r),s=_e(a);if(s!==a)throw new Error("git-lifecycle-common-dir-symlink");Ui(s);const o=S(s,le),u=S(o,he);if(H(o)&&(ai(o,"directory"),_e(o)!==o))throw new Error("git-lifecycle-runtime-directory-symlink");t=H(u)?u:S(s,xe)}catch{if(e.required)throw new Error("git-lifecycle-common-dir-invalid");return Pe()}if(!H(t)){if(e.required)throw new Error("git-lifecycle-runtime-missing");return Pe()}return ai(t,"file"),Oe(JSON.parse(He(t,"utf8")))}async function $t(i){try{await Ie(i,{required:!0});return}catch(e){if(!(e instanceof Error)||e.message!=="git-lifecycle-runtime-missing")throw e}try{await Ke(i,()=>{},{expectedRevision:0})}catch(e){if(!(e instanceof Error)||e.message!=="git-lifecycle-runtime-revision-conflict")throw e;await Ie(i,{required:!0})}}function We(i,e,t){if(!i||i.ownerStoryId!==e)return!1;const r=typeof i.sessionId=="string"?i.sessionId:"",n=Date.parse(String(i.expiresAt));return/^setup-[a-f0-9]{64}$/.test(r)&&i.idempotencyKey===r&&Number.isFinite(n)&&n>t}async function Bt(i,e,t=new Date().toISOString()){if(!pt(e))throw new Error("git-lifecycle-graph-authority-invalid");const r=S(v(i),".neocortex","state.json"),n=await F(r,"utf8"),a=f(JSON.parse(n));if(!a)throw new Error("git-lifecycle-tracked-state-invalid");const s=await Ie(i),o=Bi(a,s),u=f(f(o.epics)?.[e.epicId]),c=f(f(o.stories)?.[e.storyId]);if(!u)throw new Error("git-lifecycle-graph-authority-epic-invalid");if(!c||String(c.epic_id??e.storyId.split(".")[0])!==e.epicId)throw new Error("git-lifecycle-graph-authority-story-invalid");const d=Array.isArray(u.stories)?u.stories.filter(y=>typeof y=="string"):Object.keys(f(o.stories)??{}).filter(y=>{const b=f(f(o.stories)?.[y]);return String(b?.epic_id??y.split(".")[0])===e.epicId});if(Array.isArray(u.stories)&&!d.includes(e.storyId))throw new Error("git-lifecycle-graph-authority-story-invalid");const l=d.map(y=>{const b=f(f(o.stories)?.[y])??{};return{id:y,branch_name:typeof b.branch_name=="string"||b.branch_name===null?b.branch_name:void 0,pr_number:typeof b.pr_number=="number"||b.pr_number===null?b.pr_number:void 0,steps_completed:Array.isArray(b.steps_completed)?b.steps_completed.filter(T=>typeof T=="string"):[],checkpoint:f(b.checkpoint)}}),g=Ri({...u,id:e.epicId},l);if(g.mode!=="epic")throw new Error("git-lifecycle-graph-authority-epic-invalid");if(g.executionBranch!==e.executionBranch)throw new Error("git-lifecycle-graph-authority-branch-mismatch");if(g.originBranch!==void 0&&g.originBranch!==e.originBranch)throw new Error("git-lifecycle-graph-authority-origin-mismatch");const h=typeof f(s.epics[e.epicId])?.origin_branch=="string"?String(f(s.epics[e.epicId]).origin_branch):void 0;if(h!==void 0&&h!==e.originBranch)throw new Error("git-lifecycle-graph-authority-origin-mismatch");if(g.originBranch===void 0&&h===void 0){const{stdout:y}=await V("git",["branch","--show-current"],{cwd:i,encoding:"utf8",maxBuffer:1048576});if(String(y).trim()!==e.originBranch)throw new Error("git-lifecycle-graph-authority-origin-physical-mismatch")}const m=typeof u.graph_revision=="string"?u.graph_revision:void 0,R=typeof f(s.epics[e.epicId])?.graph_revision=="string"?String(f(s.epics[e.epicId]).graph_revision):void 0;if(m!==void 0&&!p(m))throw new Error("git-lifecycle-graph-revision-invalid");if(m===e.graphRevision&&R===e.graphRevision&&g.originBranch===e.originBranch&&h===e.originBranch)return{applied:!1,reused:!0,revision:s.revision,graphRevision:e.graphRevision};const k=Date.parse(t);if(!Number.isFinite(k))throw new Error("git-lifecycle-graph-authority-time-invalid");const E=f(u.branch_lease);if(E&&Number.isFinite(Date.parse(String(E.expiresAt)))&&Date.parse(String(E.expiresAt))>k&&!We(E,e.storyId,k))throw new Error("git-lifecycle-graph-authority-writer-active");return{applied:!0,reused:!1,revision:(await Ke(i,y=>{const b=Ni(y,e.epicId);if(b.branch_mode!==void 0&&b.branch_mode!=="epic")throw new Error("git-lifecycle-graph-authority-epic-raced");const T=b.execution_branch;if(T!==void 0&&T!==e.executionBranch)throw new Error("git-lifecycle-graph-authority-branch-raced");const Z=b.origin_branch;if(Z!==void 0&&Z!==e.originBranch)throw new Error("git-lifecycle-graph-authority-origin-raced");const x=b.graph_revision;if(x!==void 0&&x!==m)throw new Error("git-lifecycle-graph-authority-epoch-raced");const U=f(b.branch_lease);if(U&&Number.isFinite(Date.parse(String(U.expiresAt)))&&Date.parse(String(U.expiresAt))>k&&!We(U,e.storyId,k))throw new Error("git-lifecycle-graph-authority-writer-active");b.branch_mode="epic",b.execution_branch=e.executionBranch,b.origin_branch=e.originBranch,b.graph_revision=e.graphRevision},{expectedRevision:s.revision,preCommit:async()=>{if(await F(r,"utf8")!==n)throw new Error("git-lifecycle-graph-authority-tracked-state-raced")}})).document.revision,graphRevision:e.graphRevision}}async function Ke(i,e,t={}){const r=await Ce(i),n=await ri(r),a=S(n,Ci),s=await yt(a);try{const o=S(n,he),u=S(r,xe),c=await ni(H(o)?o:u);if(t.expectedRevision!==void 0&&t.expectedRevision!==c.revision)throw new Error("git-lifecycle-runtime-revision-conflict");const d=structuredClone(c),l=e(d),g=Oe({...d,schemaVersion:1,revision:c.revision+1});return await t.preCommit?.(g),await j(n,"directory"),await Qi(o),await _i(n,he,`${JSON.stringify(g,null,2)}
2
- `),await j(o,"file"),await $i(n),{document:g,value:l}}finally{await bt(a,s).catch(()=>{})}}async function $i(i){let e;try{e=await mi(i,"r")}catch(t){if(process.platform==="win32"&&(P(t,"EPERM")||P(t,"EISDIR")||P(t,"EINVAL")))return;throw t}try{await e.sync()}finally{await e.close()}}function Bi(i,e){const t=structuredClone(i),r=Ye(t,"epics"),n=Ye(t,"stories");for(const[a,s]of Object.entries(e.epics))r[a]={...f(r[a])??{},...structuredClone(s)};for(const[a,s]of Object.entries(e.stories)){const o=f(n[a])??{},u=o.steps_completed,c=s.steps_completed;s.status==="branch-ready"&&s.last_step===Ee&&Array.isArray(c)&&c.length===1&&c[0]===Ee&&s.checkpoint===void 0&&Array.isArray(u)&&u.includes(Ee)||(n[a]={...o,...structuredClone(s)})}return t}function Ye(i,e){const t=i[e];if(t===void 0){const a={};return i[e]=a,a}const r=f(t);if(r)return r;if(!Array.isArray(t))throw new Error(`git-lifecycle-tracked-${e}-invalid`);const n={};for(const a of t){const s=f(a),o=typeof s?.id=="string"?s.id.trim():"";if(!o||n[o]!==void 0)throw new Error(`git-lifecycle-tracked-${e}-ambiguous`);n[o]=s}return i[e]=n,n}function Ni(i,e){return i.epics[e]??(i.epics[e]={})}function Nt(i,e){return i.stories[e]??(i.stories[e]={})}function Je(i,e,t,r){const n=f(i);return!!(n&&n.epicId===t&&n.branch===r&&n.qaVerdict==="passed"&&n.reviewVerdict==="approved"&&ci(n,e))}async function Vt(i,e,t,r,n,a,s=[]){return Qe(i,e,t,r,n,a,s).verdict==="verified"}function Qe(i,e,t,r,n,a,s=[],o=new Date().toISOString(),u=a===void 0?"scheduling":"integration"){const c=()=>Xe({storyId:t,epicId:r,executionBranch:n,value:e,verifiedAt:o,verdict:"rejected",reasonCode:"checkpoint-git-physical-invalid"});try{if(!Je(e,t,r,n))return c();const d=f(e),l=f(d.qaReceipt),g=f(d.reviewReceipt),h=String(d.headSha),m=String(d.baseSha),R=Ae(i,t,h,g.reviewArtifactDigest===void 0),k=g.reviewArtifactDigest===void 0&&!Fi(i,t,h);if(g.reviewArtifactDigest!==void 0&&g.reviewArtifactDigest!==R?.digest)return c();const E=$(i,["rev-parse",`refs/heads/${n}`]).trim();if(M("git",["merge-base","--is-ancestor",h,E],{cwd:i,stdio:"ignore"}),!k&&R?.verdict!=="approved"){if(g.reviewArtifactDigest!==void 0||!Mi(i,d,g,l,t,r,E))return c()}else if(d.reviewReconciliation!==void 0)return c();const y=$(i,["show","--no-patch","--format=%H%n%P%n%B",h]),b=$(i,["rev-parse",`${h}^`]).trim(),T=$(i,["rev-list","--count",`${m}..${h}`]).trim();if(b!==m||T!=="1")return c();const Z=new Set(y.split(`
3
- `).map(N=>N.trim())),x=y.split(`
4
- `).map(N=>N.trim()).filter(N=>N.startsWith("Neocortex-Review-Artifact-Digest: "));if(x.length>0&&(x.length!==1||x[0]!==`Neocortex-Review-Artifact-Digest: ${R?.digest}`||g.reviewArtifactDigest!==R?.digest))return c();const ge=[`Neocortex-Story: ${t}`,`Neocortex-Epic: ${r}`,`Neocortex-Execution-Branch: ${n}`,`Neocortex-Bundle-Digest: ${String(d.bundleDigest)}`,`Neocortex-Graph-Revision: ${String(d.graphRevision)}`,...d.dispatchId?[`Neocortex-Dispatch: ${String(d.dispatchId)}`]:[],...d.bundleId?[`Neocortex-Bundle: ${String(d.bundleId)}`]:[],...d.claimRevision?[`Neocortex-Claim-Revision: ${String(d.claimRevision)}`]:[]].every(N=>Z.has(N));if(ge){const qe=[...Z].find(hi=>hi.startsWith("Neocortex-Prepared-Base: "))?.slice(25)??"";if(!/^[a-f0-9]{40,64}$/i.test(qe))return c();M("git",["merge-base","--is-ancestor",qe,h],{cwd:i,stdio:"ignore"})}const ie=$(i,["diff-tree","--no-commit-id","--name-only","-r","-z",h]).split("\0").filter(Boolean);if(ie.length===0||ie.some(N=>!fe(N)))return c();const be=w("sha256").update(y).digest("hex"),we=w("sha256").update([...ie].sort().join("\0")).digest("hex"),L=Ze({projectRoot:i,checkpoint:d,storyId:t,epicId:r,executionBranch:n,baseSha:m,headSha:h,commitBodyDigest:be,changedPathsDigest:we,changedPaths:ie,authorizedWriteSet:s.length>0?s:qi(i,t)});if(!L||l.receiptRef!==`story-qa:${String(l.sourceEvidenceDigest)}`)return c();const Be=ji({storyId:t,baseSha:m,headSha:h,bundleDigest:String(d.bundleDigest),graphRevision:String(d.graphRevision),commitBody:y,changedPaths:ie}),Ne=l.evidenceVersion==="p205-execution-v2"&&l.contract==="P205-CI-02"&&l.command===q&&p(l.manifestDigest)&&z(l.gateIds,32,!0)&&p(l.resultDigest)&&l.sourceEvidenceDigest===Gi({gitEvidenceDigest:Be,headSha:h,graphRevision:String(d.graphRevision),manifestDigest:String(l.manifestDigest),gateIds:l.gateIds,resultDigest:String(l.resultDigest)}),ui=l.evidenceVersion==="git-immutable-v1"&&l.sourceEvidenceDigest===Be,gi=l.evidenceVersion===void 0&&L.mode==="legacy-migration"&&L.binding?.qaDigest===l.sourceEvidenceDigest,ve=Vi(a,E,String(d.graphRevision))&&(Ne||L.mode==="legacy-migration"&&(ui||gi)),Se=u==="scheduling"&&a===void 0&&Ne;if(!ve&&!Se)return c();const Ve=g.reviewArtifactDigest!==void 0,Le=w("sha256").update([Ve?"root-commit-range-review-v2":"root-commit-range-review-v1",t,m,h,String(d.bundleDigest),String(d.graphRevision),String(l.receiptRef),w("sha256").update(y).digest("hex"),[...ie].sort().join(","),...Ve?[R.digest]:[]].join("\0")).digest("hex");if(g.sourceEvidenceDigest!==Le||g.receiptRef!==`story-review:${Le}`||L.mode==="legacy-migration"&&L.binding?.reviewDigest!==void 0&&L.binding.reviewDigest!==g.sourceEvidenceDigest||!ge&&(Me[t]!==h||!Array.isArray(d.receiptRefs)||!d.receiptRefs.some(N=>typeof N=="string"&&N.startsWith("legacy-direct-yolo:"))))return c();const li=w("sha256").update(["checkpoint-p205-transient-v1",t,r,n,m,h,String(d.bundleDigest),be,we,L.mode,L.effectiveClaimRevision,String(l.sourceEvidenceDigest),String(g.sourceEvidenceDigest),...d.reviewReconciliation?[String(f(d.reviewReconciliation)?.sourceEvidenceDigest)]:[],ce(a)?.executionEvidenceDigest??(Se?"scheduling-git-verification":"legacy-bounded-authority")].join("\0")).digest("hex"),te=ve?ce(a):void 0;return Object.freeze({schemaVersion:1,kind:"story_checkpoint_physical_verification",producer:"client-p205-transient-v1",storyId:t,epicId:r,branch:n,baseSha:m,headSha:h,aggregateHeadSha:te?.executionHeadSha??h,commitBodyDigest:be,changedPathsDigest:we,claimVerification:L.mode,effectiveClaimRevision:L.effectiveClaimRevision,sourceEvidenceDigest:li,command:q,manifestDigest:te?.manifestDigest??"0".repeat(64),gateIds:te?.gateIds??Object.freeze(["legacy-bounded"]),resultDigest:te?.resultDigest??"0".repeat(64),graphRevision:String(d.graphRevision),executionEvidenceDigest:te?.executionEvidenceDigest??"0".repeat(64),verifiedAt:te?.executedAt??oe(o),verdict:"verified",reasonCodes:Object.freeze([ve?"checkpoint-p205-transient-verified":Se?"checkpoint-git-scheduling-verified":"checkpoint-legacy-bounded-verified",...L.mode==="legacy-migration"?["checkpoint-legacy-migration-verified"]:[]]),publicSafe:!0})}catch{return c()}}function Lt(i,e,t,r,n,a,s=[],o=new Date().toISOString()){const u=a?"integration":"scheduling",c=Qe(i,e,t,r,n,a,s,o,u);return c.verdict!=="verified"?{kind:"rejected",verification:c}:u==="integration"?{kind:"integration-authorized",verification:c}:c.reasonCodes.includes("checkpoint-git-scheduling-verified")?{kind:"scheduling-only",evidence:Object.freeze({schemaVersion:1,kind:"story_scheduling_checkpoint_evidence",producer:"client-git-physical-v1",storyId:t,epicId:r,branch:n,baseSha:c.baseSha,headSha:c.headSha,graphRevision:c.graphRevision,effectiveClaimRevision:c.effectiveClaimRevision,sourceEvidenceDigest:c.sourceEvidenceDigest,verifiedAt:c.verifiedAt,reasonCodes:Object.freeze(["checkpoint-git-scheduling-verified"]),integrationAuthorized:!1,publicSafe:!0})}:{kind:"rejected",verification:Xe({storyId:t,epicId:r,executionBranch:n,value:e,verifiedAt:o,verdict:"rejected",reasonCode:"checkpoint-git-physical-invalid"})}}function Vi(i,e,t){const r=ce(i);return!!(r&&r.schemaVersion===1&&r.kind==="p205_transient_execution_authority"&&r.contract==="P205-CI-02"&&r.command===q&&r.graphRevision===t&&p(r.resultDigest)&&p(r.executionEvidenceDigest)&&r.executionHeadSha===e&&D(e))}function ce(i){return i&&typeof i=="object"?Fe.get(i):void 0}function qt(i){const e=ce(i);return e?Object.freeze({executionHeadSha:e.executionHeadSha,graphRevision:e.graphRevision,manifestDigest:e.manifestDigest,gateIds:Object.freeze([...e.gateIds]),resultDigest:e.resultDigest,executedAt:e.executedAt,executionEvidenceDigest:e.executionEvidenceDigest}):void 0}function zt(i,e,t){const r=ce(i);return!!(r&&r.executionHeadSha===e&&r.graphRevision===t&&r.command===q)}function Ze(i){const e=ki[i.storyId],t=typeof i.checkpoint.claimRevision=="string"?i.checkpoint.claimRevision:void 0,r=i.storyId==="P174.03.811759"||i.storyId==="P174.03.685003",n=Li(i.authorizedWriteSet);let a=[];if(i.checkpoint.rootMemoryEffect!==void 0){if(e)return;try{a=Di(i.projectRoot,i.checkpoint.rootMemoryEffect,{storyId:i.storyId,executionBranch:i.executionBranch,baseSha:i.baseSha,headRef:i.headSha,childPaths:n}).files.map(o=>o.path)}catch{return}}const s=n.length>0&&n.length===i.authorizedWriteSet.length&&i.changedPaths.every(o=>n.some(u=>Hi(u,o))||a.includes(o));if(e&&i.epicId==="P174"&&i.executionBranch==="epic/P174"&&i.baseSha===e.baseSha&&i.headSha===e.headSha&&i.checkpoint.graphRevision===e.graphRevision&&i.commitBodyDigest===e.commitBodyDigest&&i.changedPathsDigest===e.changedPathsDigest&&(r?t===void 0:t===e.effectiveClaimRevision))return i.authorizedWriteSet.length>0&&(!s||!Ue(e.effectiveClaimRevision,n))?void 0:Object.freeze({mode:"legacy-migration",effectiveClaimRevision:e.effectiveClaimRevision,binding:e});if(t!==void 0&&!(!t||!p(t))&&!(!s||!Ue(t,n)))return Object.freeze({mode:"canonical",effectiveClaimRevision:t})}function Li(i){const e=i.map(t=>t.replaceAll("\\","/").replace(/^\.\//,"").replace(/\/{2,}/g,"/"));return e.some(t=>!fe(t))?Object.freeze([]):Object.freeze([...new Set(e)].sort())}function qi(i,e){try{const t=JSON.parse(He(S(i,".neocortex","state.json"),"utf8")),r=f(t.stories),n=f(r?.[e]),a=Pi(i),s=f(a.stories[e]?.checkpoint)??f(n?.checkpoint);return zi(e,n,s)}catch{return[]}}function zi(i,e,t){const r=Array.isArray(e?.files_to_modify)?e.files_to_modify.filter(n=>typeof n=="string"):[];if(!t?.dispatchId||!t.bundleId)return r;try{return vi({...e,id:i,files_to_modify:r})}catch{return[]}}function Ue(i,e){return i===w("sha256").update(e.join("\0")).digest("hex")||i===w("sha256").update(JSON.stringify(e)).digest("hex")}function Hi(i,e){return i===e||i.endsWith("/")&&e.startsWith(i)}function Ht(i,e,t,r,n,a){try{if(!Je(e,t,r,n))return!1;const s=f(e),o=f(s.qaReceipt),u=f(s.reviewReceipt),c=String(s.headSha),d=String(s.baseSha),l=$(i,["rev-parse",`refs/heads/${n}`]).trim();if(M("git",["merge-base","--is-ancestor",c,l],{cwd:i,stdio:"ignore"}),$(i,["rev-parse",`${c}^`]).trim()!==d||$(i,["rev-list","--count",`${d}..${c}`]).trim()!=="1")return!1;const g=$(i,["show","--no-patch","--format=%H%n%P%n%B",c]),h=$(i,["diff-tree","--no-commit-id","--name-only","-r","-z",c]).split("\0").filter(Boolean);if(h.length===0||h.some(R=>!fe(R)))return!1;const m=Ze({projectRoot:i,checkpoint:s,storyId:t,epicId:r,executionBranch:n,baseSha:d,headSha:c,changedPaths:h,authorizedWriteSet:a,commitBodyDigest:w("sha256").update(g).digest("hex"),changedPathsDigest:w("sha256").update([...h].sort().join("\0")).digest("hex")});return!(m?.mode!=="legacy-migration"||!m.binding||o.sourceEvidenceDigest!==m.binding.qaDigest||o.receiptRef!==`story-qa:${m.binding.qaDigest}`||u.sourceEvidenceDigest!==m.binding.reviewDigest||u.receiptRef!==`story-review:${m.binding.reviewDigest}`||u.qaReceiptRef!==o.receiptRef)}catch{return!1}}function ji(i){return w("sha256").update(["local-yolo-tdd-git-immutable-v1",i.storyId,i.baseSha,i.headSha,i.bundleDigest,i.graphRevision,w("sha256").update(i.commitBody).digest("hex"),w("sha256").update([...i.changedPaths].sort().join("\0")).digest("hex")].join("\0")).digest("hex")}function Gi(i){return w("sha256").update(["local-yolo-tdd-p205-execution-v2",q,i.gitEvidenceDigest,i.headSha,i.graphRevision,i.manifestDigest,i.gateIds.join(","),i.resultDigest].join("\0")).digest("hex")}function Xe(i){const e=f(i.value);return Object.freeze({schemaVersion:1,kind:"story_checkpoint_physical_verification",producer:"client-p205-transient-v1",storyId:i.storyId,epicId:i.epicId,branch:i.executionBranch,baseSha:D(e?.baseSha)?String(e?.baseSha):"0".repeat(40),headSha:D(e?.headSha)?String(e?.headSha):"0".repeat(40),aggregateHeadSha:"0".repeat(40),commitBodyDigest:"0".repeat(64),changedPathsDigest:"0".repeat(64),claimVerification:"canonical",effectiveClaimRevision:"0".repeat(64),command:q,manifestDigest:"0".repeat(64),gateIds:Object.freeze([]),resultDigest:"0".repeat(64),graphRevision:"0".repeat(64),executionEvidenceDigest:"0".repeat(64),sourceEvidenceDigest:"0".repeat(64),verifiedAt:oe(i.verifiedAt),verdict:i.verdict,reasonCodes:Object.freeze([i.reasonCode]),publicSafe:!0})}function $(i,e){return M("git",[...e],{cwd:i,encoding:"utf8",maxBuffer:32*1024*1024})}function Ae(i,e,t,r=!1){if(!/^[A-Za-z][A-Za-z0-9_-]*\.[A-Za-z0-9._-]+$/.test(e)||t!==":"&&!/^[a-f0-9]{40,64}$/i.test(t))return;const n=`.neocortex/stories/${e}/review.md`;let a;try{a=M("git",["show",`${t===":"?":":`${t}:`}${n}`],{cwd:i,maxBuffer:128*1024,stdio:["ignore","pipe","ignore"]})}catch{return}if(a.length===0||a.length>64*1024||a.includes(0))return;let s;try{s=new TextDecoder("utf-8",{fatal:!0}).decode(a)}catch{return}const o=s.replace(/\r\n/g,`
5
- `).split(`
6
- `),u=new RegExp(`^#\\s+${e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}(?=\\s|$|[:\u2014-])`);if(!o.some(g=>u.test(g)))return;const c=o.filter(g=>/^Verdict\s*:/i.test(g));if(o.some(g=>/^Status\s*:\s*(pending|changes-requested|rejected)\b/i.test(g)))return;const d=o.filter(g=>/^Decision\s*:/i.test(g));if(c.length===0&&r&&d.length===1&&/^Decision\s*:\s*APPROVED\b/i.test(d[0]))return Object.freeze({verdict:"approved",digest:w("sha256").update(a).digest("hex")});if(c.length!==1)return;const l=/^Verdict\s*:\s*(approved|pending|changes-requested)(?=\s|$|[;,.])/i.exec(c[0])?.[1]?.toLowerCase();if(!(l!=="approved"&&l!=="pending"&&l!=="changes-requested"))return Object.freeze({verdict:l,digest:w("sha256").update(a).digest("hex")})}function Ti(i){return w("sha256").update(["root-historical-review-reconciliation-v1",i.storyId,i.epicId,i.checkpointHeadSha,i.approvalCommitSha,i.approvalArtifactDigest,i.qaReceiptRef,i.reviewReceiptRef,w("sha256").update(i.approvalCommitBody).digest("hex")].join("\0")).digest("hex")}function Mi(i,e,t,r,n,a,s){const o=f(e.reviewReconciliation);if(!o||!oi(o,e))return!1;const u=String(o.approvalCommitSha),c=String(e.headSha);if(u===c)return!1;try{if(M("git",["merge-base","--is-ancestor",c,u],{cwd:i,stdio:"ignore"}),M("git",["merge-base","--is-ancestor",u,s],{cwd:i,stdio:"ignore"}),!$(i,["rev-list","--first-parent",s]).split(`
7
- `).includes(u))return!1;const d=$(i,["show","--no-patch","--format=%P",u]).trim().split(/\s+/);if(d.length!==1||!D(d[0]))return!1;const l=$(i,["show","--no-patch","--format=%H%n%P%n%B",u]),g=new Set(l.split(`
8
- `).map(G=>G.trim()));if(!g.has(`Neocortex-Review-Reconciliation: ${n}`)||!g.has(`Neocortex-Original-Checkpoint: ${c}`)||!g.has(`Neocortex-Original-Review-Receipt: ${String(t.receiptRef)}`))return!1;const h=`.neocortex/stories/${n}/review.md`,m=$(i,["diff-tree","--no-commit-id","--name-only","-r","-z",u]).split("\0").filter(Boolean);if(!m.includes(h)||m.some(G=>!fe(G)))return!1;const R=Ae(i,n,u);if(R?.verdict!=="approved"||R.digest!==o.approvalArtifactDigest)return!1;const k=Ae(i,n,s);if(k?.verdict!=="approved"||k.digest!==R.digest)return!1;const E=Ti({storyId:n,epicId:a,checkpointHeadSha:c,approvalCommitSha:u,approvalArtifactDigest:R.digest,qaReceiptRef:String(r.receiptRef),reviewReceiptRef:String(t.receiptRef),approvalCommitBody:l});return o.sourceEvidenceDigest===E&&o.receiptRef===`story-review-reconciliation:${E}`}catch{return!1}}function Fi(i,e,t){if(!/^[A-Za-z][A-Za-z0-9_-]*\.[A-Za-z0-9._-]+$/.test(e)||!/^[a-f0-9]{40,64}$/i.test(t))return!1;try{return M("git",["cat-file","-e",`${t}:.neocortex/stories/${e}/review.md`],{cwd:i,stdio:"ignore",maxBuffer:1024}),!0}catch{return!1}}function oe(i){return Number.isFinite(Date.parse(i))?new Date(Date.parse(i)).toISOString():new Date().toISOString()}async function jt(i,e){if(Me[e.storyId]!==e.headSha)return;const t=[".neocortex/state.json",`.neocortex/stories/${e.storyId}/qa-gate-result.json`,`.neocortex/stories/${e.storyId}/validation-evidence.md`,`.neocortex/stories/${e.storyId}/review.md`,`docs/stories/${e.storyId}.story.md`];try{const r=(await V("git",["rev-parse",`refs/heads/${e.executionBranch}`],{cwd:i,encoding:"utf8",maxBuffer:1048576})).stdout.trim();if(await V("git",["merge-base","--is-ancestor",e.headSha,r],{cwd:i}),(await V("git",["rev-parse",`${e.headSha}^`],{cwd:i,encoding:"utf8",maxBuffer:1024*1024})).stdout.trim()!==e.baseSha)return;const a=String((await V("git",["diff","--binary","--full-index",`${e.baseSha}..${e.headSha}`],{cwd:i,encoding:"utf8",maxBuffer:32*1024*1024})).stdout);if(w("sha256").update(a).digest("hex")!==e.bundleDigest)return;const s=String((await V("git",["show","--no-patch","--format=%H%n%P%n%B",e.headSha],{cwd:i,encoding:"utf8",maxBuffer:4*1024*1024})).stdout);if(!String((await V("git",["show","--no-patch","--format=%s",e.headSha],{cwd:i,encoding:"utf8",maxBuffer:1024*1024})).stdout).trim().includes(e.storyId))return;const c=String((await V("git",["diff-tree","--no-commit-id","--name-only","-r","-z",e.headSha],{cwd:i,encoding:"utf8",maxBuffer:8*1024*1024})).stdout).split("\0").filter(Boolean);if(!t.every(y=>c.includes(y))||c.some(y=>!fe(y)))return;const d=await Promise.all(t.map(async y=>String((await V("git",["show",`${e.headSha}:${y}`],{cwd:i,encoding:"utf8",maxBuffer:16*1024*1024})).stdout))),l=JSON.parse(d[0]),g=f(l.stories),h=f(l.epics),m=f(g?.[e.storyId]),R=f(h?.[e.epicId]),k=Array.isArray(m?.steps_completed)?m.steps_completed:[],E=JSON.parse(d[1]);return m?.status!=="done"||!k.includes(Ii)||!k.includes(Ai)||R?.branch_mode!=="epic"||R.execution_branch!==e.executionBranch||E.storyId!==e.storyId||E.status!=="qa-passed"||E.safeToContinueYoloop!==!0||!Array.isArray(E.blockingChecks)||E.blockingChecks.length!==0||!d[2].includes(e.storyId)||!/P205[\s\S]{0,80}(pass|aprov)/i.test(d[2])||!d[3].includes(e.storyId)||!/approved|aprovado/i.test(d[3])||!d[4].includes(e.storyId)?void 0:`legacy-direct-yolo:${w("sha256").update(["legacy-direct-yolo-recovery-v1",e.storyId,e.epicId,e.executionBranch,e.baseSha,e.headSha,e.bundleDigest,e.graphRevision,w("sha256").update(s).digest("hex"),w("sha256").update([...c].sort().join("\0")).digest("hex"),...d.map(y=>w("sha256").update(y).digest("hex"))].join("\0")).digest("hex")}`}catch{return}}async function Wi(i){const e=await Ce(i),t=S(e,le),r=S(t,he);if(H(t)&&(await j(t,"directory"),await X(t)!==t))throw new Error("git-lifecycle-runtime-directory-symlink");return H(r)?r:S(e,xe)}async function ei(i){return ri(await Ce(i))}async function Ki(i,e){if(!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(e)||e.includes(".."))throw new Error("git-lifecycle-private-child-invalid");const t=v(i);await Y(t);const r=v(t,e);return ne(t,r),H(r)||await Ge(t,e),await Y(r,t),await Y(t),r}async function _(i,e){const t=v(e),r=v(i);ne(t,r),await Y(t),await Y(r,t)}async function Gt(i,e){await J(i,e)}async function ee(i,e,t){const r=v(e),n=v(i);ne(r,n);try{await _(n,r)}catch(s){if(P(s,"ENOENT"))return!1;throw s}const a=v(r,`${n.slice(r.length+1)}.remove.${process.pid}.${Date.now()}.${De(4).toString("hex")}`);return ne(r,a),await re(r,C(n),C(a)),await _(a,r),t&&!await t(a)?(await se(a,n,r).catch(()=>{}),!1):(await xi(r,C(a)),!0)}async function se(i,e,t){await _(i,t);try{await Re(e);return}catch(r){if(!P(r,"ENOENT"))throw r}await re(t,C(i),C(e)),await _(e,t)}async function B(i){try{await _(i,K(i));const e=S(i,"owner.json");await J(e,i);const t=await F(e);return await J(e,i),w("sha256").update(t).digest("hex")}catch{return}}async function Tt(i,e){const t=await ei(i),r=await Ki(t,"leases"),n=S(r,"integration"),a=`${n}.recovery`;if(H(a)){await _(a,r);const c=await ii(a),d=await B(a);if(c===void 0||me(c))return{ok:!1,reasonCode:"git-lifecycle-integration-backpressure"};if(!d||!await ee(a,r,async l=>await B(l)===d))return{ok:!1,reasonCode:"git-lifecycle-integration-backpressure"}}const s=oe(e.createdAt??new Date().toISOString()),o=Object.freeze({schemaVersion:1,kind:e.kind,storyId:e.storyId,sessionId:e.sessionId,idempotencyKey:e.idempotencyKey,pid:process.pid,createdAt:s,expiresAt:oe(e.expiresAt),persistent:e.persistent,ownerNonce:e.ownerNonce??De(16).toString("hex")});if(Date.parse(o.expiresAt)<=Date.parse(o.createdAt))return{ok:!1,reasonCode:"git-lifecycle-integration-lock-failed"};const u=async()=>{await Y(r,t);try{return await je(r,C(n),`${JSON.stringify(o)}
9
- `),await _(n,r),await J(S(n,"owner.json"),n),!0}catch(c){throw P(c,"EEXIST")||await ee(n,r).catch(()=>{}),c}};try{return await u(),{ok:!0,owner:o,path:n}}catch(c){if(!P(c,"EEXIST"))return{ok:!1,reasonCode:"git-lifecycle-integration-lock-failed"}}try{const c=await de(n),d=c?void 0:await ii(n);if(c&&ti(c,o))return{ok:!0,owner:c,path:n};if(c&&Date.parse(c.expiresAt)>Date.now()&&(c.persistent||me(c.pid))||d!==void 0&&me(d)||!c&&d===void 0&&await di(n))return{ok:!1,reasonCode:"git-lifecycle-integration-backpressure"};const g=await B(n);if(!g)return{ok:!1,reasonCode:"git-lifecycle-integration-lock-failed"};const h=`${n}.stale.${process.pid}.${Date.now()}`;await re(r,C(n),C(h));try{if(await _(h,r),await B(h)!==g)return await se(h,n,r).catch(()=>{}),{ok:!1,reasonCode:"git-lifecycle-integration-backpressure"};await u()}finally{await ee(h,r,async m=>await B(m)===g).catch(()=>{})}return{ok:!0,owner:o,path:n}}catch(c){return{ok:!1,reasonCode:P(c,"EEXIST")||P(c,"ENOENT")?"git-lifecycle-integration-backpressure":"git-lifecycle-integration-lock-failed"}}}async function Mt(i,e){const t=await de(i);return!!(t&&ke(t,e)&&Date.parse(t.expiresAt)>Date.now())}async function Yi(i,e){try{const t=K(i);await _(i,t);const r=await de(i);if(!r||!ke(r,e))return!1;const n=await B(i);if(!n)return!1;const a=`${i}.release.${process.pid}.${Date.now()}`;await re(t,C(i),C(a)),await _(a,t);const s=await de(a);return!s||!ke(s,e)||await B(a)!==n?(await se(a,i,t).catch(()=>{}),!1):ee(a,t,async o=>await B(o)===n)}catch(t){if(P(t,"ENOENT"))return!1;throw t}}function Ji(i){return w("sha256").update(["git-lifecycle-physical-owner-v1",i.kind,i.storyId,i.sessionId,i.idempotencyKey,String(i.pid),i.createdAt,i.expiresAt,i.persistent?"persistent":"ephemeral",i.ownerNonce].join("\0")).digest("hex")}async function Ft(i,e){if(!Q(e.storyId)||!O(e.idempotencyKey)||!p(e.ownerDigest))return"foreign";const t=await ei(i),r=S(t,"leases"),n=S(r,"integration");if(!H(n))return"absent";try{await _(r,t),await _(n,r);const a=await de(n);return!a||a.kind!=="direct"||a.storyId!==e.storyId||a.sessionId!==e.idempotencyKey||a.idempotencyKey!==e.idempotencyKey||a.persistent!==!0||Ji(a)!==e.ownerDigest?"foreign":await Yi(n,a)?"released":"foreign"}catch{return"foreign"}}async function de(i){try{await _(i,K(i));const e=S(i,"owner.json");await J(e,i);const t=JSON.parse(await F(e,"utf8"));return t.schemaVersion!==1||t.kind!=="direct"&&t.kind!=="continuous"||!Q(t.storyId)||!O(t.sessionId)||!O(t.idempotencyKey)||!Number.isSafeInteger(t.pid)||Number(t.pid)<=0||!A(t.createdAt)||!A(t.expiresAt)||typeof t.persistent!="boolean"||typeof t.ownerNonce!="string"||!/^[a-f0-9]{32}$/.test(t.ownerNonce)?void 0:t}catch{return}}async function ii(i){try{await _(i,K(i));const e=S(i,"owner.json");await J(e,i);const t=JSON.parse(await F(e,"utf8"));return Number.isSafeInteger(t.pid)&&Number(t.pid)>0?Number(t.pid):void 0}catch{return}}function ti(i,e){return i.kind===e.kind&&i.storyId===e.storyId&&i.sessionId===e.sessionId&&i.idempotencyKey===e.idempotencyKey&&i.expiresAt===e.expiresAt&&i.persistent===e.persistent}function ke(i,e){return ti(i,e)&&i.schemaVersion===e.schemaVersion&&i.pid===e.pid&&i.createdAt===e.createdAt&&i.ownerNonce===e.ownerNonce}async function Ce(i){const e=await X(i),r=(await V("git",["rev-parse","--git-common-dir"],{cwd:e,encoding:"utf8",maxBuffer:1024*1024})).stdout.trim(),n=ae(r)?v(r):v(e,r),a=await X(n);if(a!==n)throw new Error("git-lifecycle-common-dir-symlink");await Zi(a);const o=(await V("git",["rev-parse","--git-common-dir"],{cwd:e,encoding:"utf8",maxBuffer:1024*1024})).stdout.trim();if((ae(o)?v(o):v(e,o))!==n)throw new Error("git-lifecycle-common-dir-raced");return a}async function ri(i){const e=S(i,le);if(H(e)||await Ge(i,le),await j(e,"directory"),await X(e)!==e)throw new Error("git-lifecycle-runtime-directory-symlink");return e}async function ni(i,e={}){try{return await j(i,"file"),Oe(JSON.parse(await F(i,"utf8")))}catch(t){if(P(t,"ENOENT")){if(e.required)throw new Error("git-lifecycle-runtime-missing");return Pe()}throw t}}async function Qi(i){try{await j(i,"file")}catch(e){if(P(e,"ENOENT"))return;throw e}}async function j(i,e){const t=await Re(i);if(t.isSymbolicLink())throw new Error(`git-lifecycle-${e}-symlink`);if(e==="file"?!t.isFile():!t.isDirectory())throw new Error(`git-lifecycle-${e}-invalid`);if(pe()&&t.uid!==process.getuid())throw new Error(`git-lifecycle-${e}-ownership-mismatch`);if(pe()&&(t.mode&63)!==0)throw new Error(`git-lifecycle-${e}-permissions-unsafe`)}function ne(i,e){const t=v(i),r=v(e),n=bi(t,r);if(!n||n.startsWith("..")||ae(n)||K(r)!==t)throw new Error("git-lifecycle-private-path-escape")}async function Y(i,e){const t=v(i);if(e&&ne(v(e),t),await j(t,"directory"),v(await X(t))!==t)throw new Error("git-lifecycle-directory-substituted");if(e){const r=v(e);if(await j(r,"directory"),v(await X(r))!==r)throw new Error("git-lifecycle-directory-parent-substituted")}}async function J(i,e){const t=v(i);if(ne(v(e),t),await Y(v(e)),await j(t,"file"),v(await X(t))!==t)throw new Error("git-lifecycle-file-substituted")}function ai(i,e){const t=ze(i);if(t.isSymbolicLink())throw new Error(`git-lifecycle-${e}-symlink`);if(e==="file"?!t.isFile():!t.isDirectory())throw new Error(`git-lifecycle-${e}-invalid`);if(pe()&&t.uid!==process.getuid())throw new Error(`git-lifecycle-${e}-ownership-mismatch`);if(pe()&&(t.mode&63)!==0)throw new Error(`git-lifecycle-${e}-permissions-unsafe`)}async function Zi(i){const e=await Re(i);if(e.isSymbolicLink())throw new Error("git-lifecycle-common-dir-symlink");if(!e.isDirectory())throw new Error("git-lifecycle-common-dir-invalid")}function Ui(i){const e=ze(i);if(e.isSymbolicLink())throw new Error("git-lifecycle-common-dir-symlink");if(!e.isDirectory())throw new Error("git-lifecycle-common-dir-invalid")}function pe(i=process.platform){return i!=="win32"&&typeof process.getuid=="function"}function Oe(i){if(!i||typeof i!="object"||Array.isArray(i))throw new Error("git-lifecycle-runtime-invalid");const e=i;I(e,["schemaVersion","revision","epics","stories"],"git-lifecycle-runtime-unknown-field");const t=f(e.epics),r=f(e.stories);if(e.schemaVersion!==1||!Number.isSafeInteger(e.revision)||Number(e.revision)<0||!t||!r||Object.values(t).some(n=>!f(n))||Object.values(r).some(n=>!f(n)))throw new Error("git-lifecycle-runtime-invalid");for(const[n,a]of Object.entries(t))if(!Q(n)||!it(a))throw new Error("git-lifecycle-runtime-epic-invalid");for(const[n,a]of Object.entries(r))if(!Q(n)||!n.includes(".")||!tt(a,n))throw new Error("git-lifecycle-runtime-story-invalid");return e}const Xi=["branch_mode","execution_branch","origin_branch","branch_lease","origin_binding","graph_revision","aggregate_qa_receipt","terminal_git_receipt","physicalMergeStatus","pr_number","git_reconciliation_receipts","git_reconciliation_debt"],et=["checkpoint","status","last_step","steps_completed"];function it(i){return I(i,Xi,"git-lifecycle-runtime-epic-unknown-field"),(i.branch_mode===void 0||i.branch_mode==="epic")&&(i.execution_branch===void 0||W(i.execution_branch))&&(i.origin_branch===void 0||W(i.origin_branch))&&(i.branch_lease===void 0||i.branch_lease===null||rt(i.branch_lease))&&(i.origin_binding===void 0||i.origin_binding===null||ct(i.origin_binding))&&(i.graph_revision===void 0||p(i.graph_revision))&&(i.aggregate_qa_receipt===void 0||i.aggregate_qa_receipt===null||ot(i.aggregate_qa_receipt))&&(i.terminal_git_receipt===void 0||i.terminal_git_receipt===null||st(i.terminal_git_receipt))&&(i.physicalMergeStatus===void 0||["merged","pushed","denied_external_gate","blocked_local_or_repo","not_attempted"].includes(String(i.physicalMergeStatus)))&&(i.pr_number===void 0||i.pr_number===null||Number.isSafeInteger(i.pr_number)&&Number(i.pr_number)>0)&&(i.git_reconciliation_receipts===void 0||lt(i.git_reconciliation_receipts))&&(i.git_reconciliation_debt===void 0||ht(i.git_reconciliation_debt))}function tt(i,e){return I(i,et,"git-lifecycle-runtime-story-unknown-field"),(i.checkpoint===void 0||i.checkpoint===null||ci(i.checkpoint,e))&&(i.status===void 0||O(i.status))&&(i.last_step===void 0||i.last_step===null||O(i.last_step))&&(i.steps_completed===void 0||z(i.steps_completed,128))}function rt(i){const e=f(i);return e?(I(e,["schemaVersion","ownerStoryId","sessionId","idempotencyKey","acquiredAt","expiresAt","handoffGrant","handoffConsumption","setupHeadSha"],"git-lifecycle-runtime-lease-unknown-field"),e.schemaVersion===1&&Q(e.ownerStoryId)&&O(e.sessionId)&&O(e.idempotencyKey)&&A(e.acquiredAt)&&A(e.expiresAt)&&Date.parse(String(e.expiresAt))>Date.parse(String(e.acquiredAt))&&(e.setupHeadSha===void 0||D(e.setupHeadSha))&&(e.handoffGrant===void 0||nt(e.handoffGrant,e))&&(e.handoffConsumption===void 0||at(e.handoffConsumption,e))):!1}function nt(i,e){const t=f(i);return t?(I(t,["schemaVersion","kind","storyId","fromSessionId","tokenDigest","bundleDigest","claimRevision","expiresAt"],"git-lifecycle-runtime-handoff-grant-unknown-field"),t.schemaVersion===1&&t.kind==="direct-yolo-setup-handoff"&&t.storyId===e.ownerStoryId&&t.fromSessionId===e.sessionId&&p(t.tokenDigest)&&p(t.bundleDigest)&&p(t.claimRevision)&&A(t.expiresAt)&&Date.parse(String(t.expiresAt))<=Date.parse(String(e.expiresAt))):!1}function at(i,e){const t=f(i);return t?(I(t,["schemaVersion","kind","state","storyId","fromSessionId","tokenDigest","bundleDigest","claimRevision","consumedBySessionId","consumedAt"],"git-lifecycle-runtime-handoff-consumption-unknown-field"),t.schemaVersion===1&&t.kind==="direct-yolo-setup-handoff-consumption"&&t.state==="consumed"&&t.storyId===e.ownerStoryId&&O(t.fromSessionId)&&p(t.tokenDigest)&&p(t.bundleDigest)&&p(t.claimRevision)&&t.consumedBySessionId===e.sessionId&&A(t.consumedAt)):!1}function ct(i){const e=f(i);return e?(I(e,["schemaVersion","originBranch","originHeadSha","executionBranch","capturedFromCurrentCheckout","capturedAt"],"git-lifecycle-runtime-origin-unknown-field"),e.schemaVersion===1&&W(e.originBranch)&&W(e.executionBranch)&&e.originBranch!==e.executionBranch&&D(e.originHeadSha)&&e.capturedFromCurrentCheckout===!0&&A(e.capturedAt)):!1}function ot(i){const e=f(i);if(!e)return!1;I(e,["schemaVersion","evidenceVersion","producer","contract","command","verdict","executionHeadSha","graphRevision","manifestDigest","resultDigest","checkedAt","receiptRef","gateIds"],"git-lifecycle-runtime-aggregate-qa-unknown-field");const t=e.evidenceVersion==="p205-execution-v2"&&e.producer==="local-yolo-tdd"&&e.contract==="P205-CI-02"&&e.command===q&&p(e.manifestDigest)&&p(e.resultDigest),r=e.evidenceVersion===void 0&&e.producer===void 0&&e.contract===void 0&&e.command===void 0&&e.manifestDigest===void 0&&e.resultDigest===void 0;return e.schemaVersion===1&&(t||r)&&e.verdict==="passed"&&D(e.executionHeadSha)&&p(e.graphRevision)&&A(e.checkedAt)&&ue(e.receiptRef)&&z(e.gateIds,128,!0)}function st(i){const e=f(i);return e?(I(e,["schemaVersion","epicId","executionBranch","originBranch","baseSha","executionHeadSha","mergeSha","pushedSha","strategy","prNumber","physicalStatus","localGateReceiptRefs","idempotencyKey","attemptStartedAt","completedAt","retryEligible","reasonCodes","publicSafe","candidateQa","providerGuard"],"git-lifecycle-runtime-terminal-receipt-unknown-field"),e.schemaVersion===1&&Q(e.epicId)&&W(e.executionBranch)&&W(e.originBranch)&&D(e.baseSha)&&D(e.executionHeadSha)&&(e.mergeSha===void 0||D(e.mergeSha))&&(e.pushedSha===void 0||D(e.pushedSha))&&(e.strategy==="direct"||e.strategy==="single_pr")&&(e.candidateQa===void 0||ft(e.candidateQa,e))&&(e.providerGuard===void 0||dt(e.providerGuard,e))&&(e.prNumber===void 0||Number.isSafeInteger(e.prNumber)&&Number(e.prNumber)>0)&&["prepared","merged","pushed","denied_external_gate","blocked_local_or_repo"].includes(String(e.physicalStatus))&&z(e.localGateReceiptRefs,128,!0)&&O(e.idempotencyKey)&&A(e.attemptStartedAt)&&(e.completedAt===void 0||A(e.completedAt))&&typeof e.retryEligible=="boolean"&&z(e.reasonCodes,128)&&e.publicSafe===!0):!1}function dt(i,e){const t=f(i);if(!t)return!1;I(t,["schemaVersion","provider","mode","source","prHeadSha","originHeadSha","checkedAt","publicSafe"],"git-lifecycle-runtime-provider-guard-unknown-field");const r=t.mode==="recovered_exact_tree"?t.source==="physical_recovery":t.mode==="strict_up_to_date_expected_head"&&["branch_protection","ruleset","branch_protection_and_ruleset"].includes(String(t.source));return t.schemaVersion===1&&t.provider==="github"&&r&&e.strategy==="single_pr"&&e.physicalStatus==="pushed"&&Number.isSafeInteger(e.prNumber)&&Number(e.prNumber)>0&&D(t.prHeadSha)&&t.prHeadSha===e.mergeSha&&D(t.originHeadSha)&&t.originHeadSha===e.baseSha&&A(t.checkedAt)&&t.publicSafe===!0}function ft(i,e){const t=f(i);return t?(I(t,["schemaVersion","kind","contract","command","originHeadSha","executionHeadSha","mergeSha","treeSha","graphRevision","manifestDigest","resultDigest","gateIds","checkedAt","receiptRef"],"git-lifecycle-runtime-merge-candidate-unknown-field"),t.schemaVersion===1&&t.kind==="epic_merge_candidate_qa"&&t.contract==="P205-CI-02"&&t.command===q&&D(t.originHeadSha)&&t.originHeadSha===e.baseSha&&D(t.executionHeadSha)&&t.executionHeadSha===e.executionHeadSha&&D(t.mergeSha)&&t.mergeSha===e.mergeSha&&D(t.treeSha)&&p(t.graphRevision)&&p(t.manifestDigest)&&p(t.resultDigest)&&z(t.gateIds,128,!0)&&A(t.checkedAt)&&ue(t.receiptRef)):!1}function ci(i,e){const t=f(i);if(!t)return!1;I(t,["schemaVersion","storyId","epicId","branch","baseSha","headSha","commits","idempotencyKey","dispatchId","bundleId","bundleDigest","graphRevision","claimRevision","receiptRefs","qaVerdict","reviewVerdict","qaReceipt","reviewReceipt","reviewReconciliation","recordedAt","rootMemoryEffect"],"git-lifecycle-runtime-checkpoint-unknown-field");const r=String(e).split(".")[0];return t.schemaVersion===1&&t.storyId===e&&t.epicId===r&&W(t.branch)&&D(t.baseSha)&&D(t.headSha)&&t.baseSha!==t.headSha&&mt(t.commits)&&t.commits.length===1&&t.commits[0]===t.headSha&&O(t.idempotencyKey)&&(t.dispatchId===void 0||p(t.dispatchId))&&(t.bundleId===void 0||p(t.bundleId))&&p(t.bundleDigest)&&p(t.graphRevision)&&(t.claimRevision===void 0||p(t.claimRevision))&&(t.rootMemoryEffect===void 0||Si(t.rootMemoryEffect)&&t.rootMemoryEffect.storyId===e&&t.rootMemoryEffect.executionBranch===t.branch&&t.rootMemoryEffect.sourceHeadSha===t.baseSha&&t.rootMemoryEffect.childClaimRevision===t.claimRevision)&&z(t.receiptRefs,256,!0)&&(t.qaVerdict==="passed"||t.qaVerdict==="blocked")&&["approved","changes-requested","pending"].includes(String(t.reviewVerdict))&&ut(t.qaReceipt,t)&&(t.reviewVerdict==="pending"?t.reviewReceipt===void 0:gt(t.reviewReceipt,t))&&(t.reviewReconciliation===void 0||oi(t.reviewReconciliation,t))&&A(t.recordedAt)}function oi(i,e){const t=f(i),r=f(e.qaReceipt),n=f(e.reviewReceipt);return!t||!r||!n?!1:(I(t,["schemaVersion","kind","storyId","epicId","checkpointHeadSha","approvalCommitSha","approvalArtifactDigest","qaReceiptRef","reviewReceiptRef","sourceEvidenceDigest","reviewedAt","receiptRef"],"git-lifecycle-runtime-review-reconciliation-unknown-field"),t.schemaVersion===1&&t.kind==="historical_review_reconciliation"&&t.storyId===e.storyId&&t.epicId===e.epicId&&t.checkpointHeadSha===e.headSha&&D(t.approvalCommitSha)&&p(t.approvalArtifactDigest)&&t.qaReceiptRef===r.receiptRef&&t.reviewReceiptRef===n.receiptRef&&p(t.sourceEvidenceDigest)&&A(t.reviewedAt)&&Date.parse(String(t.reviewedAt))>=Date.parse(String(r.checkedAt))&&ue(t.receiptRef)&&Array.isArray(e.receiptRefs)&&e.receiptRefs.includes(t.receiptRef))}function ut(i,e){const t=f(i);return t?(I(t,["schemaVersion","kind","producer","evidenceVersion","storyId","epicId","baseSha","headSha","bundleDigest","graphRevision","contract","command","manifestDigest","gateIds","resultDigest","sourceEvidenceDigest","verdict","checkedAt","receiptRef"],"git-lifecycle-runtime-story-qa-unknown-field"),t.schemaVersion===1&&t.kind==="story_qa_evidence"&&t.producer==="local-yolo-tdd"&&(t.evidenceVersion===void 0||t.evidenceVersion==="git-immutable-v1"||t.evidenceVersion==="p205-execution-v2"&&t.contract==="P205-CI-02"&&t.command===q&&p(t.manifestDigest)&&z(t.gateIds,32,!0)&&p(t.resultDigest))&&si(t,e)&&p(t.sourceEvidenceDigest)&&t.verdict===e.qaVerdict&&(t.verdict==="passed"||t.verdict==="blocked")&&A(t.checkedAt)&&ue(t.receiptRef)&&e.receiptRefs.includes(t.receiptRef)):!1}function gt(i,e){const t=f(i),r=f(e.qaReceipt);return!t||!r?!1:(I(t,["schemaVersion","kind","producer","storyId","epicId","baseSha","headSha","bundleDigest","graphRevision","sourceEvidenceDigest","reviewArtifactDigest","qaReceiptRef","verdict","reviewedAt","receiptRef"],"git-lifecycle-runtime-story-review-unknown-field"),t.schemaVersion===1&&t.kind==="story_review_evidence"&&t.producer==="root-commit-range-review"&&si(t,e)&&p(t.sourceEvidenceDigest)&&(t.reviewArtifactDigest===void 0||p(t.reviewArtifactDigest))&&t.qaReceiptRef===r.receiptRef&&t.receiptRef!==r.receiptRef&&(t.verdict==="approved"||t.verdict==="changes-requested")&&(t.verdict==="approved"?e.reviewVerdict==="approved":e.reviewVerdict==="changes-requested")&&A(t.reviewedAt)&&Date.parse(String(t.reviewedAt))>=Date.parse(String(r.checkedAt))&&ue(t.receiptRef)&&e.receiptRefs.includes(t.receiptRef))}function si(i,e){return["storyId","epicId","baseSha","headSha","bundleDigest","graphRevision"].every(t=>i[t]===e[t])}function lt(i){return!Array.isArray(i)||i.length>1024?!1:i.every(e=>{const t=f(e);return t?(I(t,["schemaVersion","resourceId","action","outcome","idempotencyKey","reasonCodes","recordedAt","publicSafe"],"git-lifecycle-runtime-reconciliation-receipt-unknown-field"),t.schemaVersion===1&&O(t.resourceId)&&["no_op","delete_local_branch","delete_remote_branch","close_pull_request"].includes(String(t.action))&&["applied","already_absent","dry_run","failed"].includes(String(t.outcome))&&O(t.idempotencyKey)&&z(t.reasonCodes,128)&&A(t.recordedAt)&&t.publicSafe===!0):!1})}function ht(i){return!Array.isArray(i)||i.length>1024?!1:i.every(e=>{const t=f(e);return t?(I(t,["schemaVersion","resourceId","action","managed","reasonCodes","evidence","publicSafe"],"git-lifecycle-runtime-reconciliation-debt-unknown-field"),t.schemaVersion===1&&O(t.resourceId)&&t.action==="preserve"&&typeof t.managed=="boolean"&&z(t.reasonCodes,128)&&z(t.evidence,16)&&t.publicSafe===!0):!1})}function I(i,e,t){const r=new Set(e);if(Object.keys(i).some(n=>!r.has(n)))throw new Error(t)}function Q(i){return typeof i=="string"&&/^P[1-9][0-9]*(?:\.[0-9]+)*$/.test(i)}function pt(i){const e=f(i);return e?Object.keys(e).length===10&&e.schemaVersion===1&&e.kind==="git_lifecycle_graph_authority"&&e.producer==="server-continuous-yoloop-v1"&&(e.source==="yolo"||e.source==="yoloop")&&Q(e.epicId)&&Q(e.storyId)&&String(e.storyId).startsWith(`${String(e.epicId)}.`)&&W(e.executionBranch)&&W(e.originBranch)&&e.originBranch!==e.executionBranch&&typeof e.graphRevision=="string"&&/^[a-f0-9]{64}$/.test(e.graphRevision)&&e.publicSafe===!0:!1}function W(i){return typeof i=="string"&&/^[A-Za-z0-9][A-Za-z0-9._/-]{0,239}$/.test(i)&&!i.includes("..")&&!i.includes("//")&&!i.includes("@{")&&!i.endsWith("/")&&!i.endsWith(".")&&!i.split("/").some(e=>!e||e.startsWith(".")||e.endsWith(".lock"))}function fe(i){return i.length>0&&i.length<=1024&&!ae(i)&&!i.includes("\0")&&!i.replaceAll("\\","/").split("/").includes("..")}function O(i){return typeof i=="string"&&i.length>0&&i.length<=240&&/^[A-Za-z0-9._:/#\-[\]]+$/.test(i)&&!i.includes("..")}function ue(i){return O(i)}function D(i){return typeof i=="string"&&/^[a-f0-9]{40,64}$/i.test(i)}function p(i){return typeof i=="string"&&/^[a-f0-9]{64}$/i.test(i)}function A(i){return typeof i=="string"&&i.length<=64&&Number.isFinite(Date.parse(i))}function z(i,e,t=!1){return Array.isArray(i)&&i.length<=e&&(!t||i.length>0)&&i.every(O)}function mt(i){return Array.isArray(i)&&i.length>0&&i.length<=256&&i.every(D)}function Pe(){return{schemaVersion:1,revision:0,epics:{},stories:{}}}async function yt(i){const e=K(i),t={pid:process.pid,nonce:De(16).toString("hex")},r=async()=>{await Y(e),await je(e,C(i),JSON.stringify(t));try{await _(i,e),await J(S(i,"owner.json"),i)}catch(n){throw await ee(i,e).catch(()=>{}),n}};try{await r()}catch(n){if(!P(n,"EEXIST"))throw n;await j(i,"directory");const a=await $e(i);if(a!==void 0&&me(a.pid))throw new Error("git-lifecycle-runtime-lock-active");if(a===void 0&&await di(i))throw new Error("git-lifecycle-runtime-lock-active");const s=await B(i);if(!s)throw new Error("git-lifecycle-runtime-lock-owner-invalid");const o=`${i}.stale.${process.pid}.${Date.now()}`;await re(e,C(i),C(o));try{if(await _(o,e),await B(o)!==s)throw await se(o,i,e).catch(()=>{}),new Error("git-lifecycle-runtime-lock-owner-replaced");await r()}finally{await ee(o,e,async u=>await B(u)===s).catch(()=>{})}}return t}async function bt(i,e){const t=K(i);try{await _(i,t);const r=await $e(i);if(!r||!fi(r,e))return!1;const n=await B(i);if(!n)return!1;const a=`${i}.release.${process.pid}.${Date.now()}`;await re(t,C(i),C(a)),await _(a,t);const s=await $e(a);return!s||!fi(s,e)||await B(a)!==n?(await se(a,i,t).catch(()=>{}),!1):ee(a,t,async o=>await B(o)===n)}catch(r){if(P(r,"ENOENT"))return!1;throw r}}async function di(i){try{return Date.now()-(await yi(i)).mtimeMs<3e4}catch{return!0}}async function $e(i){try{await _(i,K(i));const e=S(i,"owner.json");await J(e,i);const t=JSON.parse(await F(e,"utf8"));return Number.isSafeInteger(t.pid)&&Number(t.pid)>0&&(t.nonce===void 0||typeof t.nonce=="string"&&/^[a-f0-9]{32}$/.test(t.nonce))?{pid:Number(t.pid),...typeof t.nonce=="string"?{nonce:t.nonce}:{}}:void 0}catch{return}}function fi(i,e){return i.pid===e.pid&&i.nonce!==void 0&&i.nonce===e.nonce}function me(i){try{return process.kill(i,0),!0}catch(e){return P(e,"EPERM")}}function f(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:void 0}function P(i,e){return i instanceof Error&&"code"in i&&i.code===e}export{le as GIT_LIFECYCLE_RUNTIME_DIRECTORY,he as GIT_LIFECYCLE_RUNTIME_FILE,Me as LEGACY_DIRECT_YOLO_RECOVERY_COMMITS,ki as LEGACY_P174_CHECKPOINT_MIGRATIONS,q as P205_COMPLETE_COMMAND,Tt as acquireGitLifecyclePhysicalIntegrationLock,Bi as applyGitLifecycleRuntimeOverlay,_ as assertGitLifecyclePrivateDirectory,Gt as assertGitLifecyclePrivateFile,Lt as classifyGitLifecycleStoryCheckpointProjectionSync,Ji as computeGitLifecyclePhysicalIntegrationLockOwnerDigest,Ti as computeHistoricalReviewReconciliationDigest,ji as computeImmutableStoryQaEvidenceDigest,jt as computeLegacyDirectYoloRecoveryReceiptRef,Gi as computeP205StoryQaEvidenceDigest,Ki as ensureGitLifecyclePrivateChildDirectory,$t as ensureGitLifecycleRuntimeInitialized,Ni as ensureRuntimeEpic,Nt as ensureRuntimeStory,Oi as executeP205TransientAuthorities,Pt as executeP205TransientAuthority,ei as gitLifecycleRuntimeDirectory,zi as gitLifecycleStoryWriteSet,Je as isAcceptedGitLifecycleStoryCheckpoint,Ke as mutateGitLifecycleRuntime,zt as p205TransientAuthorityMatchesAggregate,Bt as persistGitLifecycleGraphAuthority,pe as posixSecurityAvailable,qt as projectP205TransientExecutionEvidence,Ae as publicStoryReviewArtifactFromGit,Ie as readGitLifecycleRuntime,Pi as readGitLifecycleRuntimeSync,Yi as releaseGitLifecyclePhysicalIntegrationLock,Ft as releaseGitLifecyclePhysicalIntegrationLockForCheckpoint,ee as removeGitLifecyclePrivateDirectory,Vt as verifyAcceptedGitLifecycleStoryCheckpoint,Qe as verifyAcceptedGitLifecycleStoryCheckpointSync,Mt as verifyGitLifecyclePhysicalIntegrationLock,Ht as verifyLegacyCheckpointMigrationCandidateSync};
1
+ /**
2
+ * Durable runtime evidence for root-owned Git lifecycle operations.
3
+ *
4
+ * The document lives in a private Neocortex-owned directory below the Git
5
+ * common-dir, so it is shared by manually-created worktrees but is never part
6
+ * of a story commit. The common-dir itself may legitimately be group-writable;
7
+ * authority comes from the private child directory and fresh physical checks.
8
+ */
9
+ import { execFile, execFileSync } from 'node:child_process';
10
+ import { createHash, randomBytes } from 'node:crypto';
11
+ import { lstat, open, readFile, realpath, stat } from 'node:fs/promises';
12
+ import { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs';
13
+ import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
14
+ import { promisify } from 'node:util';
15
+ import { publicWriteSet } from '../../../shared/dist/yoloop/public-story-write-set.js';
16
+ import { isRootProjectMemoryEffect } from '../private-execution/shared-root-memory-types.js';
17
+ import { verifyRootMemoryPhysicalWriteSet } from '../private-execution/root-memory-git-evidence.js';
18
+ import { resolveBranchUnit, } from '../../../shared/dist/yoloop/branch-unit.js';
19
+ import { anchoredAtomicWriteFile, anchoredCreateOwnedDirectory, anchoredEnsureDirectory, anchoredExecuteGit, anchoredExecuteNode, anchoredRemoveDirectory, anchoredRenameDirectory, } from './secure-filesystem-boundary.js';
20
+ import { GIT_LIFECYCLE_COMMIT_PUSH_STEP_ID, GIT_LIFECYCLE_REVIEW_PR_STEP_ID, GIT_LIFECYCLE_SETUP_BRANCH_STEP_ID, } from './git-lifecycle-step-ids.js';
21
+ const execFileAsync = promisify(execFile);
22
+ export const GIT_LIFECYCLE_RUNTIME_DIRECTORY = 'neocortex-yoloop';
23
+ export const GIT_LIFECYCLE_RUNTIME_FILE = 'runtime.json';
24
+ export const P205_COMPLETE_COMMAND = 'node scripts/test-root.mjs --gate complete';
25
+ export const LEGACY_DIRECT_YOLO_RECOVERY_COMMITS = Object.freeze({
26
+ 'P174.03.029264': '339141be7339a0edf55710cbccb6e6558f514792',
27
+ });
28
+ /**
29
+ * Exact immutable bindings for the seven P174 checkpoints predating P205-v2.
30
+ * This table is only an allowlist: every value is recomputed from Git and the
31
+ * independent QA/review receipts before a fresh aggregate P205 may migrate it.
32
+ */
33
+ export const LEGACY_P174_CHECKPOINT_MIGRATIONS = Object.freeze({
34
+ 'P174.03.811759': Object.freeze({ baseSha: '749c8fc732deaf26c81a8e08cc94b83c8cf0f5ba', headSha: '84162456d9cbe5000ffd0ed3eb3d0e88cfeba499', graphRevision: '60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445', effectiveClaimRevision: '11f54704f5007f6139159419cfd7781f908b1502e117365c6d84011384579ed2', commitBodyDigest: 'b4fc759a49765fd8c0b3d0f5d1c333c2a4bf28d324e83eedabb12f9530df3738', changedPathsDigest: '11f54704f5007f6139159419cfd7781f908b1502e117365c6d84011384579ed2', qaDigest: 'f1100775e21f5d0d990f77303a2a06d936908f016983e32a63316d81aee6b9fc', reviewDigest: '50400bbcddb6f02408837cf99077de5d3af8c84822ab23a8b5499c338af7103a' }),
35
+ 'P174.03.685003': Object.freeze({ baseSha: '84162456d9cbe5000ffd0ed3eb3d0e88cfeba499', headSha: '272a7fee89e4b9324d35701df1c83b671fa96299', graphRevision: '60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445', effectiveClaimRevision: '6cc3c9033f1c4711dcbe3c676d1d7f9906614c7d6e95228e843d1c63cefbc641', commitBodyDigest: 'e97966e26ce3ba31d5951b9fdac67808c88c1fd8be7ec1e73982f3f1cc7ffd81', changedPathsDigest: '6cc3c9033f1c4711dcbe3c676d1d7f9906614c7d6e95228e843d1c63cefbc641', qaDigest: '7439329a93321f39fb6b0de0a41c22e58373a399f9048e362dff4ffa6f1a7d89', reviewDigest: '1b2d12262e84acf9da7d06149a0effb11274ebdc986affbfd47635b855ec7135' }),
36
+ 'P174.03.029264': Object.freeze({ baseSha: '272a7fee89e4b9324d35701df1c83b671fa96299', headSha: '339141be7339a0edf55710cbccb6e6558f514792', graphRevision: '60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445', effectiveClaimRevision: '1053bb2baf487e38fa28d7e8cfb872c5c4da66802534ee226bbc2c7455a6a5ab', commitBodyDigest: '0f269a00dcd168a6f548ff10dfa667b193c948ac9c7211f5cb11a0561757c371', changedPathsDigest: '1053bb2baf487e38fa28d7e8cfb872c5c4da66802534ee226bbc2c7455a6a5ab', qaDigest: 'ef0fd2c6e216557825cd8521ccb2f9960a3af48f18e7a5ada441bee8bfda400b', reviewDigest: '372d0f4a2f32978c8b39f20167637b3917187edb75d49875e93200c4c5a19d0d' }),
37
+ 'P174.03.913359': Object.freeze({ baseSha: '339141be7339a0edf55710cbccb6e6558f514792', headSha: '4ccc90b85c49b27f718090bf25bc452f1b84e5e3', graphRevision: '60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445', effectiveClaimRevision: '12b5cfba2667e8064309807776c1b323561dc8d853f9afe5f676007b8494b2b5', commitBodyDigest: '89a6653197509e75246a758d2706876b337770571a1cac47d9d85ec96c196459', changedPathsDigest: '12b5cfba2667e8064309807776c1b323561dc8d853f9afe5f676007b8494b2b5', qaDigest: 'c7d3762ad30e4f9356243df17a1dd7123cbf1b660808d68176f44d1cd5da6449', reviewDigest: 'e4ecf577e174fd66f31aa4a0113ab672ced16b12ee531f6831aa533795145ba1' }),
38
+ 'P174.03.724350': Object.freeze({ baseSha: '4ccc90b85c49b27f718090bf25bc452f1b84e5e3', headSha: '568e9464fb086304146b9eb4594281f91302aef1', graphRevision: '60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445', effectiveClaimRevision: 'c8b8b6b96edf9cea78925e2f961bd74069d77af03783a5368b8ac8025bf1fefe', commitBodyDigest: 'b3032338bfcd6c9dc460e9426220a1966dedeff9968c4064fdd36a315560721b', changedPathsDigest: 'c8b8b6b96edf9cea78925e2f961bd74069d77af03783a5368b8ac8025bf1fefe', qaDigest: '91f2f5b00935b1b04bb5d400df500cbc97c412072078b713c6e5ba8fda669bd0', reviewDigest: '9bc92069d3b3a76cc8b02d339d432690258be1376464b69e152ed8fd3d682f05' }),
39
+ 'P174.03.011976': Object.freeze({ baseSha: '568e9464fb086304146b9eb4594281f91302aef1', headSha: '195599b1a99a99a8416c4c26c9ab9990847338a4', graphRevision: '60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445', effectiveClaimRevision: '49b7f2a0a967b2ba4249615c6d08834445a60df95045352838808eb4d6743b21', commitBodyDigest: 'b8a28392fac663dd614b695a49c894b7cab116d45722a42850e3b24e2aaf36f4', changedPathsDigest: '49b7f2a0a967b2ba4249615c6d08834445a60df95045352838808eb4d6743b21', qaDigest: '6eb4459a7d80c4a268fc931297e8156ed2a320584f8775f0787d91b42c0929d4', reviewDigest: '98b21de29f7cceeec73da19000c6c144395cce6eb699c6a82dfab505d4ab7b44' }),
40
+ 'P174.03.686659': Object.freeze({ baseSha: '195599b1a99a99a8416c4c26c9ab9990847338a4', headSha: '0db3681a5dad4e25ce37d967c636eb9d54915556', graphRevision: '60b343b4b810e6d5abc3159275012b9ab9ab517f9d780e7550fa5b49be740445', effectiveClaimRevision: '5c0fdbf820d677c288981b6f73f7d2d9a04ce7f4713687f12a2e984705244b8e', commitBodyDigest: '143257f4018ad773afc0c7280bd8aab8786851a9c20b65f8110881ff54656839', changedPathsDigest: '5c0fdbf820d677c288981b6f73f7d2d9a04ce7f4713687f12a2e984705244b8e', qaDigest: '6303dbf473e2815a3671aeb2c25528d8daba484b92a5a40d1eea5215e1f6763e', reviewDigest: '1bd6695846ed9340c8677cfa9b573880532187aeee487efaaa70577a0c94e7f2' }),
41
+ });
42
+ const LEGACY_STORE_FILE = 'neocortex-yoloop-runtime.json';
43
+ const LOCK_DIRECTORY = 'runtime.lock';
44
+ const TRANSIENT_P205_AUTHORITIES = new WeakMap();
45
+ export async function executeP205TransientAuthority(projectRoot, input) {
46
+ const authorities = await executeP205TransientAuthorities(projectRoot, {
47
+ headSha: input.headSha,
48
+ graphRevisions: [input.graphRevision],
49
+ ...(input.nowIso !== undefined ? { nowIso: input.nowIso } : {}),
50
+ ...(input.anchoredWorkspace !== undefined ? { anchoredWorkspace: input.anchoredWorkspace } : {}),
51
+ });
52
+ return authorities.get(input.graphRevision);
53
+ }
54
+ /**
55
+ * Execute the physical P205 gate once for one immutable HEAD and mint a
56
+ * separate opaque authority for every historical graph epoch accepted at that
57
+ * HEAD. The graph revision remains part of each evidence digest; only the
58
+ * expensive, graph-independent command execution is shared.
59
+ */
60
+ export async function executeP205TransientAuthorities(projectRoot, input) {
61
+ const graphRevisions = [...new Set(input.graphRevisions)];
62
+ if (!isSha(input.headSha) || graphRevisions.length === 0 || graphRevisions.some((value) => !isHash(value))) {
63
+ throw new Error('p205-transient-binding-invalid');
64
+ }
65
+ const anchor = input.anchoredWorkspace;
66
+ if (anchor && resolve(projectRoot) !== join(resolve(anchor.parent), anchor.name)) {
67
+ throw new Error('p205-transient-anchor-binding-invalid');
68
+ }
69
+ const runGit = async (args) => anchor
70
+ ? (await anchoredExecuteGit(anchor.parent, anchor.name, anchor.identity, args)).toString('utf8')
71
+ : String((await execFileAsync('git', [...args], { cwd: projectRoot, encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 })).stdout);
72
+ const readProducer = async () => {
73
+ if (anchor)
74
+ return JSON.parse((await anchoredExecuteNode(anchor.parent, anchor.name, anchor.identity, [
75
+ '-e', "const fs=require('node:fs');process.stdout.write(JSON.stringify({manifest:fs.readFileSync('config/release-gates.v2.json','utf8'),runner:fs.readFileSync('scripts/test-root.mjs','utf8')}));",
76
+ ])).toString('utf8'));
77
+ return { manifest: await readFile(join(projectRoot, 'config', 'release-gates.v2.json'), 'utf8'),
78
+ runner: await readFile(join(projectRoot, 'scripts', 'test-root.mjs'), 'utf8') };
79
+ };
80
+ const beforeHead = (await runGit(['rev-parse', 'HEAD'])).trim();
81
+ if (beforeHead !== input.headSha)
82
+ throw new Error('p205-transient-head-mismatch');
83
+ const { manifest: manifestRaw, runner: runnerRaw } = await readProducer();
84
+ const [committedManifest, committedRunner] = await Promise.all([
85
+ runGit(['show', `${input.headSha}:config/release-gates.v2.json`]),
86
+ runGit(['show', `${input.headSha}:scripts/test-root.mjs`]),
87
+ ]);
88
+ if (manifestRaw !== committedManifest || runnerRaw !== committedRunner) {
89
+ throw new Error('p205-transient-producer-dirty');
90
+ }
91
+ const manifest = JSON.parse(manifestRaw);
92
+ const gateIds = Array.isArray(manifest.requiredCompleteClasses)
93
+ ? manifest.requiredCompleteClasses.filter((item) => typeof item === 'string' && /^[a-z0-9-]+$/.test(item))
94
+ : [];
95
+ if (manifest.contract !== 'P205-CI-02' || gateIds.length === 0)
96
+ throw new Error('p205-transient-manifest-invalid');
97
+ const gateEnv = { ...process.env };
98
+ delete gateEnv.NEOCORTEX_PROFILE_DIR;
99
+ const output = anchor
100
+ ? (await anchoredExecuteNode(anchor.parent, anchor.name, anchor.identity, ['scripts/test-root.mjs', '--gate', 'complete'])).toString('utf8')
101
+ : String((await execFileAsync(process.execPath, ['scripts/test-root.mjs', '--gate', 'complete'], {
102
+ cwd: projectRoot, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, env: gateEnv,
103
+ })).stdout);
104
+ const { manifest: afterManifest, runner: afterRunner } = await readProducer();
105
+ if (afterManifest !== manifestRaw || afterRunner !== runnerRaw) {
106
+ throw new Error('p205-transient-producer-raced');
107
+ }
108
+ const afterHead = (await runGit(['rev-parse', 'HEAD'])).trim();
109
+ if (afterHead !== input.headSha)
110
+ throw new Error('p205-transient-head-raced');
111
+ const manifestDigest = createHash('sha256').update(manifestRaw).digest('hex');
112
+ const resultDigest = createHash('sha256').update(output).digest('hex');
113
+ const executedAt = normalizedIso(input.nowIso ?? new Date().toISOString());
114
+ const authorities = new Map();
115
+ const frozenGateIds = Object.freeze(gateIds);
116
+ for (const graphRevision of graphRevisions) {
117
+ const executionEvidenceDigest = createHash('sha256').update([
118
+ 'p205-transient-execution-v1', P205_COMPLETE_COMMAND, input.headSha, graphRevision,
119
+ manifestDigest, gateIds.join(','), resultDigest, executedAt,
120
+ ].join('\0')).digest('hex');
121
+ const evidence = Object.freeze({
122
+ schemaVersion: 1, kind: 'p205_transient_execution_authority',
123
+ contract: 'P205-CI-02', command: P205_COMPLETE_COMMAND,
124
+ executionHeadSha: input.headSha, graphRevision,
125
+ manifestDigest, gateIds: frozenGateIds, resultDigest, executedAt,
126
+ executionEvidenceDigest,
127
+ });
128
+ const authority = Object.freeze(Object.create(null));
129
+ TRANSIENT_P205_AUTHORITIES.set(authority, evidence);
130
+ authorities.set(graphRevision, authority);
131
+ }
132
+ return authorities;
133
+ }
134
+ export async function readGitLifecycleRuntime(projectRoot, options = {}) {
135
+ const storePath = await runtimeStorePath(projectRoot);
136
+ return readDocument(storePath, options);
137
+ }
138
+ export function readGitLifecycleRuntimeSync(projectRoot, options = {}) {
139
+ let path;
140
+ try {
141
+ const commonDir = execFileSync('git', ['rev-parse', '--git-common-dir'], {
142
+ cwd: projectRoot,
143
+ encoding: 'utf8',
144
+ stdio: ['ignore', 'pipe', 'ignore'],
145
+ }).trim();
146
+ const canonicalRoot = realpathSync(projectRoot);
147
+ const absolute = isAbsolute(commonDir) ? resolve(commonDir) : resolve(canonicalRoot, commonDir);
148
+ const canonicalCommonDir = realpathSync(absolute);
149
+ if (canonicalCommonDir !== absolute)
150
+ throw new Error('git-lifecycle-common-dir-symlink');
151
+ assertCommonDirectoryEntrySync(canonicalCommonDir);
152
+ const runtimeDirectory = join(canonicalCommonDir, GIT_LIFECYCLE_RUNTIME_DIRECTORY);
153
+ const currentPath = join(runtimeDirectory, GIT_LIFECYCLE_RUNTIME_FILE);
154
+ if (existsSync(runtimeDirectory)) {
155
+ assertSafeFilesystemEntrySync(runtimeDirectory, 'directory');
156
+ if (realpathSync(runtimeDirectory) !== runtimeDirectory)
157
+ throw new Error('git-lifecycle-runtime-directory-symlink');
158
+ }
159
+ path = existsSync(currentPath) ? currentPath : join(canonicalCommonDir, LEGACY_STORE_FILE);
160
+ }
161
+ catch {
162
+ if (options.required)
163
+ throw new Error('git-lifecycle-common-dir-invalid');
164
+ return emptyDocument();
165
+ }
166
+ if (!existsSync(path)) {
167
+ if (options.required)
168
+ throw new Error('git-lifecycle-runtime-missing');
169
+ return emptyDocument();
170
+ }
171
+ assertSafeFilesystemEntrySync(path, 'file');
172
+ return validateDocument(JSON.parse(readFileSync(path, 'utf8')));
173
+ }
174
+ /**
175
+ * Materialize the private lifecycle sidecar for repositories that already
176
+ * declare the epic-branch contract in tracked state but predate the sidecar.
177
+ * Missing state is the only recoverable condition: corrupt, substituted or
178
+ * permission-unsafe paths continue to fail closed.
179
+ */
180
+ export async function ensureGitLifecycleRuntimeInitialized(projectRoot) {
181
+ try {
182
+ await readGitLifecycleRuntime(projectRoot, { required: true });
183
+ return;
184
+ }
185
+ catch (error) {
186
+ if (!(error instanceof Error) || error.message !== 'git-lifecycle-runtime-missing')
187
+ throw error;
188
+ }
189
+ try {
190
+ await mutateGitLifecycleRuntime(projectRoot, () => undefined, { expectedRevision: 0 });
191
+ }
192
+ catch (error) {
193
+ // Concurrent first-use initialization is idempotent. Accept only a proven
194
+ // winning document; every other filesystem or validation error propagates.
195
+ if (!(error instanceof Error) || error.message !== 'git-lifecycle-runtime-revision-conflict')
196
+ throw error;
197
+ await readGitLifecycleRuntime(projectRoot, { required: true });
198
+ }
199
+ }
200
+ function isLiveSameStorySetupLease(lease, storyId, nowMs) {
201
+ if (!lease || lease.ownerStoryId !== storyId)
202
+ return false;
203
+ const sessionId = typeof lease.sessionId === 'string' ? lease.sessionId : '';
204
+ const expiresAt = Date.parse(String(lease.expiresAt));
205
+ return /^setup-[a-f0-9]{64}$/.test(sessionId)
206
+ && lease.idempotencyKey === sessionId
207
+ && Number.isFinite(expiresAt)
208
+ && expiresAt > nowMs;
209
+ }
210
+ /**
211
+ * Close the server-plan -> client -> P207 sidecar handoff. The authority is
212
+ * accepted only for the exact tracked epic/story/branch projection that the
213
+ * server selected. A genuinely new epic may bootstrap its deterministic
214
+ * `epic/<ID>` unit into the private sidecar after the complete tracked topology
215
+ * proves there is no legacy story Git progress. Tracked-state drift, explicit
216
+ * lifecycle conflicts and sidecar revision races fail closed; no tracked,
217
+ * staged or working-tree bytes are modified.
218
+ */
219
+ export async function persistGitLifecycleGraphAuthority(projectRoot, authority, nowIso = new Date().toISOString()) {
220
+ if (!isGitLifecycleGraphAuthority(authority))
221
+ throw new Error('git-lifecycle-graph-authority-invalid');
222
+ const trackedPath = join(resolve(projectRoot), '.neocortex', 'state.json');
223
+ const trackedRaw = await readFile(trackedPath, 'utf8');
224
+ const tracked = asRecord(JSON.parse(trackedRaw));
225
+ if (!tracked)
226
+ throw new Error('git-lifecycle-tracked-state-invalid');
227
+ // An absent sidecar is the normal first-use state for a new epic. Reading
228
+ // without `required` yields revision zero while corrupt/unsafe stores still
229
+ // fail closed. The authenticated authority mutation below materializes the
230
+ // sidecar only after every tracked-state invariant has passed.
231
+ const runtime = await readGitLifecycleRuntime(projectRoot);
232
+ const effective = applyGitLifecycleRuntimeOverlay(tracked, runtime);
233
+ const epic = asRecord(asRecord(effective.epics)?.[authority.epicId]);
234
+ const story = asRecord(asRecord(effective.stories)?.[authority.storyId]);
235
+ if (!epic)
236
+ throw new Error('git-lifecycle-graph-authority-epic-invalid');
237
+ if (!story || String(story.epic_id ?? authority.storyId.split('.')[0]) !== authority.epicId) {
238
+ throw new Error('git-lifecycle-graph-authority-story-invalid');
239
+ }
240
+ const declaredStoryIds = Array.isArray(epic.stories)
241
+ ? epic.stories.filter((value) => typeof value === 'string')
242
+ : Object.keys(asRecord(effective.stories) ?? {}).filter((storyId) => {
243
+ const candidate = asRecord(asRecord(effective.stories)?.[storyId]);
244
+ return String(candidate?.epic_id ?? storyId.split('.')[0]) === authority.epicId;
245
+ });
246
+ if (Array.isArray(epic.stories) && !declaredStoryIds.includes(authority.storyId)) {
247
+ throw new Error('git-lifecycle-graph-authority-story-invalid');
248
+ }
249
+ const branchStories = declaredStoryIds.map((storyId) => {
250
+ const candidate = asRecord(asRecord(effective.stories)?.[storyId]) ?? {};
251
+ return {
252
+ id: storyId,
253
+ branch_name: typeof candidate.branch_name === 'string' || candidate.branch_name === null
254
+ ? candidate.branch_name : undefined,
255
+ pr_number: typeof candidate.pr_number === 'number' || candidate.pr_number === null
256
+ ? candidate.pr_number : undefined,
257
+ steps_completed: Array.isArray(candidate.steps_completed)
258
+ ? candidate.steps_completed.filter((value) => typeof value === 'string') : [],
259
+ checkpoint: asRecord(candidate.checkpoint),
260
+ };
261
+ });
262
+ const branchUnit = resolveBranchUnit({ ...epic, id: authority.epicId }, branchStories);
263
+ if (branchUnit.mode !== 'epic')
264
+ throw new Error('git-lifecycle-graph-authority-epic-invalid');
265
+ if (branchUnit.executionBranch !== authority.executionBranch) {
266
+ throw new Error('git-lifecycle-graph-authority-branch-mismatch');
267
+ }
268
+ if (branchUnit.originBranch !== undefined && branchUnit.originBranch !== authority.originBranch) {
269
+ throw new Error('git-lifecycle-graph-authority-origin-mismatch');
270
+ }
271
+ const runtimeOriginBranch = typeof asRecord(runtime.epics[authority.epicId])?.origin_branch === 'string'
272
+ ? String(asRecord(runtime.epics[authority.epicId]).origin_branch)
273
+ : undefined;
274
+ if (runtimeOriginBranch !== undefined && runtimeOriginBranch !== authority.originBranch) {
275
+ throw new Error('git-lifecycle-graph-authority-origin-mismatch');
276
+ }
277
+ // Bootstrap may capture the origin only from the physical checkout selected
278
+ // by the operator. Once recorded, later invocations can legitimately run on
279
+ // the epic execution branch and are bound by the persisted origin instead.
280
+ if (branchUnit.originBranch === undefined && runtimeOriginBranch === undefined) {
281
+ const { stdout } = await execFileAsync('git', ['branch', '--show-current'], {
282
+ cwd: projectRoot, encoding: 'utf8', maxBuffer: 1024 * 1024,
283
+ });
284
+ if (String(stdout).trim() !== authority.originBranch) {
285
+ throw new Error('git-lifecycle-graph-authority-origin-physical-mismatch');
286
+ }
287
+ }
288
+ const currentGraphRevision = typeof epic.graph_revision === 'string' ? epic.graph_revision : undefined;
289
+ const runtimeGraphRevision = typeof asRecord(runtime.epics[authority.epicId])?.graph_revision === 'string'
290
+ ? String(asRecord(runtime.epics[authority.epicId]).graph_revision)
291
+ : undefined;
292
+ if (currentGraphRevision !== undefined && !isHash(currentGraphRevision)) {
293
+ throw new Error('git-lifecycle-graph-revision-invalid');
294
+ }
295
+ if (currentGraphRevision === authority.graphRevision && runtimeGraphRevision === authority.graphRevision
296
+ && branchUnit.originBranch === authority.originBranch && runtimeOriginBranch === authority.originBranch) {
297
+ return { applied: false, reused: true, revision: runtime.revision, graphRevision: authority.graphRevision };
298
+ }
299
+ const nowMs = Date.parse(nowIso);
300
+ if (!Number.isFinite(nowMs))
301
+ throw new Error('git-lifecycle-graph-authority-time-invalid');
302
+ const lease = asRecord(epic.branch_lease);
303
+ if (lease && Number.isFinite(Date.parse(String(lease.expiresAt)))
304
+ && Date.parse(String(lease.expiresAt)) > nowMs
305
+ && !isLiveSameStorySetupLease(lease, authority.storyId, nowMs)) {
306
+ throw new Error('git-lifecycle-graph-authority-writer-active');
307
+ }
308
+ const mutation = await mutateGitLifecycleRuntime(projectRoot, (document) => {
309
+ const runtimeEpic = ensureRuntimeEpic(document, authority.epicId);
310
+ if (runtimeEpic.branch_mode !== undefined && runtimeEpic.branch_mode !== 'epic') {
311
+ throw new Error('git-lifecycle-graph-authority-epic-raced');
312
+ }
313
+ const persistedBranch = runtimeEpic.execution_branch;
314
+ if (persistedBranch !== undefined && persistedBranch !== authority.executionBranch) {
315
+ throw new Error('git-lifecycle-graph-authority-branch-raced');
316
+ }
317
+ const persistedOrigin = runtimeEpic.origin_branch;
318
+ if (persistedOrigin !== undefined && persistedOrigin !== authority.originBranch) {
319
+ throw new Error('git-lifecycle-graph-authority-origin-raced');
320
+ }
321
+ const persistedRevision = runtimeEpic.graph_revision;
322
+ if (persistedRevision !== undefined && persistedRevision !== currentGraphRevision) {
323
+ throw new Error('git-lifecycle-graph-authority-epoch-raced');
324
+ }
325
+ const persistedLease = asRecord(runtimeEpic.branch_lease);
326
+ if (persistedLease && Number.isFinite(Date.parse(String(persistedLease.expiresAt)))
327
+ && Date.parse(String(persistedLease.expiresAt)) > nowMs
328
+ && !isLiveSameStorySetupLease(persistedLease, authority.storyId, nowMs)) {
329
+ throw new Error('git-lifecycle-graph-authority-writer-active');
330
+ }
331
+ runtimeEpic.branch_mode = 'epic';
332
+ runtimeEpic.execution_branch = authority.executionBranch;
333
+ runtimeEpic.origin_branch = authority.originBranch;
334
+ runtimeEpic.graph_revision = authority.graphRevision;
335
+ }, {
336
+ expectedRevision: runtime.revision,
337
+ preCommit: async () => {
338
+ if (await readFile(trackedPath, 'utf8') !== trackedRaw) {
339
+ throw new Error('git-lifecycle-graph-authority-tracked-state-raced');
340
+ }
341
+ },
342
+ });
343
+ return {
344
+ applied: true,
345
+ reused: false,
346
+ revision: mutation.document.revision,
347
+ graphRevision: authority.graphRevision,
348
+ };
349
+ }
350
+ export async function mutateGitLifecycleRuntime(projectRoot, mutate, options = {}) {
351
+ const commonDir = await gitCommonDir(projectRoot);
352
+ const runtimeDirectory = await ensureRuntimeDirectory(commonDir);
353
+ const lockPath = join(runtimeDirectory, LOCK_DIRECTORY);
354
+ const lockOwner = await acquireLock(lockPath);
355
+ try {
356
+ const storePath = join(runtimeDirectory, GIT_LIFECYCLE_RUNTIME_FILE);
357
+ const legacyStorePath = join(commonDir, LEGACY_STORE_FILE);
358
+ const current = await readDocument(existsSync(storePath) ? storePath : legacyStorePath);
359
+ if (options.expectedRevision !== undefined && options.expectedRevision !== current.revision) {
360
+ throw new Error('git-lifecycle-runtime-revision-conflict');
361
+ }
362
+ const mutable = structuredClone(current);
363
+ const value = mutate(mutable);
364
+ if (current.schemaVersion === 2 && options.nextSchemaVersion !== undefined
365
+ && options.nextSchemaVersion !== 2) {
366
+ throw new Error('git-lifecycle-runtime-schema-downgrade');
367
+ }
368
+ const next = validateDocument({
369
+ ...mutable,
370
+ schemaVersion: options.nextSchemaVersion ?? current.schemaVersion,
371
+ revision: current.revision + 1,
372
+ });
373
+ await options.preCommit?.(next);
374
+ await assertSafeFilesystemEntry(runtimeDirectory, 'directory');
375
+ await assertStoreDestinationSafe(storePath);
376
+ await anchoredAtomicWriteFile(runtimeDirectory, GIT_LIFECYCLE_RUNTIME_FILE, `${JSON.stringify(next, null, 2)}\n`);
377
+ await assertSafeFilesystemEntry(storePath, 'file');
378
+ await syncDirectory(runtimeDirectory);
379
+ return { document: next, value };
380
+ }
381
+ finally {
382
+ await releaseRuntimeLock(lockPath, lockOwner).catch(() => undefined);
383
+ }
384
+ }
385
+ async function syncDirectory(path) {
386
+ let handle;
387
+ try {
388
+ handle = await open(path, 'r');
389
+ }
390
+ catch (error) {
391
+ // Node cannot open directories on Windows. The file itself has already
392
+ // been flushed; keep the stronger parent-directory flush everywhere else.
393
+ if (process.platform === 'win32' && (isNodeError(error, 'EPERM') || isNodeError(error, 'EISDIR') || isNodeError(error, 'EINVAL')))
394
+ return;
395
+ throw error;
396
+ }
397
+ try {
398
+ await handle.sync();
399
+ }
400
+ finally {
401
+ await handle.close();
402
+ }
403
+ }
404
+ /** Merge the runtime overlay over tracked state without mutating either input. */
405
+ export function applyGitLifecycleRuntimeOverlay(tracked, runtime) {
406
+ const merged = structuredClone(tracked);
407
+ const epics = normalizeTrackedCollection(merged, 'epics');
408
+ const stories = normalizeTrackedCollection(merged, 'stories');
409
+ for (const [epicId, overlay] of Object.entries(runtime.epics)) {
410
+ epics[epicId] = { ...(asRecord(epics[epicId]) ?? {}), ...structuredClone(overlay) };
411
+ }
412
+ for (const [storyId, overlay] of Object.entries(runtime.stories)) {
413
+ const trackedStory = asRecord(stories[storyId]) ?? {};
414
+ const trackedSteps = trackedStory.steps_completed;
415
+ const runtimeSteps = overlay.steps_completed;
416
+ const c01OnlyOverlay = overlay.status === 'branch-ready'
417
+ && overlay.last_step === GIT_LIFECYCLE_SETUP_BRANCH_STEP_ID
418
+ && Array.isArray(runtimeSteps) && runtimeSteps.length === 1
419
+ && runtimeSteps[0] === GIT_LIFECYCLE_SETUP_BRANCH_STEP_ID
420
+ && overlay.checkpoint === undefined;
421
+ // Once the tracked story includes C01, later tracked steps must not be
422
+ // masked by the private C01-only bridge used before the first commit.
423
+ if (c01OnlyOverlay && Array.isArray(trackedSteps)
424
+ && trackedSteps.includes(GIT_LIFECYCLE_SETUP_BRANCH_STEP_ID))
425
+ continue;
426
+ stories[storyId] = { ...trackedStory, ...structuredClone(overlay) };
427
+ }
428
+ return merged;
429
+ }
430
+ /**
431
+ * Older project state used arrays while current consumers require records keyed
432
+ * by canonical IDs. Normalize only the in-memory projection and fail closed on
433
+ * ambiguous entries; the tracked document remains byte-for-byte untouched.
434
+ */
435
+ function normalizeTrackedCollection(parent, key) {
436
+ const current = parent[key];
437
+ if (current === undefined) {
438
+ const created = {};
439
+ parent[key] = created;
440
+ return created;
441
+ }
442
+ const record = asRecord(current);
443
+ if (record)
444
+ return record;
445
+ if (!Array.isArray(current))
446
+ throw new Error(`git-lifecycle-tracked-${key}-invalid`);
447
+ const normalized = {};
448
+ for (const entry of current) {
449
+ const value = asRecord(entry);
450
+ const id = typeof value?.id === 'string' ? value.id.trim() : '';
451
+ if (!id || normalized[id] !== undefined)
452
+ throw new Error(`git-lifecycle-tracked-${key}-ambiguous`);
453
+ normalized[id] = value;
454
+ }
455
+ parent[key] = normalized;
456
+ return normalized;
457
+ }
458
+ export function ensureRuntimeEpic(document, epicId) {
459
+ return document.epics[epicId] ?? (document.epics[epicId] = {});
460
+ }
461
+ export function ensureRuntimeStory(document, storyId) {
462
+ return document.stories[storyId] ?? (document.stories[storyId] = {});
463
+ }
464
+ /** Runtime-safe accepted-checkpoint predicate kept in the published client closure. */
465
+ export function isAcceptedGitLifecycleStoryCheckpoint(value, storyId, epicId, executionBranch) {
466
+ const item = asRecord(value);
467
+ return Boolean(item && item.epicId === epicId && item.branch === executionBranch
468
+ && item.qaVerdict === 'passed' && item.reviewVerdict === 'approved'
469
+ && validateCheckpoint(item, storyId));
470
+ }
471
+ /**
472
+ * Structural JSON is untrusted under a same-UID threat model. Recompute the
473
+ * commit-range/review authority from Git before a checkpoint can authorize
474
+ * aggregate QA or terminal integration.
475
+ */
476
+ export async function verifyAcceptedGitLifecycleStoryCheckpoint(projectRoot, value, storyId, epicId, executionBranch, transientAuthority, authorizedWriteSet = []) {
477
+ return verifyAcceptedGitLifecycleStoryCheckpointSync(projectRoot, value, storyId, epicId, executionBranch, transientAuthority, authorizedWriteSet).verdict === 'verified';
478
+ }
479
+ /**
480
+ * Synchronous variant used at the state-snapshot boundary, before any runtime
481
+ * overlay is sent to the server. It returns a fresh, public, Git-derived receipt.
482
+ */
483
+ export function verifyAcceptedGitLifecycleStoryCheckpointSync(projectRoot, value, storyId, epicId, executionBranch, transientAuthority, authorizedWriteSet = [], nowIso = new Date().toISOString(), mode = transientAuthority === undefined ? 'scheduling' : 'integration') {
484
+ const rejected = () => checkpointVerification({
485
+ storyId, epicId, executionBranch, value, verifiedAt: nowIso,
486
+ verdict: 'rejected', reasonCode: 'checkpoint-git-physical-invalid',
487
+ });
488
+ try {
489
+ if (!isAcceptedGitLifecycleStoryCheckpoint(value, storyId, epicId, executionBranch))
490
+ return rejected();
491
+ const checkpoint = asRecord(value);
492
+ const qa = asRecord(checkpoint.qaReceipt);
493
+ const review = asRecord(checkpoint.reviewReceipt);
494
+ const headSha = String(checkpoint.headSha);
495
+ const baseSha = String(checkpoint.baseSha);
496
+ const publicReview = publicStoryReviewArtifactFromGit(projectRoot, storyId, headSha, review.reviewArtifactDigest === undefined);
497
+ const legacyWithoutPublicArtifact = review.reviewArtifactDigest === undefined
498
+ && !publicStoryReviewBlobExistsInGit(projectRoot, storyId, headSha);
499
+ if (review.reviewArtifactDigest !== undefined && review.reviewArtifactDigest !== publicReview?.digest)
500
+ return rejected();
501
+ const branchHead = gitSync(projectRoot, ['rev-parse', `refs/heads/${executionBranch}`]).trim();
502
+ execFileSync('git', ['merge-base', '--is-ancestor', headSha, branchHead], { cwd: projectRoot, stdio: 'ignore' });
503
+ const historicalReviewPending = !legacyWithoutPublicArtifact && publicReview?.verdict !== 'approved';
504
+ if (historicalReviewPending) {
505
+ if (review.reviewArtifactDigest !== undefined || !verifyHistoricalReviewReconciliation(projectRoot, checkpoint, review, qa, storyId, epicId, branchHead))
506
+ return rejected();
507
+ }
508
+ else if (checkpoint.reviewReconciliation !== undefined) {
509
+ // A supplemental receipt cannot replace or shadow an already approved blob.
510
+ return rejected();
511
+ }
512
+ const commitBody = gitSync(projectRoot, ['show', '--no-patch', '--format=%H%n%P%n%B', headSha]);
513
+ const parentSha = gitSync(projectRoot, ['rev-parse', `${headSha}^`]).trim();
514
+ const commitCount = gitSync(projectRoot, ['rev-list', '--count', `${baseSha}..${headSha}`]).trim();
515
+ if (parentSha !== baseSha || commitCount !== '1')
516
+ return rejected();
517
+ const trailers = new Set(commitBody.split('\n').map((line) => line.trim()));
518
+ const reviewTrailers = commitBody.split('\n').map((line) => line.trim())
519
+ .filter((line) => line.startsWith('Neocortex-Review-Artifact-Digest: '));
520
+ if (reviewTrailers.length > 0 && (reviewTrailers.length !== 1
521
+ || reviewTrailers[0] !== `Neocortex-Review-Artifact-Digest: ${publicReview?.digest}`
522
+ || review.reviewArtifactDigest !== publicReview?.digest))
523
+ return rejected();
524
+ const requiredTrailers = [
525
+ `Neocortex-Story: ${storyId}`,
526
+ `Neocortex-Epic: ${epicId}`,
527
+ `Neocortex-Execution-Branch: ${executionBranch}`,
528
+ `Neocortex-Bundle-Digest: ${String(checkpoint.bundleDigest)}`,
529
+ `Neocortex-Graph-Revision: ${String(checkpoint.graphRevision)}`,
530
+ ...(checkpoint.dispatchId ? [`Neocortex-Dispatch: ${String(checkpoint.dispatchId)}`] : []),
531
+ ...(checkpoint.bundleId ? [`Neocortex-Bundle: ${String(checkpoint.bundleId)}`] : []),
532
+ ...(checkpoint.claimRevision ? [`Neocortex-Claim-Revision: ${String(checkpoint.claimRevision)}`] : []),
533
+ ];
534
+ const hasCanonicalTrailers = requiredTrailers.every((trailer) => trailers.has(trailer));
535
+ if (hasCanonicalTrailers) {
536
+ const preparedBaseLine = [...trailers].find((line) => line.startsWith('Neocortex-Prepared-Base: '));
537
+ const preparedBase = preparedBaseLine?.slice('Neocortex-Prepared-Base: '.length) ?? '';
538
+ if (!/^[a-f0-9]{40,64}$/i.test(preparedBase))
539
+ return rejected();
540
+ execFileSync('git', ['merge-base', '--is-ancestor', preparedBase, headSha], { cwd: projectRoot, stdio: 'ignore' });
541
+ }
542
+ const changedRaw = gitSync(projectRoot, [
543
+ 'diff-tree', '--no-commit-id', '--name-only', '-r', '-z', headSha,
544
+ ]);
545
+ const changedPaths = changedRaw.split('\0').filter(Boolean);
546
+ if (changedPaths.length === 0 || changedPaths.some((path) => !safeRepositoryPath(path)))
547
+ return rejected();
548
+ const commitBodyDigest = createHash('sha256').update(commitBody).digest('hex');
549
+ const changedPathsDigest = createHash('sha256').update([...changedPaths].sort().join('\0')).digest('hex');
550
+ const claimProof = checkpointClaimProof({
551
+ projectRoot, checkpoint, storyId, epicId, executionBranch, baseSha, headSha,
552
+ commitBodyDigest, changedPathsDigest, changedPaths,
553
+ authorizedWriteSet: authorizedWriteSet.length > 0
554
+ ? authorizedWriteSet : trackedStoryWriteSet(projectRoot, storyId),
555
+ });
556
+ if (!claimProof)
557
+ return rejected();
558
+ if (qa.receiptRef !== `story-qa:${String(qa.sourceEvidenceDigest)}`)
559
+ return rejected();
560
+ const expectedQaDigest = computeImmutableStoryQaEvidenceDigest({
561
+ storyId, baseSha, headSha,
562
+ bundleDigest: String(checkpoint.bundleDigest), graphRevision: String(checkpoint.graphRevision),
563
+ commitBody, changedPaths,
564
+ });
565
+ const executionQa = qa.evidenceVersion === 'p205-execution-v2'
566
+ && qa.contract === 'P205-CI-02'
567
+ && qa.command === P205_COMPLETE_COMMAND
568
+ && isHash(qa.manifestDigest)
569
+ && safeStringArray(qa.gateIds, 32, true)
570
+ && isHash(qa.resultDigest)
571
+ && qa.sourceEvidenceDigest === computeP205StoryQaEvidenceDigest({
572
+ gitEvidenceDigest: expectedQaDigest,
573
+ headSha,
574
+ graphRevision: String(checkpoint.graphRevision),
575
+ manifestDigest: String(qa.manifestDigest),
576
+ gateIds: qa.gateIds,
577
+ resultDigest: String(qa.resultDigest),
578
+ });
579
+ const immutableQa = qa.evidenceVersion === 'git-immutable-v1'
580
+ && qa.sourceEvidenceDigest === expectedQaDigest;
581
+ const boundedLegacyQa = qa.evidenceVersion === undefined
582
+ && claimProof.mode === 'legacy-migration'
583
+ && claimProof.binding?.qaDigest === qa.sourceEvidenceDigest;
584
+ const aggregateP205 = transientP205AuthorityMatches(transientAuthority, branchHead, String(checkpoint.graphRevision));
585
+ const transientQa = aggregateP205 && (executionQa
586
+ || claimProof.mode === 'legacy-migration' && (immutableQa || boundedLegacyQa));
587
+ const schedulingQa = mode === 'scheduling' && transientAuthority === undefined && executionQa;
588
+ if (!transientQa && !schedulingQa)
589
+ return rejected();
590
+ const artifactBound = review.reviewArtifactDigest !== undefined;
591
+ const expectedReviewDigest = createHash('sha256').update([
592
+ artifactBound ? 'root-commit-range-review-v2' : 'root-commit-range-review-v1', storyId, baseSha, headSha,
593
+ String(checkpoint.bundleDigest), String(checkpoint.graphRevision), String(qa.receiptRef),
594
+ createHash('sha256').update(commitBody).digest('hex'), [...changedPaths].sort().join(','),
595
+ ...(artifactBound ? [publicReview.digest] : []),
596
+ ].join('\0')).digest('hex');
597
+ if (review.sourceEvidenceDigest !== expectedReviewDigest
598
+ || review.receiptRef !== `story-review:${expectedReviewDigest}`)
599
+ return rejected();
600
+ if (claimProof.mode === 'legacy-migration'
601
+ && claimProof.binding?.reviewDigest !== undefined
602
+ && claimProof.binding.reviewDigest !== review.sourceEvidenceDigest)
603
+ return rejected();
604
+ if (!hasCanonicalTrailers) {
605
+ if (LEGACY_DIRECT_YOLO_RECOVERY_COMMITS[storyId] !== headSha
606
+ || !Array.isArray(checkpoint.receiptRefs)
607
+ || !checkpoint.receiptRefs.some((item) => typeof item === 'string' && item.startsWith('legacy-direct-yolo:'))) {
608
+ return rejected();
609
+ }
610
+ }
611
+ const sourceEvidenceDigest = createHash('sha256').update([
612
+ 'checkpoint-p205-transient-v1', storyId, epicId, executionBranch, baseSha, headSha,
613
+ String(checkpoint.bundleDigest), commitBodyDigest, changedPathsDigest,
614
+ claimProof.mode, claimProof.effectiveClaimRevision,
615
+ String(qa.sourceEvidenceDigest), String(review.sourceEvidenceDigest),
616
+ ...(checkpoint.reviewReconciliation
617
+ ? [String(asRecord(checkpoint.reviewReconciliation)?.sourceEvidenceDigest)] : []),
618
+ transientAuthorityEvidence(transientAuthority)?.executionEvidenceDigest
619
+ ?? (schedulingQa ? 'scheduling-git-verification' : 'legacy-bounded-authority'),
620
+ ].join('\0')).digest('hex');
621
+ const effectiveAuthority = transientQa ? transientAuthorityEvidence(transientAuthority) : undefined;
622
+ return Object.freeze({
623
+ schemaVersion: 1, kind: 'story_checkpoint_physical_verification', producer: 'client-p205-transient-v1',
624
+ storyId, epicId, branch: executionBranch, baseSha, headSha,
625
+ aggregateHeadSha: effectiveAuthority?.executionHeadSha ?? headSha,
626
+ commitBodyDigest, changedPathsDigest,
627
+ claimVerification: claimProof.mode,
628
+ effectiveClaimRevision: claimProof.effectiveClaimRevision,
629
+ sourceEvidenceDigest,
630
+ command: P205_COMPLETE_COMMAND,
631
+ manifestDigest: effectiveAuthority?.manifestDigest ?? '0'.repeat(64),
632
+ gateIds: effectiveAuthority?.gateIds ?? Object.freeze(['legacy-bounded']),
633
+ resultDigest: effectiveAuthority?.resultDigest ?? '0'.repeat(64),
634
+ graphRevision: String(checkpoint.graphRevision),
635
+ executionEvidenceDigest: effectiveAuthority?.executionEvidenceDigest ?? '0'.repeat(64),
636
+ verifiedAt: effectiveAuthority?.executedAt ?? normalizedIso(nowIso), verdict: 'verified',
637
+ reasonCodes: Object.freeze([
638
+ transientQa ? 'checkpoint-p205-transient-verified'
639
+ : schedulingQa ? 'checkpoint-git-scheduling-verified'
640
+ : 'checkpoint-legacy-bounded-verified',
641
+ ...(claimProof.mode === 'legacy-migration' ? ['checkpoint-legacy-migration-verified'] : []),
642
+ ]), publicSafe: true,
643
+ });
644
+ }
645
+ catch {
646
+ return rejected();
647
+ }
648
+ }
649
+ /**
650
+ * Classify a checkpoint for one story without allowing physical scheduling
651
+ * evidence to impersonate transient P205 integration authority.
652
+ */
653
+ export function classifyGitLifecycleStoryCheckpointProjectionSync(projectRoot, value, storyId, epicId, executionBranch, transientAuthority, authorizedWriteSet = [], nowIso = new Date().toISOString()) {
654
+ const mode = transientAuthority ? 'integration' : 'scheduling';
655
+ const verification = verifyAcceptedGitLifecycleStoryCheckpointSync(projectRoot, value, storyId, epicId, executionBranch, transientAuthority, authorizedWriteSet, nowIso, mode);
656
+ if (verification.verdict !== 'verified')
657
+ return { kind: 'rejected', verification };
658
+ if (mode === 'integration')
659
+ return { kind: 'integration-authorized', verification };
660
+ if (!verification.reasonCodes.includes('checkpoint-git-scheduling-verified')) {
661
+ return { kind: 'rejected', verification: checkpointVerification({
662
+ storyId, epicId, executionBranch, value, verifiedAt: nowIso,
663
+ verdict: 'rejected', reasonCode: 'checkpoint-git-physical-invalid',
664
+ }) };
665
+ }
666
+ return {
667
+ kind: 'scheduling-only',
668
+ evidence: Object.freeze({
669
+ schemaVersion: 1,
670
+ kind: 'story_scheduling_checkpoint_evidence',
671
+ producer: 'client-git-physical-v1',
672
+ storyId,
673
+ epicId,
674
+ branch: executionBranch,
675
+ baseSha: verification.baseSha,
676
+ headSha: verification.headSha,
677
+ graphRevision: verification.graphRevision,
678
+ effectiveClaimRevision: verification.effectiveClaimRevision,
679
+ sourceEvidenceDigest: verification.sourceEvidenceDigest,
680
+ verifiedAt: verification.verifiedAt,
681
+ reasonCodes: Object.freeze(['checkpoint-git-scheduling-verified']),
682
+ integrationAuthorized: false,
683
+ publicSafe: true,
684
+ }),
685
+ };
686
+ }
687
+ function transientP205AuthorityMatches(authority, aggregateHeadSha, graphRevision) {
688
+ const evidence = transientAuthorityEvidence(authority);
689
+ return Boolean(evidence
690
+ && evidence.schemaVersion === 1 && evidence.kind === 'p205_transient_execution_authority'
691
+ && evidence.contract === 'P205-CI-02' && evidence.command === P205_COMPLETE_COMMAND
692
+ && evidence.graphRevision === graphRevision
693
+ && isHash(evidence.resultDigest) && isHash(evidence.executionEvidenceDigest)
694
+ && evidence.executionHeadSha === aggregateHeadSha && isSha(aggregateHeadSha));
695
+ }
696
+ function transientAuthorityEvidence(authority) {
697
+ return authority && typeof authority === 'object' ? TRANSIENT_P205_AUTHORITIES.get(authority) : undefined;
698
+ }
699
+ /** Read-only projection for persistence/audit; it never recreates authority. */
700
+ export function projectP205TransientExecutionEvidence(authority) {
701
+ const evidence = transientAuthorityEvidence(authority);
702
+ return evidence ? Object.freeze({
703
+ executionHeadSha: evidence.executionHeadSha,
704
+ graphRevision: evidence.graphRevision,
705
+ manifestDigest: evidence.manifestDigest,
706
+ gateIds: Object.freeze([...evidence.gateIds]),
707
+ resultDigest: evidence.resultDigest,
708
+ executedAt: evidence.executedAt,
709
+ executionEvidenceDigest: evidence.executionEvidenceDigest,
710
+ }) : undefined;
711
+ }
712
+ export function p205TransientAuthorityMatchesAggregate(authority, headSha, graphRevision) {
713
+ const evidence = transientAuthorityEvidence(authority);
714
+ return Boolean(evidence && evidence.executionHeadSha === headSha
715
+ && evidence.graphRevision === graphRevision && evidence.command === P205_COMPLETE_COMMAND);
716
+ }
717
+ function checkpointClaimProof(input) {
718
+ const legacy = LEGACY_P174_CHECKPOINT_MIGRATIONS[input.storyId];
719
+ const persistedClaim = typeof input.checkpoint.claimRevision === 'string' ? input.checkpoint.claimRevision : undefined;
720
+ const claimlessLegacy = input.storyId === 'P174.03.811759' || input.storyId === 'P174.03.685003';
721
+ const normalized = normalizeAuthorizedWriteSet(input.authorizedWriteSet);
722
+ let rootMemoryPaths = [];
723
+ if (input.checkpoint.rootMemoryEffect !== undefined) {
724
+ if (legacy)
725
+ return undefined;
726
+ try {
727
+ rootMemoryPaths = verifyRootMemoryPhysicalWriteSet(input.projectRoot, input.checkpoint.rootMemoryEffect, {
728
+ storyId: input.storyId, executionBranch: input.executionBranch, baseSha: input.baseSha,
729
+ headRef: input.headSha, childPaths: normalized,
730
+ }).files.map(file => file.path);
731
+ }
732
+ catch {
733
+ return undefined;
734
+ }
735
+ }
736
+ const declaredWriteSetValid = normalized.length > 0 && normalized.length === input.authorizedWriteSet.length
737
+ && input.changedPaths.every((path) => normalized.some((scope) => writeSetCovers(scope, path)) || rootMemoryPaths.includes(path));
738
+ if (legacy
739
+ && input.epicId === 'P174' && input.executionBranch === 'epic/P174'
740
+ && input.baseSha === legacy.baseSha && input.headSha === legacy.headSha
741
+ && input.checkpoint.graphRevision === legacy.graphRevision
742
+ && input.commitBodyDigest === legacy.commitBodyDigest
743
+ && input.changedPathsDigest === legacy.changedPathsDigest
744
+ && (claimlessLegacy ? persistedClaim === undefined : persistedClaim === legacy.effectiveClaimRevision)) {
745
+ if (input.authorizedWriteSet.length > 0
746
+ && (!declaredWriteSetValid || !claimMatchesWriteSet(legacy.effectiveClaimRevision, normalized)))
747
+ return undefined;
748
+ return Object.freeze({ mode: 'legacy-migration', effectiveClaimRevision: legacy.effectiveClaimRevision, binding: legacy });
749
+ }
750
+ // Claimless compatibility is closed over the seven immutable P174 ranges
751
+ // above. Format, age, story name and otherwise-valid JSON evidence never
752
+ // authorize a generic migration.
753
+ if (persistedClaim === undefined)
754
+ return undefined;
755
+ if (!persistedClaim || !isHash(persistedClaim))
756
+ return undefined;
757
+ if (!declaredWriteSetValid || !claimMatchesWriteSet(persistedClaim, normalized))
758
+ return undefined;
759
+ return Object.freeze({ mode: 'canonical', effectiveClaimRevision: persistedClaim });
760
+ }
761
+ function normalizeAuthorizedWriteSet(values) {
762
+ const normalized = values.map((value) => value.replaceAll('\\', '/').replace(/^\.\//, '').replace(/\/{2,}/g, '/'));
763
+ if (normalized.some((value) => !safeRepositoryPath(value)))
764
+ return Object.freeze([]);
765
+ return Object.freeze([...new Set(normalized)].sort());
766
+ }
767
+ function trackedStoryWriteSet(projectRoot, storyId) {
768
+ try {
769
+ const state = JSON.parse(readFileSync(join(projectRoot, '.neocortex', 'state.json'), 'utf8'));
770
+ const stories = asRecord(state.stories);
771
+ const story = asRecord(stories?.[storyId]);
772
+ const runtime = readGitLifecycleRuntimeSync(projectRoot);
773
+ const checkpoint = asRecord(runtime.stories[storyId]?.checkpoint) ?? asRecord(story?.checkpoint);
774
+ return gitLifecycleStoryWriteSet(storyId, story, checkpoint);
775
+ }
776
+ catch {
777
+ return [];
778
+ }
779
+ }
780
+ /** Reconstruct the same closed claim used by the direct or continuous planner. */
781
+ export function gitLifecycleStoryWriteSet(storyId, story, checkpoint) {
782
+ const authored = Array.isArray(story?.files_to_modify)
783
+ ? story.files_to_modify.filter((value) => typeof value === 'string') : [];
784
+ if (!checkpoint?.dispatchId || !checkpoint.bundleId)
785
+ return authored;
786
+ try {
787
+ return publicWriteSet({ ...story, id: storyId, files_to_modify: authored });
788
+ }
789
+ catch {
790
+ return [];
791
+ }
792
+ }
793
+ function claimMatchesWriteSet(claimRevision, writeSet) {
794
+ return claimRevision === createHash('sha256').update(writeSet.join('\0')).digest('hex')
795
+ || claimRevision === createHash('sha256').update(JSON.stringify(writeSet)).digest('hex');
796
+ }
797
+ function writeSetCovers(scope, path) {
798
+ return scope === path || scope.endsWith('/') && path.startsWith(scope);
799
+ }
800
+ /** Pre-P205 admission for only the exact seven immutable P174 migrations. */
801
+ export function verifyLegacyCheckpointMigrationCandidateSync(projectRoot, value, storyId, epicId, executionBranch, authorizedWriteSet) {
802
+ try {
803
+ if (!isAcceptedGitLifecycleStoryCheckpoint(value, storyId, epicId, executionBranch))
804
+ return false;
805
+ const checkpoint = asRecord(value);
806
+ const qa = asRecord(checkpoint.qaReceipt);
807
+ const review = asRecord(checkpoint.reviewReceipt);
808
+ const headSha = String(checkpoint.headSha);
809
+ const baseSha = String(checkpoint.baseSha);
810
+ const branchHead = gitSync(projectRoot, ['rev-parse', `refs/heads/${executionBranch}`]).trim();
811
+ execFileSync('git', ['merge-base', '--is-ancestor', headSha, branchHead], { cwd: projectRoot, stdio: 'ignore' });
812
+ if (gitSync(projectRoot, ['rev-parse', `${headSha}^`]).trim() !== baseSha
813
+ || gitSync(projectRoot, ['rev-list', '--count', `${baseSha}..${headSha}`]).trim() !== '1')
814
+ return false;
815
+ const commitBody = gitSync(projectRoot, ['show', '--no-patch', '--format=%H%n%P%n%B', headSha]);
816
+ const changedPaths = gitSync(projectRoot, ['diff-tree', '--no-commit-id', '--name-only', '-r', '-z', headSha])
817
+ .split('\0').filter(Boolean);
818
+ if (changedPaths.length === 0 || changedPaths.some((path) => !safeRepositoryPath(path)))
819
+ return false;
820
+ const proof = checkpointClaimProof({
821
+ projectRoot, checkpoint, storyId, epicId, executionBranch, baseSha, headSha, changedPaths, authorizedWriteSet,
822
+ commitBodyDigest: createHash('sha256').update(commitBody).digest('hex'),
823
+ changedPathsDigest: createHash('sha256').update([...changedPaths].sort().join('\0')).digest('hex'),
824
+ });
825
+ if (proof?.mode !== 'legacy-migration' || !proof.binding
826
+ || qa.sourceEvidenceDigest !== proof.binding.qaDigest
827
+ || qa.receiptRef !== `story-qa:${proof.binding.qaDigest}`
828
+ || review.sourceEvidenceDigest !== proof.binding.reviewDigest
829
+ || review.receiptRef !== `story-review:${proof.binding.reviewDigest}`
830
+ || review.qaReceiptRef !== qa.receiptRef)
831
+ return false;
832
+ return true;
833
+ }
834
+ catch {
835
+ return false;
836
+ }
837
+ }
838
+ export function computeImmutableStoryQaEvidenceDigest(input) {
839
+ return createHash('sha256').update([
840
+ 'local-yolo-tdd-git-immutable-v1', input.storyId, input.baseSha, input.headSha,
841
+ input.bundleDigest, input.graphRevision,
842
+ createHash('sha256').update(input.commitBody).digest('hex'),
843
+ createHash('sha256').update([...input.changedPaths].sort().join('\0')).digest('hex'),
844
+ ].join('\0')).digest('hex');
845
+ }
846
+ /** Bind an actual successful P205 execution to immutable Git authority. */
847
+ export function computeP205StoryQaEvidenceDigest(input) {
848
+ return createHash('sha256').update([
849
+ 'local-yolo-tdd-p205-execution-v2', P205_COMPLETE_COMMAND,
850
+ input.gitEvidenceDigest, input.headSha, input.graphRevision,
851
+ input.manifestDigest, input.gateIds.join(','), input.resultDigest,
852
+ ].join('\0')).digest('hex');
853
+ }
854
+ function checkpointVerification(input) {
855
+ const checkpoint = asRecord(input.value);
856
+ return Object.freeze({
857
+ schemaVersion: 1, kind: 'story_checkpoint_physical_verification', producer: 'client-p205-transient-v1',
858
+ storyId: input.storyId, epicId: input.epicId, branch: input.executionBranch,
859
+ baseSha: isSha(checkpoint?.baseSha) ? String(checkpoint?.baseSha) : '0'.repeat(40),
860
+ headSha: isSha(checkpoint?.headSha) ? String(checkpoint?.headSha) : '0'.repeat(40),
861
+ aggregateHeadSha: '0'.repeat(40),
862
+ commitBodyDigest: '0'.repeat(64), changedPathsDigest: '0'.repeat(64),
863
+ claimVerification: 'canonical', effectiveClaimRevision: '0'.repeat(64),
864
+ command: P205_COMPLETE_COMMAND, manifestDigest: '0'.repeat(64), gateIds: Object.freeze([]),
865
+ resultDigest: '0'.repeat(64), graphRevision: '0'.repeat(64), executionEvidenceDigest: '0'.repeat(64),
866
+ sourceEvidenceDigest: '0'.repeat(64), verifiedAt: normalizedIso(input.verifiedAt),
867
+ verdict: input.verdict, reasonCodes: Object.freeze([input.reasonCode]), publicSafe: true,
868
+ });
869
+ }
870
+ function gitSync(cwd, args) {
871
+ return execFileSync('git', [...args], { cwd, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
872
+ }
873
+ /** Read public review evidence from Git, never from the mutable worktree. */
874
+ export function publicStoryReviewArtifactFromGit(projectRoot, storyId, revision, allowLegacyDecision = false) {
875
+ if (!/^[A-Za-z][A-Za-z0-9_-]*\.[A-Za-z0-9._-]+$/.test(storyId))
876
+ return undefined;
877
+ if (revision !== ':' && !/^[a-f0-9]{40,64}$/i.test(revision))
878
+ return undefined;
879
+ const path = `.neocortex/stories/${storyId}/review.md`;
880
+ let bytes;
881
+ try {
882
+ bytes = execFileSync('git', ['show', `${revision === ':' ? ':' : `${revision}:`}${path}`], {
883
+ cwd: projectRoot, maxBuffer: 128 * 1024, stdio: ['ignore', 'pipe', 'ignore'],
884
+ });
885
+ }
886
+ catch {
887
+ return undefined;
888
+ }
889
+ if (bytes.length === 0 || bytes.length > 64 * 1024 || bytes.includes(0))
890
+ return undefined;
891
+ let content;
892
+ try {
893
+ content = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
894
+ }
895
+ catch {
896
+ return undefined;
897
+ }
898
+ const lines = content.replace(/\r\n/g, '\n').split('\n');
899
+ const storyHeading = new RegExp(`^#\\s+${storyId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?=\\s|$|[:—-])`);
900
+ if (!lines.some((line) => storyHeading.test(line)))
901
+ return undefined;
902
+ const verdictLines = lines.filter((line) => /^Verdict\s*:/i.test(line));
903
+ if (lines.some((line) => /^Status\s*:\s*(pending|changes-requested|rejected)\b/i.test(line))) {
904
+ return undefined;
905
+ }
906
+ const legacyDecisionLines = lines.filter((line) => /^Decision\s*:/i.test(line));
907
+ if (verdictLines.length === 0 && allowLegacyDecision
908
+ && legacyDecisionLines.length === 1
909
+ && /^Decision\s*:\s*APPROVED\b/i.test(legacyDecisionLines[0])) {
910
+ return Object.freeze({ verdict: 'approved', digest: createHash('sha256').update(bytes).digest('hex') });
911
+ }
912
+ if (verdictLines.length !== 1)
913
+ return undefined;
914
+ const verdict = /^Verdict\s*:\s*(approved|pending|changes-requested)(?=\s|$|[;,.])/i.exec(verdictLines[0])?.[1]?.toLowerCase();
915
+ if (verdict !== 'approved' && verdict !== 'pending' && verdict !== 'changes-requested')
916
+ return undefined;
917
+ return Object.freeze({ verdict, digest: createHash('sha256').update(bytes).digest('hex') });
918
+ }
919
+ /** Binds a later root approval to the exact immutable range of a legacy checkpoint. */
920
+ export function computeHistoricalReviewReconciliationDigest(input) {
921
+ return createHash('sha256').update([
922
+ 'root-historical-review-reconciliation-v1', input.storyId, input.epicId,
923
+ input.checkpointHeadSha, input.approvalCommitSha, input.approvalArtifactDigest,
924
+ input.qaReceiptRef, input.reviewReceiptRef,
925
+ createHash('sha256').update(input.approvalCommitBody).digest('hex'),
926
+ ].join('\0')).digest('hex');
927
+ }
928
+ function verifyHistoricalReviewReconciliation(projectRoot, checkpoint, review, qa, storyId, epicId, branchHead) {
929
+ const receipt = asRecord(checkpoint.reviewReconciliation);
930
+ if (!receipt || !validateHistoricalReviewReconciliation(receipt, checkpoint))
931
+ return false;
932
+ const approvalSha = String(receipt.approvalCommitSha);
933
+ const checkpointHead = String(checkpoint.headSha);
934
+ if (approvalSha === checkpointHead)
935
+ return false;
936
+ try {
937
+ execFileSync('git', ['merge-base', '--is-ancestor', checkpointHead, approvalSha], { cwd: projectRoot, stdio: 'ignore' });
938
+ execFileSync('git', ['merge-base', '--is-ancestor', approvalSha, branchHead], { cwd: projectRoot, stdio: 'ignore' });
939
+ if (!gitSync(projectRoot, ['rev-list', '--first-parent', branchHead]).split('\n').includes(approvalSha))
940
+ return false;
941
+ const parents = gitSync(projectRoot, ['show', '--no-patch', '--format=%P', approvalSha]).trim().split(/\s+/);
942
+ if (parents.length !== 1 || !isSha(parents[0]))
943
+ return false;
944
+ const approvalCommitBody = gitSync(projectRoot, ['show', '--no-patch', '--format=%H%n%P%n%B', approvalSha]);
945
+ const trailers = new Set(approvalCommitBody.split('\n').map((line) => line.trim()));
946
+ if (!trailers.has(`Neocortex-Review-Reconciliation: ${storyId}`)
947
+ || !trailers.has(`Neocortex-Original-Checkpoint: ${checkpointHead}`)
948
+ || !trailers.has(`Neocortex-Original-Review-Receipt: ${String(review.receiptRef)}`))
949
+ return false;
950
+ const reviewPath = `.neocortex/stories/${storyId}/review.md`;
951
+ const changedPaths = gitSync(projectRoot, ['diff-tree', '--no-commit-id', '--name-only', '-r', '-z', approvalSha])
952
+ .split('\0').filter(Boolean);
953
+ if (!changedPaths.includes(reviewPath) || changedPaths.some((path) => !safeRepositoryPath(path)))
954
+ return false;
955
+ const approved = publicStoryReviewArtifactFromGit(projectRoot, storyId, approvalSha);
956
+ if (approved?.verdict !== 'approved' || approved.digest !== receipt.approvalArtifactDigest)
957
+ return false;
958
+ const current = publicStoryReviewArtifactFromGit(projectRoot, storyId, branchHead);
959
+ if (current?.verdict !== 'approved' || current.digest !== approved.digest)
960
+ return false;
961
+ const digest = computeHistoricalReviewReconciliationDigest({
962
+ storyId, epicId, checkpointHeadSha: checkpointHead, approvalCommitSha: approvalSha,
963
+ approvalArtifactDigest: approved.digest, qaReceiptRef: String(qa.receiptRef),
964
+ reviewReceiptRef: String(review.receiptRef), approvalCommitBody,
965
+ });
966
+ return receipt.sourceEvidenceDigest === digest
967
+ && receipt.receiptRef === `story-review-reconciliation:${digest}`;
968
+ }
969
+ catch {
970
+ return false;
971
+ }
972
+ }
973
+ function publicStoryReviewBlobExistsInGit(projectRoot, storyId, headSha) {
974
+ if (!/^[A-Za-z][A-Za-z0-9_-]*\.[A-Za-z0-9._-]+$/.test(storyId)
975
+ || !/^[a-f0-9]{40,64}$/i.test(headSha))
976
+ return false;
977
+ try {
978
+ execFileSync('git', ['cat-file', '-e', `${headSha}:.neocortex/stories/${storyId}/review.md`], {
979
+ cwd: projectRoot, stdio: 'ignore', maxBuffer: 1024,
980
+ });
981
+ return true;
982
+ }
983
+ catch {
984
+ return false;
985
+ }
986
+ }
987
+ function normalizedIso(value) {
988
+ return Number.isFinite(Date.parse(value)) ? new Date(Date.parse(value)).toISOString() : new Date().toISOString();
989
+ }
990
+ /**
991
+ * Bounded migration evidence for direct-*yolo commits created before canonical
992
+ * trailers were mandatory. The receipt is derived only from immutable commit
993
+ * data and story-owned evidence blobs; JSON sidecar shape alone is never enough.
994
+ */
995
+ export async function computeLegacyDirectYoloRecoveryReceiptRef(projectRoot, input) {
996
+ if (LEGACY_DIRECT_YOLO_RECOVERY_COMMITS[input.storyId] !== input.headSha)
997
+ return undefined;
998
+ const evidencePaths = [
999
+ '.neocortex/state.json',
1000
+ `.neocortex/stories/${input.storyId}/qa-gate-result.json`,
1001
+ `.neocortex/stories/${input.storyId}/validation-evidence.md`,
1002
+ `.neocortex/stories/${input.storyId}/review.md`,
1003
+ `docs/stories/${input.storyId}.story.md`,
1004
+ ];
1005
+ try {
1006
+ const branchHead = (await execFileAsync('git', ['rev-parse', `refs/heads/${input.executionBranch}`], {
1007
+ cwd: projectRoot, encoding: 'utf8', maxBuffer: 1024 * 1024,
1008
+ })).stdout.trim();
1009
+ await execFileAsync('git', ['merge-base', '--is-ancestor', input.headSha, branchHead], { cwd: projectRoot });
1010
+ const parentSha = (await execFileAsync('git', ['rev-parse', `${input.headSha}^`], {
1011
+ cwd: projectRoot, encoding: 'utf8', maxBuffer: 1024 * 1024,
1012
+ })).stdout.trim();
1013
+ if (parentSha !== input.baseSha)
1014
+ return undefined;
1015
+ const immutablePayload = String((await execFileAsync('git', [
1016
+ 'diff', '--binary', '--full-index', `${input.baseSha}..${input.headSha}`,
1017
+ ], { cwd: projectRoot, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 })).stdout);
1018
+ if (createHash('sha256').update(immutablePayload).digest('hex') !== input.bundleDigest)
1019
+ return undefined;
1020
+ const commitBody = String((await execFileAsync('git', ['show', '--no-patch', '--format=%H%n%P%n%B', input.headSha], {
1021
+ cwd: projectRoot, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024,
1022
+ })).stdout);
1023
+ const subject = String((await execFileAsync('git', ['show', '--no-patch', '--format=%s', input.headSha], {
1024
+ cwd: projectRoot, encoding: 'utf8', maxBuffer: 1024 * 1024,
1025
+ })).stdout).trim();
1026
+ if (!subject.includes(input.storyId))
1027
+ return undefined;
1028
+ const changedRaw = String((await execFileAsync('git', [
1029
+ 'diff-tree', '--no-commit-id', '--name-only', '-r', '-z', input.headSha,
1030
+ ], { cwd: projectRoot, encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 })).stdout);
1031
+ const changedPaths = changedRaw.split('\0').filter(Boolean);
1032
+ if (!evidencePaths.every((path) => changedPaths.includes(path))
1033
+ || changedPaths.some((path) => !safeRepositoryPath(path)))
1034
+ return undefined;
1035
+ const blobs = await Promise.all(evidencePaths.map(async (path) => String((await execFileAsync('git', [
1036
+ 'show', `${input.headSha}:${path}`,
1037
+ ], { cwd: projectRoot, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 })).stdout)));
1038
+ const state = JSON.parse(blobs[0]);
1039
+ const stories = asRecord(state.stories);
1040
+ const epics = asRecord(state.epics);
1041
+ const story = asRecord(stories?.[input.storyId]);
1042
+ const epic = asRecord(epics?.[input.epicId]);
1043
+ const steps = Array.isArray(story?.steps_completed) ? story.steps_completed : [];
1044
+ const qa = JSON.parse(blobs[1]);
1045
+ if (story?.status !== 'done' || !steps.includes(GIT_LIFECYCLE_COMMIT_PUSH_STEP_ID) || !steps.includes(GIT_LIFECYCLE_REVIEW_PR_STEP_ID)
1046
+ || epic?.branch_mode !== 'epic' || epic.execution_branch !== input.executionBranch
1047
+ || qa.storyId !== input.storyId || qa.status !== 'qa-passed' || qa.safeToContinueYoloop !== true
1048
+ || !Array.isArray(qa.blockingChecks) || qa.blockingChecks.length !== 0
1049
+ || !blobs[2].includes(input.storyId) || !/P205[\s\S]{0,80}(pass|aprov)/i.test(blobs[2])
1050
+ || !blobs[3].includes(input.storyId) || !/approved|aprovado/i.test(blobs[3])
1051
+ || !blobs[4].includes(input.storyId))
1052
+ return undefined;
1053
+ const digest = createHash('sha256').update([
1054
+ 'legacy-direct-yolo-recovery-v1', input.storyId, input.epicId, input.executionBranch,
1055
+ input.baseSha, input.headSha, input.bundleDigest, input.graphRevision,
1056
+ createHash('sha256').update(commitBody).digest('hex'),
1057
+ createHash('sha256').update([...changedPaths].sort().join('\0')).digest('hex'),
1058
+ ...blobs.map((blob) => createHash('sha256').update(blob).digest('hex')),
1059
+ ].join('\0')).digest('hex');
1060
+ return `legacy-direct-yolo:${digest}`;
1061
+ }
1062
+ catch {
1063
+ return undefined;
1064
+ }
1065
+ }
1066
+ async function runtimeStorePath(projectRoot) {
1067
+ const commonDir = await gitCommonDir(projectRoot);
1068
+ const runtimeDirectory = join(commonDir, GIT_LIFECYCLE_RUNTIME_DIRECTORY);
1069
+ const currentPath = join(runtimeDirectory, GIT_LIFECYCLE_RUNTIME_FILE);
1070
+ if (existsSync(runtimeDirectory)) {
1071
+ await assertSafeFilesystemEntry(runtimeDirectory, 'directory');
1072
+ if (await realpath(runtimeDirectory) !== runtimeDirectory)
1073
+ throw new Error('git-lifecycle-runtime-directory-symlink');
1074
+ }
1075
+ return existsSync(currentPath) ? currentPath : join(commonDir, LEGACY_STORE_FILE);
1076
+ }
1077
+ /** Resolve/create the private root used by lifecycle state, locks and sandboxes. */
1078
+ export async function gitLifecycleRuntimeDirectory(projectRoot) {
1079
+ return ensureRuntimeDirectory(await gitCommonDir(projectRoot));
1080
+ }
1081
+ /** Create or validate one private direct child without following substitutions. */
1082
+ export async function ensureGitLifecyclePrivateChildDirectory(parent, childName) {
1083
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(childName) || childName.includes('..')) {
1084
+ throw new Error('git-lifecycle-private-child-invalid');
1085
+ }
1086
+ const canonicalParent = resolve(parent);
1087
+ await assertPrivateDirectory(canonicalParent);
1088
+ const child = resolve(canonicalParent, childName);
1089
+ assertDirectChild(canonicalParent, child);
1090
+ if (!existsSync(child))
1091
+ await anchoredEnsureDirectory(canonicalParent, childName);
1092
+ await assertPrivateDirectory(child, canonicalParent);
1093
+ await assertPrivateDirectory(canonicalParent);
1094
+ return child;
1095
+ }
1096
+ /** Validate a private directory and every direct parent binding before use. */
1097
+ export async function assertGitLifecyclePrivateDirectory(path, parent) {
1098
+ const canonicalParent = resolve(parent);
1099
+ const child = resolve(path);
1100
+ assertDirectChild(canonicalParent, child);
1101
+ await assertPrivateDirectory(canonicalParent);
1102
+ await assertPrivateDirectory(child, canonicalParent);
1103
+ }
1104
+ export async function assertGitLifecyclePrivateFile(path, parent) {
1105
+ await assertSafePrivateFile(path, parent);
1106
+ }
1107
+ /** Remove only the validated directory object renamed inside its validated parent. */
1108
+ export async function removeGitLifecyclePrivateDirectory(path, parent, verifyRenamed) {
1109
+ const canonicalParent = resolve(parent);
1110
+ const child = resolve(path);
1111
+ assertDirectChild(canonicalParent, child);
1112
+ try {
1113
+ await assertGitLifecyclePrivateDirectory(child, canonicalParent);
1114
+ }
1115
+ catch (error) {
1116
+ if (isNodeError(error, 'ENOENT'))
1117
+ return false;
1118
+ throw error;
1119
+ }
1120
+ const quarantine = resolve(canonicalParent, `${child.slice(canonicalParent.length + 1)}.remove.${process.pid}.${Date.now()}.${randomBytes(4).toString('hex')}`);
1121
+ assertDirectChild(canonicalParent, quarantine);
1122
+ await anchoredRenameDirectory(canonicalParent, basename(child), basename(quarantine));
1123
+ await assertGitLifecyclePrivateDirectory(quarantine, canonicalParent);
1124
+ if (verifyRenamed && !(await verifyRenamed(quarantine))) {
1125
+ await restoreQuarantinedPrivateDirectory(quarantine, child, canonicalParent).catch(() => undefined);
1126
+ return false;
1127
+ }
1128
+ await anchoredRemoveDirectory(canonicalParent, basename(quarantine));
1129
+ return true;
1130
+ }
1131
+ async function restoreQuarantinedPrivateDirectory(quarantine, original, parent) {
1132
+ await assertGitLifecyclePrivateDirectory(quarantine, parent);
1133
+ try {
1134
+ await lstat(original);
1135
+ return;
1136
+ }
1137
+ catch (error) {
1138
+ if (!isNodeError(error, 'ENOENT'))
1139
+ throw error;
1140
+ }
1141
+ await anchoredRenameDirectory(parent, basename(quarantine), basename(original));
1142
+ await assertGitLifecyclePrivateDirectory(original, parent);
1143
+ }
1144
+ async function privateOwnerFingerprint(path) {
1145
+ try {
1146
+ await assertGitLifecyclePrivateDirectory(path, dirname(path));
1147
+ const ownerPath = join(path, 'owner.json');
1148
+ await assertSafePrivateFile(ownerPath, path);
1149
+ const owner = await readFile(ownerPath);
1150
+ await assertSafePrivateFile(ownerPath, path);
1151
+ return createHash('sha256').update(owner).digest('hex');
1152
+ }
1153
+ catch {
1154
+ return undefined;
1155
+ }
1156
+ }
1157
+ /** One physical writer lock shared by direct `*yolo` and continuous DAG integration. */
1158
+ export async function acquireGitLifecyclePhysicalIntegrationLock(projectRoot, requested) {
1159
+ const runtimeDirectory = await gitLifecycleRuntimeDirectory(projectRoot);
1160
+ const leasesRoot = await ensureGitLifecyclePrivateChildDirectory(runtimeDirectory, 'leases');
1161
+ const lockPath = join(leasesRoot, 'integration');
1162
+ const legacyRecoveryPath = `${lockPath}.recovery`;
1163
+ if (existsSync(legacyRecoveryPath)) {
1164
+ await assertGitLifecyclePrivateDirectory(legacyRecoveryPath, leasesRoot);
1165
+ const recoveryPid = await readLegacyPhysicalIntegrationOwnerPid(legacyRecoveryPath);
1166
+ const recoveryFingerprint = await privateOwnerFingerprint(legacyRecoveryPath);
1167
+ if (recoveryPid === undefined || processExists(recoveryPid)) {
1168
+ return { ok: false, reasonCode: 'git-lifecycle-integration-backpressure' };
1169
+ }
1170
+ if (!recoveryFingerprint || !(await removeGitLifecyclePrivateDirectory(legacyRecoveryPath, leasesRoot, async (renamed) => await privateOwnerFingerprint(renamed) === recoveryFingerprint)))
1171
+ return { ok: false, reasonCode: 'git-lifecycle-integration-backpressure' };
1172
+ }
1173
+ const nowIso = normalizedIso(requested.createdAt ?? new Date().toISOString());
1174
+ const owner = Object.freeze({
1175
+ schemaVersion: 1, kind: requested.kind, storyId: requested.storyId,
1176
+ sessionId: requested.sessionId, idempotencyKey: requested.idempotencyKey,
1177
+ pid: process.pid, createdAt: nowIso, expiresAt: normalizedIso(requested.expiresAt),
1178
+ persistent: requested.persistent,
1179
+ ownerNonce: requested.ownerNonce ?? randomBytes(16).toString('hex'),
1180
+ });
1181
+ if (Date.parse(owner.expiresAt) <= Date.parse(owner.createdAt)) {
1182
+ return { ok: false, reasonCode: 'git-lifecycle-integration-lock-failed' };
1183
+ }
1184
+ const create = async () => {
1185
+ await assertPrivateDirectory(leasesRoot, runtimeDirectory);
1186
+ try {
1187
+ await anchoredCreateOwnedDirectory(leasesRoot, basename(lockPath), `${JSON.stringify(owner)}\n`);
1188
+ await assertGitLifecyclePrivateDirectory(lockPath, leasesRoot);
1189
+ await assertSafePrivateFile(join(lockPath, 'owner.json'), lockPath);
1190
+ return true;
1191
+ }
1192
+ catch (error) {
1193
+ if (!isNodeError(error, 'EEXIST')) {
1194
+ await removeGitLifecyclePrivateDirectory(lockPath, leasesRoot).catch(() => undefined);
1195
+ }
1196
+ throw error;
1197
+ }
1198
+ };
1199
+ try {
1200
+ await create();
1201
+ return { ok: true, owner, path: lockPath };
1202
+ }
1203
+ catch (error) {
1204
+ if (!isNodeError(error, 'EEXIST'))
1205
+ return { ok: false, reasonCode: 'git-lifecycle-integration-lock-failed' };
1206
+ }
1207
+ try {
1208
+ const current = await readPhysicalIntegrationOwner(lockPath);
1209
+ const legacyPid = current ? undefined : await readLegacyPhysicalIntegrationOwnerPid(lockPath);
1210
+ if (current && samePhysicalIntegrationOwner(current, owner))
1211
+ return { ok: true, owner: current, path: lockPath };
1212
+ const currentActive = current && Date.parse(current.expiresAt) > Date.now()
1213
+ && (current.persistent || processExists(current.pid));
1214
+ if (currentActive || legacyPid !== undefined && processExists(legacyPid)
1215
+ || (!current && legacyPid === undefined && await recentlyCreated(lockPath))) {
1216
+ return { ok: false, reasonCode: 'git-lifecycle-integration-backpressure' };
1217
+ }
1218
+ const ownerFingerprint = await privateOwnerFingerprint(lockPath);
1219
+ if (!ownerFingerprint)
1220
+ return { ok: false, reasonCode: 'git-lifecycle-integration-lock-failed' };
1221
+ const quarantine = `${lockPath}.stale.${process.pid}.${Date.now()}`;
1222
+ await anchoredRenameDirectory(leasesRoot, basename(lockPath), basename(quarantine));
1223
+ try {
1224
+ await assertGitLifecyclePrivateDirectory(quarantine, leasesRoot);
1225
+ if (await privateOwnerFingerprint(quarantine) !== ownerFingerprint) {
1226
+ await restoreQuarantinedPrivateDirectory(quarantine, lockPath, leasesRoot).catch(() => undefined);
1227
+ return { ok: false, reasonCode: 'git-lifecycle-integration-backpressure' };
1228
+ }
1229
+ await create();
1230
+ }
1231
+ finally {
1232
+ await removeGitLifecyclePrivateDirectory(quarantine, leasesRoot, async (renamed) => await privateOwnerFingerprint(renamed) === ownerFingerprint).catch(() => undefined);
1233
+ }
1234
+ return { ok: true, owner, path: lockPath };
1235
+ }
1236
+ catch (error) {
1237
+ return {
1238
+ ok: false,
1239
+ reasonCode: isNodeError(error, 'EEXIST') || isNodeError(error, 'ENOENT')
1240
+ ? 'git-lifecycle-integration-backpressure'
1241
+ : 'git-lifecycle-integration-lock-failed',
1242
+ };
1243
+ }
1244
+ }
1245
+ export async function verifyGitLifecyclePhysicalIntegrationLock(path, expected) {
1246
+ const current = await readPhysicalIntegrationOwner(path);
1247
+ return Boolean(current && samePhysicalIntegrationOwnerExact(current, expected)
1248
+ && Date.parse(current.expiresAt) > Date.now());
1249
+ }
1250
+ export async function releaseGitLifecyclePhysicalIntegrationLock(path, expected) {
1251
+ try {
1252
+ const parent = dirname(path);
1253
+ await assertGitLifecyclePrivateDirectory(path, parent);
1254
+ const current = await readPhysicalIntegrationOwner(path);
1255
+ if (!current || !samePhysicalIntegrationOwnerExact(current, expected))
1256
+ return false;
1257
+ const ownerFingerprint = await privateOwnerFingerprint(path);
1258
+ if (!ownerFingerprint)
1259
+ return false;
1260
+ const quarantine = `${path}.release.${process.pid}.${Date.now()}`;
1261
+ await anchoredRenameDirectory(parent, basename(path), basename(quarantine));
1262
+ await assertGitLifecyclePrivateDirectory(quarantine, parent);
1263
+ const renamedOwner = await readPhysicalIntegrationOwner(quarantine);
1264
+ if (!renamedOwner || !samePhysicalIntegrationOwnerExact(renamedOwner, expected)
1265
+ || await privateOwnerFingerprint(quarantine) !== ownerFingerprint) {
1266
+ await restoreQuarantinedPrivateDirectory(quarantine, path, parent).catch(() => undefined);
1267
+ return false;
1268
+ }
1269
+ return removeGitLifecyclePrivateDirectory(quarantine, parent, async (renamed) => await privateOwnerFingerprint(renamed) === ownerFingerprint);
1270
+ }
1271
+ catch (error) {
1272
+ if (isNodeError(error, 'ENOENT'))
1273
+ return false;
1274
+ throw error;
1275
+ }
1276
+ }
1277
+ export function computeGitLifecyclePhysicalIntegrationLockOwnerDigest(owner) {
1278
+ return createHash('sha256').update([
1279
+ 'git-lifecycle-physical-owner-v1', owner.kind, owner.storyId, owner.sessionId,
1280
+ owner.idempotencyKey, String(owner.pid), owner.createdAt, owner.expiresAt,
1281
+ owner.persistent ? 'persistent' : 'ephemeral', owner.ownerNonce,
1282
+ ].join('\0')).digest('hex');
1283
+ }
1284
+ /** Recover only the exact persistent lock whose digest was bound to a checkpoint. */
1285
+ export async function releaseGitLifecyclePhysicalIntegrationLockForCheckpoint(projectRoot, input) {
1286
+ if (!isCanonicalLifecycleId(input.storyId) || !safeToken(input.idempotencyKey) || !isHash(input.ownerDigest)) {
1287
+ return 'foreign';
1288
+ }
1289
+ const runtimeDirectory = await gitLifecycleRuntimeDirectory(projectRoot);
1290
+ const leasesRoot = join(runtimeDirectory, 'leases');
1291
+ const lockPath = join(leasesRoot, 'integration');
1292
+ if (!existsSync(lockPath))
1293
+ return 'absent';
1294
+ try {
1295
+ await assertGitLifecyclePrivateDirectory(leasesRoot, runtimeDirectory);
1296
+ await assertGitLifecyclePrivateDirectory(lockPath, leasesRoot);
1297
+ const current = await readPhysicalIntegrationOwner(lockPath);
1298
+ if (!current || current.kind !== 'direct' || current.storyId !== input.storyId
1299
+ || current.sessionId !== input.idempotencyKey || current.idempotencyKey !== input.idempotencyKey
1300
+ || current.persistent !== true
1301
+ || computeGitLifecyclePhysicalIntegrationLockOwnerDigest(current) !== input.ownerDigest)
1302
+ return 'foreign';
1303
+ return await releaseGitLifecyclePhysicalIntegrationLock(lockPath, current) ? 'released' : 'foreign';
1304
+ }
1305
+ catch {
1306
+ return 'foreign';
1307
+ }
1308
+ }
1309
+ async function readPhysicalIntegrationOwner(path) {
1310
+ try {
1311
+ await assertGitLifecyclePrivateDirectory(path, dirname(path));
1312
+ const ownerPath = join(path, 'owner.json');
1313
+ await assertSafePrivateFile(ownerPath, path);
1314
+ const item = JSON.parse(await readFile(ownerPath, 'utf8'));
1315
+ if (item.schemaVersion !== 1 || (item.kind !== 'direct' && item.kind !== 'continuous')
1316
+ || !isCanonicalLifecycleId(item.storyId) || !safeToken(item.sessionId) || !safeToken(item.idempotencyKey)
1317
+ || !Number.isSafeInteger(item.pid) || Number(item.pid) <= 0 || !isIso(item.createdAt) || !isIso(item.expiresAt)
1318
+ || typeof item.persistent !== 'boolean' || typeof item.ownerNonce !== 'string'
1319
+ || !/^[a-f0-9]{32}$/.test(item.ownerNonce))
1320
+ return undefined;
1321
+ return item;
1322
+ }
1323
+ catch {
1324
+ return undefined;
1325
+ }
1326
+ }
1327
+ async function readLegacyPhysicalIntegrationOwnerPid(path) {
1328
+ try {
1329
+ await assertGitLifecyclePrivateDirectory(path, dirname(path));
1330
+ const ownerPath = join(path, 'owner.json');
1331
+ await assertSafePrivateFile(ownerPath, path);
1332
+ const item = JSON.parse(await readFile(ownerPath, 'utf8'));
1333
+ return Number.isSafeInteger(item.pid) && Number(item.pid) > 0 ? Number(item.pid) : undefined;
1334
+ }
1335
+ catch {
1336
+ return undefined;
1337
+ }
1338
+ }
1339
+ function samePhysicalIntegrationOwner(left, right) {
1340
+ return left.kind === right.kind && left.storyId === right.storyId
1341
+ && left.sessionId === right.sessionId && left.idempotencyKey === right.idempotencyKey
1342
+ && left.expiresAt === right.expiresAt && left.persistent === right.persistent;
1343
+ }
1344
+ function samePhysicalIntegrationOwnerExact(left, right) {
1345
+ return samePhysicalIntegrationOwner(left, right)
1346
+ && left.schemaVersion === right.schemaVersion && left.pid === right.pid && left.createdAt === right.createdAt
1347
+ && left.ownerNonce === right.ownerNonce;
1348
+ }
1349
+ async function gitCommonDir(projectRoot) {
1350
+ const canonicalRoot = await realpath(projectRoot);
1351
+ const result = await execFileAsync('git', ['rev-parse', '--git-common-dir'], {
1352
+ cwd: canonicalRoot,
1353
+ encoding: 'utf8',
1354
+ maxBuffer: 1024 * 1024,
1355
+ });
1356
+ const commonDir = result.stdout.trim();
1357
+ const resolvedCommonDir = isAbsolute(commonDir) ? resolve(commonDir) : resolve(canonicalRoot, commonDir);
1358
+ const canonicalCommonDir = await realpath(resolvedCommonDir);
1359
+ if (canonicalCommonDir !== resolvedCommonDir)
1360
+ throw new Error('git-lifecycle-common-dir-symlink');
1361
+ await assertCommonDirectoryEntry(canonicalCommonDir);
1362
+ const revalidated = await execFileAsync('git', ['rev-parse', '--git-common-dir'], {
1363
+ cwd: canonicalRoot,
1364
+ encoding: 'utf8',
1365
+ maxBuffer: 1024 * 1024,
1366
+ });
1367
+ const secondRaw = revalidated.stdout.trim();
1368
+ const second = isAbsolute(secondRaw) ? resolve(secondRaw) : resolve(canonicalRoot, secondRaw);
1369
+ if (second !== resolvedCommonDir)
1370
+ throw new Error('git-lifecycle-common-dir-raced');
1371
+ return canonicalCommonDir;
1372
+ }
1373
+ async function ensureRuntimeDirectory(commonDir) {
1374
+ const runtimeDirectory = join(commonDir, GIT_LIFECYCLE_RUNTIME_DIRECTORY);
1375
+ if (!existsSync(runtimeDirectory)) {
1376
+ await anchoredEnsureDirectory(commonDir, GIT_LIFECYCLE_RUNTIME_DIRECTORY);
1377
+ }
1378
+ await assertSafeFilesystemEntry(runtimeDirectory, 'directory');
1379
+ if (await realpath(runtimeDirectory) !== runtimeDirectory)
1380
+ throw new Error('git-lifecycle-runtime-directory-symlink');
1381
+ return runtimeDirectory;
1382
+ }
1383
+ async function readDocument(path, options = {}) {
1384
+ try {
1385
+ await assertSafeFilesystemEntry(path, 'file');
1386
+ return validateDocument(JSON.parse(await readFile(path, 'utf8')));
1387
+ }
1388
+ catch (error) {
1389
+ if (isNodeError(error, 'ENOENT')) {
1390
+ if (options.required)
1391
+ throw new Error('git-lifecycle-runtime-missing');
1392
+ return emptyDocument();
1393
+ }
1394
+ throw error;
1395
+ }
1396
+ }
1397
+ async function assertStoreDestinationSafe(path) {
1398
+ try {
1399
+ await assertSafeFilesystemEntry(path, 'file');
1400
+ }
1401
+ catch (error) {
1402
+ if (isNodeError(error, 'ENOENT'))
1403
+ return;
1404
+ throw error;
1405
+ }
1406
+ }
1407
+ async function assertSafeFilesystemEntry(path, kind) {
1408
+ const info = await lstat(path);
1409
+ if (info.isSymbolicLink())
1410
+ throw new Error(`git-lifecycle-${kind}-symlink`);
1411
+ if (kind === 'file' ? !info.isFile() : !info.isDirectory())
1412
+ throw new Error(`git-lifecycle-${kind}-invalid`);
1413
+ if (posixSecurityAvailable() && info.uid !== process.getuid())
1414
+ throw new Error(`git-lifecycle-${kind}-ownership-mismatch`);
1415
+ if (posixSecurityAvailable() && (info.mode & 0o077) !== 0)
1416
+ throw new Error(`git-lifecycle-${kind}-permissions-unsafe`);
1417
+ }
1418
+ function assertDirectChild(parent, child) {
1419
+ const normalizedParent = resolve(parent);
1420
+ const normalizedChild = resolve(child);
1421
+ const childRelative = relative(normalizedParent, normalizedChild);
1422
+ if (!childRelative || childRelative.startsWith('..') || isAbsolute(childRelative)
1423
+ || dirname(normalizedChild) !== normalizedParent) {
1424
+ throw new Error('git-lifecycle-private-path-escape');
1425
+ }
1426
+ }
1427
+ async function assertPrivateDirectory(path, expectedParent) {
1428
+ const normalized = resolve(path);
1429
+ if (expectedParent)
1430
+ assertDirectChild(resolve(expectedParent), normalized);
1431
+ await assertSafeFilesystemEntry(normalized, 'directory');
1432
+ if (resolve(await realpath(normalized)) !== normalized)
1433
+ throw new Error('git-lifecycle-directory-substituted');
1434
+ if (expectedParent) {
1435
+ const parent = resolve(expectedParent);
1436
+ await assertSafeFilesystemEntry(parent, 'directory');
1437
+ if (resolve(await realpath(parent)) !== parent)
1438
+ throw new Error('git-lifecycle-directory-parent-substituted');
1439
+ }
1440
+ }
1441
+ async function assertSafePrivateFile(path, expectedParent) {
1442
+ const normalized = resolve(path);
1443
+ assertDirectChild(resolve(expectedParent), normalized);
1444
+ await assertPrivateDirectory(resolve(expectedParent));
1445
+ await assertSafeFilesystemEntry(normalized, 'file');
1446
+ if (resolve(await realpath(normalized)) !== normalized)
1447
+ throw new Error('git-lifecycle-file-substituted');
1448
+ }
1449
+ function assertSafeFilesystemEntrySync(path, kind) {
1450
+ const info = lstatSync(path);
1451
+ if (info.isSymbolicLink())
1452
+ throw new Error(`git-lifecycle-${kind}-symlink`);
1453
+ if (kind === 'file' ? !info.isFile() : !info.isDirectory())
1454
+ throw new Error(`git-lifecycle-${kind}-invalid`);
1455
+ if (posixSecurityAvailable() && info.uid !== process.getuid())
1456
+ throw new Error(`git-lifecycle-${kind}-ownership-mismatch`);
1457
+ if (posixSecurityAvailable() && (info.mode & 0o077) !== 0)
1458
+ throw new Error(`git-lifecycle-${kind}-permissions-unsafe`);
1459
+ }
1460
+ async function assertCommonDirectoryEntry(path) {
1461
+ const info = await lstat(path);
1462
+ if (info.isSymbolicLink())
1463
+ throw new Error('git-lifecycle-common-dir-symlink');
1464
+ if (!info.isDirectory())
1465
+ throw new Error('git-lifecycle-common-dir-invalid');
1466
+ }
1467
+ function assertCommonDirectoryEntrySync(path) {
1468
+ const info = lstatSync(path);
1469
+ if (info.isSymbolicLink())
1470
+ throw new Error('git-lifecycle-common-dir-symlink');
1471
+ if (!info.isDirectory())
1472
+ throw new Error('git-lifecycle-common-dir-invalid');
1473
+ }
1474
+ export function posixSecurityAvailable(platform = process.platform) {
1475
+ return platform !== 'win32' && typeof process.getuid === 'function';
1476
+ }
1477
+ function validateDocument(value) {
1478
+ if (!value || typeof value !== 'object' || Array.isArray(value))
1479
+ throw new Error('git-lifecycle-runtime-invalid');
1480
+ const record = value;
1481
+ const schemaVersion = record.schemaVersion;
1482
+ assertExactKeys(record, schemaVersion === 2
1483
+ ? ['schemaVersion', 'revision', 'epics', 'stories', 'operational']
1484
+ : ['schemaVersion', 'revision', 'epics', 'stories'], 'git-lifecycle-runtime-unknown-field');
1485
+ const epics = asRecord(record.epics);
1486
+ const stories = asRecord(record.stories);
1487
+ if ((schemaVersion !== 1 && schemaVersion !== 2)
1488
+ || !Number.isSafeInteger(record.revision) || Number(record.revision) < 0
1489
+ || !epics || !stories
1490
+ || Object.values(epics).some((entry) => !asRecord(entry))
1491
+ || Object.values(stories).some((entry) => !asRecord(entry))
1492
+ || (schemaVersion === 2 && !validateOperationalEnvelope(record.operational))) {
1493
+ throw new Error('git-lifecycle-runtime-invalid');
1494
+ }
1495
+ for (const [epicId, overlay] of Object.entries(epics)) {
1496
+ if (!isCanonicalLifecycleId(epicId) || !validateEpicOverlay(overlay)) {
1497
+ throw new Error('git-lifecycle-runtime-epic-invalid');
1498
+ }
1499
+ }
1500
+ for (const [storyId, overlay] of Object.entries(stories)) {
1501
+ if (!isCanonicalLifecycleId(storyId) || !storyId.includes('.') || !validateStoryOverlay(overlay, storyId)) {
1502
+ throw new Error('git-lifecycle-runtime-story-invalid');
1503
+ }
1504
+ }
1505
+ return record;
1506
+ }
1507
+ /**
1508
+ * Keep the physical store closed even before a domain consumer reads v2. The
1509
+ * domain module validates every nested field; this boundary additionally
1510
+ * rejects future/private top-level fields and unbounded collections so a
1511
+ * malformed document can never be treated as a legacy v1 fallback.
1512
+ */
1513
+ function validateOperationalEnvelope(value) {
1514
+ const item = asRecord(value);
1515
+ if (!item)
1516
+ return false;
1517
+ assertExactKeys(item, [
1518
+ 'schemaVersion', 'repositoryFingerprint', 'writerEpoch', 'capabilities',
1519
+ 'identities', 'yoloop', 'projectionBindings', 'activeJournal',
1520
+ 'backfillReceipts', 'reconciliationReceipts', 'rollbackReceipts',
1521
+ ], 'git-lifecycle-runtime-operational-unknown-field');
1522
+ const identities = asRecord(item.identities);
1523
+ const yoloop = asRecord(item.yoloop);
1524
+ const bindings = asRecord(item.projectionBindings);
1525
+ return item.schemaVersion === 'EpicOperationalState/1.0'
1526
+ && isHash(item.repositoryFingerprint)
1527
+ && Number.isSafeInteger(item.writerEpoch) && Number(item.writerEpoch) >= 1
1528
+ && Array.isArray(item.capabilities) && item.capabilities.length <= 16
1529
+ && item.capabilities.every((capability) => safeToken(capability))
1530
+ && Boolean(identities) && Object.keys(identities).length <= 2_048
1531
+ && Boolean(yoloop) && yoloop.schemaVersion === 'CommonYoloopAuthority/1.0'
1532
+ && Boolean(bindings)
1533
+ && (item.activeJournal === null || Boolean(asRecord(item.activeJournal)))
1534
+ && validateBoundedRecordArray(item.backfillReceipts, 64)
1535
+ && validateBoundedRecordArray(item.reconciliationReceipts, 64)
1536
+ && validateBoundedRecordArray(item.rollbackReceipts, 64);
1537
+ }
1538
+ function validateBoundedRecordArray(value, max) {
1539
+ return Array.isArray(value) && value.length <= max && value.every((entry) => Boolean(asRecord(entry)));
1540
+ }
1541
+ const EPIC_OVERLAY_KEYS = [
1542
+ 'branch_mode', 'execution_branch', 'origin_branch', 'branch_lease', 'origin_binding',
1543
+ 'graph_revision', 'aggregate_qa_receipt', 'terminal_git_receipt', 'physicalMergeStatus',
1544
+ 'pr_number', 'git_reconciliation_receipts', 'git_reconciliation_debt',
1545
+ ];
1546
+ const STORY_OVERLAY_KEYS = ['checkpoint', 'status', 'last_step', 'steps_completed'];
1547
+ function validateEpicOverlay(value) {
1548
+ assertExactKeys(value, EPIC_OVERLAY_KEYS, 'git-lifecycle-runtime-epic-unknown-field');
1549
+ return (value.branch_mode === undefined || value.branch_mode === 'epic')
1550
+ && (value.execution_branch === undefined || safeGitRef(value.execution_branch))
1551
+ && (value.origin_branch === undefined || safeGitRef(value.origin_branch))
1552
+ && (value.branch_lease === undefined || value.branch_lease === null || validateLease(value.branch_lease))
1553
+ && (value.origin_binding === undefined || value.origin_binding === null || validateOriginBinding(value.origin_binding))
1554
+ && (value.graph_revision === undefined || isHash(value.graph_revision))
1555
+ && (value.aggregate_qa_receipt === undefined || value.aggregate_qa_receipt === null || validateAggregateQa(value.aggregate_qa_receipt))
1556
+ && (value.terminal_git_receipt === undefined || value.terminal_git_receipt === null || validateTerminalReceipt(value.terminal_git_receipt))
1557
+ && (value.physicalMergeStatus === undefined || ['merged', 'pushed', 'denied_external_gate', 'blocked_local_or_repo', 'not_attempted'].includes(String(value.physicalMergeStatus)))
1558
+ && (value.pr_number === undefined || value.pr_number === null || Number.isSafeInteger(value.pr_number) && Number(value.pr_number) > 0)
1559
+ && (value.git_reconciliation_receipts === undefined || validateReconciliationReceipts(value.git_reconciliation_receipts))
1560
+ && (value.git_reconciliation_debt === undefined || validateReconciliationDebt(value.git_reconciliation_debt));
1561
+ }
1562
+ function validateStoryOverlay(value, storyId) {
1563
+ assertExactKeys(value, STORY_OVERLAY_KEYS, 'git-lifecycle-runtime-story-unknown-field');
1564
+ return (value.checkpoint === undefined || value.checkpoint === null || validateCheckpoint(value.checkpoint, storyId))
1565
+ && (value.status === undefined || safeToken(value.status))
1566
+ && (value.last_step === undefined || value.last_step === null || safeToken(value.last_step))
1567
+ && (value.steps_completed === undefined || safeStringArray(value.steps_completed, 128));
1568
+ }
1569
+ function validateLease(value) {
1570
+ const item = asRecord(value);
1571
+ if (!item)
1572
+ return false;
1573
+ assertExactKeys(item, [
1574
+ 'schemaVersion', 'ownerStoryId', 'sessionId', 'idempotencyKey', 'acquiredAt', 'expiresAt',
1575
+ 'handoffGrant', 'handoffConsumption', 'setupHeadSha',
1576
+ ], 'git-lifecycle-runtime-lease-unknown-field');
1577
+ return item.schemaVersion === 1 && isCanonicalLifecycleId(item.ownerStoryId)
1578
+ && safeToken(item.sessionId) && safeToken(item.idempotencyKey)
1579
+ && isIso(item.acquiredAt) && isIso(item.expiresAt)
1580
+ && Date.parse(String(item.expiresAt)) > Date.parse(String(item.acquiredAt))
1581
+ && (item.setupHeadSha === undefined || isSha(item.setupHeadSha))
1582
+ && (item.handoffGrant === undefined || validateDirectYoloSetupHandoffGrant(item.handoffGrant, item))
1583
+ && (item.handoffConsumption === undefined || validateDirectYoloSetupHandoffConsumption(item.handoffConsumption, item));
1584
+ }
1585
+ function validateDirectYoloSetupHandoffGrant(value, lease) {
1586
+ const item = asRecord(value);
1587
+ if (!item)
1588
+ return false;
1589
+ assertExactKeys(item, [
1590
+ 'schemaVersion', 'kind', 'storyId', 'fromSessionId', 'tokenDigest',
1591
+ 'bundleDigest', 'claimRevision', 'expiresAt',
1592
+ ], 'git-lifecycle-runtime-handoff-grant-unknown-field');
1593
+ return item.schemaVersion === 1 && item.kind === 'direct-yolo-setup-handoff'
1594
+ && item.storyId === lease.ownerStoryId && item.fromSessionId === lease.sessionId
1595
+ && isHash(item.tokenDigest) && isHash(item.bundleDigest) && isHash(item.claimRevision)
1596
+ && isIso(item.expiresAt)
1597
+ && Date.parse(String(item.expiresAt)) <= Date.parse(String(lease.expiresAt));
1598
+ }
1599
+ function validateDirectYoloSetupHandoffConsumption(value, lease) {
1600
+ const item = asRecord(value);
1601
+ if (!item)
1602
+ return false;
1603
+ assertExactKeys(item, [
1604
+ 'schemaVersion', 'kind', 'state', 'storyId', 'fromSessionId', 'tokenDigest',
1605
+ 'bundleDigest', 'claimRevision', 'consumedBySessionId', 'consumedAt',
1606
+ ], 'git-lifecycle-runtime-handoff-consumption-unknown-field');
1607
+ return item.schemaVersion === 1 && item.kind === 'direct-yolo-setup-handoff-consumption'
1608
+ && item.state === 'consumed' && item.storyId === lease.ownerStoryId
1609
+ && safeToken(item.fromSessionId) && isHash(item.tokenDigest)
1610
+ && isHash(item.bundleDigest) && isHash(item.claimRevision)
1611
+ && item.consumedBySessionId === lease.sessionId && isIso(item.consumedAt);
1612
+ }
1613
+ function validateOriginBinding(value) {
1614
+ const item = asRecord(value);
1615
+ if (!item)
1616
+ return false;
1617
+ assertExactKeys(item, ['schemaVersion', 'originBranch', 'originHeadSha', 'executionBranch', 'capturedFromCurrentCheckout', 'capturedAt'], 'git-lifecycle-runtime-origin-unknown-field');
1618
+ return item.schemaVersion === 1 && safeGitRef(item.originBranch) && safeGitRef(item.executionBranch)
1619
+ && item.originBranch !== item.executionBranch && isSha(item.originHeadSha)
1620
+ && item.capturedFromCurrentCheckout === true && isIso(item.capturedAt);
1621
+ }
1622
+ function validateAggregateQa(value) {
1623
+ const item = asRecord(value);
1624
+ if (!item)
1625
+ return false;
1626
+ assertExactKeys(item, [
1627
+ 'schemaVersion', 'evidenceVersion', 'producer', 'contract', 'command', 'verdict',
1628
+ 'executionHeadSha', 'graphRevision', 'manifestDigest', 'resultDigest', 'checkedAt', 'receiptRef', 'gateIds',
1629
+ ], 'git-lifecycle-runtime-aggregate-qa-unknown-field');
1630
+ const executionV2 = item.evidenceVersion === 'p205-execution-v2'
1631
+ && item.producer === 'local-yolo-tdd' && item.contract === 'P205-CI-02'
1632
+ && item.command === P205_COMPLETE_COMMAND && isHash(item.manifestDigest) && isHash(item.resultDigest);
1633
+ const legacy = item.evidenceVersion === undefined && item.producer === undefined && item.contract === undefined
1634
+ && item.command === undefined && item.manifestDigest === undefined && item.resultDigest === undefined;
1635
+ return item.schemaVersion === 1 && (executionV2 || legacy) && item.verdict === 'passed' && isSha(item.executionHeadSha)
1636
+ && isHash(item.graphRevision) && isIso(item.checkedAt) && safeReceiptRef(item.receiptRef)
1637
+ && safeStringArray(item.gateIds, 128, true);
1638
+ }
1639
+ function validateTerminalReceipt(value) {
1640
+ const item = asRecord(value);
1641
+ if (!item)
1642
+ return false;
1643
+ assertExactKeys(item, [
1644
+ 'schemaVersion', 'epicId', 'executionBranch', 'originBranch', 'baseSha', 'executionHeadSha',
1645
+ 'mergeSha', 'pushedSha', 'strategy', 'prNumber', 'physicalStatus', 'localGateReceiptRefs',
1646
+ 'idempotencyKey', 'attemptStartedAt', 'completedAt', 'retryEligible', 'reasonCodes', 'publicSafe', 'candidateQa',
1647
+ 'providerGuard',
1648
+ ], 'git-lifecycle-runtime-terminal-receipt-unknown-field');
1649
+ return item.schemaVersion === 1 && isCanonicalLifecycleId(item.epicId)
1650
+ && safeGitRef(item.executionBranch) && safeGitRef(item.originBranch) && isSha(item.baseSha) && isSha(item.executionHeadSha)
1651
+ && (item.mergeSha === undefined || isSha(item.mergeSha)) && (item.pushedSha === undefined || isSha(item.pushedSha))
1652
+ && (item.strategy === 'direct' || item.strategy === 'single_pr')
1653
+ && (item.candidateQa === undefined || validateMergeCandidateQaReceipt(item.candidateQa, item))
1654
+ && (item.providerGuard === undefined || validateProviderGuardReceipt(item.providerGuard, item))
1655
+ && (item.prNumber === undefined || Number.isSafeInteger(item.prNumber) && Number(item.prNumber) > 0)
1656
+ && ['prepared', 'merged', 'pushed', 'denied_external_gate', 'blocked_local_or_repo'].includes(String(item.physicalStatus))
1657
+ && safeStringArray(item.localGateReceiptRefs, 128, true) && safeToken(item.idempotencyKey)
1658
+ && isIso(item.attemptStartedAt) && (item.completedAt === undefined || isIso(item.completedAt))
1659
+ && typeof item.retryEligible === 'boolean' && safeStringArray(item.reasonCodes, 128)
1660
+ && item.publicSafe === true;
1661
+ }
1662
+ function validateProviderGuardReceipt(value, terminal) {
1663
+ const item = asRecord(value);
1664
+ if (!item)
1665
+ return false;
1666
+ assertExactKeys(item, [
1667
+ 'schemaVersion', 'provider', 'mode', 'source', 'prHeadSha', 'originHeadSha', 'checkedAt', 'publicSafe',
1668
+ ], 'git-lifecycle-runtime-provider-guard-unknown-field');
1669
+ const modeSourceValid = item.mode === 'recovered_exact_tree'
1670
+ ? item.source === 'physical_recovery'
1671
+ : item.mode === 'strict_up_to_date_expected_head'
1672
+ && ['branch_protection', 'ruleset', 'branch_protection_and_ruleset'].includes(String(item.source));
1673
+ return item.schemaVersion === 1 && item.provider === 'github' && modeSourceValid
1674
+ && terminal.strategy === 'single_pr' && terminal.physicalStatus === 'pushed'
1675
+ && Number.isSafeInteger(terminal.prNumber) && Number(terminal.prNumber) > 0
1676
+ && isSha(item.prHeadSha) && item.prHeadSha === terminal.mergeSha
1677
+ && isSha(item.originHeadSha) && item.originHeadSha === terminal.baseSha
1678
+ && isIso(item.checkedAt) && item.publicSafe === true;
1679
+ }
1680
+ function validateMergeCandidateQaReceipt(value, terminal) {
1681
+ const item = asRecord(value);
1682
+ if (!item)
1683
+ return false;
1684
+ assertExactKeys(item, ['schemaVersion', 'kind', 'contract', 'command', 'originHeadSha', 'executionHeadSha',
1685
+ 'mergeSha', 'treeSha', 'graphRevision', 'manifestDigest', 'resultDigest', 'gateIds', 'checkedAt', 'receiptRef'], 'git-lifecycle-runtime-merge-candidate-unknown-field');
1686
+ return item.schemaVersion === 1 && item.kind === 'epic_merge_candidate_qa'
1687
+ && item.contract === 'P205-CI-02' && item.command === P205_COMPLETE_COMMAND
1688
+ && isSha(item.originHeadSha) && item.originHeadSha === terminal.baseSha
1689
+ && isSha(item.executionHeadSha) && item.executionHeadSha === terminal.executionHeadSha
1690
+ && isSha(item.mergeSha) && item.mergeSha === terminal.mergeSha
1691
+ && isSha(item.treeSha) && isHash(item.graphRevision) && isHash(item.manifestDigest) && isHash(item.resultDigest)
1692
+ && safeStringArray(item.gateIds, 128, true) && isIso(item.checkedAt) && safeReceiptRef(item.receiptRef);
1693
+ }
1694
+ function validateCheckpoint(value, storyId) {
1695
+ const item = asRecord(value);
1696
+ if (!item)
1697
+ return false;
1698
+ assertExactKeys(item, [
1699
+ 'schemaVersion', 'storyId', 'epicId', 'branch', 'baseSha', 'headSha', 'commits', 'idempotencyKey',
1700
+ 'dispatchId', 'bundleId', 'bundleDigest', 'graphRevision', 'claimRevision', 'receiptRefs',
1701
+ 'qaVerdict', 'reviewVerdict', 'qaReceipt', 'reviewReceipt', 'reviewReconciliation', 'recordedAt', 'rootMemoryEffect',
1702
+ ], 'git-lifecycle-runtime-checkpoint-unknown-field');
1703
+ const epicId = String(storyId).split('.')[0];
1704
+ return item.schemaVersion === 1 && item.storyId === storyId && item.epicId === epicId
1705
+ && safeGitRef(item.branch) && isSha(item.baseSha) && isSha(item.headSha) && item.baseSha !== item.headSha
1706
+ && safeShaArray(item.commits) && item.commits.length === 1 && item.commits[0] === item.headSha
1707
+ && safeToken(item.idempotencyKey) && (item.dispatchId === undefined || isHash(item.dispatchId))
1708
+ && (item.bundleId === undefined || isHash(item.bundleId)) && isHash(item.bundleDigest)
1709
+ && isHash(item.graphRevision) && (item.claimRevision === undefined || isHash(item.claimRevision))
1710
+ && (item.rootMemoryEffect === undefined || isRootProjectMemoryEffect(item.rootMemoryEffect)
1711
+ && item.rootMemoryEffect.storyId === storyId && item.rootMemoryEffect.executionBranch === item.branch
1712
+ && item.rootMemoryEffect.sourceHeadSha === item.baseSha && item.rootMemoryEffect.childClaimRevision === item.claimRevision)
1713
+ && safeStringArray(item.receiptRefs, 256, true)
1714
+ && (item.qaVerdict === 'passed' || item.qaVerdict === 'blocked')
1715
+ && ['approved', 'changes-requested', 'pending'].includes(String(item.reviewVerdict))
1716
+ && validateStoryQaReceipt(item.qaReceipt, item)
1717
+ && (item.reviewVerdict === 'pending' ? item.reviewReceipt === undefined : validateStoryReviewReceipt(item.reviewReceipt, item))
1718
+ && (item.reviewReconciliation === undefined || validateHistoricalReviewReconciliation(item.reviewReconciliation, item))
1719
+ && isIso(item.recordedAt);
1720
+ }
1721
+ function validateHistoricalReviewReconciliation(value, checkpoint) {
1722
+ const item = asRecord(value);
1723
+ const qa = asRecord(checkpoint.qaReceipt);
1724
+ const review = asRecord(checkpoint.reviewReceipt);
1725
+ if (!item || !qa || !review)
1726
+ return false;
1727
+ assertExactKeys(item, [
1728
+ 'schemaVersion', 'kind', 'storyId', 'epicId', 'checkpointHeadSha', 'approvalCommitSha',
1729
+ 'approvalArtifactDigest', 'qaReceiptRef', 'reviewReceiptRef', 'sourceEvidenceDigest', 'reviewedAt', 'receiptRef',
1730
+ ], 'git-lifecycle-runtime-review-reconciliation-unknown-field');
1731
+ return item.schemaVersion === 1 && item.kind === 'historical_review_reconciliation'
1732
+ && item.storyId === checkpoint.storyId && item.epicId === checkpoint.epicId
1733
+ && item.checkpointHeadSha === checkpoint.headSha && isSha(item.approvalCommitSha)
1734
+ && isHash(item.approvalArtifactDigest) && item.qaReceiptRef === qa.receiptRef
1735
+ && item.reviewReceiptRef === review.receiptRef && isHash(item.sourceEvidenceDigest)
1736
+ && isIso(item.reviewedAt) && Date.parse(String(item.reviewedAt)) >= Date.parse(String(qa.checkedAt))
1737
+ && safeReceiptRef(item.receiptRef)
1738
+ && Array.isArray(checkpoint.receiptRefs) && checkpoint.receiptRefs.includes(item.receiptRef);
1739
+ }
1740
+ function validateStoryQaReceipt(value, checkpoint) {
1741
+ const item = asRecord(value);
1742
+ if (!item)
1743
+ return false;
1744
+ assertExactKeys(item, [
1745
+ 'schemaVersion', 'kind', 'producer', 'evidenceVersion', 'storyId', 'epicId', 'baseSha', 'headSha', 'bundleDigest',
1746
+ 'graphRevision', 'contract', 'command', 'manifestDigest', 'gateIds', 'resultDigest',
1747
+ 'sourceEvidenceDigest', 'verdict', 'checkedAt', 'receiptRef',
1748
+ ], 'git-lifecycle-runtime-story-qa-unknown-field');
1749
+ return item.schemaVersion === 1 && item.kind === 'story_qa_evidence' && item.producer === 'local-yolo-tdd'
1750
+ && (item.evidenceVersion === undefined || item.evidenceVersion === 'git-immutable-v1'
1751
+ || item.evidenceVersion === 'p205-execution-v2' && item.contract === 'P205-CI-02'
1752
+ && item.command === P205_COMPLETE_COMMAND && isHash(item.manifestDigest)
1753
+ && safeStringArray(item.gateIds, 32, true) && isHash(item.resultDigest))
1754
+ && evidenceBindingMatches(item, checkpoint) && isHash(item.sourceEvidenceDigest)
1755
+ && item.verdict === checkpoint.qaVerdict && (item.verdict === 'passed' || item.verdict === 'blocked')
1756
+ && isIso(item.checkedAt) && safeReceiptRef(item.receiptRef)
1757
+ && checkpoint.receiptRefs.includes(item.receiptRef);
1758
+ }
1759
+ function validateStoryReviewReceipt(value, checkpoint) {
1760
+ const item = asRecord(value);
1761
+ const qa = asRecord(checkpoint.qaReceipt);
1762
+ if (!item || !qa)
1763
+ return false;
1764
+ assertExactKeys(item, [
1765
+ 'schemaVersion', 'kind', 'producer', 'storyId', 'epicId', 'baseSha', 'headSha', 'bundleDigest',
1766
+ 'graphRevision', 'sourceEvidenceDigest', 'reviewArtifactDigest', 'qaReceiptRef', 'verdict', 'reviewedAt', 'receiptRef',
1767
+ ], 'git-lifecycle-runtime-story-review-unknown-field');
1768
+ return item.schemaVersion === 1 && item.kind === 'story_review_evidence' && item.producer === 'root-commit-range-review'
1769
+ && evidenceBindingMatches(item, checkpoint) && isHash(item.sourceEvidenceDigest)
1770
+ && (item.reviewArtifactDigest === undefined || isHash(item.reviewArtifactDigest))
1771
+ && item.qaReceiptRef === qa.receiptRef && item.receiptRef !== qa.receiptRef
1772
+ && (item.verdict === 'approved' || item.verdict === 'changes-requested')
1773
+ && (item.verdict === 'approved' ? checkpoint.reviewVerdict === 'approved' : checkpoint.reviewVerdict === 'changes-requested')
1774
+ && isIso(item.reviewedAt) && Date.parse(String(item.reviewedAt)) >= Date.parse(String(qa.checkedAt))
1775
+ && safeReceiptRef(item.receiptRef) && checkpoint.receiptRefs.includes(item.receiptRef);
1776
+ }
1777
+ function evidenceBindingMatches(item, checkpoint) {
1778
+ return ['storyId', 'epicId', 'baseSha', 'headSha', 'bundleDigest', 'graphRevision']
1779
+ .every((key) => item[key] === checkpoint[key]);
1780
+ }
1781
+ function validateReconciliationReceipts(value) {
1782
+ if (!Array.isArray(value) || value.length > 1024)
1783
+ return false;
1784
+ return value.every((entry) => {
1785
+ const item = asRecord(entry);
1786
+ if (!item)
1787
+ return false;
1788
+ assertExactKeys(item, ['schemaVersion', 'resourceId', 'action', 'outcome', 'idempotencyKey', 'reasonCodes', 'recordedAt', 'publicSafe'], 'git-lifecycle-runtime-reconciliation-receipt-unknown-field');
1789
+ return item.schemaVersion === 1 && safeToken(item.resourceId)
1790
+ && ['no_op', 'delete_local_branch', 'delete_remote_branch', 'close_pull_request'].includes(String(item.action))
1791
+ && ['applied', 'already_absent', 'dry_run', 'failed'].includes(String(item.outcome))
1792
+ && safeToken(item.idempotencyKey) && safeStringArray(item.reasonCodes, 128) && isIso(item.recordedAt) && item.publicSafe === true;
1793
+ });
1794
+ }
1795
+ function validateReconciliationDebt(value) {
1796
+ if (!Array.isArray(value) || value.length > 1024)
1797
+ return false;
1798
+ return value.every((entry) => {
1799
+ const item = asRecord(entry);
1800
+ if (!item)
1801
+ return false;
1802
+ assertExactKeys(item, ['schemaVersion', 'resourceId', 'action', 'managed', 'reasonCodes', 'evidence', 'publicSafe'], 'git-lifecycle-runtime-reconciliation-debt-unknown-field');
1803
+ return item.schemaVersion === 1 && safeToken(item.resourceId) && item.action === 'preserve'
1804
+ && typeof item.managed === 'boolean' && safeStringArray(item.reasonCodes, 128)
1805
+ && safeStringArray(item.evidence, 16) && item.publicSafe === true;
1806
+ });
1807
+ }
1808
+ function assertExactKeys(record, allowed, reasonCode) {
1809
+ const allow = new Set(allowed);
1810
+ if (Object.keys(record).some((key) => !allow.has(key)))
1811
+ throw new Error(reasonCode);
1812
+ }
1813
+ function isCanonicalLifecycleId(value) {
1814
+ return typeof value === 'string' && /^P[1-9][0-9]*(?:\.[0-9]+)*$/.test(value);
1815
+ }
1816
+ function isGitLifecycleGraphAuthority(value) {
1817
+ const item = asRecord(value);
1818
+ if (!item)
1819
+ return false;
1820
+ return Object.keys(item).length === 10
1821
+ && item.schemaVersion === 1
1822
+ && item.kind === 'git_lifecycle_graph_authority'
1823
+ && item.producer === 'server-continuous-yoloop-v1'
1824
+ && (item.source === 'yolo' || item.source === 'yoloop')
1825
+ && isCanonicalLifecycleId(item.epicId)
1826
+ && isCanonicalLifecycleId(item.storyId)
1827
+ && String(item.storyId).startsWith(`${String(item.epicId)}.`)
1828
+ && safeGitRef(item.executionBranch)
1829
+ && safeGitRef(item.originBranch)
1830
+ && item.originBranch !== item.executionBranch
1831
+ && typeof item.graphRevision === 'string'
1832
+ && /^[a-f0-9]{64}$/.test(item.graphRevision)
1833
+ && item.publicSafe === true;
1834
+ }
1835
+ function safeGitRef(value) {
1836
+ return typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._/-]{0,239}$/.test(value)
1837
+ && !value.includes('..') && !value.includes('//') && !value.includes('@{')
1838
+ && !value.endsWith('/') && !value.endsWith('.') && !value.split('/').some((part) => !part || part.startsWith('.') || part.endsWith('.lock'));
1839
+ }
1840
+ function safeRepositoryPath(value) {
1841
+ return value.length > 0 && value.length <= 1024 && !isAbsolute(value)
1842
+ && !value.includes('\0') && !value.replaceAll('\\', '/').split('/').includes('..');
1843
+ }
1844
+ function safeToken(value) {
1845
+ return typeof value === 'string' && value.length > 0 && value.length <= 240 && /^[A-Za-z0-9._:/#\-[\]]+$/.test(value) && !value.includes('..');
1846
+ }
1847
+ function safeReceiptRef(value) {
1848
+ return safeToken(value);
1849
+ }
1850
+ function isSha(value) {
1851
+ return typeof value === 'string' && /^[a-f0-9]{40,64}$/i.test(value);
1852
+ }
1853
+ function isHash(value) {
1854
+ return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value);
1855
+ }
1856
+ function isIso(value) {
1857
+ return typeof value === 'string' && value.length <= 64 && Number.isFinite(Date.parse(value));
1858
+ }
1859
+ function safeStringArray(value, max, nonEmpty = false) {
1860
+ return Array.isArray(value) && value.length <= max && (!nonEmpty || value.length > 0) && value.every(safeToken);
1861
+ }
1862
+ function safeShaArray(value) {
1863
+ return Array.isArray(value) && value.length > 0 && value.length <= 256 && value.every(isSha);
1864
+ }
1865
+ function emptyDocument() {
1866
+ return { schemaVersion: 1, revision: 0, epics: {}, stories: {} };
1867
+ }
1868
+ async function acquireLock(lockPath) {
1869
+ const parent = dirname(lockPath);
1870
+ const requested = { pid: process.pid, nonce: randomBytes(16).toString('hex') };
1871
+ const create = async () => {
1872
+ await assertPrivateDirectory(parent);
1873
+ await anchoredCreateOwnedDirectory(parent, basename(lockPath), JSON.stringify(requested));
1874
+ try {
1875
+ await assertGitLifecyclePrivateDirectory(lockPath, parent);
1876
+ await assertSafePrivateFile(join(lockPath, 'owner.json'), lockPath);
1877
+ }
1878
+ catch (error) {
1879
+ await removeGitLifecyclePrivateDirectory(lockPath, parent).catch(() => undefined);
1880
+ throw error;
1881
+ }
1882
+ };
1883
+ try {
1884
+ await create();
1885
+ }
1886
+ catch (error) {
1887
+ if (!isNodeError(error, 'EEXIST'))
1888
+ throw error;
1889
+ await assertSafeFilesystemEntry(lockPath, 'directory');
1890
+ const owner = await readOwner(lockPath);
1891
+ if (owner !== undefined && processExists(owner.pid))
1892
+ throw new Error('git-lifecycle-runtime-lock-active');
1893
+ if (owner === undefined && await recentlyCreated(lockPath))
1894
+ throw new Error('git-lifecycle-runtime-lock-active');
1895
+ const ownerFingerprint = await privateOwnerFingerprint(lockPath);
1896
+ if (!ownerFingerprint)
1897
+ throw new Error('git-lifecycle-runtime-lock-owner-invalid');
1898
+ const quarantine = `${lockPath}.stale.${process.pid}.${Date.now()}`;
1899
+ await anchoredRenameDirectory(parent, basename(lockPath), basename(quarantine));
1900
+ try {
1901
+ await assertGitLifecyclePrivateDirectory(quarantine, parent);
1902
+ if (await privateOwnerFingerprint(quarantine) !== ownerFingerprint) {
1903
+ await restoreQuarantinedPrivateDirectory(quarantine, lockPath, parent).catch(() => undefined);
1904
+ throw new Error('git-lifecycle-runtime-lock-owner-replaced');
1905
+ }
1906
+ await create();
1907
+ }
1908
+ finally {
1909
+ await removeGitLifecyclePrivateDirectory(quarantine, parent, async (renamed) => await privateOwnerFingerprint(renamed) === ownerFingerprint).catch(() => undefined);
1910
+ }
1911
+ }
1912
+ return requested;
1913
+ }
1914
+ async function releaseRuntimeLock(lockPath, expected) {
1915
+ const parent = dirname(lockPath);
1916
+ try {
1917
+ await assertGitLifecyclePrivateDirectory(lockPath, parent);
1918
+ const current = await readOwner(lockPath);
1919
+ if (!current || !sameRuntimeLockOwner(current, expected))
1920
+ return false;
1921
+ const ownerFingerprint = await privateOwnerFingerprint(lockPath);
1922
+ if (!ownerFingerprint)
1923
+ return false;
1924
+ const quarantine = `${lockPath}.release.${process.pid}.${Date.now()}`;
1925
+ await anchoredRenameDirectory(parent, basename(lockPath), basename(quarantine));
1926
+ await assertGitLifecyclePrivateDirectory(quarantine, parent);
1927
+ const renamed = await readOwner(quarantine);
1928
+ if (!renamed || !sameRuntimeLockOwner(renamed, expected)
1929
+ || await privateOwnerFingerprint(quarantine) !== ownerFingerprint) {
1930
+ await restoreQuarantinedPrivateDirectory(quarantine, lockPath, parent).catch(() => undefined);
1931
+ return false;
1932
+ }
1933
+ return removeGitLifecyclePrivateDirectory(quarantine, parent, async (renamedPath) => await privateOwnerFingerprint(renamedPath) === ownerFingerprint);
1934
+ }
1935
+ catch (error) {
1936
+ if (isNodeError(error, 'ENOENT'))
1937
+ return false;
1938
+ throw error;
1939
+ }
1940
+ }
1941
+ async function recentlyCreated(path) {
1942
+ try {
1943
+ return Date.now() - (await stat(path)).mtimeMs < 30_000;
1944
+ }
1945
+ catch {
1946
+ return true;
1947
+ }
1948
+ }
1949
+ async function readOwner(lockPath) {
1950
+ try {
1951
+ await assertGitLifecyclePrivateDirectory(lockPath, dirname(lockPath));
1952
+ const ownerPath = join(lockPath, 'owner.json');
1953
+ await assertSafePrivateFile(ownerPath, lockPath);
1954
+ const value = JSON.parse(await readFile(ownerPath, 'utf8'));
1955
+ return Number.isSafeInteger(value.pid) && Number(value.pid) > 0
1956
+ && (value.nonce === undefined || typeof value.nonce === 'string' && /^[a-f0-9]{32}$/.test(value.nonce))
1957
+ ? { pid: Number(value.pid), ...(typeof value.nonce === 'string' ? { nonce: value.nonce } : {}) }
1958
+ : undefined;
1959
+ }
1960
+ catch {
1961
+ return undefined;
1962
+ }
1963
+ }
1964
+ function sameRuntimeLockOwner(left, right) {
1965
+ return left.pid === right.pid && left.nonce !== undefined && left.nonce === right.nonce;
1966
+ }
1967
+ function processExists(pid) {
1968
+ try {
1969
+ process.kill(pid, 0);
1970
+ return true;
1971
+ }
1972
+ catch (error) {
1973
+ return isNodeError(error, 'EPERM');
1974
+ }
1975
+ }
1976
+ function asRecord(value) {
1977
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
1978
+ }
1979
+ function isNodeError(error, code) {
1980
+ return error instanceof Error && 'code' in error && error.code === code;
1981
+ }