@ornexus/neocortex 4.60.68 → 4.60.70

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 (232) hide show
  1. package/dist/release-source.json +1 -1
  2. package/dist/sbom.cdx.json +1194 -1194
  3. package/install.ps1 +1 -1
  4. package/install.sh +1 -1
  5. package/package.json +3 -3
  6. package/packages/client/dist/adapters/adapter-registry.js +1 -108
  7. package/packages/client/dist/adapters/antigravity-adapter.js +2 -77
  8. package/packages/client/dist/adapters/claude-code-adapter.js +3 -79
  9. package/packages/client/dist/adapters/codex-adapter.js +2 -80
  10. package/packages/client/dist/adapters/cursor-adapter.js +4 -115
  11. package/packages/client/dist/adapters/gemini-adapter.js +2 -71
  12. package/packages/client/dist/adapters/grok-adapter.js +2 -42
  13. package/packages/client/dist/adapters/index.js +1 -22
  14. package/packages/client/dist/adapters/platform-detector.js +1 -212
  15. package/packages/client/dist/adapters/target-adapter.js +0 -12
  16. package/packages/client/dist/adapters/vscode-adapter.js +2 -72
  17. package/packages/client/dist/agent/refresh-stubs.js +2 -417
  18. package/packages/client/dist/agent/update-agent-yaml.js +1 -102
  19. package/packages/client/dist/agent/update-description.js +1 -251
  20. package/packages/client/dist/cache/crypto-utils.js +1 -78
  21. package/packages/client/dist/cache/encrypted-cache.js +1 -108
  22. package/packages/client/dist/cache/in-memory-asset-cache.js +1 -73
  23. package/packages/client/dist/cache/index.js +1 -13
  24. package/packages/client/dist/cache/protected-pi-boundary.js +1 -44
  25. package/packages/client/dist/checkpoint/checkpoint-client-reader.js +2 -127
  26. package/packages/client/dist/checkpoint/index.js +1 -11
  27. package/packages/client/dist/checkpoint/shared-checkpoint-types.js +1 -32
  28. package/packages/client/dist/cli.js +3 -290
  29. package/packages/client/dist/commands/activate.js +8 -394
  30. package/packages/client/dist/commands/architecture-answers.js +7 -274
  31. package/packages/client/dist/commands/architecture-memory-terminal.js +4 -253
  32. package/packages/client/dist/commands/architecture-non-ui-continuation.js +1 -100
  33. package/packages/client/dist/commands/architecture-root-binding.js +1 -51
  34. package/packages/client/dist/commands/cache-status.js +2 -112
  35. package/packages/client/dist/commands/config.js +1 -88
  36. package/packages/client/dist/commands/doctor.js +2 -635
  37. package/packages/client/dist/commands/invoke.d.ts +6 -0
  38. package/packages/client/dist/commands/invoke.js +142 -7140
  39. package/packages/client/dist/commands/legacy-epic-terminal.js +1 -71
  40. package/packages/client/dist/commands/legacy-story-branch.js +1 -96
  41. package/packages/client/dist/commands/legacy-story-pr.js +1 -276
  42. package/packages/client/dist/commands/mutation-qa-checkpoint.js +5 -285
  43. package/packages/client/dist/commands/mutation-qa-gate.js +1 -54
  44. package/packages/client/dist/commands/mutation-qa-git.js +1 -41
  45. package/packages/client/dist/commands/mutation-qa-policy.js +1 -102
  46. package/packages/client/dist/commands/mutation-qa-run.js +3 -219
  47. package/packages/client/dist/commands/mutation-qa-verdict.js +1 -149
  48. package/packages/client/dist/commands/mutation-qa.js +1 -220
  49. package/packages/client/dist/commands/native-agent.js +2 -136
  50. package/packages/client/dist/commands/planning-diagnosis.js +1 -43
  51. package/packages/client/dist/commands/planning-generation.js +1 -53
  52. package/packages/client/dist/commands/planning-memory.js +1 -40
  53. package/packages/client/dist/commands/planning-research.js +1 -42
  54. package/packages/client/dist/commands/private-session.js +1 -75
  55. package/packages/client/dist/commands/property-qa.js +1 -139
  56. package/packages/client/dist/commands/qa-control.js +1 -16
  57. package/packages/client/dist/commands/qa-scan-browser-launch.js +1 -55
  58. package/packages/client/dist/commands/qa-scan-browser-route.js +1 -28
  59. package/packages/client/dist/commands/qa-scan-capture.js +5 -663
  60. package/packages/client/dist/commands/qa-scan-discovery.js +3 -376
  61. package/packages/client/dist/commands/qa-scan-review.js +5 -826
  62. package/packages/client/dist/commands/qa-synthetic-backend-capture.js +1 -266
  63. package/packages/client/dist/commands/qa-synthetic-backend-review.js +1 -297
  64. package/packages/client/dist/commands/qa-synthetic-backend.js +2 -340
  65. package/packages/client/dist/commands/qa-synthetic-broker-port.js +1 -85
  66. package/packages/client/dist/commands/qa-synthetic-browser.js +1 -39
  67. package/packages/client/dist/commands/refactor-characterization.js +21 -671
  68. package/packages/client/dist/commands/refactor-commit.js +13 -399
  69. package/packages/client/dist/commands/refactor-control.js +3 -142
  70. package/packages/client/dist/commands/refactor-cpp-assertions.js +3 -93
  71. package/packages/client/dist/commands/refactor-review.js +5 -283
  72. package/packages/client/dist/commands/refactor-root.js +7 -588
  73. package/packages/client/dist/commands/refresh-memory.js +3 -156
  74. package/packages/client/dist/commands/shared-architecture-non-ui.js +1 -24
  75. package/packages/client/dist/commands/story-checkpoint.js +1 -171
  76. package/packages/client/dist/config/profile-directory.js +1 -13
  77. package/packages/client/dist/config/resolver-selection.js +1 -278
  78. package/packages/client/dist/config/secure-config.js +12 -281
  79. package/packages/client/dist/config.js +2 -223
  80. package/packages/client/dist/constants.js +1 -25
  81. package/packages/client/dist/context/context-collector.js +2 -222
  82. package/packages/client/dist/context/context-sanitizer.js +1 -145
  83. package/packages/client/dist/continuity/autonomous-governance.js +1 -285
  84. package/packages/client/dist/continuity/continuity-client-state-store.js +1 -1587
  85. package/packages/client/dist/continuity/invoke-hooks.js +1 -129
  86. package/packages/client/dist/continuity/migrations/001-initial-schema.js +2 -12
  87. package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +2 -12
  88. package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +2 -12
  89. package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +2 -12
  90. package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +2 -12
  91. package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +2 -10
  92. package/packages/client/dist/continuity/native-agent-store.js +1 -69
  93. package/packages/client/dist/continuity/native-agent-types.js +1 -90
  94. package/packages/client/dist/continuity/outbox.js +1 -117
  95. package/packages/client/dist/continuity/runner-bootstrap-policy.js +1 -172
  96. package/packages/client/dist/continuity/sqlite-store.js +81 -2536
  97. package/packages/client/dist/effects/invoke-effect-gateways.js +1 -62
  98. package/packages/client/dist/errors/error-messages.js +2 -228
  99. package/packages/client/dist/errors/frontmatter-diagnostics.js +1 -125
  100. package/packages/client/dist/evidence/collector.js +1 -347
  101. package/packages/client/dist/evidence/index.js +1 -1
  102. package/packages/client/dist/evidence/types.js +0 -2
  103. package/packages/client/dist/fs/atomic-state-replace.js +1 -152
  104. package/packages/client/dist/generated/command-registry.generated.js +1 -4076
  105. package/packages/client/dist/generated/public-projections.generated.js +1 -149
  106. package/packages/client/dist/git/direct-yolo-c01-retry.js +4 -138
  107. package/packages/client/dist/git/direct-yolo-checkpoint.js +4 -1234
  108. package/packages/client/dist/git/git-lifecycle-step-ids.js +1 -14
  109. package/packages/client/dist/git/git-lifecycle-store.js +9 -1938
  110. package/packages/client/dist/git/legacy-story-branch-checkpoint.js +9 -259
  111. package/packages/client/dist/git/legacy-story-branch-setup.js +4 -348
  112. package/packages/client/dist/git/legacy-story-pr.js +70 -3538
  113. package/packages/client/dist/git/secure-filesystem-boundary.js +5 -182
  114. package/packages/client/dist/graph-retrieval/pre-command-hook.js +1 -284
  115. package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +1 -237
  116. package/packages/client/dist/i18n/first-run.js +2 -60
  117. package/packages/client/dist/index.js +1 -47
  118. package/packages/client/dist/invocation/invoke-input.js +1 -24
  119. package/packages/client/dist/license/index.js +1 -5
  120. package/packages/client/dist/license/license-client.js +1 -304
  121. package/packages/client/dist/machine/fingerprint.js +2 -163
  122. package/packages/client/dist/machine/index.js +1 -5
  123. package/packages/client/dist/memory/project-memory-path-scope.js +1 -62
  124. package/packages/client/dist/memory/project-memory-writer.js +40 -1007
  125. package/packages/client/dist/memory/shared-project-memory-types.js +2 -253
  126. package/packages/client/dist/migrations/project-identity-capability.js +1 -50
  127. package/packages/client/dist/migrations/project-identity.js +7 -493
  128. package/packages/client/dist/migrations/state-json.js +4 -344
  129. package/packages/client/dist/policy/architecture-policy.js +2 -184
  130. package/packages/client/dist/policy/index.js +1 -7
  131. package/packages/client/dist/policy/shared-policy-types.js +0 -38
  132. package/packages/client/dist/policy/subagent-approval-policy.js +1 -55
  133. package/packages/client/dist/private-execution/clean-baseline.js +2 -279
  134. package/packages/client/dist/private-execution/codex-executor.js +4 -574
  135. package/packages/client/dist/private-execution/codex-host-runtime.js +1 -137
  136. package/packages/client/dist/private-execution/codex-lifecycle-guard.js +1 -97
  137. package/packages/client/dist/private-execution/codex-scoped-permissions.js +1 -128
  138. package/packages/client/dist/private-execution/delivery.js +13 -410
  139. package/packages/client/dist/private-execution/diagnosis-root-review.js +2 -294
  140. package/packages/client/dist/private-execution/frozen-handoff.js +6 -19
  141. package/packages/client/dist/private-execution/implementation-validation.js +2 -106
  142. package/packages/client/dist/private-execution/isolated-admission.js +23 -1432
  143. package/packages/client/dist/private-execution/isolated-session.js +5 -725
  144. package/packages/client/dist/private-execution/isolated-workspace.js +1 -122
  145. package/packages/client/dist/private-execution/pact-consumer-runtime.js +3 -409
  146. package/packages/client/dist/private-execution/pact-test-source.js +1 -258
  147. package/packages/client/dist/private-execution/pact-toolchain.js +3 -290
  148. package/packages/client/dist/private-execution/pact-write-set.js +1 -172
  149. package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +5 -406
  150. package/packages/client/dist/private-execution/planning-generation-checkpoint.js +5 -462
  151. package/packages/client/dist/private-execution/planning-memory-checkpoint.js +3 -139
  152. package/packages/client/dist/private-execution/planning-research-checkpoint.js +4 -325
  153. package/packages/client/dist/private-execution/private-report-store.js +1 -42
  154. package/packages/client/dist/private-execution/property-test-candidate.js +1 -103
  155. package/packages/client/dist/private-execution/property-test-choices.js +3 -147
  156. package/packages/client/dist/private-execution/property-test-plan.js +7 -271
  157. package/packages/client/dist/private-execution/property-test-review.js +4 -258
  158. package/packages/client/dist/private-execution/property-test-runtime.js +9 -372
  159. package/packages/client/dist/private-execution/property-test-source.js +1 -139
  160. package/packages/client/dist/private-execution/root-checkpoint.js +1 -80
  161. package/packages/client/dist/private-execution/root-memory-git-evidence.js +3 -135
  162. package/packages/client/dist/private-execution/root-memory-recovery.js +1 -93
  163. package/packages/client/dist/private-execution/root-memory-request.js +1 -29
  164. package/packages/client/dist/private-execution/root-memory-scope.js +1 -178
  165. package/packages/client/dist/private-execution/root-memory-writer.js +1 -102
  166. package/packages/client/dist/private-execution/session-lease.js +1 -63
  167. package/packages/client/dist/private-execution/shared-private-admission-types.js +1 -50
  168. package/packages/client/dist/private-execution/shared-project-validation.js +1 -66
  169. package/packages/client/dist/private-execution/shared-root-memory-types.js +1 -61
  170. package/packages/client/dist/private-execution/ux-source-validation.js +1 -372
  171. package/packages/client/dist/private-execution/ux-write-set.js +2 -191
  172. package/packages/client/dist/resilience/circuit-breaker.js +1 -170
  173. package/packages/client/dist/resilience/degradation-manager.js +1 -164
  174. package/packages/client/dist/resilience/freshness-indicator.js +1 -100
  175. package/packages/client/dist/resilience/index.js +1 -8
  176. package/packages/client/dist/resilience/recovery-detector.js +1 -74
  177. package/packages/client/dist/resolvers/asset-resolver.js +0 -13
  178. package/packages/client/dist/resolvers/local-resolver.js +8 -255
  179. package/packages/client/dist/resolvers/remote-resolver.js +1 -233
  180. package/packages/client/dist/runner/binary.js +1 -404
  181. package/packages/client/dist/runner/cli.js +17 -1571
  182. package/packages/client/dist/runner/headless/adapters.js +1 -49
  183. package/packages/client/dist/runner/headless/claude-code-adapter.js +1 -79
  184. package/packages/client/dist/runner/headless/continuous-story-workspace.js +11 -1628
  185. package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +1 -69
  186. package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +2 -110
  187. package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +8 -1236
  188. package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +2 -318
  189. package/packages/client/dist/runner/headless/fake-adapter.js +1 -62
  190. package/packages/client/dist/runner/headless/managed-profile.js +1 -132
  191. package/packages/client/dist/runner/headless/opencode-adapter.js +2 -145
  192. package/packages/client/dist/runner/headless/prepared-story-bundle.js +1 -43
  193. package/packages/client/dist/runner/headless/resolver.js +1 -231
  194. package/packages/client/dist/runner/headless/run-yolo.js +1 -885
  195. package/packages/client/dist/runner/headless/run-yoloop.js +2 -2574
  196. package/packages/client/dist/runner/headless/types.js +2 -574
  197. package/packages/client/dist/runner/local-console.js +13 -396
  198. package/packages/client/dist/runner/portal-sync-worker.js +1 -1487
  199. package/packages/client/dist/runner/project-identity.js +1 -106
  200. package/packages/client/dist/runner/scheduler.js +7 -995
  201. package/packages/client/dist/runner-cli.js +2 -18
  202. package/packages/client/dist/state/invocation-shape.js +1 -13
  203. package/packages/client/dist/state/project-state-snapshot.js +1 -1677
  204. package/packages/client/dist/state/state-json-repair.js +3 -785
  205. package/packages/client/dist/state/structural-dag-projection.js +1 -118
  206. package/packages/client/dist/telemetry/index.js +1 -5
  207. package/packages/client/dist/telemetry/offline-queue.js +1 -131
  208. package/packages/client/dist/tier/index.js +1 -5
  209. package/packages/client/dist/tier/tier-aware-client.js +1 -405
  210. package/packages/client/dist/types/index.js +1 -38
  211. package/packages/client/dist/version.js +1 -27
  212. package/packages/client/dist/yolo/change-evidence-collector.js +1 -126
  213. package/packages/client/dist/yolo/invoke-envelope-capability.js +1 -62
  214. package/packages/client/dist/yolo/spec-envelope-collector.js +5 -185
  215. package/packages/client/dist/yoloop/discovery-hook.js +2 -300
  216. package/packages/client/dist/yoloop/index.js +1 -8
  217. package/packages/client/dist/yoloop/invoke-hooks.js +12 -669
  218. package/packages/client/dist/yoloop/shared-discover-epics.js +1 -1007
  219. package/packages/client/dist/yoloop/shared-yoloop-types.js +1 -33
  220. package/packages/client/dist/yoloop/target-normalization.js +1 -180
  221. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1723
  222. package/targets-stubs/antigravity/gemini.md +1 -1
  223. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  224. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  225. package/targets-stubs/claude-code/neocortex-root.md +2 -2
  226. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  227. package/targets-stubs/claude-code/neocortex.md +2 -2
  228. package/targets-stubs/codex/AGENTS.md +1 -1
  229. package/targets-stubs/cursor/agent.md +2 -2
  230. package/targets-stubs/gemini-cli/agent.md +2 -2
  231. package/targets-stubs/opencode/neocortex-root.md +1 -1
  232. package/targets-stubs/vscode/neocortex.agent.md +2 -2
