@ornexus/neocortex 4.60.73 → 4.60.75

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