@@ -1,1938 +1,9 @@
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
- const next = validateDocument({
365
- ...mutable,
366
- schemaVersion: 1,
367
- revision: current.revision + 1,
368
- });
369
- await options.preCommit?.(next);
370
- await assertSafeFilesystemEntry(runtimeDirectory, 'directory');
371
- await assertStoreDestinationSafe(storePath);
372
- await anchoredAtomicWriteFile(runtimeDirectory, GIT_LIFECYCLE_RUNTIME_FILE, `${JSON.stringify(next, null, 2)}\n`);
373
- await assertSafeFilesystemEntry(storePath, 'file');
374
- await syncDirectory(runtimeDirectory);
375
- return { document: next, value };
376
- }
377
- finally {
378
- await releaseRuntimeLock(lockPath, lockOwner).catch(() => undefined);
379
- }
380
- }
381
- async function syncDirectory(path) {
382
- let handle;
383
- try {
384
- handle = await open(path, 'r');
385
- }
386
- catch (error) {
387
- // Node cannot open directories on Windows. The file itself has already
388
- // been flushed; keep the stronger parent-directory flush everywhere else.
389
- if (process.platform === 'win32' && (isNodeError(error, 'EPERM') || isNodeError(error, 'EISDIR') || isNodeError(error, 'EINVAL')))
390
- return;
391
- throw error;
392
- }
393
- try {
394
- await handle.sync();
395
- }
396
- finally {
397
- await handle.close();
398
- }
399
- }
400
- /** Merge the runtime overlay over tracked state without mutating either input. */
401
- export function applyGitLifecycleRuntimeOverlay(tracked, runtime) {
402
- const merged = structuredClone(tracked);
403
- const epics = normalizeTrackedCollection(merged, 'epics');
404
- const stories = normalizeTrackedCollection(merged, 'stories');
405
- for (const [epicId, overlay] of Object.entries(runtime.epics)) {
406
- epics[epicId] = { ...(asRecord(epics[epicId]) ?? {}), ...structuredClone(overlay) };
407
- }
408
- for (const [storyId, overlay] of Object.entries(runtime.stories)) {
409
- const trackedStory = asRecord(stories[storyId]) ?? {};
410
- const trackedSteps = trackedStory.steps_completed;
411
- const runtimeSteps = overlay.steps_completed;
412
- const c01OnlyOverlay = overlay.status === 'branch-ready'
413
- && overlay.last_step === GIT_LIFECYCLE_SETUP_BRANCH_STEP_ID
414
- && Array.isArray(runtimeSteps) && runtimeSteps.length === 1
415
- && runtimeSteps[0] === GIT_LIFECYCLE_SETUP_BRANCH_STEP_ID
416
- && overlay.checkpoint === undefined;
417
- // Once the tracked story includes C01, later tracked steps must not be
418
- // masked by the private C01-only bridge used before the first commit.
419
- if (c01OnlyOverlay && Array.isArray(trackedSteps)
420
- && trackedSteps.includes(GIT_LIFECYCLE_SETUP_BRANCH_STEP_ID))
421
- continue;
422
- stories[storyId] = { ...trackedStory, ...structuredClone(overlay) };
423
- }
424
- return merged;
425
- }
426
- /**
427
- * Older project state used arrays while current consumers require records keyed
428
- * by canonical IDs. Normalize only the in-memory projection and fail closed on
429
- * ambiguous entries; the tracked document remains byte-for-byte untouched.
430
- */
431
- function normalizeTrackedCollection(parent, key) {
432
- const current = parent[key];
433
- if (current === undefined) {
434
- const created = {};
435
- parent[key] = created;
436
- return created;
437
- }
438
- const record = asRecord(current);
439
- if (record)
440
- return record;
441
- if (!Array.isArray(current))
442
- throw new Error(`git-lifecycle-tracked-${key}-invalid`);
443
- const normalized = {};
444
- for (const entry of current) {
445
- const value = asRecord(entry);
446
- const id = typeof value?.id === 'string' ? value.id.trim() : '';
447
- if (!id || normalized[id] !== undefined)
448
- throw new Error(`git-lifecycle-tracked-${key}-ambiguous`);
449
- normalized[id] = value;
450
- }
451
- parent[key] = normalized;
452
- return normalized;
453
- }
454
- export function ensureRuntimeEpic(document, epicId) {
455
- return document.epics[epicId] ?? (document.epics[epicId] = {});
456
- }
457
- export function ensureRuntimeStory(document, storyId) {
458
- return document.stories[storyId] ?? (document.stories[storyId] = {});
459
- }
460
- /** Runtime-safe accepted-checkpoint predicate kept in the published client closure. */
461
- export function isAcceptedGitLifecycleStoryCheckpoint(value, storyId, epicId, executionBranch) {
462
- const item = asRecord(value);
463
- return Boolean(item && item.epicId === epicId && item.branch === executionBranch
464
- && item.qaVerdict === 'passed' && item.reviewVerdict === 'approved'
465
- && validateCheckpoint(item, storyId));
466
- }
467
- /**
468
- * Structural JSON is untrusted under a same-UID threat model. Recompute the
469
- * commit-range/review authority from Git before a checkpoint can authorize
470
- * aggregate QA or terminal integration.
471
- */
472
- export async function verifyAcceptedGitLifecycleStoryCheckpoint(projectRoot, value, storyId, epicId, executionBranch, transientAuthority, authorizedWriteSet = []) {
473
- return verifyAcceptedGitLifecycleStoryCheckpointSync(projectRoot, value, storyId, epicId, executionBranch, transientAuthority, authorizedWriteSet).verdict === 'verified';
474
- }
475
- /**
476
- * Synchronous variant used at the state-snapshot boundary, before any runtime
477
- * overlay is sent to the server. It returns a fresh, public, Git-derived receipt.
478
- */
479
- export function verifyAcceptedGitLifecycleStoryCheckpointSync(projectRoot, value, storyId, epicId, executionBranch, transientAuthority, authorizedWriteSet = [], nowIso = new Date().toISOString(), mode = transientAuthority === undefined ? 'scheduling' : 'integration') {
480
- const rejected = () => checkpointVerification({
481
- storyId, epicId, executionBranch, value, verifiedAt: nowIso,
482
- verdict: 'rejected', reasonCode: 'checkpoint-git-physical-invalid',
483
- });
484
- try {
485
- if (!isAcceptedGitLifecycleStoryCheckpoint(value, storyId, epicId, executionBranch))
486
- return rejected();
487
- const checkpoint = asRecord(value);
488
- const qa = asRecord(checkpoint.qaReceipt);
489
- const review = asRecord(checkpoint.reviewReceipt);
490
- const headSha = String(checkpoint.headSha);
491
- const baseSha = String(checkpoint.baseSha);
492
- const publicReview = publicStoryReviewArtifactFromGit(projectRoot, storyId, headSha, review.reviewArtifactDigest === undefined);
493
- const legacyWithoutPublicArtifact = review.reviewArtifactDigest === undefined
494
- && !publicStoryReviewBlobExistsInGit(projectRoot, storyId, headSha);
495
- if (review.reviewArtifactDigest !== undefined && review.reviewArtifactDigest !== publicReview?.digest)
496
- return rejected();
497
- const branchHead = gitSync(projectRoot, ['rev-parse', `refs/heads/${executionBranch}`]).trim();
498
- execFileSync('git', ['merge-base', '--is-ancestor', headSha, branchHead], { cwd: projectRoot, stdio: 'ignore' });
499
- const historicalReviewPending = !legacyWithoutPublicArtifact && publicReview?.verdict !== 'approved';
500
- if (historicalReviewPending) {
501
- if (review.reviewArtifactDigest !== undefined || !verifyHistoricalReviewReconciliation(projectRoot, checkpoint, review, qa, storyId, epicId, branchHead))
502
- return rejected();
503
- }
504
- else if (checkpoint.reviewReconciliation !== undefined) {
505
- // A supplemental receipt cannot replace or shadow an already approved blob.
506
- return rejected();
507
- }
508
- const commitBody = gitSync(projectRoot, ['show', '--no-patch', '--format=%H%n%P%n%B', headSha]);
509
- const parentSha = gitSync(projectRoot, ['rev-parse', `${headSha}^`]).trim();
510
- const commitCount = gitSync(projectRoot, ['rev-list', '--count', `${baseSha}..${headSha}`]).trim();
511
- if (parentSha !== baseSha || commitCount !== '1')
512
- return rejected();
513
- const trailers = new Set(commitBody.split('\n').map((line) => line.trim()));
514
- const reviewTrailers = commitBody.split('\n').map((line) => line.trim())
515
- .filter((line) => line.startsWith('Neocortex-Review-Artifact-Digest: '));
516
- if (reviewTrailers.length > 0 && (reviewTrailers.length !== 1
517
- || reviewTrailers[0] !== `Neocortex-Review-Artifact-Digest: ${publicReview?.digest}`
518
- || review.reviewArtifactDigest !== publicReview?.digest))
519
- return rejected();
520
- const requiredTrailers = [
521
- `Neocortex-Story: ${storyId}`,
522
- `Neocortex-Epic: ${epicId}`,
523
- `Neocortex-Execution-Branch: ${executionBranch}`,
524
- `Neocortex-Bundle-Digest: ${String(checkpoint.bundleDigest)}`,
525
- `Neocortex-Graph-Revision: ${String(checkpoint.graphRevision)}`,
526
- ...(checkpoint.dispatchId ? [`Neocortex-Dispatch: ${String(checkpoint.dispatchId)}`] : []),
527
- ...(checkpoint.bundleId ? [`Neocortex-Bundle: ${String(checkpoint.bundleId)}`] : []),
528
- ...(checkpoint.claimRevision ? [`Neocortex-Claim-Revision: ${String(checkpoint.claimRevision)}`] : []),
529
- ];
530
- const hasCanonicalTrailers = requiredTrailers.every((trailer) => trailers.has(trailer));
531
- if (hasCanonicalTrailers) {
532
- const preparedBaseLine = [...trailers].find((line) => line.startsWith('Neocortex-Prepared-Base: '));
533
- const preparedBase = preparedBaseLine?.slice('Neocortex-Prepared-Base: '.length) ?? '';
534
- if (!/^[a-f0-9]{40,64}$/i.test(preparedBase))
535
- return rejected();
536
- execFileSync('git', ['merge-base', '--is-ancestor', preparedBase, headSha], { cwd: projectRoot, stdio: 'ignore' });
537
- }
538
- const changedRaw = gitSync(projectRoot, [
539
- 'diff-tree', '--no-commit-id', '--name-only', '-r', '-z', headSha,
540
- ]);
541
- const changedPaths = changedRaw.split('\0').filter(Boolean);
542
- if (changedPaths.length === 0 || changedPaths.some((path) => !safeRepositoryPath(path)))
543
- return rejected();
544
- const commitBodyDigest = createHash('sha256').update(commitBody).digest('hex');
545
- const changedPathsDigest = createHash('sha256').update([...changedPaths].sort().join('\0')).digest('hex');
546
- const claimProof = checkpointClaimProof({
547
- projectRoot, checkpoint, storyId, epicId, executionBranch, baseSha, headSha,
548
- commitBodyDigest, changedPathsDigest, changedPaths,
549
- authorizedWriteSet: authorizedWriteSet.length > 0
550
- ? authorizedWriteSet : trackedStoryWriteSet(projectRoot, storyId),
551
- });
552
- if (!claimProof)
553
- return rejected();
554
- if (qa.receiptRef !== `story-qa:${String(qa.sourceEvidenceDigest)}`)
555
- return rejected();
556
- const expectedQaDigest = computeImmutableStoryQaEvidenceDigest({
557
- storyId, baseSha, headSha,
558
- bundleDigest: String(checkpoint.bundleDigest), graphRevision: String(checkpoint.graphRevision),
559
- commitBody, changedPaths,
560
- });
561
- const executionQa = qa.evidenceVersion === 'p205-execution-v2'
562
- && qa.contract === 'P205-CI-02'
563
- && qa.command === P205_COMPLETE_COMMAND
564
- && isHash(qa.manifestDigest)
565
- && safeStringArray(qa.gateIds, 32, true)
566
- && isHash(qa.resultDigest)
567
- && qa.sourceEvidenceDigest === computeP205StoryQaEvidenceDigest({
568
- gitEvidenceDigest: expectedQaDigest,
569
- headSha,
570
- graphRevision: String(checkpoint.graphRevision),
571
- manifestDigest: String(qa.manifestDigest),
572
- gateIds: qa.gateIds,
573
- resultDigest: String(qa.resultDigest),
574
- });
575
- const immutableQa = qa.evidenceVersion === 'git-immutable-v1'
576
- && qa.sourceEvidenceDigest === expectedQaDigest;
577
- const boundedLegacyQa = qa.evidenceVersion === undefined
578
- && claimProof.mode === 'legacy-migration'
579
- && claimProof.binding?.qaDigest === qa.sourceEvidenceDigest;
580
- const aggregateP205 = transientP205AuthorityMatches(transientAuthority, branchHead, String(checkpoint.graphRevision));
581
- const transientQa = aggregateP205 && (executionQa
582
- || claimProof.mode === 'legacy-migration' && (immutableQa || boundedLegacyQa));
583
- const schedulingQa = mode === 'scheduling' && transientAuthority === undefined && executionQa;
584
- if (!transientQa && !schedulingQa)
585
- return rejected();
586
- const artifactBound = review.reviewArtifactDigest !== undefined;
587
- const expectedReviewDigest = createHash('sha256').update([
588
- artifactBound ? 'root-commit-range-review-v2' : 'root-commit-range-review-v1', storyId, baseSha, headSha,
589
- String(checkpoint.bundleDigest), String(checkpoint.graphRevision), String(qa.receiptRef),
590
- createHash('sha256').update(commitBody).digest('hex'), [...changedPaths].sort().join(','),
591
- ...(artifactBound ? [publicReview.digest] : []),
592
- ].join('\0')).digest('hex');
593
- if (review.sourceEvidenceDigest !== expectedReviewDigest
594
- || review.receiptRef !== `story-review:${expectedReviewDigest}`)
595
- return rejected();
596
- if (claimProof.mode === 'legacy-migration'
597
- && claimProof.binding?.reviewDigest !== undefined
598
- && claimProof.binding.reviewDigest !== review.sourceEvidenceDigest)
599
- return rejected();
600
- if (!hasCanonicalTrailers) {
601
- if (LEGACY_DIRECT_YOLO_RECOVERY_COMMITS[storyId] !== headSha
602
- || !Array.isArray(checkpoint.receiptRefs)
603
- || !checkpoint.receiptRefs.some((item) => typeof item === 'string' && item.startsWith('legacy-direct-yolo:'))) {
604
- return rejected();
605
- }
606
- }
607
- const sourceEvidenceDigest = createHash('sha256').update([
608
- 'checkpoint-p205-transient-v1', storyId, epicId, executionBranch, baseSha, headSha,
609
- String(checkpoint.bundleDigest), commitBodyDigest, changedPathsDigest,
610
- claimProof.mode, claimProof.effectiveClaimRevision,
611
- String(qa.sourceEvidenceDigest), String(review.sourceEvidenceDigest),
612
- ...(checkpoint.reviewReconciliation
613
- ? [String(asRecord(checkpoint.reviewReconciliation)?.sourceEvidenceDigest)] : []),
614
- transientAuthorityEvidence(transientAuthority)?.executionEvidenceDigest
615
- ?? (schedulingQa ? 'scheduling-git-verification' : 'legacy-bounded-authority'),
616
- ].join('\0')).digest('hex');
617
- const effectiveAuthority = transientQa ? transientAuthorityEvidence(transientAuthority) : undefined;
618
- return Object.freeze({
619
- schemaVersion: 1, kind: 'story_checkpoint_physical_verification', producer: 'client-p205-transient-v1',
620
- storyId, epicId, branch: executionBranch, baseSha, headSha,
621
- aggregateHeadSha: effectiveAuthority?.executionHeadSha ?? headSha,
622
- commitBodyDigest, changedPathsDigest,
623
- claimVerification: claimProof.mode,
624
- effectiveClaimRevision: claimProof.effectiveClaimRevision,
625
- sourceEvidenceDigest,
626
- command: P205_COMPLETE_COMMAND,
627
- manifestDigest: effectiveAuthority?.manifestDigest ?? '0'.repeat(64),
628
- gateIds: effectiveAuthority?.gateIds ?? Object.freeze(['legacy-bounded']),
629
- resultDigest: effectiveAuthority?.resultDigest ?? '0'.repeat(64),
630
- graphRevision: String(checkpoint.graphRevision),
631
- executionEvidenceDigest: effectiveAuthority?.executionEvidenceDigest ?? '0'.repeat(64),
632
- verifiedAt: effectiveAuthority?.executedAt ?? normalizedIso(nowIso), verdict: 'verified',
633
- reasonCodes: Object.freeze([
634
- transientQa ? 'checkpoint-p205-transient-verified'
635
- : schedulingQa ? 'checkpoint-git-scheduling-verified'
636
- : 'checkpoint-legacy-bounded-verified',
637
- ...(claimProof.mode === 'legacy-migration' ? ['checkpoint-legacy-migration-verified'] : []),
638
- ]), publicSafe: true,
639
- });
640
- }
641
- catch {
642
- return rejected();
643
- }
644
- }
645
- /**
646
- * Classify a checkpoint for one story without allowing physical scheduling
647
- * evidence to impersonate transient P205 integration authority.
648
- */
649
- export function classifyGitLifecycleStoryCheckpointProjectionSync(projectRoot, value, storyId, epicId, executionBranch, transientAuthority, authorizedWriteSet = [], nowIso = new Date().toISOString()) {
650
- const mode = transientAuthority ? 'integration' : 'scheduling';
651
- const verification = verifyAcceptedGitLifecycleStoryCheckpointSync(projectRoot, value, storyId, epicId, executionBranch, transientAuthority, authorizedWriteSet, nowIso, mode);
652
- if (verification.verdict !== 'verified')
653
- return { kind: 'rejected', verification };
654
- if (mode === 'integration')
655
- return { kind: 'integration-authorized', verification };
656
- if (!verification.reasonCodes.includes('checkpoint-git-scheduling-verified')) {
657
- return { kind: 'rejected', verification: checkpointVerification({
658
- storyId, epicId, executionBranch, value, verifiedAt: nowIso,
659
- verdict: 'rejected', reasonCode: 'checkpoint-git-physical-invalid',
660
- }) };
661
- }
662
- return {
663
- kind: 'scheduling-only',
664
- evidence: Object.freeze({
665
- schemaVersion: 1,
666
- kind: 'story_scheduling_checkpoint_evidence',
667
- producer: 'client-git-physical-v1',
668
- storyId,
669
- epicId,
670
- branch: executionBranch,
671
- baseSha: verification.baseSha,
672
- headSha: verification.headSha,
673
- graphRevision: verification.graphRevision,
674
- effectiveClaimRevision: verification.effectiveClaimRevision,
675
- sourceEvidenceDigest: verification.sourceEvidenceDigest,
676
- verifiedAt: verification.verifiedAt,
677
- reasonCodes: Object.freeze(['checkpoint-git-scheduling-verified']),
678
- integrationAuthorized: false,
679
- publicSafe: true,
680
- }),
681
- };
682
- }
683
- function transientP205AuthorityMatches(authority, aggregateHeadSha, graphRevision) {
684
- const evidence = transientAuthorityEvidence(authority);
685
- return Boolean(evidence
686
- && evidence.schemaVersion === 1 && evidence.kind === 'p205_transient_execution_authority'
687
- && evidence.contract === 'P205-CI-02' && evidence.command === P205_COMPLETE_COMMAND
688
- && evidence.graphRevision === graphRevision
689
- && isHash(evidence.resultDigest) && isHash(evidence.executionEvidenceDigest)
690
- && evidence.executionHeadSha === aggregateHeadSha && isSha(aggregateHeadSha));
691
- }
692
- function transientAuthorityEvidence(authority) {
693
- return authority && typeof authority === 'object' ? TRANSIENT_P205_AUTHORITIES.get(authority) : undefined;
694
- }
695
- /** Read-only projection for persistence/audit; it never recreates authority. */
696
- export function projectP205TransientExecutionEvidence(authority) {
697
- const evidence = transientAuthorityEvidence(authority);
698
- return evidence ? Object.freeze({
699
- executionHeadSha: evidence.executionHeadSha,
700
- graphRevision: evidence.graphRevision,
701
- manifestDigest: evidence.manifestDigest,
702
- gateIds: Object.freeze([...evidence.gateIds]),
703
- resultDigest: evidence.resultDigest,
704
- executedAt: evidence.executedAt,
705
- executionEvidenceDigest: evidence.executionEvidenceDigest,
706
- }) : undefined;
707
- }
708
- export function p205TransientAuthorityMatchesAggregate(authority, headSha, graphRevision) {
709
- const evidence = transientAuthorityEvidence(authority);
710
- return Boolean(evidence && evidence.executionHeadSha === headSha
711
- && evidence.graphRevision === graphRevision && evidence.command === P205_COMPLETE_COMMAND);
712
- }
713
- function checkpointClaimProof(input) {
714
- const legacy = LEGACY_P174_CHECKPOINT_MIGRATIONS[input.storyId];
715
- const persistedClaim = typeof input.checkpoint.claimRevision === 'string' ? input.checkpoint.claimRevision : undefined;
716
- const claimlessLegacy = input.storyId === 'P174.03.811759' || input.storyId === 'P174.03.685003';
717
- const normalized = normalizeAuthorizedWriteSet(input.authorizedWriteSet);
718
- let rootMemoryPaths = [];
719
- if (input.checkpoint.rootMemoryEffect !== undefined) {
720
- if (legacy)
721
- return undefined;
722
- try {
723
- rootMemoryPaths = verifyRootMemoryPhysicalWriteSet(input.projectRoot, input.checkpoint.rootMemoryEffect, {
724
- storyId: input.storyId, executionBranch: input.executionBranch, baseSha: input.baseSha,
725
- headRef: input.headSha, childPaths: normalized,
726
- }).files.map(file => file.path);
727
- }
728
- catch {
729
- return undefined;
730
- }
731
- }
732
- const declaredWriteSetValid = normalized.length > 0 && normalized.length === input.authorizedWriteSet.length
733
- && input.changedPaths.every((path) => normalized.some((scope) => writeSetCovers(scope, path)) || rootMemoryPaths.includes(path));
734
- if (legacy
735
- && input.epicId === 'P174' && input.executionBranch === 'epic/P174'
736
- && input.baseSha === legacy.baseSha && input.headSha === legacy.headSha
737
- && input.checkpoint.graphRevision === legacy.graphRevision
738
- && input.commitBodyDigest === legacy.commitBodyDigest
739
- && input.changedPathsDigest === legacy.changedPathsDigest
740
- && (claimlessLegacy ? persistedClaim === undefined : persistedClaim === legacy.effectiveClaimRevision)) {
741
- if (input.authorizedWriteSet.length > 0
742
- && (!declaredWriteSetValid || !claimMatchesWriteSet(legacy.effectiveClaimRevision, normalized)))
743
- return undefined;
744
- return Object.freeze({ mode: 'legacy-migration', effectiveClaimRevision: legacy.effectiveClaimRevision, binding: legacy });
745
- }
746
- // Claimless compatibility is closed over the seven immutable P174 ranges
747
- // above. Format, age, story name and otherwise-valid JSON evidence never
748
- // authorize a generic migration.
749
- if (persistedClaim === undefined)
750
- return undefined;
751
- if (!persistedClaim || !isHash(persistedClaim))
752
- return undefined;
753
- if (!declaredWriteSetValid || !claimMatchesWriteSet(persistedClaim, normalized))
754
- return undefined;
755
- return Object.freeze({ mode: 'canonical', effectiveClaimRevision: persistedClaim });
756
- }
757
- function normalizeAuthorizedWriteSet(values) {
758
- const normalized = values.map((value) => value.replaceAll('\\', '/').replace(/^\.\//, '').replace(/\/{2,}/g, '/'));
759
- if (normalized.some((value) => !safeRepositoryPath(value)))
760
- return Object.freeze([]);
761
- return Object.freeze([...new Set(normalized)].sort());
762
- }
763
- function trackedStoryWriteSet(projectRoot, storyId) {
764
- try {
765
- const state = JSON.parse(readFileSync(join(projectRoot, '.neocortex', 'state.json'), 'utf8'));
766
- const stories = asRecord(state.stories);
767
- const story = asRecord(stories?.[storyId]);
768
- const runtime = readGitLifecycleRuntimeSync(projectRoot);
769
- const checkpoint = asRecord(runtime.stories[storyId]?.checkpoint) ?? asRecord(story?.checkpoint);
770
- return gitLifecycleStoryWriteSet(storyId, story, checkpoint);
771
- }
772
- catch {
773
- return [];
774
- }
775
- }
776
- /** Reconstruct the same closed claim used by the direct or continuous planner. */
777
- export function gitLifecycleStoryWriteSet(storyId, story, checkpoint) {
778
- const authored = Array.isArray(story?.files_to_modify)
779
- ? story.files_to_modify.filter((value) => typeof value === 'string') : [];
780
- if (!checkpoint?.dispatchId || !checkpoint.bundleId)
781
- return authored;
782
- try {
783
- return publicWriteSet({ ...story, id: storyId, files_to_modify: authored });
784
- }
785
- catch {
786
- return [];
787
- }
788
- }
789
- function claimMatchesWriteSet(claimRevision, writeSet) {
790
- return claimRevision === createHash('sha256').update(writeSet.join('\0')).digest('hex')
791
- || claimRevision === createHash('sha256').update(JSON.stringify(writeSet)).digest('hex');
792
- }
793
- function writeSetCovers(scope, path) {
794
- return scope === path || scope.endsWith('/') && path.startsWith(scope);
795
- }
796
- /** Pre-P205 admission for only the exact seven immutable P174 migrations. */
797
- export function verifyLegacyCheckpointMigrationCandidateSync(projectRoot, value, storyId, epicId, executionBranch, authorizedWriteSet) {
798
- try {
799
- if (!isAcceptedGitLifecycleStoryCheckpoint(value, storyId, epicId, executionBranch))
800
- return false;
801
- const checkpoint = asRecord(value);
802
- const qa = asRecord(checkpoint.qaReceipt);
803
- const review = asRecord(checkpoint.reviewReceipt);
804
- const headSha = String(checkpoint.headSha);
805
- const baseSha = String(checkpoint.baseSha);
806
- const branchHead = gitSync(projectRoot, ['rev-parse', `refs/heads/${executionBranch}`]).trim();
807
- execFileSync('git', ['merge-base', '--is-ancestor', headSha, branchHead], { cwd: projectRoot, stdio: 'ignore' });
808
- if (gitSync(projectRoot, ['rev-parse', `${headSha}^`]).trim() !== baseSha
809
- || gitSync(projectRoot, ['rev-list', '--count', `${baseSha}..${headSha}`]).trim() !== '1')
810
- return false;
811
- const commitBody = gitSync(projectRoot, ['show', '--no-patch', '--format=%H%n%P%n%B', headSha]);
812
- const changedPaths = gitSync(projectRoot, ['diff-tree', '--no-commit-id', '--name-only', '-r', '-z', headSha])
813
- .split('\0').filter(Boolean);
814
- if (changedPaths.length === 0 || changedPaths.some((path) => !safeRepositoryPath(path)))
815
- return false;
816
- const proof = checkpointClaimProof({
817
- projectRoot, checkpoint, storyId, epicId, executionBranch, baseSha, headSha, changedPaths, authorizedWriteSet,
818
- commitBodyDigest: createHash('sha256').update(commitBody).digest('hex'),
819
- changedPathsDigest: createHash('sha256').update([...changedPaths].sort().join('\0')).digest('hex'),
820
- });
821
- if (proof?.mode !== 'legacy-migration' || !proof.binding
822
- || qa.sourceEvidenceDigest !== proof.binding.qaDigest
823
- || qa.receiptRef !== `story-qa:${proof.binding.qaDigest}`
824
- || review.sourceEvidenceDigest !== proof.binding.reviewDigest
825
- || review.receiptRef !== `story-review:${proof.binding.reviewDigest}`
826
- || review.qaReceiptRef !== qa.receiptRef)
827
- return false;
828
- return true;
829
- }
830
- catch {
831
- return false;
832
- }
833
- }
834
- export function computeImmutableStoryQaEvidenceDigest(input) {
835
- return createHash('sha256').update([
836
- 'local-yolo-tdd-git-immutable-v1', input.storyId, input.baseSha, input.headSha,
837
- input.bundleDigest, input.graphRevision,
838
- createHash('sha256').update(input.commitBody).digest('hex'),
839
- createHash('sha256').update([...input.changedPaths].sort().join('\0')).digest('hex'),
840
- ].join('\0')).digest('hex');
841
- }
842
- /** Bind an actual successful P205 execution to immutable Git authority. */
843
- export function computeP205StoryQaEvidenceDigest(input) {
844
- return createHash('sha256').update([
845
- 'local-yolo-tdd-p205-execution-v2', P205_COMPLETE_COMMAND,
846
- input.gitEvidenceDigest, input.headSha, input.graphRevision,
847
- input.manifestDigest, input.gateIds.join(','), input.resultDigest,
848
- ].join('\0')).digest('hex');
849
- }
850
- function checkpointVerification(input) {
851
- const checkpoint = asRecord(input.value);
852
- return Object.freeze({
853
- schemaVersion: 1, kind: 'story_checkpoint_physical_verification', producer: 'client-p205-transient-v1',
854
- storyId: input.storyId, epicId: input.epicId, branch: input.executionBranch,
855
- baseSha: isSha(checkpoint?.baseSha) ? String(checkpoint?.baseSha) : '0'.repeat(40),
856
- headSha: isSha(checkpoint?.headSha) ? String(checkpoint?.headSha) : '0'.repeat(40),
857
- aggregateHeadSha: '0'.repeat(40),
858
- commitBodyDigest: '0'.repeat(64), changedPathsDigest: '0'.repeat(64),
859
- claimVerification: 'canonical', effectiveClaimRevision: '0'.repeat(64),
860
- command: P205_COMPLETE_COMMAND, manifestDigest: '0'.repeat(64), gateIds: Object.freeze([]),
861
- resultDigest: '0'.repeat(64), graphRevision: '0'.repeat(64), executionEvidenceDigest: '0'.repeat(64),
862
- sourceEvidenceDigest: '0'.repeat(64), verifiedAt: normalizedIso(input.verifiedAt),
863
- verdict: input.verdict, reasonCodes: Object.freeze([input.reasonCode]), publicSafe: true,
864
- });
865
- }
866
- function gitSync(cwd, args) {
867
- return execFileSync('git', [...args], { cwd, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
868
- }
869
- /** Read public review evidence from Git, never from the mutable worktree. */
870
- export function publicStoryReviewArtifactFromGit(projectRoot, storyId, revision, allowLegacyDecision = false) {
871
- if (!/^[A-Za-z][A-Za-z0-9_-]*\.[A-Za-z0-9._-]+$/.test(storyId))
872
- return undefined;
873
- if (revision !== ':' && !/^[a-f0-9]{40,64}$/i.test(revision))
874
- return undefined;
875
- const path = `.neocortex/stories/${storyId}/review.md`;
876
- let bytes;
877
- try {
878
- bytes = execFileSync('git', ['show', `${revision === ':' ? ':' : `${revision}:`}${path}`], {
879
- cwd: projectRoot, maxBuffer: 128 * 1024, stdio: ['ignore', 'pipe', 'ignore'],
880
- });
881
- }
882
- catch {
883
- return undefined;
884
- }
885
- if (bytes.length === 0 || bytes.length > 64 * 1024 || bytes.includes(0))
886
- return undefined;
887
- let content;
888
- try {
889
- content = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
890
- }
891
- catch {
892
- return undefined;
893
- }
894
- const lines = content.replace(/\r\n/g, '\n').split('\n');
895
- const storyHeading = new RegExp(`^#\\s+${storyId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?=\\s|$|[:—-])`);
896
- if (!lines.some((line) => storyHeading.test(line)))
897
- return undefined;
898
- const verdictLines = lines.filter((line) => /^Verdict\s*:/i.test(line));
899
- if (lines.some((line) => /^Status\s*:\s*(pending|changes-requested|rejected)\b/i.test(line))) {
900
- return undefined;
901
- }
902
- const legacyDecisionLines = lines.filter((line) => /^Decision\s*:/i.test(line));
903
- if (verdictLines.length === 0 && allowLegacyDecision
904
- && legacyDecisionLines.length === 1
905
- && /^Decision\s*:\s*APPROVED\b/i.test(legacyDecisionLines[0])) {
906
- return Object.freeze({ verdict: 'approved', digest: createHash('sha256').update(bytes).digest('hex') });
907
- }
908
- if (verdictLines.length !== 1)
909
- return undefined;
910
- const verdict = /^Verdict\s*:\s*(approved|pending|changes-requested)(?=\s|$|[;,.])/i.exec(verdictLines[0])?.[1]?.toLowerCase();
911
- if (verdict !== 'approved' && verdict !== 'pending' && verdict !== 'changes-requested')
912
- return undefined;
913
- return Object.freeze({ verdict, digest: createHash('sha256').update(bytes).digest('hex') });
914
- }
915
- /** Binds a later root approval to the exact immutable range of a legacy checkpoint. */
916
- export function computeHistoricalReviewReconciliationDigest(input) {
917
- return createHash('sha256').update([
918
- 'root-historical-review-reconciliation-v1', input.storyId, input.epicId,
919
- input.checkpointHeadSha, input.approvalCommitSha, input.approvalArtifactDigest,
920
- input.qaReceiptRef, input.reviewReceiptRef,
921
- createHash('sha256').update(input.approvalCommitBody).digest('hex'),
922
- ].join('\0')).digest('hex');
923
- }
924
- function verifyHistoricalReviewReconciliation(projectRoot, checkpoint, review, qa, storyId, epicId, branchHead) {
925
- const receipt = asRecord(checkpoint.reviewReconciliation);
926
- if (!receipt || !validateHistoricalReviewReconciliation(receipt, checkpoint))
927
- return false;
928
- const approvalSha = String(receipt.approvalCommitSha);
929
- const checkpointHead = String(checkpoint.headSha);
930
- if (approvalSha === checkpointHead)
931
- return false;
932
- try {
933
- execFileSync('git', ['merge-base', '--is-ancestor', checkpointHead, approvalSha], { cwd: projectRoot, stdio: 'ignore' });
934
- execFileSync('git', ['merge-base', '--is-ancestor', approvalSha, branchHead], { cwd: projectRoot, stdio: 'ignore' });
935
- if (!gitSync(projectRoot, ['rev-list', '--first-parent', branchHead]).split('\n').includes(approvalSha))
936
- return false;
937
- const parents = gitSync(projectRoot, ['show', '--no-patch', '--format=%P', approvalSha]).trim().split(/\s+/);
938
- if (parents.length !== 1 || !isSha(parents[0]))
939
- return false;
940
- const approvalCommitBody = gitSync(projectRoot, ['show', '--no-patch', '--format=%H%n%P%n%B', approvalSha]);
941
- const trailers = new Set(approvalCommitBody.split('\n').map((line) => line.trim()));
942
- if (!trailers.has(`Neocortex-Review-Reconciliation: ${storyId}`)
943
- || !trailers.has(`Neocortex-Original-Checkpoint: ${checkpointHead}`)
944
- || !trailers.has(`Neocortex-Original-Review-Receipt: ${String(review.receiptRef)}`))
945
- return false;
946
- const reviewPath = `.neocortex/stories/${storyId}/review.md`;
947
- const changedPaths = gitSync(projectRoot, ['diff-tree', '--no-commit-id', '--name-only', '-r', '-z', approvalSha])
948
- .split('\0').filter(Boolean);
949
- if (!changedPaths.includes(reviewPath) || changedPaths.some((path) => !safeRepositoryPath(path)))
950
- return false;
951
- const approved = publicStoryReviewArtifactFromGit(projectRoot, storyId, approvalSha);
952
- if (approved?.verdict !== 'approved' || approved.digest !== receipt.approvalArtifactDigest)
953
- return false;
954
- const current = publicStoryReviewArtifactFromGit(projectRoot, storyId, branchHead);
955
- if (current?.verdict !== 'approved' || current.digest !== approved.digest)
956
- return false;
957
- const digest = computeHistoricalReviewReconciliationDigest({
958
- storyId, epicId, checkpointHeadSha: checkpointHead, approvalCommitSha: approvalSha,
959
- approvalArtifactDigest: approved.digest, qaReceiptRef: String(qa.receiptRef),
960
- reviewReceiptRef: String(review.receiptRef), approvalCommitBody,
961
- });
962
- return receipt.sourceEvidenceDigest === digest
963
- && receipt.receiptRef === `story-review-reconciliation:${digest}`;
964
- }
965
- catch {
966
- return false;
967
- }
968
- }
969
- function publicStoryReviewBlobExistsInGit(projectRoot, storyId, headSha) {
970
- if (!/^[A-Za-z][A-Za-z0-9_-]*\.[A-Za-z0-9._-]+$/.test(storyId)
971
- || !/^[a-f0-9]{40,64}$/i.test(headSha))
972
- return false;
973
- try {
974
- execFileSync('git', ['cat-file', '-e', `${headSha}:.neocortex/stories/${storyId}/review.md`], {
975
- cwd: projectRoot, stdio: 'ignore', maxBuffer: 1024,
976
- });
977
- return true;
978
- }
979
- catch {
980
- return false;
981
- }
982
- }
983
- function normalizedIso(value) {
984
- return Number.isFinite(Date.parse(value)) ? new Date(Date.parse(value)).toISOString() : new Date().toISOString();
985
- }
986
- /**
987
- * Bounded migration evidence for direct-*yolo commits created before canonical
988
- * trailers were mandatory. The receipt is derived only from immutable commit
989
- * data and story-owned evidence blobs; JSON sidecar shape alone is never enough.
990
- */
991
- export async function computeLegacyDirectYoloRecoveryReceiptRef(projectRoot, input) {
992
- if (LEGACY_DIRECT_YOLO_RECOVERY_COMMITS[input.storyId] !== input.headSha)
993
- return undefined;
994
- const evidencePaths = [
995
- '.neocortex/state.json',
996
- `.neocortex/stories/${input.storyId}/qa-gate-result.json`,
997
- `.neocortex/stories/${input.storyId}/validation-evidence.md`,
998
- `.neocortex/stories/${input.storyId}/review.md`,
999
- `docs/stories/${input.storyId}.story.md`,
1000
- ];
1001
- try {
1002
- const branchHead = (await execFileAsync('git', ['rev-parse', `refs/heads/${input.executionBranch}`], {
1003
- cwd: projectRoot, encoding: 'utf8', maxBuffer: 1024 * 1024,
1004
- })).stdout.trim();
1005
- await execFileAsync('git', ['merge-base', '--is-ancestor', input.headSha, branchHead], { cwd: projectRoot });
1006
- const parentSha = (await execFileAsync('git', ['rev-parse', `${input.headSha}^`], {
1007
- cwd: projectRoot, encoding: 'utf8', maxBuffer: 1024 * 1024,
1008
- })).stdout.trim();
1009
- if (parentSha !== input.baseSha)
1010
- return undefined;
1011
- const immutablePayload = String((await execFileAsync('git', [
1012
- 'diff', '--binary', '--full-index', `${input.baseSha}..${input.headSha}`,
1013
- ], { cwd: projectRoot, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 })).stdout);
1014
- if (createHash('sha256').update(immutablePayload).digest('hex') !== input.bundleDigest)
1015
- return undefined;
1016
- const commitBody = String((await execFileAsync('git', ['show', '--no-patch', '--format=%H%n%P%n%B', input.headSha], {
1017
- cwd: projectRoot, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024,
1018
- })).stdout);
1019
- const subject = String((await execFileAsync('git', ['show', '--no-patch', '--format=%s', input.headSha], {
1020
- cwd: projectRoot, encoding: 'utf8', maxBuffer: 1024 * 1024,
1021
- })).stdout).trim();
1022
- if (!subject.includes(input.storyId))
1023
- return undefined;
1024
- const changedRaw = String((await execFileAsync('git', [
1025
- 'diff-tree', '--no-commit-id', '--name-only', '-r', '-z', input.headSha,
1026
- ], { cwd: projectRoot, encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 })).stdout);
1027
- const changedPaths = changedRaw.split('\0').filter(Boolean);
1028
- if (!evidencePaths.every((path) => changedPaths.includes(path))
1029
- || changedPaths.some((path) => !safeRepositoryPath(path)))
1030
- return undefined;
1031
- const blobs = await Promise.all(evidencePaths.map(async (path) => String((await execFileAsync('git', [
1032
- 'show', `${input.headSha}:${path}`,
1033
- ], { cwd: projectRoot, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 })).stdout)));
1034
- const state = JSON.parse(blobs[0]);
1035
- const stories = asRecord(state.stories);
1036
- const epics = asRecord(state.epics);
1037
- const story = asRecord(stories?.[input.storyId]);
1038
- const epic = asRecord(epics?.[input.epicId]);
1039
- const steps = Array.isArray(story?.steps_completed) ? story.steps_completed : [];
1040
- const qa = JSON.parse(blobs[1]);
1041
- if (story?.status !== 'done' || !steps.includes(GIT_LIFECYCLE_COMMIT_PUSH_STEP_ID) || !steps.includes(GIT_LIFECYCLE_REVIEW_PR_STEP_ID)
1042
- || epic?.branch_mode !== 'epic' || epic.execution_branch !== input.executionBranch
1043
- || qa.storyId !== input.storyId || qa.status !== 'qa-passed' || qa.safeToContinueYoloop !== true
1044
- || !Array.isArray(qa.blockingChecks) || qa.blockingChecks.length !== 0
1045
- || !blobs[2].includes(input.storyId) || !/P205[\s\S]{0,80}(pass|aprov)/i.test(blobs[2])
1046
- || !blobs[3].includes(input.storyId) || !/approved|aprovado/i.test(blobs[3])
1047
- || !blobs[4].includes(input.storyId))
1048
- return undefined;
1049
- const digest = createHash('sha256').update([
1050
- 'legacy-direct-yolo-recovery-v1', input.storyId, input.epicId, input.executionBranch,
1051
- input.baseSha, input.headSha, input.bundleDigest, input.graphRevision,
1052
- createHash('sha256').update(commitBody).digest('hex'),
1053
- createHash('sha256').update([...changedPaths].sort().join('\0')).digest('hex'),
1054
- ...blobs.map((blob) => createHash('sha256').update(blob).digest('hex')),
1055
- ].join('\0')).digest('hex');
1056
- return `legacy-direct-yolo:${digest}`;
1057
- }
1058
- catch {
1059
- return undefined;
1060
- }
1061
- }
1062
- async function runtimeStorePath(projectRoot) {
1063
- const commonDir = await gitCommonDir(projectRoot);
1064
- const runtimeDirectory = join(commonDir, GIT_LIFECYCLE_RUNTIME_DIRECTORY);
1065
- const currentPath = join(runtimeDirectory, GIT_LIFECYCLE_RUNTIME_FILE);
1066
- if (existsSync(runtimeDirectory)) {
1067
- await assertSafeFilesystemEntry(runtimeDirectory, 'directory');
1068
- if (await realpath(runtimeDirectory) !== runtimeDirectory)
1069
- throw new Error('git-lifecycle-runtime-directory-symlink');
1070
- }
1071
- return existsSync(currentPath) ? currentPath : join(commonDir, LEGACY_STORE_FILE);
1072
- }
1073
- /** Resolve/create the private root used by lifecycle state, locks and sandboxes. */
1074
- export async function gitLifecycleRuntimeDirectory(projectRoot) {
1075
- return ensureRuntimeDirectory(await gitCommonDir(projectRoot));
1076
- }
1077
- /** Create or validate one private direct child without following substitutions. */
1078
- export async function ensureGitLifecyclePrivateChildDirectory(parent, childName) {
1079
- if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(childName) || childName.includes('..')) {
1080
- throw new Error('git-lifecycle-private-child-invalid');
1081
- }
1082
- const canonicalParent = resolve(parent);
1083
- await assertPrivateDirectory(canonicalParent);
1084
- const child = resolve(canonicalParent, childName);
1085
- assertDirectChild(canonicalParent, child);
1086
- if (!existsSync(child))
1087
- await anchoredEnsureDirectory(canonicalParent, childName);
1088
- await assertPrivateDirectory(child, canonicalParent);
1089
- await assertPrivateDirectory(canonicalParent);
1090
- return child;
1091
- }
1092
- /** Validate a private directory and every direct parent binding before use. */
1093
- export async function assertGitLifecyclePrivateDirectory(path, parent) {
1094
- const canonicalParent = resolve(parent);
1095
- const child = resolve(path);
1096
- assertDirectChild(canonicalParent, child);
1097
- await assertPrivateDirectory(canonicalParent);
1098
- await assertPrivateDirectory(child, canonicalParent);
1099
- }
1100
- export async function assertGitLifecyclePrivateFile(path, parent) {
1101
- await assertSafePrivateFile(path, parent);
1102
- }
1103
- /** Remove only the validated directory object renamed inside its validated parent. */
1104
- export async function removeGitLifecyclePrivateDirectory(path, parent, verifyRenamed) {
1105
- const canonicalParent = resolve(parent);
1106
- const child = resolve(path);
1107
- assertDirectChild(canonicalParent, child);
1108
- try {
1109
- await assertGitLifecyclePrivateDirectory(child, canonicalParent);
1110
- }
1111
- catch (error) {
1112
- if (isNodeError(error, 'ENOENT'))
1113
- return false;
1114
- throw error;
1115
- }
1116
- const quarantine = resolve(canonicalParent, `${child.slice(canonicalParent.length + 1)}.remove.${process.pid}.${Date.now()}.${randomBytes(4).toString('hex')}`);
1117
- assertDirectChild(canonicalParent, quarantine);
1118
- await anchoredRenameDirectory(canonicalParent, basename(child), basename(quarantine));
1119
- await assertGitLifecyclePrivateDirectory(quarantine, canonicalParent);
1120
- if (verifyRenamed && !(await verifyRenamed(quarantine))) {
1121
- await restoreQuarantinedPrivateDirectory(quarantine, child, canonicalParent).catch(() => undefined);
1122
- return false;
1123
- }
1124
- await anchoredRemoveDirectory(canonicalParent, basename(quarantine));
1125
- return true;
1126
- }
1127
- async function restoreQuarantinedPrivateDirectory(quarantine, original, parent) {
1128
- await assertGitLifecyclePrivateDirectory(quarantine, parent);
1129
- try {
1130
- await lstat(original);
1131
- return;
1132
- }
1133
- catch (error) {
1134
- if (!isNodeError(error, 'ENOENT'))
1135
- throw error;
1136
- }
1137
- await anchoredRenameDirectory(parent, basename(quarantine), basename(original));
1138
- await assertGitLifecyclePrivateDirectory(original, parent);
1139
- }
1140
- async function privateOwnerFingerprint(path) {
1141
- try {
1142
- await assertGitLifecyclePrivateDirectory(path, dirname(path));
1143
- const ownerPath = join(path, 'owner.json');
1144
- await assertSafePrivateFile(ownerPath, path);
1145
- const owner = await readFile(ownerPath);
1146
- await assertSafePrivateFile(ownerPath, path);
1147
- return createHash('sha256').update(owner).digest('hex');
1148
- }
1149
- catch {
1150
- return undefined;
1151
- }
1152
- }
1153
- /** One physical writer lock shared by direct `*yolo` and continuous DAG integration. */
1154
- export async function acquireGitLifecyclePhysicalIntegrationLock(projectRoot, requested) {
1155
- const runtimeDirectory = await gitLifecycleRuntimeDirectory(projectRoot);
1156
- const leasesRoot = await ensureGitLifecyclePrivateChildDirectory(runtimeDirectory, 'leases');
1157
- const lockPath = join(leasesRoot, 'integration');
1158
- const legacyRecoveryPath = `${lockPath}.recovery`;
1159
- if (existsSync(legacyRecoveryPath)) {
1160
- await assertGitLifecyclePrivateDirectory(legacyRecoveryPath, leasesRoot);
1161
- const recoveryPid = await readLegacyPhysicalIntegrationOwnerPid(legacyRecoveryPath);
1162
- const recoveryFingerprint = await privateOwnerFingerprint(legacyRecoveryPath);
1163
- if (recoveryPid === undefined || processExists(recoveryPid)) {
1164
- return { ok: false, reasonCode: 'git-lifecycle-integration-backpressure' };
1165
- }
1166
- if (!recoveryFingerprint || !(await removeGitLifecyclePrivateDirectory(legacyRecoveryPath, leasesRoot, async (renamed) => await privateOwnerFingerprint(renamed) === recoveryFingerprint)))
1167
- return { ok: false, reasonCode: 'git-lifecycle-integration-backpressure' };
1168
- }
1169
- const nowIso = normalizedIso(requested.createdAt ?? new Date().toISOString());
1170
- const owner = Object.freeze({
1171
- schemaVersion: 1, kind: requested.kind, storyId: requested.storyId,
1172
- sessionId: requested.sessionId, idempotencyKey: requested.idempotencyKey,
1173
- pid: process.pid, createdAt: nowIso, expiresAt: normalizedIso(requested.expiresAt),
1174
- persistent: requested.persistent,
1175
- ownerNonce: requested.ownerNonce ?? randomBytes(16).toString('hex'),
1176
- });
1177
- if (Date.parse(owner.expiresAt) <= Date.parse(owner.createdAt)) {
1178
- return { ok: false, reasonCode: 'git-lifecycle-integration-lock-failed' };
1179
- }
1180
- const create = async () => {
1181
- await assertPrivateDirectory(leasesRoot, runtimeDirectory);
1182
- try {
1183
- await anchoredCreateOwnedDirectory(leasesRoot, basename(lockPath), `${JSON.stringify(owner)}\n`);
1184
- await assertGitLifecyclePrivateDirectory(lockPath, leasesRoot);
1185
- await assertSafePrivateFile(join(lockPath, 'owner.json'), lockPath);
1186
- return true;
1187
- }
1188
- catch (error) {
1189
- if (!isNodeError(error, 'EEXIST')) {
1190
- await removeGitLifecyclePrivateDirectory(lockPath, leasesRoot).catch(() => undefined);
1191
- }
1192
- throw error;
1193
- }
1194
- };
1195
- try {
1196
- await create();
1197
- return { ok: true, owner, path: lockPath };
1198
- }
1199
- catch (error) {
1200
- if (!isNodeError(error, 'EEXIST'))
1201
- return { ok: false, reasonCode: 'git-lifecycle-integration-lock-failed' };
1202
- }
1203
- try {
1204
- const current = await readPhysicalIntegrationOwner(lockPath);
1205
- const legacyPid = current ? undefined : await readLegacyPhysicalIntegrationOwnerPid(lockPath);
1206
- if (current && samePhysicalIntegrationOwner(current, owner))
1207
- return { ok: true, owner: current, path: lockPath };
1208
- const currentActive = current && Date.parse(current.expiresAt) > Date.now()
1209
- && (current.persistent || processExists(current.pid));
1210
- if (currentActive || legacyPid !== undefined && processExists(legacyPid)
1211
- || (!current && legacyPid === undefined && await recentlyCreated(lockPath))) {
1212
- return { ok: false, reasonCode: 'git-lifecycle-integration-backpressure' };
1213
- }
1214
- const ownerFingerprint = await privateOwnerFingerprint(lockPath);
1215
- if (!ownerFingerprint)
1216
- return { ok: false, reasonCode: 'git-lifecycle-integration-lock-failed' };
1217
- const quarantine = `${lockPath}.stale.${process.pid}.${Date.now()}`;
1218
- await anchoredRenameDirectory(leasesRoot, basename(lockPath), basename(quarantine));
1219
- try {
1220
- await assertGitLifecyclePrivateDirectory(quarantine, leasesRoot);
1221
- if (await privateOwnerFingerprint(quarantine) !== ownerFingerprint) {
1222
- await restoreQuarantinedPrivateDirectory(quarantine, lockPath, leasesRoot).catch(() => undefined);
1223
- return { ok: false, reasonCode: 'git-lifecycle-integration-backpressure' };
1224
- }
1225
- await create();
1226
- }
1227
- finally {
1228
- await removeGitLifecyclePrivateDirectory(quarantine, leasesRoot, async (renamed) => await privateOwnerFingerprint(renamed) === ownerFingerprint).catch(() => undefined);
1229
- }
1230
- return { ok: true, owner, path: lockPath };
1231
- }
1232
- catch (error) {
1233
- return {
1234
- ok: false,
1235
- reasonCode: isNodeError(error, 'EEXIST') || isNodeError(error, 'ENOENT')
1236
- ? 'git-lifecycle-integration-backpressure'
1237
- : 'git-lifecycle-integration-lock-failed',
1238
- };
1239
- }
1240
- }
1241
- export async function verifyGitLifecyclePhysicalIntegrationLock(path, expected) {
1242
- const current = await readPhysicalIntegrationOwner(path);
1243
- return Boolean(current && samePhysicalIntegrationOwnerExact(current, expected)
1244
- && Date.parse(current.expiresAt) > Date.now());
1245
- }
1246
- export async function releaseGitLifecyclePhysicalIntegrationLock(path, expected) {
1247
- try {
1248
- const parent = dirname(path);
1249
- await assertGitLifecyclePrivateDirectory(path, parent);
1250
- const current = await readPhysicalIntegrationOwner(path);
1251
- if (!current || !samePhysicalIntegrationOwnerExact(current, expected))
1252
- return false;
1253
- const ownerFingerprint = await privateOwnerFingerprint(path);
1254
- if (!ownerFingerprint)
1255
- return false;
1256
- const quarantine = `${path}.release.${process.pid}.${Date.now()}`;
1257
- await anchoredRenameDirectory(parent, basename(path), basename(quarantine));
1258
- await assertGitLifecyclePrivateDirectory(quarantine, parent);
1259
- const renamedOwner = await readPhysicalIntegrationOwner(quarantine);
1260
- if (!renamedOwner || !samePhysicalIntegrationOwnerExact(renamedOwner, expected)
1261
- || await privateOwnerFingerprint(quarantine) !== ownerFingerprint) {
1262
- await restoreQuarantinedPrivateDirectory(quarantine, path, parent).catch(() => undefined);
1263
- return false;
1264
- }
1265
- return removeGitLifecyclePrivateDirectory(quarantine, parent, async (renamed) => await privateOwnerFingerprint(renamed) === ownerFingerprint);
1266
- }
1267
- catch (error) {
1268
- if (isNodeError(error, 'ENOENT'))
1269
- return false;
1270
- throw error;
1271
- }
1272
- }
1273
- export function computeGitLifecyclePhysicalIntegrationLockOwnerDigest(owner) {
1274
- return createHash('sha256').update([
1275
- 'git-lifecycle-physical-owner-v1', owner.kind, owner.storyId, owner.sessionId,
1276
- owner.idempotencyKey, String(owner.pid), owner.createdAt, owner.expiresAt,
1277
- owner.persistent ? 'persistent' : 'ephemeral', owner.ownerNonce,
1278
- ].join('\0')).digest('hex');
1279
- }
1280
- /** Recover only the exact persistent lock whose digest was bound to a checkpoint. */
1281
- export async function releaseGitLifecyclePhysicalIntegrationLockForCheckpoint(projectRoot, input) {
1282
- if (!isCanonicalLifecycleId(input.storyId) || !safeToken(input.idempotencyKey) || !isHash(input.ownerDigest)) {
1283
- return 'foreign';
1284
- }
1285
- const runtimeDirectory = await gitLifecycleRuntimeDirectory(projectRoot);
1286
- const leasesRoot = join(runtimeDirectory, 'leases');
1287
- const lockPath = join(leasesRoot, 'integration');
1288
- if (!existsSync(lockPath))
1289
- return 'absent';
1290
- try {
1291
- await assertGitLifecyclePrivateDirectory(leasesRoot, runtimeDirectory);
1292
- await assertGitLifecyclePrivateDirectory(lockPath, leasesRoot);
1293
- const current = await readPhysicalIntegrationOwner(lockPath);
1294
- if (!current || current.kind !== 'direct' || current.storyId !== input.storyId
1295
- || current.sessionId !== input.idempotencyKey || current.idempotencyKey !== input.idempotencyKey
1296
- || current.persistent !== true
1297
- || computeGitLifecyclePhysicalIntegrationLockOwnerDigest(current) !== input.ownerDigest)
1298
- return 'foreign';
1299
- return await releaseGitLifecyclePhysicalIntegrationLock(lockPath, current) ? 'released' : 'foreign';
1300
- }
1301
- catch {
1302
- return 'foreign';
1303
- }
1304
- }
1305
- async function readPhysicalIntegrationOwner(path) {
1306
- try {
1307
- await assertGitLifecyclePrivateDirectory(path, dirname(path));
1308
- const ownerPath = join(path, 'owner.json');
1309
- await assertSafePrivateFile(ownerPath, path);
1310
- const item = JSON.parse(await readFile(ownerPath, 'utf8'));
1311
- if (item.schemaVersion !== 1 || (item.kind !== 'direct' && item.kind !== 'continuous')
1312
- || !isCanonicalLifecycleId(item.storyId) || !safeToken(item.sessionId) || !safeToken(item.idempotencyKey)
1313
- || !Number.isSafeInteger(item.pid) || Number(item.pid) <= 0 || !isIso(item.createdAt) || !isIso(item.expiresAt)
1314
- || typeof item.persistent !== 'boolean' || typeof item.ownerNonce !== 'string'
1315
- || !/^[a-f0-9]{32}$/.test(item.ownerNonce))
1316
- return undefined;
1317
- return item;
1318
- }
1319
- catch {
1320
- return undefined;
1321
- }
1322
- }
1323
- async function readLegacyPhysicalIntegrationOwnerPid(path) {
1324
- try {
1325
- await assertGitLifecyclePrivateDirectory(path, dirname(path));
1326
- const ownerPath = join(path, 'owner.json');
1327
- await assertSafePrivateFile(ownerPath, path);
1328
- const item = JSON.parse(await readFile(ownerPath, 'utf8'));
1329
- return Number.isSafeInteger(item.pid) && Number(item.pid) > 0 ? Number(item.pid) : undefined;
1330
- }
1331
- catch {
1332
- return undefined;
1333
- }
1334
- }
1335
- function samePhysicalIntegrationOwner(left, right) {
1336
- return left.kind === right.kind && left.storyId === right.storyId
1337
- && left.sessionId === right.sessionId && left.idempotencyKey === right.idempotencyKey
1338
- && left.expiresAt === right.expiresAt && left.persistent === right.persistent;
1339
- }
1340
- function samePhysicalIntegrationOwnerExact(left, right) {
1341
- return samePhysicalIntegrationOwner(left, right)
1342
- && left.schemaVersion === right.schemaVersion && left.pid === right.pid && left.createdAt === right.createdAt
1343
- && left.ownerNonce === right.ownerNonce;
1344
- }
1345
- async function gitCommonDir(projectRoot) {
1346
- const canonicalRoot = await realpath(projectRoot);
1347
- const result = await execFileAsync('git', ['rev-parse', '--git-common-dir'], {
1348
- cwd: canonicalRoot,
1349
- encoding: 'utf8',
1350
- maxBuffer: 1024 * 1024,
1351
- });
1352
- const commonDir = result.stdout.trim();
1353
- const resolvedCommonDir = isAbsolute(commonDir) ? resolve(commonDir) : resolve(canonicalRoot, commonDir);
1354
- const canonicalCommonDir = await realpath(resolvedCommonDir);
1355
- if (canonicalCommonDir !== resolvedCommonDir)
1356
- throw new Error('git-lifecycle-common-dir-symlink');
1357
- await assertCommonDirectoryEntry(canonicalCommonDir);
1358
- const revalidated = await execFileAsync('git', ['rev-parse', '--git-common-dir'], {
1359
- cwd: canonicalRoot,
1360
- encoding: 'utf8',
1361
- maxBuffer: 1024 * 1024,
1362
- });
1363
- const secondRaw = revalidated.stdout.trim();
1364
- const second = isAbsolute(secondRaw) ? resolve(secondRaw) : resolve(canonicalRoot, secondRaw);
1365
- if (second !== resolvedCommonDir)
1366
- throw new Error('git-lifecycle-common-dir-raced');
1367
- return canonicalCommonDir;
1368
- }
1369
- async function ensureRuntimeDirectory(commonDir) {
1370
- const runtimeDirectory = join(commonDir, GIT_LIFECYCLE_RUNTIME_DIRECTORY);
1371
- if (!existsSync(runtimeDirectory)) {
1372
- await anchoredEnsureDirectory(commonDir, GIT_LIFECYCLE_RUNTIME_DIRECTORY);
1373
- }
1374
- await assertSafeFilesystemEntry(runtimeDirectory, 'directory');
1375
- if (await realpath(runtimeDirectory) !== runtimeDirectory)
1376
- throw new Error('git-lifecycle-runtime-directory-symlink');
1377
- return runtimeDirectory;
1378
- }
1379
- async function readDocument(path, options = {}) {
1380
- try {
1381
- await assertSafeFilesystemEntry(path, 'file');
1382
- return validateDocument(JSON.parse(await readFile(path, 'utf8')));
1383
- }
1384
- catch (error) {
1385
- if (isNodeError(error, 'ENOENT')) {
1386
- if (options.required)
1387
- throw new Error('git-lifecycle-runtime-missing');
1388
- return emptyDocument();
1389
- }
1390
- throw error;
1391
- }
1392
- }
1393
- async function assertStoreDestinationSafe(path) {
1394
- try {
1395
- await assertSafeFilesystemEntry(path, 'file');
1396
- }
1397
- catch (error) {
1398
- if (isNodeError(error, 'ENOENT'))
1399
- return;
1400
- throw error;
1401
- }
1402
- }
1403
- async function assertSafeFilesystemEntry(path, kind) {
1404
- const info = await lstat(path);
1405
- if (info.isSymbolicLink())
1406
- throw new Error(`git-lifecycle-${kind}-symlink`);
1407
- if (kind === 'file' ? !info.isFile() : !info.isDirectory())
1408
- throw new Error(`git-lifecycle-${kind}-invalid`);
1409
- if (posixSecurityAvailable() && info.uid !== process.getuid())
1410
- throw new Error(`git-lifecycle-${kind}-ownership-mismatch`);
1411
- if (posixSecurityAvailable() && (info.mode & 0o077) !== 0)
1412
- throw new Error(`git-lifecycle-${kind}-permissions-unsafe`);
1413
- }
1414
- function assertDirectChild(parent, child) {
1415
- const normalizedParent = resolve(parent);
1416
- const normalizedChild = resolve(child);
1417
- const childRelative = relative(normalizedParent, normalizedChild);
1418
- if (!childRelative || childRelative.startsWith('..') || isAbsolute(childRelative)
1419
- || dirname(normalizedChild) !== normalizedParent) {
1420
- throw new Error('git-lifecycle-private-path-escape');
1421
- }
1422
- }
1423
- async function assertPrivateDirectory(path, expectedParent) {
1424
- const normalized = resolve(path);
1425
- if (expectedParent)
1426
- assertDirectChild(resolve(expectedParent), normalized);
1427
- await assertSafeFilesystemEntry(normalized, 'directory');
1428
- if (resolve(await realpath(normalized)) !== normalized)
1429
- throw new Error('git-lifecycle-directory-substituted');
1430
- if (expectedParent) {
1431
- const parent = resolve(expectedParent);
1432
- await assertSafeFilesystemEntry(parent, 'directory');
1433
- if (resolve(await realpath(parent)) !== parent)
1434
- throw new Error('git-lifecycle-directory-parent-substituted');
1435
- }
1436
- }
1437
- async function assertSafePrivateFile(path, expectedParent) {
1438
- const normalized = resolve(path);
1439
- assertDirectChild(resolve(expectedParent), normalized);
1440
- await assertPrivateDirectory(resolve(expectedParent));
1441
- await assertSafeFilesystemEntry(normalized, 'file');
1442
- if (resolve(await realpath(normalized)) !== normalized)
1443
- throw new Error('git-lifecycle-file-substituted');
1444
- }
1445
- function assertSafeFilesystemEntrySync(path, kind) {
1446
- const info = lstatSync(path);
1447
- if (info.isSymbolicLink())
1448
- throw new Error(`git-lifecycle-${kind}-symlink`);
1449
- if (kind === 'file' ? !info.isFile() : !info.isDirectory())
1450
- throw new Error(`git-lifecycle-${kind}-invalid`);
1451
- if (posixSecurityAvailable() && info.uid !== process.getuid())
1452
- throw new Error(`git-lifecycle-${kind}-ownership-mismatch`);
1453
- if (posixSecurityAvailable() && (info.mode & 0o077) !== 0)
1454
- throw new Error(`git-lifecycle-${kind}-permissions-unsafe`);
1455
- }
1456
- async function assertCommonDirectoryEntry(path) {
1457
- const info = await lstat(path);
1458
- if (info.isSymbolicLink())
1459
- throw new Error('git-lifecycle-common-dir-symlink');
1460
- if (!info.isDirectory())
1461
- throw new Error('git-lifecycle-common-dir-invalid');
1462
- }
1463
- function assertCommonDirectoryEntrySync(path) {
1464
- const info = lstatSync(path);
1465
- if (info.isSymbolicLink())
1466
- throw new Error('git-lifecycle-common-dir-symlink');
1467
- if (!info.isDirectory())
1468
- throw new Error('git-lifecycle-common-dir-invalid');
1469
- }
1470
- export function posixSecurityAvailable(platform = process.platform) {
1471
- return platform !== 'win32' && typeof process.getuid === 'function';
1472
- }
1473
- function validateDocument(value) {
1474
- if (!value || typeof value !== 'object' || Array.isArray(value))
1475
- throw new Error('git-lifecycle-runtime-invalid');
1476
- const record = value;
1477
- assertExactKeys(record, ['schemaVersion', 'revision', 'epics', 'stories'], 'git-lifecycle-runtime-unknown-field');
1478
- const epics = asRecord(record.epics);
1479
- const stories = asRecord(record.stories);
1480
- if (record.schemaVersion !== 1 || !Number.isSafeInteger(record.revision) || Number(record.revision) < 0
1481
- || !epics || !stories
1482
- || Object.values(epics).some((entry) => !asRecord(entry))
1483
- || Object.values(stories).some((entry) => !asRecord(entry))) {
1484
- throw new Error('git-lifecycle-runtime-invalid');
1485
- }
1486
- for (const [epicId, overlay] of Object.entries(epics)) {
1487
- if (!isCanonicalLifecycleId(epicId) || !validateEpicOverlay(overlay)) {
1488
- throw new Error('git-lifecycle-runtime-epic-invalid');
1489
- }
1490
- }
1491
- for (const [storyId, overlay] of Object.entries(stories)) {
1492
- if (!isCanonicalLifecycleId(storyId) || !storyId.includes('.') || !validateStoryOverlay(overlay, storyId)) {
1493
- throw new Error('git-lifecycle-runtime-story-invalid');
1494
- }
1495
- }
1496
- return record;
1497
- }
1498
- const EPIC_OVERLAY_KEYS = [
1499
- 'branch_mode', 'execution_branch', 'origin_branch', 'branch_lease', 'origin_binding',
1500
- 'graph_revision', 'aggregate_qa_receipt', 'terminal_git_receipt', 'physicalMergeStatus',
1501
- 'pr_number', 'git_reconciliation_receipts', 'git_reconciliation_debt',
1502
- ];
1503
- const STORY_OVERLAY_KEYS = ['checkpoint', 'status', 'last_step', 'steps_completed'];
1504
- function validateEpicOverlay(value) {
1505
- assertExactKeys(value, EPIC_OVERLAY_KEYS, 'git-lifecycle-runtime-epic-unknown-field');
1506
- return (value.branch_mode === undefined || value.branch_mode === 'epic')
1507
- && (value.execution_branch === undefined || safeGitRef(value.execution_branch))
1508
- && (value.origin_branch === undefined || safeGitRef(value.origin_branch))
1509
- && (value.branch_lease === undefined || value.branch_lease === null || validateLease(value.branch_lease))
1510
- && (value.origin_binding === undefined || value.origin_binding === null || validateOriginBinding(value.origin_binding))
1511
- && (value.graph_revision === undefined || isHash(value.graph_revision))
1512
- && (value.aggregate_qa_receipt === undefined || value.aggregate_qa_receipt === null || validateAggregateQa(value.aggregate_qa_receipt))
1513
- && (value.terminal_git_receipt === undefined || value.terminal_git_receipt === null || validateTerminalReceipt(value.terminal_git_receipt))
1514
- && (value.physicalMergeStatus === undefined || ['merged', 'pushed', 'denied_external_gate', 'blocked_local_or_repo', 'not_attempted'].includes(String(value.physicalMergeStatus)))
1515
- && (value.pr_number === undefined || value.pr_number === null || Number.isSafeInteger(value.pr_number) && Number(value.pr_number) > 0)
1516
- && (value.git_reconciliation_receipts === undefined || validateReconciliationReceipts(value.git_reconciliation_receipts))
1517
- && (value.git_reconciliation_debt === undefined || validateReconciliationDebt(value.git_reconciliation_debt));
1518
- }
1519
- function validateStoryOverlay(value, storyId) {
1520
- assertExactKeys(value, STORY_OVERLAY_KEYS, 'git-lifecycle-runtime-story-unknown-field');
1521
- return (value.checkpoint === undefined || value.checkpoint === null || validateCheckpoint(value.checkpoint, storyId))
1522
- && (value.status === undefined || safeToken(value.status))
1523
- && (value.last_step === undefined || value.last_step === null || safeToken(value.last_step))
1524
- && (value.steps_completed === undefined || safeStringArray(value.steps_completed, 128));
1525
- }
1526
- function validateLease(value) {
1527
- const item = asRecord(value);
1528
- if (!item)
1529
- return false;
1530
- assertExactKeys(item, [
1531
- 'schemaVersion', 'ownerStoryId', 'sessionId', 'idempotencyKey', 'acquiredAt', 'expiresAt',
1532
- 'handoffGrant', 'handoffConsumption', 'setupHeadSha',
1533
- ], 'git-lifecycle-runtime-lease-unknown-field');
1534
- return item.schemaVersion === 1 && isCanonicalLifecycleId(item.ownerStoryId)
1535
- && safeToken(item.sessionId) && safeToken(item.idempotencyKey)
1536
- && isIso(item.acquiredAt) && isIso(item.expiresAt)
1537
- && Date.parse(String(item.expiresAt)) > Date.parse(String(item.acquiredAt))
1538
- && (item.setupHeadSha === undefined || isSha(item.setupHeadSha))
1539
- && (item.handoffGrant === undefined || validateDirectYoloSetupHandoffGrant(item.handoffGrant, item))
1540
- && (item.handoffConsumption === undefined || validateDirectYoloSetupHandoffConsumption(item.handoffConsumption, item));
1541
- }
1542
- function validateDirectYoloSetupHandoffGrant(value, lease) {
1543
- const item = asRecord(value);
1544
- if (!item)
1545
- return false;
1546
- assertExactKeys(item, [
1547
- 'schemaVersion', 'kind', 'storyId', 'fromSessionId', 'tokenDigest',
1548
- 'bundleDigest', 'claimRevision', 'expiresAt',
1549
- ], 'git-lifecycle-runtime-handoff-grant-unknown-field');
1550
- return item.schemaVersion === 1 && item.kind === 'direct-yolo-setup-handoff'
1551
- && item.storyId === lease.ownerStoryId && item.fromSessionId === lease.sessionId
1552
- && isHash(item.tokenDigest) && isHash(item.bundleDigest) && isHash(item.claimRevision)
1553
- && isIso(item.expiresAt)
1554
- && Date.parse(String(item.expiresAt)) <= Date.parse(String(lease.expiresAt));
1555
- }
1556
- function validateDirectYoloSetupHandoffConsumption(value, lease) {
1557
- const item = asRecord(value);
1558
- if (!item)
1559
- return false;
1560
- assertExactKeys(item, [
1561
- 'schemaVersion', 'kind', 'state', 'storyId', 'fromSessionId', 'tokenDigest',
1562
- 'bundleDigest', 'claimRevision', 'consumedBySessionId', 'consumedAt',
1563
- ], 'git-lifecycle-runtime-handoff-consumption-unknown-field');
1564
- return item.schemaVersion === 1 && item.kind === 'direct-yolo-setup-handoff-consumption'
1565
- && item.state === 'consumed' && item.storyId === lease.ownerStoryId
1566
- && safeToken(item.fromSessionId) && isHash(item.tokenDigest)
1567
- && isHash(item.bundleDigest) && isHash(item.claimRevision)
1568
- && item.consumedBySessionId === lease.sessionId && isIso(item.consumedAt);
1569
- }
1570
- function validateOriginBinding(value) {
1571
- const item = asRecord(value);
1572
- if (!item)
1573
- return false;
1574
- assertExactKeys(item, ['schemaVersion', 'originBranch', 'originHeadSha', 'executionBranch', 'capturedFromCurrentCheckout', 'capturedAt'], 'git-lifecycle-runtime-origin-unknown-field');
1575
- return item.schemaVersion === 1 && safeGitRef(item.originBranch) && safeGitRef(item.executionBranch)
1576
- && item.originBranch !== item.executionBranch && isSha(item.originHeadSha)
1577
- && item.capturedFromCurrentCheckout === true && isIso(item.capturedAt);
1578
- }
1579
- function validateAggregateQa(value) {
1580
- const item = asRecord(value);
1581
- if (!item)
1582
- return false;
1583
- assertExactKeys(item, [
1584
- 'schemaVersion', 'evidenceVersion', 'producer', 'contract', 'command', 'verdict',
1585
- 'executionHeadSha', 'graphRevision', 'manifestDigest', 'resultDigest', 'checkedAt', 'receiptRef', 'gateIds',
1586
- ], 'git-lifecycle-runtime-aggregate-qa-unknown-field');
1587
- const executionV2 = item.evidenceVersion === 'p205-execution-v2'
1588
- && item.producer === 'local-yolo-tdd' && item.contract === 'P205-CI-02'
1589
- && item.command === P205_COMPLETE_COMMAND && isHash(item.manifestDigest) && isHash(item.resultDigest);
1590
- const legacy = item.evidenceVersion === undefined && item.producer === undefined && item.contract === undefined
1591
- && item.command === undefined && item.manifestDigest === undefined && item.resultDigest === undefined;
1592
- return item.schemaVersion === 1 && (executionV2 || legacy) && item.verdict === 'passed' && isSha(item.executionHeadSha)
1593
- && isHash(item.graphRevision) && isIso(item.checkedAt) && safeReceiptRef(item.receiptRef)
1594
- && safeStringArray(item.gateIds, 128, true);
1595
- }
1596
- function validateTerminalReceipt(value) {
1597
- const item = asRecord(value);
1598
- if (!item)
1599
- return false;
1600
- assertExactKeys(item, [
1601
- 'schemaVersion', 'epicId', 'executionBranch', 'originBranch', 'baseSha', 'executionHeadSha',
1602
- 'mergeSha', 'pushedSha', 'strategy', 'prNumber', 'physicalStatus', 'localGateReceiptRefs',
1603
- 'idempotencyKey', 'attemptStartedAt', 'completedAt', 'retryEligible', 'reasonCodes', 'publicSafe', 'candidateQa',
1604
- 'providerGuard',
1605
- ], 'git-lifecycle-runtime-terminal-receipt-unknown-field');
1606
- return item.schemaVersion === 1 && isCanonicalLifecycleId(item.epicId)
1607
- && safeGitRef(item.executionBranch) && safeGitRef(item.originBranch) && isSha(item.baseSha) && isSha(item.executionHeadSha)
1608
- && (item.mergeSha === undefined || isSha(item.mergeSha)) && (item.pushedSha === undefined || isSha(item.pushedSha))
1609
- && (item.strategy === 'direct' || item.strategy === 'single_pr')
1610
- && (item.candidateQa === undefined || validateMergeCandidateQaReceipt(item.candidateQa, item))
1611
- && (item.providerGuard === undefined || validateProviderGuardReceipt(item.providerGuard, item))
1612
- && (item.prNumber === undefined || Number.isSafeInteger(item.prNumber) && Number(item.prNumber) > 0)
1613
- && ['prepared', 'merged', 'pushed', 'denied_external_gate', 'blocked_local_or_repo'].includes(String(item.physicalStatus))
1614
- && safeStringArray(item.localGateReceiptRefs, 128, true) && safeToken(item.idempotencyKey)
1615
- && isIso(item.attemptStartedAt) && (item.completedAt === undefined || isIso(item.completedAt))
1616
- && typeof item.retryEligible === 'boolean' && safeStringArray(item.reasonCodes, 128)
1617
- && item.publicSafe === true;
1618
- }
1619
- function validateProviderGuardReceipt(value, terminal) {
1620
- const item = asRecord(value);
1621
- if (!item)
1622
- return false;
1623
- assertExactKeys(item, [
1624
- 'schemaVersion', 'provider', 'mode', 'source', 'prHeadSha', 'originHeadSha', 'checkedAt', 'publicSafe',
1625
- ], 'git-lifecycle-runtime-provider-guard-unknown-field');
1626
- const modeSourceValid = item.mode === 'recovered_exact_tree'
1627
- ? item.source === 'physical_recovery'
1628
- : item.mode === 'strict_up_to_date_expected_head'
1629
- && ['branch_protection', 'ruleset', 'branch_protection_and_ruleset'].includes(String(item.source));
1630
- return item.schemaVersion === 1 && item.provider === 'github' && modeSourceValid
1631
- && terminal.strategy === 'single_pr' && terminal.physicalStatus === 'pushed'
1632
- && Number.isSafeInteger(terminal.prNumber) && Number(terminal.prNumber) > 0
1633
- && isSha(item.prHeadSha) && item.prHeadSha === terminal.mergeSha
1634
- && isSha(item.originHeadSha) && item.originHeadSha === terminal.baseSha
1635
- && isIso(item.checkedAt) && item.publicSafe === true;
1636
- }
1637
- function validateMergeCandidateQaReceipt(value, terminal) {
1638
- const item = asRecord(value);
1639
- if (!item)
1640
- return false;
1641
- assertExactKeys(item, ['schemaVersion', 'kind', 'contract', 'command', 'originHeadSha', 'executionHeadSha',
1642
- 'mergeSha', 'treeSha', 'graphRevision', 'manifestDigest', 'resultDigest', 'gateIds', 'checkedAt', 'receiptRef'], 'git-lifecycle-runtime-merge-candidate-unknown-field');
1643
- return item.schemaVersion === 1 && item.kind === 'epic_merge_candidate_qa'
1644
- && item.contract === 'P205-CI-02' && item.command === P205_COMPLETE_COMMAND
1645
- && isSha(item.originHeadSha) && item.originHeadSha === terminal.baseSha
1646
- && isSha(item.executionHeadSha) && item.executionHeadSha === terminal.executionHeadSha
1647
- && isSha(item.mergeSha) && item.mergeSha === terminal.mergeSha
1648
- && isSha(item.treeSha) && isHash(item.graphRevision) && isHash(item.manifestDigest) && isHash(item.resultDigest)
1649
- && safeStringArray(item.gateIds, 128, true) && isIso(item.checkedAt) && safeReceiptRef(item.receiptRef);
1650
- }
1651
- function validateCheckpoint(value, storyId) {
1652
- const item = asRecord(value);
1653
- if (!item)
1654
- return false;
1655
- assertExactKeys(item, [
1656
- 'schemaVersion', 'storyId', 'epicId', 'branch', 'baseSha', 'headSha', 'commits', 'idempotencyKey',
1657
- 'dispatchId', 'bundleId', 'bundleDigest', 'graphRevision', 'claimRevision', 'receiptRefs',
1658
- 'qaVerdict', 'reviewVerdict', 'qaReceipt', 'reviewReceipt', 'reviewReconciliation', 'recordedAt', 'rootMemoryEffect',
1659
- ], 'git-lifecycle-runtime-checkpoint-unknown-field');
1660
- const epicId = String(storyId).split('.')[0];
1661
- return item.schemaVersion === 1 && item.storyId === storyId && item.epicId === epicId
1662
- && safeGitRef(item.branch) && isSha(item.baseSha) && isSha(item.headSha) && item.baseSha !== item.headSha
1663
- && safeShaArray(item.commits) && item.commits.length === 1 && item.commits[0] === item.headSha
1664
- && safeToken(item.idempotencyKey) && (item.dispatchId === undefined || isHash(item.dispatchId))
1665
- && (item.bundleId === undefined || isHash(item.bundleId)) && isHash(item.bundleDigest)
1666
- && isHash(item.graphRevision) && (item.claimRevision === undefined || isHash(item.claimRevision))
1667
- && (item.rootMemoryEffect === undefined || isRootProjectMemoryEffect(item.rootMemoryEffect)
1668
- && item.rootMemoryEffect.storyId === storyId && item.rootMemoryEffect.executionBranch === item.branch
1669
- && item.rootMemoryEffect.sourceHeadSha === item.baseSha && item.rootMemoryEffect.childClaimRevision === item.claimRevision)
1670
- && safeStringArray(item.receiptRefs, 256, true)
1671
- && (item.qaVerdict === 'passed' || item.qaVerdict === 'blocked')
1672
- && ['approved', 'changes-requested', 'pending'].includes(String(item.reviewVerdict))
1673
- && validateStoryQaReceipt(item.qaReceipt, item)
1674
- && (item.reviewVerdict === 'pending' ? item.reviewReceipt === undefined : validateStoryReviewReceipt(item.reviewReceipt, item))
1675
- && (item.reviewReconciliation === undefined || validateHistoricalReviewReconciliation(item.reviewReconciliation, item))
1676
- && isIso(item.recordedAt);
1677
- }
1678
- function validateHistoricalReviewReconciliation(value, checkpoint) {
1679
- const item = asRecord(value);
1680
- const qa = asRecord(checkpoint.qaReceipt);
1681
- const review = asRecord(checkpoint.reviewReceipt);
1682
- if (!item || !qa || !review)
1683
- return false;
1684
- assertExactKeys(item, [
1685
- 'schemaVersion', 'kind', 'storyId', 'epicId', 'checkpointHeadSha', 'approvalCommitSha',
1686
- 'approvalArtifactDigest', 'qaReceiptRef', 'reviewReceiptRef', 'sourceEvidenceDigest', 'reviewedAt', 'receiptRef',
1687
- ], 'git-lifecycle-runtime-review-reconciliation-unknown-field');
1688
- return item.schemaVersion === 1 && item.kind === 'historical_review_reconciliation'
1689
- && item.storyId === checkpoint.storyId && item.epicId === checkpoint.epicId
1690
- && item.checkpointHeadSha === checkpoint.headSha && isSha(item.approvalCommitSha)
1691
- && isHash(item.approvalArtifactDigest) && item.qaReceiptRef === qa.receiptRef
1692
- && item.reviewReceiptRef === review.receiptRef && isHash(item.sourceEvidenceDigest)
1693
- && isIso(item.reviewedAt) && Date.parse(String(item.reviewedAt)) >= Date.parse(String(qa.checkedAt))
1694
- && safeReceiptRef(item.receiptRef)
1695
- && Array.isArray(checkpoint.receiptRefs) && checkpoint.receiptRefs.includes(item.receiptRef);
1696
- }
1697
- function validateStoryQaReceipt(value, checkpoint) {
1698
- const item = asRecord(value);
1699
- if (!item)
1700
- return false;
1701
- assertExactKeys(item, [
1702
- 'schemaVersion', 'kind', 'producer', 'evidenceVersion', 'storyId', 'epicId', 'baseSha', 'headSha', 'bundleDigest',
1703
- 'graphRevision', 'contract', 'command', 'manifestDigest', 'gateIds', 'resultDigest',
1704
- 'sourceEvidenceDigest', 'verdict', 'checkedAt', 'receiptRef',
1705
- ], 'git-lifecycle-runtime-story-qa-unknown-field');
1706
- return item.schemaVersion === 1 && item.kind === 'story_qa_evidence' && item.producer === 'local-yolo-tdd'
1707
- && (item.evidenceVersion === undefined || item.evidenceVersion === 'git-immutable-v1'
1708
- || item.evidenceVersion === 'p205-execution-v2' && item.contract === 'P205-CI-02'
1709
- && item.command === P205_COMPLETE_COMMAND && isHash(item.manifestDigest)
1710
- && safeStringArray(item.gateIds, 32, true) && isHash(item.resultDigest))
1711
- && evidenceBindingMatches(item, checkpoint) && isHash(item.sourceEvidenceDigest)
1712
- && item.verdict === checkpoint.qaVerdict && (item.verdict === 'passed' || item.verdict === 'blocked')
1713
- && isIso(item.checkedAt) && safeReceiptRef(item.receiptRef)
1714
- && checkpoint.receiptRefs.includes(item.receiptRef);
1715
- }
1716
- function validateStoryReviewReceipt(value, checkpoint) {
1717
- const item = asRecord(value);
1718
- const qa = asRecord(checkpoint.qaReceipt);
1719
- if (!item || !qa)
1720
- return false;
1721
- assertExactKeys(item, [
1722
- 'schemaVersion', 'kind', 'producer', 'storyId', 'epicId', 'baseSha', 'headSha', 'bundleDigest',
1723
- 'graphRevision', 'sourceEvidenceDigest', 'reviewArtifactDigest', 'qaReceiptRef', 'verdict', 'reviewedAt', 'receiptRef',
1724
- ], 'git-lifecycle-runtime-story-review-unknown-field');
1725
- return item.schemaVersion === 1 && item.kind === 'story_review_evidence' && item.producer === 'root-commit-range-review'
1726
- && evidenceBindingMatches(item, checkpoint) && isHash(item.sourceEvidenceDigest)
1727
- && (item.reviewArtifactDigest === undefined || isHash(item.reviewArtifactDigest))
1728
- && item.qaReceiptRef === qa.receiptRef && item.receiptRef !== qa.receiptRef
1729
- && (item.verdict === 'approved' || item.verdict === 'changes-requested')
1730
- && (item.verdict === 'approved' ? checkpoint.reviewVerdict === 'approved' : checkpoint.reviewVerdict === 'changes-requested')
1731
- && isIso(item.reviewedAt) && Date.parse(String(item.reviewedAt)) >= Date.parse(String(qa.checkedAt))
1732
- && safeReceiptRef(item.receiptRef) && checkpoint.receiptRefs.includes(item.receiptRef);
1733
- }
1734
- function evidenceBindingMatches(item, checkpoint) {
1735
- return ['storyId', 'epicId', 'baseSha', 'headSha', 'bundleDigest', 'graphRevision']
1736
- .every((key) => item[key] === checkpoint[key]);
1737
- }
1738
- function validateReconciliationReceipts(value) {
1739
- if (!Array.isArray(value) || value.length > 1024)
1740
- return false;
1741
- return value.every((entry) => {
1742
- const item = asRecord(entry);
1743
- if (!item)
1744
- return false;
1745
- assertExactKeys(item, ['schemaVersion', 'resourceId', 'action', 'outcome', 'idempotencyKey', 'reasonCodes', 'recordedAt', 'publicSafe'], 'git-lifecycle-runtime-reconciliation-receipt-unknown-field');
1746
- return item.schemaVersion === 1 && safeToken(item.resourceId)
1747
- && ['no_op', 'delete_local_branch', 'delete_remote_branch', 'close_pull_request'].includes(String(item.action))
1748
- && ['applied', 'already_absent', 'dry_run', 'failed'].includes(String(item.outcome))
1749
- && safeToken(item.idempotencyKey) && safeStringArray(item.reasonCodes, 128) && isIso(item.recordedAt) && item.publicSafe === true;
1750
- });
1751
- }
1752
- function validateReconciliationDebt(value) {
1753
- if (!Array.isArray(value) || value.length > 1024)
1754
- return false;
1755
- return value.every((entry) => {
1756
- const item = asRecord(entry);
1757
- if (!item)
1758
- return false;
1759
- assertExactKeys(item, ['schemaVersion', 'resourceId', 'action', 'managed', 'reasonCodes', 'evidence', 'publicSafe'], 'git-lifecycle-runtime-reconciliation-debt-unknown-field');
1760
- return item.schemaVersion === 1 && safeToken(item.resourceId) && item.action === 'preserve'
1761
- && typeof item.managed === 'boolean' && safeStringArray(item.reasonCodes, 128)
1762
- && safeStringArray(item.evidence, 16) && item.publicSafe === true;
1763
- });
1764
- }
1765
- function assertExactKeys(record, allowed, reasonCode) {
1766
- const allow = new Set(allowed);
1767
- if (Object.keys(record).some((key) => !allow.has(key)))
1768
- throw new Error(reasonCode);
1769
- }
1770
- function isCanonicalLifecycleId(value) {
1771
- return typeof value === 'string' && /^P[1-9][0-9]*(?:\.[0-9]+)*$/.test(value);
1772
- }
1773
- function isGitLifecycleGraphAuthority(value) {
1774
- const item = asRecord(value);
1775
- if (!item)
1776
- return false;
1777
- return Object.keys(item).length === 10
1778
- && item.schemaVersion === 1
1779
- && item.kind === 'git_lifecycle_graph_authority'
1780
- && item.producer === 'server-continuous-yoloop-v1'
1781
- && (item.source === 'yolo' || item.source === 'yoloop')
1782
- && isCanonicalLifecycleId(item.epicId)
1783
- && isCanonicalLifecycleId(item.storyId)
1784
- && String(item.storyId).startsWith(`${String(item.epicId)}.`)
1785
- && safeGitRef(item.executionBranch)
1786
- && safeGitRef(item.originBranch)
1787
- && item.originBranch !== item.executionBranch
1788
- && typeof item.graphRevision === 'string'
1789
- && /^[a-f0-9]{64}$/.test(item.graphRevision)
1790
- && item.publicSafe === true;
1791
- }
1792
- function safeGitRef(value) {
1793
- return typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._/-]{0,239}$/.test(value)
1794
- && !value.includes('..') && !value.includes('//') && !value.includes('@{')
1795
- && !value.endsWith('/') && !value.endsWith('.') && !value.split('/').some((part) => !part || part.startsWith('.') || part.endsWith('.lock'));
1796
- }
1797
- function safeRepositoryPath(value) {
1798
- return value.length > 0 && value.length <= 1024 && !isAbsolute(value)
1799
- && !value.includes('\0') && !value.replaceAll('\\', '/').split('/').includes('..');
1800
- }
1801
- function safeToken(value) {
1802
- return typeof value === 'string' && value.length > 0 && value.length <= 240 && /^[A-Za-z0-9._:/#\-[\]]+$/.test(value) && !value.includes('..');
1803
- }
1804
- function safeReceiptRef(value) {
1805
- return safeToken(value);
1806
- }
1807
- function isSha(value) {
1808
- return typeof value === 'string' && /^[a-f0-9]{40,64}$/i.test(value);
1809
- }
1810
- function isHash(value) {
1811
- return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value);
1812
- }
1813
- function isIso(value) {
1814
- return typeof value === 'string' && value.length <= 64 && Number.isFinite(Date.parse(value));
1815
- }
1816
- function safeStringArray(value, max, nonEmpty = false) {
1817
- return Array.isArray(value) && value.length <= max && (!nonEmpty || value.length > 0) && value.every(safeToken);
1818
- }
1819
- function safeShaArray(value) {
1820
- return Array.isArray(value) && value.length > 0 && value.length <= 256 && value.every(isSha);
1821
- }
1822
- function emptyDocument() {
1823
- return { schemaVersion: 1, revision: 0, epics: {}, stories: {} };
1824
- }
1825
- async function acquireLock(lockPath) {
1826
- const parent = dirname(lockPath);
1827
- const requested = { pid: process.pid, nonce: randomBytes(16).toString('hex') };
1828
- const create = async () => {
1829
- await assertPrivateDirectory(parent);
1830
- await anchoredCreateOwnedDirectory(parent, basename(lockPath), JSON.stringify(requested));
1831
- try {
1832
- await assertGitLifecyclePrivateDirectory(lockPath, parent);
1833
- await assertSafePrivateFile(join(lockPath, 'owner.json'), lockPath);
1834
- }
1835
- catch (error) {
1836
- await removeGitLifecyclePrivateDirectory(lockPath, parent).catch(() => undefined);
1837
- throw error;
1838
- }
1839
- };
1840
- try {
1841
- await create();
1842
- }
1843
- catch (error) {
1844
- if (!isNodeError(error, 'EEXIST'))
1845
- throw error;
1846
- await assertSafeFilesystemEntry(lockPath, 'directory');
1847
- const owner = await readOwner(lockPath);
1848
- if (owner !== undefined && processExists(owner.pid))
1849
- throw new Error('git-lifecycle-runtime-lock-active');
1850
- if (owner === undefined && await recentlyCreated(lockPath))
1851
- throw new Error('git-lifecycle-runtime-lock-active');
1852
- const ownerFingerprint = await privateOwnerFingerprint(lockPath);
1853
- if (!ownerFingerprint)
1854
- throw new Error('git-lifecycle-runtime-lock-owner-invalid');
1855
- const quarantine = `${lockPath}.stale.${process.pid}.${Date.now()}`;
1856
- await anchoredRenameDirectory(parent, basename(lockPath), basename(quarantine));
1857
- try {
1858
- await assertGitLifecyclePrivateDirectory(quarantine, parent);
1859
- if (await privateOwnerFingerprint(quarantine) !== ownerFingerprint) {
1860
- await restoreQuarantinedPrivateDirectory(quarantine, lockPath, parent).catch(() => undefined);
1861
- throw new Error('git-lifecycle-runtime-lock-owner-replaced');
1862
- }
1863
- await create();
1864
- }
1865
- finally {
1866
- await removeGitLifecyclePrivateDirectory(quarantine, parent, async (renamed) => await privateOwnerFingerprint(renamed) === ownerFingerprint).catch(() => undefined);
1867
- }
1868
- }
1869
- return requested;
1870
- }
1871
- async function releaseRuntimeLock(lockPath, expected) {
1872
- const parent = dirname(lockPath);
1873
- try {
1874
- await assertGitLifecyclePrivateDirectory(lockPath, parent);
1875
- const current = await readOwner(lockPath);
1876
- if (!current || !sameRuntimeLockOwner(current, expected))
1877
- return false;
1878
- const ownerFingerprint = await privateOwnerFingerprint(lockPath);
1879
- if (!ownerFingerprint)
1880
- return false;
1881
- const quarantine = `${lockPath}.release.${process.pid}.${Date.now()}`;
1882
- await anchoredRenameDirectory(parent, basename(lockPath), basename(quarantine));
1883
- await assertGitLifecyclePrivateDirectory(quarantine, parent);
1884
- const renamed = await readOwner(quarantine);
1885
- if (!renamed || !sameRuntimeLockOwner(renamed, expected)
1886
- || await privateOwnerFingerprint(quarantine) !== ownerFingerprint) {
1887
- await restoreQuarantinedPrivateDirectory(quarantine, lockPath, parent).catch(() => undefined);
1888
- return false;
1889
- }
1890
- return removeGitLifecyclePrivateDirectory(quarantine, parent, async (renamedPath) => await privateOwnerFingerprint(renamedPath) === ownerFingerprint);
1891
- }
1892
- catch (error) {
1893
- if (isNodeError(error, 'ENOENT'))
1894
- return false;
1895
- throw error;
1896
- }
1897
- }
1898
- async function recentlyCreated(path) {
1899
- try {
1900
- return Date.now() - (await stat(path)).mtimeMs < 30_000;
1901
- }
1902
- catch {
1903
- return true;
1904
- }
1905
- }
1906
- async function readOwner(lockPath) {
1907
- try {
1908
- await assertGitLifecyclePrivateDirectory(lockPath, dirname(lockPath));
1909
- const ownerPath = join(lockPath, 'owner.json');
1910
- await assertSafePrivateFile(ownerPath, lockPath);
1911
- const value = JSON.parse(await readFile(ownerPath, 'utf8'));
1912
- return Number.isSafeInteger(value.pid) && Number(value.pid) > 0
1913
- && (value.nonce === undefined || typeof value.nonce === 'string' && /^[a-f0-9]{32}$/.test(value.nonce))
1914
- ? { pid: Number(value.pid), ...(typeof value.nonce === 'string' ? { nonce: value.nonce } : {}) }
1915
- : undefined;
1916
- }
1917
- catch {
1918
- return undefined;
1919
- }
1920
- }
1921
- function sameRuntimeLockOwner(left, right) {
1922
- return left.pid === right.pid && left.nonce !== undefined && left.nonce === right.nonce;
1923
- }
1924
- function processExists(pid) {
1925
- try {
1926
- process.kill(pid, 0);
1927
- return true;
1928
- }
1929
- catch (error) {
1930
- return isNodeError(error, 'EPERM');
1931
- }
1932
- }
1933
- function asRecord(value) {
1934
- return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
1935
- }
1936
- function isNodeError(error, code) {
1937
- return error instanceof Error && 'code' in error && error.code === code;
1938
- }
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};