@ornexus/neocortex 4.60.72 → 4.60.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/dist/release-source.json +1 -1
  2. package/dist/sbom.cdx.json +24 -24
  3. package/install.ps1 +1 -1
  4. package/install.sh +1 -1
  5. package/package.json +2 -2
  6. package/packages/client/dist/adapters/adapter-registry.js +108 -1
  7. package/packages/client/dist/adapters/antigravity-adapter.js +77 -2
  8. package/packages/client/dist/adapters/claude-code-adapter.js +79 -3
  9. package/packages/client/dist/adapters/codex-adapter.js +80 -2
  10. package/packages/client/dist/adapters/cursor-adapter.js +115 -4
  11. package/packages/client/dist/adapters/gemini-adapter.js +71 -2
  12. package/packages/client/dist/adapters/grok-adapter.js +42 -2
  13. package/packages/client/dist/adapters/index.js +22 -1
  14. package/packages/client/dist/adapters/platform-detector.js +212 -1
  15. package/packages/client/dist/adapters/target-adapter.js +12 -0
  16. package/packages/client/dist/adapters/vscode-adapter.js +72 -2
  17. package/packages/client/dist/agent/refresh-stubs.js +417 -2
  18. package/packages/client/dist/agent/update-agent-yaml.js +102 -1
  19. package/packages/client/dist/agent/update-description.js +251 -1
  20. package/packages/client/dist/cache/crypto-utils.js +78 -1
  21. package/packages/client/dist/cache/encrypted-cache.js +108 -1
  22. package/packages/client/dist/cache/in-memory-asset-cache.js +73 -1
  23. package/packages/client/dist/cache/index.js +13 -1
  24. package/packages/client/dist/cache/protected-pi-boundary.js +44 -1
  25. package/packages/client/dist/checkpoint/checkpoint-client-reader.js +127 -2
  26. package/packages/client/dist/checkpoint/index.js +11 -1
  27. package/packages/client/dist/checkpoint/shared-checkpoint-types.js +32 -1
  28. package/packages/client/dist/cli.js +358 -5
  29. package/packages/client/dist/commands/activate.js +394 -8
  30. package/packages/client/dist/commands/architecture-answers.js +274 -7
  31. package/packages/client/dist/commands/architecture-memory-terminal.js +253 -4
  32. package/packages/client/dist/commands/architecture-non-ui-continuation.js +100 -1
  33. package/packages/client/dist/commands/architecture-root-binding.js +51 -1
  34. package/packages/client/dist/commands/cache-status.js +112 -2
  35. package/packages/client/dist/commands/config.js +88 -1
  36. package/packages/client/dist/commands/doctor.js +635 -2
  37. package/packages/client/dist/commands/invoke.js +7200 -143
  38. package/packages/client/dist/commands/legacy-epic-terminal.js +71 -1
  39. package/packages/client/dist/commands/legacy-story-branch.js +96 -1
  40. package/packages/client/dist/commands/legacy-story-pr.js +276 -1
  41. package/packages/client/dist/commands/mutation-qa-checkpoint.js +285 -5
  42. package/packages/client/dist/commands/mutation-qa-gate.js +54 -1
  43. package/packages/client/dist/commands/mutation-qa-git.js +41 -1
  44. package/packages/client/dist/commands/mutation-qa-policy.js +102 -1
  45. package/packages/client/dist/commands/mutation-qa-run.js +219 -3
  46. package/packages/client/dist/commands/mutation-qa-verdict.js +149 -1
  47. package/packages/client/dist/commands/mutation-qa.js +220 -1
  48. package/packages/client/dist/commands/native-agent.js +136 -2
  49. package/packages/client/dist/commands/planning-diagnosis.js +43 -1
  50. package/packages/client/dist/commands/planning-generation.js +53 -1
  51. package/packages/client/dist/commands/planning-memory.js +40 -1
  52. package/packages/client/dist/commands/planning-research.js +42 -1
  53. package/packages/client/dist/commands/private-session.js +75 -1
  54. package/packages/client/dist/commands/property-qa.js +139 -1
  55. package/packages/client/dist/commands/qa-control.js +16 -1
  56. package/packages/client/dist/commands/qa-scan-browser-launch.js +55 -1
  57. package/packages/client/dist/commands/qa-scan-browser-route.js +28 -1
  58. package/packages/client/dist/commands/qa-scan-capture.js +663 -5
  59. package/packages/client/dist/commands/qa-scan-discovery.js +376 -3
  60. package/packages/client/dist/commands/qa-scan-review.js +826 -5
  61. package/packages/client/dist/commands/qa-synthetic-backend-capture.js +266 -1
  62. package/packages/client/dist/commands/qa-synthetic-backend-review.js +297 -1
  63. package/packages/client/dist/commands/qa-synthetic-backend.js +340 -2
  64. package/packages/client/dist/commands/qa-synthetic-broker-port.js +85 -1
  65. package/packages/client/dist/commands/qa-synthetic-browser.js +39 -1
  66. package/packages/client/dist/commands/refactor-characterization.js +671 -21
  67. package/packages/client/dist/commands/refactor-commit.js +399 -13
  68. package/packages/client/dist/commands/refactor-control.js +142 -3
  69. package/packages/client/dist/commands/refactor-cpp-assertions.js +93 -3
  70. package/packages/client/dist/commands/refactor-review.js +283 -5
  71. package/packages/client/dist/commands/refactor-root.js +588 -7
  72. package/packages/client/dist/commands/refresh-memory.js +156 -3
  73. package/packages/client/dist/commands/shared-architecture-non-ui.js +24 -1
  74. package/packages/client/dist/commands/story-checkpoint.js +171 -1
  75. package/packages/client/dist/config/profile-directory.js +13 -1
  76. package/packages/client/dist/config/resolver-selection.js +278 -1
  77. package/packages/client/dist/config/secure-config.js +281 -12
  78. package/packages/client/dist/config.js +223 -2
  79. package/packages/client/dist/constants.js +25 -1
  80. package/packages/client/dist/context/context-collector.js +222 -2
  81. package/packages/client/dist/context/context-sanitizer.js +145 -1
  82. package/packages/client/dist/continuity/autonomous-governance.js +285 -1
  83. package/packages/client/dist/continuity/continuity-client-state-store.js +1587 -1
  84. package/packages/client/dist/continuity/invoke-hooks.js +129 -1
  85. package/packages/client/dist/continuity/migrations/001-initial-schema.js +12 -2
  86. package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +12 -2
  87. package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +12 -2
  88. package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +12 -2
  89. package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +12 -2
  90. package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +10 -2
  91. package/packages/client/dist/continuity/native-agent-store.js +69 -1
  92. package/packages/client/dist/continuity/native-agent-types.js +90 -1
  93. package/packages/client/dist/continuity/outbox.js +117 -1
  94. package/packages/client/dist/continuity/runner-bootstrap-policy.js +172 -1
  95. package/packages/client/dist/continuity/sqlite-store.d.ts +22 -1
  96. package/packages/client/dist/continuity/sqlite-store.js +2612 -81
  97. package/packages/client/dist/effects/invoke-effect-gateways.js +62 -1
  98. package/packages/client/dist/errors/error-messages.js +228 -2
  99. package/packages/client/dist/errors/frontmatter-diagnostics.js +125 -1
  100. package/packages/client/dist/evidence/collector.js +347 -1
  101. package/packages/client/dist/evidence/index.js +1 -1
  102. package/packages/client/dist/evidence/types.js +2 -0
  103. package/packages/client/dist/fs/atomic-state-replace.js +152 -1
  104. package/packages/client/dist/generated/command-registry.generated.js +4076 -1
  105. package/packages/client/dist/generated/public-projections.generated.js +149 -1
  106. package/packages/client/dist/git/direct-yolo-c01-retry.js +138 -4
  107. package/packages/client/dist/git/direct-yolo-checkpoint.js +1234 -4
  108. package/packages/client/dist/git/epic-operational-state.d.ts +198 -0
  109. package/packages/client/dist/git/epic-operational-state.js +672 -0
  110. package/packages/client/dist/git/git-lifecycle-step-ids.js +14 -1
  111. package/packages/client/dist/git/git-lifecycle-store.d.ts +9 -1
  112. package/packages/client/dist/git/git-lifecycle-store.js +1981 -9
  113. package/packages/client/dist/git/legacy-story-branch-checkpoint.js +259 -9
  114. package/packages/client/dist/git/legacy-story-branch-setup.js +348 -4
  115. package/packages/client/dist/git/legacy-story-pr.js +3538 -70
  116. package/packages/client/dist/git/secure-filesystem-boundary.js +182 -5
  117. package/packages/client/dist/graph-retrieval/pre-command-hook.js +284 -1
  118. package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +237 -1
  119. package/packages/client/dist/i18n/first-run.js +60 -2
  120. package/packages/client/dist/index.js +47 -1
  121. package/packages/client/dist/invocation/invoke-input.js +24 -1
  122. package/packages/client/dist/license/index.js +5 -1
  123. package/packages/client/dist/license/license-client.js +304 -1
  124. package/packages/client/dist/machine/fingerprint.js +163 -2
  125. package/packages/client/dist/machine/index.js +5 -1
  126. package/packages/client/dist/memory/project-memory-path-scope.js +62 -1
  127. package/packages/client/dist/memory/project-memory-writer.js +1007 -40
  128. package/packages/client/dist/memory/shared-project-memory-types.js +253 -2
  129. package/packages/client/dist/migrations/epic-lifecycle-backfill.d.ts +97 -0
  130. package/packages/client/dist/migrations/epic-lifecycle-backfill.js +1060 -0
  131. package/packages/client/dist/migrations/project-identity-capability.js +50 -1
  132. package/packages/client/dist/migrations/project-identity.js +493 -7
  133. package/packages/client/dist/migrations/state-json.d.ts +22 -0
  134. package/packages/client/dist/migrations/state-json.js +393 -4
  135. package/packages/client/dist/policy/architecture-policy.js +184 -2
  136. package/packages/client/dist/policy/index.js +7 -1
  137. package/packages/client/dist/policy/shared-policy-types.js +38 -0
  138. package/packages/client/dist/policy/subagent-approval-policy.js +55 -1
  139. package/packages/client/dist/private-execution/clean-baseline.js +279 -2
  140. package/packages/client/dist/private-execution/codex-executor.js +574 -4
  141. package/packages/client/dist/private-execution/codex-host-runtime.js +137 -1
  142. package/packages/client/dist/private-execution/codex-lifecycle-guard.js +97 -1
  143. package/packages/client/dist/private-execution/codex-scoped-permissions.js +128 -1
  144. package/packages/client/dist/private-execution/delivery.js +410 -13
  145. package/packages/client/dist/private-execution/diagnosis-root-review.js +294 -2
  146. package/packages/client/dist/private-execution/frozen-handoff.js +19 -6
  147. package/packages/client/dist/private-execution/implementation-validation.js +106 -2
  148. package/packages/client/dist/private-execution/isolated-admission.js +1432 -23
  149. package/packages/client/dist/private-execution/isolated-session.js +725 -5
  150. package/packages/client/dist/private-execution/isolated-workspace.js +122 -1
  151. package/packages/client/dist/private-execution/pact-consumer-runtime.js +409 -3
  152. package/packages/client/dist/private-execution/pact-test-source.js +258 -1
  153. package/packages/client/dist/private-execution/pact-toolchain.js +290 -3
  154. package/packages/client/dist/private-execution/pact-write-set.js +172 -1
  155. package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +406 -5
  156. package/packages/client/dist/private-execution/planning-generation-checkpoint.js +462 -5
  157. package/packages/client/dist/private-execution/planning-memory-checkpoint.js +139 -3
  158. package/packages/client/dist/private-execution/planning-research-checkpoint.js +325 -4
  159. package/packages/client/dist/private-execution/private-report-store.js +42 -1
  160. package/packages/client/dist/private-execution/property-test-candidate.js +103 -1
  161. package/packages/client/dist/private-execution/property-test-choices.js +147 -3
  162. package/packages/client/dist/private-execution/property-test-plan.js +271 -7
  163. package/packages/client/dist/private-execution/property-test-review.js +258 -4
  164. package/packages/client/dist/private-execution/property-test-runtime.js +372 -9
  165. package/packages/client/dist/private-execution/property-test-source.js +139 -1
  166. package/packages/client/dist/private-execution/root-checkpoint.js +80 -1
  167. package/packages/client/dist/private-execution/root-memory-git-evidence.js +135 -3
  168. package/packages/client/dist/private-execution/root-memory-recovery.js +93 -1
  169. package/packages/client/dist/private-execution/root-memory-request.js +29 -1
  170. package/packages/client/dist/private-execution/root-memory-scope.js +178 -1
  171. package/packages/client/dist/private-execution/root-memory-writer.js +102 -1
  172. package/packages/client/dist/private-execution/session-lease.js +63 -1
  173. package/packages/client/dist/private-execution/shared-private-admission-types.js +50 -1
  174. package/packages/client/dist/private-execution/shared-project-validation.js +66 -1
  175. package/packages/client/dist/private-execution/shared-root-memory-types.js +61 -1
  176. package/packages/client/dist/private-execution/ux-source-validation.js +372 -1
  177. package/packages/client/dist/private-execution/ux-write-set.js +191 -2
  178. package/packages/client/dist/registry/registry-reconciliation.js +234 -1
  179. package/packages/client/dist/resilience/circuit-breaker.js +170 -1
  180. package/packages/client/dist/resilience/degradation-manager.js +164 -1
  181. package/packages/client/dist/resilience/freshness-indicator.js +100 -1
  182. package/packages/client/dist/resilience/index.js +8 -1
  183. package/packages/client/dist/resilience/recovery-detector.js +74 -1
  184. package/packages/client/dist/resolvers/asset-resolver.js +13 -0
  185. package/packages/client/dist/resolvers/local-resolver.js +255 -8
  186. package/packages/client/dist/resolvers/remote-resolver.js +233 -1
  187. package/packages/client/dist/runner/binary.js +404 -1
  188. package/packages/client/dist/runner/cli.js +1571 -17
  189. package/packages/client/dist/runner/headless/adapters.js +49 -1
  190. package/packages/client/dist/runner/headless/claude-code-adapter.js +79 -1
  191. package/packages/client/dist/runner/headless/continuous-story-workspace.js +1628 -11
  192. package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +69 -1
  193. package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +110 -2
  194. package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +1236 -8
  195. package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +318 -2
  196. package/packages/client/dist/runner/headless/fake-adapter.js +62 -1
  197. package/packages/client/dist/runner/headless/managed-profile.js +132 -1
  198. package/packages/client/dist/runner/headless/opencode-adapter.js +145 -2
  199. package/packages/client/dist/runner/headless/prepared-story-bundle.js +43 -1
  200. package/packages/client/dist/runner/headless/resolver.js +231 -1
  201. package/packages/client/dist/runner/headless/run-yolo.js +885 -1
  202. package/packages/client/dist/runner/headless/run-yoloop.js +2574 -2
  203. package/packages/client/dist/runner/headless/types.js +574 -2
  204. package/packages/client/dist/runner/local-console.js +396 -13
  205. package/packages/client/dist/runner/portal-sync-worker.js +1487 -1
  206. package/packages/client/dist/runner/project-identity.js +106 -1
  207. package/packages/client/dist/runner/scheduler.js +995 -7
  208. package/packages/client/dist/runner-cli.js +18 -2
  209. package/packages/client/dist/state/invocation-shape.js +13 -1
  210. package/packages/client/dist/state/project-state-snapshot.d.ts +8 -0
  211. package/packages/client/dist/state/project-state-snapshot.js +1731 -1
  212. package/packages/client/dist/state/state-json-repair.js +785 -3
  213. package/packages/client/dist/state/structural-dag-projection.js +118 -1
  214. package/packages/client/dist/telemetry/index.js +5 -1
  215. package/packages/client/dist/telemetry/offline-queue.js +131 -1
  216. package/packages/client/dist/tier/index.js +5 -1
  217. package/packages/client/dist/tier/tier-aware-client.js +405 -1
  218. package/packages/client/dist/types/index.js +38 -1
  219. package/packages/client/dist/version.js +27 -1
  220. package/packages/client/dist/yolo/change-evidence-collector.js +126 -1
  221. package/packages/client/dist/yolo/invoke-envelope-capability.js +62 -1
  222. package/packages/client/dist/yolo/spec-envelope-collector.js +184 -5
  223. package/packages/client/dist/yoloop/discovery-hook.js +300 -2
  224. package/packages/client/dist/yoloop/index.js +8 -1
  225. package/packages/client/dist/yoloop/invoke-hooks.js +669 -12
  226. package/packages/client/dist/yoloop/shared-discover-epics.js +1007 -1
  227. package/packages/client/dist/yoloop/shared-yoloop-types.d.ts +65 -1
  228. package/packages/client/dist/yoloop/shared-yoloop-types.js +33 -1
  229. package/packages/client/dist/yoloop/target-normalization.js +180 -1
  230. package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +8 -0
  231. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1920 -1
  232. package/packages/shared/dist/yolo/spec-envelope.js +3 -6
  233. package/targets-stubs/antigravity/gemini.md +1 -1
  234. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  235. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  236. package/targets-stubs/claude-code/neocortex-root.md +2 -2
  237. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  238. package/targets-stubs/claude-code/neocortex.md +2 -2
  239. package/targets-stubs/codex/AGENTS.md +1 -1
  240. package/targets-stubs/cursor/agent.md +2 -2
  241. package/targets-stubs/gemini-cli/agent.md +2 -2
  242. package/targets-stubs/opencode/neocortex-root.md +1 -1
  243. package/targets-stubs/vscode/neocortex.agent.md +2 -2
@@ -0,0 +1,1060 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, writeFileSync, } from 'node:fs';
4
+ import { isAbsolute, join, relative, resolve } from 'node:path';
5
+ import { inspectEpicOperationalSqliteBinding, } from '../continuity/sqlite-store.js';
6
+ import { GIT_LIFECYCLE_RUNTIME_DIRECTORY, GIT_LIFECYCLE_RUNTIME_FILE, mutateGitLifecycleRuntime, readGitLifecycleRuntimeSync, } from '../git/git-lifecycle-store.js';
7
+ import { EPIC_OPERATIONAL_PAYLOAD_LIMIT_BYTES, EPIC_OPERATIONAL_RECEIPT_CAP, assertEpicOperationalState, createEpicOperationalIdentity, createInitialEpicOperationalState, epicOperationalCanonicalJson, epicOperationalHash, mutateEpicOperationalState, readEpicOperationalState, } from '../git/epic-operational-state.js';
8
+ import { createDefaultCounters } from '../yoloop/yoloop-client-state-store.js';
9
+ import { applyStateMigration, previewStateMigration, readStateMigrationSourceSnapshot, restoreStateMigrationPreimage, STATE_MIGRATION_INPUT_LIMIT_BYTES, } from './state-json.js';
10
+ const CHECKOUT_CAP = 32;
11
+ const PUBLIC_PATH_LIMIT = 1_024;
12
+ const HASH = /^[a-f0-9]{64}$/;
13
+ const SAFE_ID = /^[A-Za-z0-9._:-]{1,128}$/;
14
+ const BACKUP_SCHEMA = 'EpicLifecycleBackfillBackup/1.0';
15
+ const YOLOOP_CAPABILITIES = Object.freeze([
16
+ 'rollover-session-v1',
17
+ 'successor-transition-v1',
18
+ 'continuation-replan-v1',
19
+ 'cross-epic-continuation-reconcile-v1',
20
+ ]);
21
+ const PRIVATE_PLANS = new WeakMap();
22
+ const APPROVALS = new WeakMap();
23
+ export async function previewEpicLifecycleBackfill(projectRoot, options) {
24
+ validateOptions(options);
25
+ const preview = await collectPreview(projectRoot, options);
26
+ const plan = buildPlan(preview);
27
+ if (Buffer.byteLength(epicOperationalCanonicalJson(plan), 'utf8') > EPIC_OPERATIONAL_PAYLOAD_LIMIT_BYTES) {
28
+ throw new Error('epic-operational-plan-oversized');
29
+ }
30
+ const frozen = deepFreeze(plan);
31
+ PRIVATE_PLANS.set(frozen, preview);
32
+ return frozen;
33
+ }
34
+ export function approveEpicLifecycleBackfill(plan) {
35
+ const preview = PRIVATE_PLANS.get(plan);
36
+ if (!preview || plan.blockers.length > 0 || plan.conflicts.length > 0
37
+ || computePlanDigest(plan) !== plan.planDigest) {
38
+ throw new Error('epic-operational-approval-invalid');
39
+ }
40
+ const authority = Object.freeze(Object.create(null));
41
+ APPROVALS.set(authority, { plan, preview });
42
+ return authority;
43
+ }
44
+ export async function applyEpicLifecycleBackfill(projectRoot, plan, approval, hooks = {}) {
45
+ assertPlan(plan);
46
+ const root = realpathSync(resolve(projectRoot));
47
+ const current = readGitLifecycleRuntimeSync(root);
48
+ if (current.schemaVersion === 2 && current.operational !== undefined) {
49
+ assertEpicOperationalState(current.operational);
50
+ const existing = current.operational.backfillReceipts.find((receipt) => receipt.operationId === plan.operationId || receipt.idempotencyKey === plan.idempotencyKey);
51
+ if (existing) {
52
+ if (existing.planDigest !== plan.planDigest || existing.sourceInventoryDigest !== plan.sourceInventoryDigest) {
53
+ throw new Error('epic-operational-replay-conflict');
54
+ }
55
+ return {
56
+ applied: false,
57
+ replay: true,
58
+ receipt: existing,
59
+ revision: current.revision,
60
+ writerEpoch: current.operational.writerEpoch,
61
+ };
62
+ }
63
+ }
64
+ let backup;
65
+ let document = current;
66
+ let operational = current.schemaVersion === 2 && current.operational !== undefined
67
+ ? current.operational
68
+ : null;
69
+ const active = operational?.activeJournal;
70
+ if (active) {
71
+ if (active.kind !== 'backfill' || active.operationId !== plan.operationId
72
+ || active.idempotencyKey !== plan.idempotencyKey || active.payloadHash !== plan.planDigest) {
73
+ throw new Error('epic-operational-journal-conflict');
74
+ }
75
+ if (active.phase === 'aborted-before-authority')
76
+ throw new Error('epic-operational-source-raced');
77
+ backup = readBackfillBackup(root, active.backupManifestRef, active.preimageDigest, plan);
78
+ }
79
+ else {
80
+ const binding = approval ? APPROVALS.get(approval) : undefined;
81
+ if (!binding || binding.plan !== plan || binding.preview.projectRoot !== root
82
+ || computePlanDigest(plan) !== plan.planDigest) {
83
+ throw new Error('epic-operational-approval-invalid');
84
+ }
85
+ if (document.schemaVersion !== plan.expectedRuntimeSchema && !(plan.expectedRuntimeSchema === 0 && document.revision === 0)) {
86
+ throw new Error('epic-operational-revision-conflict');
87
+ }
88
+ if (document.revision !== plan.expectedRevision)
89
+ throw new Error('epic-operational-revision-conflict');
90
+ verifyPrivateSources(binding.preview.sourceBindings, binding.preview.tracked.byteDigest);
91
+ verifyPhysicalBranch(root, binding.preview.physicalBranch);
92
+ backup = writeBackfillBackup(binding.preview, plan);
93
+ await hooks.afterBackup?.();
94
+ const journal = backfillJournal(plan, backup, 'prepared');
95
+ const preparedOperational = {
96
+ ...binding.preview.initialOperational,
97
+ activeJournal: journal,
98
+ };
99
+ const promoted = await mutateGitLifecycleRuntime(root, (mutable) => {
100
+ if (mutable.schemaVersion !== document.schemaVersion || mutable.revision !== plan.expectedRevision) {
101
+ throw new Error('epic-operational-revision-conflict');
102
+ }
103
+ mutable.operational = preparedOperational;
104
+ }, {
105
+ expectedRevision: plan.expectedRevision,
106
+ nextSchemaVersion: 2,
107
+ preCommit: async () => {
108
+ verifyPrivateSources(binding.preview.sourceBindings, binding.preview.tracked.byteDigest);
109
+ verifyPhysicalBranch(root, binding.preview.physicalBranch);
110
+ },
111
+ });
112
+ document = promoted.document;
113
+ operational = document.operational;
114
+ await hooks.afterPrepared?.();
115
+ }
116
+ if (!operational)
117
+ throw new Error('epic-operational-store-missing');
118
+ try {
119
+ if (operational.activeJournal?.phase === 'prepared') {
120
+ verifyBackupSources(backup, backup.plan.trackedStateMigration.sourceByteDigest);
121
+ verifyPhysicalBranch(root, backup.physicalBranch);
122
+ const transitioned = await mutateEpicOperationalState(root, {
123
+ identityHash: plan.identity.identityHash,
124
+ expectedRevision: document.revision,
125
+ expectedWriterEpoch: plan.nextWriterEpoch,
126
+ }, (state) => {
127
+ state.activeJournal = {
128
+ ...state.activeJournal,
129
+ phase: 'authority-applied',
130
+ };
131
+ });
132
+ document = transitioned.document;
133
+ operational = document.operational;
134
+ await hooks.afterAuthorityApplied?.();
135
+ }
136
+ }
137
+ catch (error) {
138
+ if (operational.activeJournal?.phase === 'prepared' && isSourceRace(error)) {
139
+ await markBackfillAborted(root, document, operational, plan);
140
+ }
141
+ throw error;
142
+ }
143
+ let trackedPostDigest = backup.plan.trackedStateMigration.targetByteDigest;
144
+ if (operational.activeJournal?.phase === 'authority-applied') {
145
+ verifyBackupSourcesOneOf(backup, backup.trackedMigration
146
+ ? [backup.plan.trackedStateMigration.sourceByteDigest, trackedPostDigest]
147
+ : [trackedPostDigest]);
148
+ if (backup.trackedMigration) {
149
+ const trackedBeforeApply = readStateMigrationSourceSnapshot(root).byteDigest;
150
+ applyStateMigration(root, {
151
+ operationId: backup.trackedMigration.operationId,
152
+ sourceCanonicalHash: backup.trackedMigration.sourceCanonicalHash,
153
+ targetCanonicalHash: backup.trackedMigration.targetCanonicalHash,
154
+ ...(trackedBeforeApply === backup.plan.trackedStateMigration.sourceByteDigest
155
+ ? { sourceByteDigest: backup.plan.trackedStateMigration.sourceByteDigest }
156
+ : {}),
157
+ });
158
+ }
159
+ const trackedReadback = readStateMigrationSourceSnapshot(root);
160
+ trackedPostDigest = trackedReadback.byteDigest;
161
+ if (trackedPostDigest !== backup.plan.trackedStateMigration.targetByteDigest) {
162
+ throw new Error('epic-operational-projection-conflict');
163
+ }
164
+ await hooks.afterTrackedProjection?.();
165
+ const sqlite = await inspectEpicOperationalSqliteBinding({
166
+ projectRoot: root,
167
+ authorityRevision: document.revision,
168
+ sourceDigest: plan.authorityPostimageDigest,
169
+ });
170
+ const transitioned = await mutateEpicOperationalState(root, {
171
+ identityHash: plan.identity.identityHash,
172
+ expectedRevision: document.revision,
173
+ expectedWriterEpoch: plan.nextWriterEpoch,
174
+ }, (state) => {
175
+ state.projectionBindings = {
176
+ ...state.projectionBindings,
177
+ trackedState: {
178
+ status: 'bound',
179
+ sourceDigest: trackedPostDigest,
180
+ authorityRevision: document.revision,
181
+ reasonCodes: ['epic-operational-tracked-state-bound'],
182
+ },
183
+ sqlite: {
184
+ status: sqlite.status,
185
+ sourceDigest: sqlite.sourceDigest,
186
+ authorityRevision: document.revision,
187
+ reasonCodes: sqlite.reasonCodes,
188
+ },
189
+ };
190
+ state.activeJournal = {
191
+ ...state.activeJournal,
192
+ phase: 'projections-applied',
193
+ projectionStatuses: {
194
+ trackedState: 'applied',
195
+ sqlite: sqlite.status === 'bound' ? 'applied' : 'unavailable',
196
+ },
197
+ };
198
+ });
199
+ document = transitioned.document;
200
+ operational = document.operational;
201
+ await hooks.afterProjectionsApplied?.();
202
+ }
203
+ if (operational.activeJournal?.phase !== 'projections-applied') {
204
+ throw new Error('epic-operational-journal-corrupt');
205
+ }
206
+ verifyBackupSources(backup, trackedPostDigest);
207
+ const receipt = backfillReceipt(plan, backup, trackedPostDigest, document.revision + 1);
208
+ const committed = await mutateEpicOperationalState(root, {
209
+ identityHash: plan.identity.identityHash,
210
+ expectedRevision: document.revision,
211
+ expectedWriterEpoch: plan.nextWriterEpoch,
212
+ }, (state) => {
213
+ if (state.backfillReceipts.length >= EPIC_OPERATIONAL_RECEIPT_CAP) {
214
+ throw new Error('epic-operational-receipt-capacity');
215
+ }
216
+ state.backfillReceipts = [...state.backfillReceipts, receipt];
217
+ state.activeJournal = null;
218
+ });
219
+ const finalState = committed.document.operational;
220
+ return {
221
+ applied: true,
222
+ replay: false,
223
+ receipt,
224
+ revision: committed.document.revision,
225
+ writerEpoch: finalState.writerEpoch,
226
+ };
227
+ }
228
+ export async function rollbackEpicLifecycleBackfill(input) {
229
+ const root = realpathSync(resolve(input.projectRoot));
230
+ let { document, operational } = readEpicOperationalState(root, {
231
+ expectedRevision: input.expectedRevision,
232
+ expectedWriterEpoch: input.expectedWriterEpoch,
233
+ });
234
+ if (operational.activeJournal)
235
+ throw new Error('epic-operational-journal-conflict');
236
+ const target = operational.backfillReceipts.find((receipt) => receipt.operationId === input.targetOperationId);
237
+ if (!target)
238
+ throw new Error('epic-operational-rollback-target-missing');
239
+ const existing = operational.rollbackReceipts.find((receipt) => receipt.targetOperationId === input.targetOperationId);
240
+ if (existing)
241
+ return { receipt: existing, revision: document.revision };
242
+ if (operational.rollbackReceipts.length >= EPIC_OPERATIONAL_RECEIPT_CAP) {
243
+ throw new Error('epic-operational-receipt-capacity');
244
+ }
245
+ const backupRef = `backups/${input.targetOperationId}.backfill.json`;
246
+ const backup = readBackfillBackup(root, backupRef, target.backupManifestDigest, undefined);
247
+ const tracked = readStateMigrationSourceSnapshot(root);
248
+ if (tracked.byteDigest !== target.postimageDigest)
249
+ throw new Error('epic-operational-rollback-postimage-conflict');
250
+ const operationId = `elrb_${epicOperationalHash('p224-backfill-rollback-id-v1', {
251
+ targetOperationId: input.targetOperationId,
252
+ expectedRevision: input.expectedRevision,
253
+ expectedWriterEpoch: input.expectedWriterEpoch,
254
+ }).slice(0, 32)}`;
255
+ const payloadHash = epicOperationalHash('p224-backfill-rollback-v1', {
256
+ operationId,
257
+ targetOperationId: input.targetOperationId,
258
+ expectedRevision: input.expectedRevision,
259
+ expectedWriterEpoch: input.expectedWriterEpoch,
260
+ postimageDigest: target.postimageDigest,
261
+ preimageDigest: target.preimageDigest,
262
+ });
263
+ const journal = {
264
+ schemaVersion: 'EpicOperationalJournal/1.0',
265
+ kind: 'rollback',
266
+ operationId,
267
+ idempotencyKey: operationId,
268
+ payloadHash,
269
+ identityHash: target.identityHash,
270
+ expectedRevision: input.expectedRevision,
271
+ expectedWriterEpoch: input.expectedWriterEpoch,
272
+ preimageDigest: target.postimageDigest,
273
+ backupManifestRef: backupRef,
274
+ phase: 'prepared',
275
+ projectionStatuses: { trackedState: 'pending', sqlite: 'not-applicable' },
276
+ reasonCodes: ['epic-operational-backup-verified'],
277
+ };
278
+ let transition = await mutateEpicOperationalState(root, {
279
+ identityHash: target.identityHash,
280
+ expectedRevision: document.revision,
281
+ expectedWriterEpoch: operational.writerEpoch,
282
+ }, (state) => {
283
+ state.activeJournal = journal;
284
+ });
285
+ document = transition.document;
286
+ operational = document.operational;
287
+ transition = await mutateEpicOperationalState(root, {
288
+ identityHash: target.identityHash,
289
+ expectedRevision: document.revision,
290
+ expectedWriterEpoch: operational.writerEpoch,
291
+ }, (state) => {
292
+ state.writerEpoch = state.writerEpoch + 1;
293
+ state.activeJournal = {
294
+ ...state.activeJournal,
295
+ phase: 'authority-applied',
296
+ };
297
+ });
298
+ document = transition.document;
299
+ operational = document.operational;
300
+ if (backup.trackedMigration) {
301
+ restoreStateMigrationPreimage({
302
+ projectRoot: root,
303
+ operationId: backup.trackedMigration.operationId,
304
+ expectedCurrentDigest: target.postimageDigest,
305
+ expectedBackupDigest: target.preimageDigest,
306
+ });
307
+ }
308
+ else if (target.preimageDigest !== target.postimageDigest) {
309
+ throw new Error('epic-operational-rollback-backup-invalid');
310
+ }
311
+ const restored = readStateMigrationSourceSnapshot(root);
312
+ if (restored.byteDigest !== target.preimageDigest)
313
+ throw new Error('epic-operational-rollback-readback-failed');
314
+ transition = await mutateEpicOperationalState(root, {
315
+ identityHash: target.identityHash,
316
+ expectedRevision: document.revision,
317
+ expectedWriterEpoch: operational.writerEpoch,
318
+ }, (state) => {
319
+ state.projectionBindings = {
320
+ ...state.projectionBindings,
321
+ trackedState: {
322
+ status: 'bound',
323
+ sourceDigest: restored.byteDigest,
324
+ authorityRevision: document.revision,
325
+ reasonCodes: ['epic-operational-rollback-projection-restored'],
326
+ },
327
+ };
328
+ state.activeJournal = {
329
+ ...state.activeJournal,
330
+ phase: 'projections-applied',
331
+ projectionStatuses: { trackedState: 'applied', sqlite: 'not-applicable' },
332
+ };
333
+ });
334
+ document = transition.document;
335
+ operational = document.operational;
336
+ const receipt = {
337
+ schemaVersion: 'EpicOperationalRollbackReceipt/1.0',
338
+ operationId,
339
+ targetOperationId: input.targetOperationId,
340
+ expectedPostimageDigest: target.postimageDigest,
341
+ restoredPreimageDigest: target.preimageDigest,
342
+ reasonCodes: ['epic-operational-rollback-verified'],
343
+ publicSafe: true,
344
+ };
345
+ const committed = await mutateEpicOperationalState(root, {
346
+ identityHash: target.identityHash,
347
+ expectedRevision: document.revision,
348
+ expectedWriterEpoch: operational.writerEpoch,
349
+ }, (state) => {
350
+ state.rollbackReceipts = [...state.rollbackReceipts, receipt];
351
+ state.activeJournal = null;
352
+ });
353
+ return { receipt, revision: committed.document.revision };
354
+ }
355
+ async function collectPreview(projectRoot, options) {
356
+ const root = realpathSync(resolve(projectRoot));
357
+ const commonDir = resolveCommonDir(root);
358
+ const runtimePath = resolveRuntimePath(commonDir);
359
+ const runtimeRaw = existsSync(runtimePath) ? readBoundedRaw(runtimePath).raw : null;
360
+ const runtime = readGitLifecycleRuntimeSync(root);
361
+ const tracked = readStateMigrationSourceSnapshot(root);
362
+ const trackedMigration = previewStateMigration(root);
363
+ const trackedTargetRaw = trackedMigration
364
+ ? `${JSON.stringify(trackedMigration.targetState, null, 2)}\n`
365
+ : tracked.raw;
366
+ const trackedTargetDigest = sha256(trackedTargetRaw);
367
+ const identity = createEpicOperationalIdentity(root, options);
368
+ const nowIso = options.nowIso ?? new Date().toISOString();
369
+ if (!Number.isFinite(Date.parse(nowIso)))
370
+ throw new Error('epic-operational-time-invalid');
371
+ const worktrees = listWorktrees(root, commonDir);
372
+ const sources = [];
373
+ const bindings = [];
374
+ sources.push(sourceFromRaw('common-runtime', commonDir, runtimeRaw, 'authority-candidate'));
375
+ sources.push(sourceFromRaw('tracked-state', root, tracked.raw, 'projection'));
376
+ bindings.push({ path: join(root, '.neocortex', 'state.json'), presence: 'present', digest: tracked.byteDigest, kind: 'tracked-state' });
377
+ const validStates = [];
378
+ let selectedLock = null;
379
+ const collectionBlockers = [];
380
+ for (const worktree of worktrees) {
381
+ const statePath = join(worktree.path, '.neocortex', 'yoloop-state.json');
382
+ const lockPath = join(worktree.path, '.neocortex', 'yoloop.lock');
383
+ const state = inspectLegacyJson(statePath, 'legacy-yoloop-state', worktree.fingerprint, strictCounters);
384
+ sources.push(state.source);
385
+ bindings.push(state.binding);
386
+ if (state.parsed)
387
+ validStates.push({ fingerprint: worktree.fingerprint, parsed: state.parsed, digest: state.source.canonicalDigest });
388
+ if (state.source.presence === 'corrupt' || state.source.presence === 'unsafe' || state.source.presence === 'unsupported') {
389
+ collectionBlockers.push(...state.source.reasonCodes);
390
+ }
391
+ const lock = inspectLegacyJson(lockPath, 'legacy-yoloop-lock', worktree.fingerprint, strictLock);
392
+ sources.push(lock.source);
393
+ bindings.push(lock.binding);
394
+ if (lock.parsed)
395
+ selectedLock = lock.parsed;
396
+ if (lock.source.presence !== 'missing') {
397
+ if (lock.parsed)
398
+ collectionBlockers.push('epic-operational-writer-active');
399
+ else
400
+ collectionBlockers.push(...lock.source.reasonCodes);
401
+ }
402
+ }
403
+ const stateDigests = [...new Set(validStates.map((entry) => entry.digest))];
404
+ if (stateDigests.length > 1)
405
+ collectionBlockers.push('epic-operational-source-conflict');
406
+ const selected = validStates[0];
407
+ if (!selected && hasTrackedProgress(tracked.state))
408
+ collectionBlockers.push('epic-operational-progress-source-missing');
409
+ const selectedYoloop = selected?.parsed ?? createDefaultCounters(nowIso);
410
+ const initialOperational = createInitialEpicOperationalState({
411
+ identity,
412
+ yoloop: {
413
+ lock: null,
414
+ state: selectedYoloop,
415
+ capabilities: YOLOOP_CAPABILITIES,
416
+ },
417
+ trackedStateDigest: tracked.byteDigest,
418
+ sqliteStatus: 'unavailable',
419
+ sqliteReasonCodes: ['epic-operational-projection-unavailable'],
420
+ });
421
+ const sqlite = await inspectEpicOperationalSqliteBinding({
422
+ projectRoot: root,
423
+ authorityRevision: runtime.revision,
424
+ sourceDigest: epicOperationalHash('p224-backfill-authority-v1', initialOperational),
425
+ ...(options.sqliteAdapter ? { adapter: options.sqliteAdapter } : {}),
426
+ ...(options.sqliteAdapterLoader ? { adapterLoader: options.sqliteAdapterLoader } : {}),
427
+ });
428
+ sources.push({
429
+ schemaVersion: 'EpicLifecycleBackfillSource/1.0',
430
+ kind: 'sqlite-projection',
431
+ sourceFingerprint: epicOperationalHash('p224-backfill-source-v1', { kind: 'sqlite-projection', repository: identity.repositoryFingerprint }),
432
+ presence: sqlite.status === 'unavailable' ? 'unavailable' : 'present',
433
+ byteLength: null,
434
+ byteDigest: sqlite.sourceDigest,
435
+ canonicalDigest: sqlite.sourceDigest,
436
+ sourceSchema: 'continuity-state_snapshots',
437
+ status: 'projection',
438
+ role: 'projection',
439
+ reasonCodes: sqlite.reasonCodes,
440
+ publicSafe: true,
441
+ });
442
+ const physicalBranch = execFileSync('git', ['branch', '--show-current'], { cwd: root, encoding: 'utf8' }).trim();
443
+ const privatePreview = {
444
+ projectRoot: root,
445
+ commonDir,
446
+ runtimePath,
447
+ runtimeRaw,
448
+ runtime,
449
+ tracked,
450
+ trackedMigration,
451
+ trackedTargetRaw,
452
+ trackedTargetDigest,
453
+ selectedYoloop,
454
+ selectedYoloopLock: selectedLock,
455
+ initialOperational,
456
+ sourceBindings: bindings,
457
+ publicSources: sources.sort(compareSources),
458
+ collectionBlockers,
459
+ physicalBranch,
460
+ options: {
461
+ epicId: options.epicId,
462
+ executionBranch: options.executionBranch,
463
+ graphRevision: options.graphRevision,
464
+ originBindingDigest: options.originBindingDigest,
465
+ managedClients: options.managedClients ?? [],
466
+ nowIso,
467
+ ...(options.sqliteAdapter ? { sqliteAdapter: options.sqliteAdapter } : {}),
468
+ ...(options.sqliteAdapterLoader ? { sqliteAdapterLoader: options.sqliteAdapterLoader } : {}),
469
+ },
470
+ };
471
+ return privatePreview;
472
+ }
473
+ function buildPlan(preview) {
474
+ const sourceInventory = preview.collectionBlockers;
475
+ const sources = collectPublicSources(preview);
476
+ const blockers = [...sourceInventory];
477
+ if (preview.physicalBranch !== preview.options.executionBranch)
478
+ blockers.push('epic-operational-branch-mismatch');
479
+ const trackedEpic = findTrackedEpic(preview.tracked.state, preview.options.epicId);
480
+ const runtimeEpic = record(preview.runtime.epics[preview.options.epicId]);
481
+ if (!trackedEpic)
482
+ blockers.push('epic-operational-epic-missing');
483
+ if (trackedEpic && trackedEpic.branch_mode !== undefined && trackedEpic.branch_mode !== 'epic') {
484
+ blockers.push('epic-operational-branch-mode-conflict');
485
+ }
486
+ if (trackedEpic && typeof trackedEpic.execution_branch === 'string'
487
+ && trackedEpic.execution_branch !== preview.options.executionBranch)
488
+ blockers.push('epic-operational-branch-mismatch');
489
+ if (trackedEpic && typeof trackedEpic.graph_revision === 'string'
490
+ && trackedEpic.graph_revision !== preview.options.graphRevision)
491
+ blockers.push('epic-operational-graph-mismatch');
492
+ if (runtimeEpic && typeof runtimeEpic.execution_branch === 'string'
493
+ && runtimeEpic.execution_branch !== preview.options.executionBranch)
494
+ blockers.push('epic-operational-branch-mismatch');
495
+ if (runtimeEpic && typeof runtimeEpic.graph_revision === 'string'
496
+ && runtimeEpic.graph_revision !== preview.options.graphRevision)
497
+ blockers.push('epic-operational-graph-mismatch');
498
+ if (isLiveLease(trackedEpic?.branch_lease, preview.options.nowIso)
499
+ || isLiveLease(runtimeEpic?.branch_lease, preview.options.nowIso)) {
500
+ blockers.push('epic-operational-writer-active');
501
+ }
502
+ const expectedWriterEpoch = preview.runtime.schemaVersion === 2 && preview.runtime.operational
503
+ ? Number(preview.runtime.operational.writerEpoch) : 0;
504
+ for (const client of preview.options.managedClients) {
505
+ if (client.activeWriter)
506
+ blockers.push('epic-operational-writer-active');
507
+ if (client.writerEpoch !== expectedWriterEpoch
508
+ || !client.capabilities.includes('epic-operational-state-v1')) {
509
+ blockers.push('epic-operational-legacy-writer-unfenced');
510
+ }
511
+ }
512
+ if (preview.runtime.schemaVersion === 2) {
513
+ try {
514
+ assertEpicOperationalState(preview.runtime.operational);
515
+ const state = preview.runtime.operational;
516
+ blockers.push('epic-operational-already-adopted');
517
+ if (state.activeJournal)
518
+ blockers.push('epic-operational-journal-conflict');
519
+ if (state.backfillReceipts.length >= EPIC_OPERATIONAL_RECEIPT_CAP)
520
+ blockers.push('epic-operational-receipt-capacity');
521
+ }
522
+ catch {
523
+ blockers.push('epic-operational-store-corrupt');
524
+ }
525
+ }
526
+ const requiredSources = sources.filter((source) => source.kind !== 'sqlite-projection');
527
+ const sourceInventoryDigest = epicOperationalHash('p224-backfill-source-inventory-v1', sources);
528
+ const requiredSourceDigest = epicOperationalHash('p224-backfill-required-sources-v1', requiredSources);
529
+ const checkoutFingerprints = [...new Set(sources
530
+ .filter((source) => source.kind === 'legacy-yoloop-state')
531
+ .map((source) => source.sourceFingerprint))].sort();
532
+ const managedClientFenceDigest = epicOperationalHash('p224-managed-client-fence-v1', preview.options.managedClients);
533
+ const migration = preview.trackedMigration;
534
+ const body = {
535
+ schemaVersion: 'EpicLifecycleBackfillPlan/1.0',
536
+ repositoryFingerprint: preview.initialOperational.repositoryFingerprint,
537
+ identity: Object.values(preview.initialOperational.identities)[0],
538
+ expectedRuntimeSchema: (preview.runtimeRaw === null ? 0 : preview.runtime.schemaVersion),
539
+ expectedRevision: preview.runtime.revision,
540
+ expectedWriterEpoch,
541
+ nextWriterEpoch: Math.max(1, expectedWriterEpoch + 1),
542
+ sourceInventory: sources,
543
+ sourceInventoryDigest,
544
+ requiredSourceDigest,
545
+ checkoutCount: checkoutFingerprints.length,
546
+ checkoutSetDigest: epicOperationalHash('p224-backfill-checkout-set-v1', checkoutFingerprints),
547
+ managedClientFenceDigest,
548
+ selectedYoloopSourceFingerprint: sources.find((source) => source.kind === 'legacy-yoloop-state' && source.presence === 'present')?.sourceFingerprint ?? null,
549
+ selectedYoloopCanonicalDigest: epicOperationalHash('p224-backfill-yoloop-v1', preview.selectedYoloop),
550
+ trackedStateMigration: {
551
+ outcome: migration ? 'migrate' : 'unchanged',
552
+ operationId: migration?.operationId ?? null,
553
+ sourceByteDigest: preview.tracked.byteDigest,
554
+ targetByteDigest: preview.trackedTargetDigest,
555
+ sourceCanonicalHash: migration?.sourceCanonicalHash ?? null,
556
+ targetCanonicalHash: migration?.targetCanonicalHash ?? null,
557
+ },
558
+ conflicts: [...new Set(blockers.filter((reason) => reason === 'epic-operational-source-conflict'))].sort(),
559
+ blockers: [...new Set(blockers)].sort(),
560
+ proposedEffects: [
561
+ 'promote-common-runtime-v2',
562
+ 'install-common-yoloop-authority',
563
+ ...(migration ? ['migrate-owned-tracked-state'] : []),
564
+ 'record-explicit-sqlite-binding',
565
+ 'append-backfill-receipt',
566
+ ],
567
+ rollbackPreconditions: [
568
+ 'exact-runtime-revision-and-writer-epoch',
569
+ 'exact-tracked-postimage',
570
+ 'verified-private-backup',
571
+ 'quiescent-writers',
572
+ ],
573
+ authorityPostimageDigest: epicOperationalHash('p224-backfill-authority-v1', preview.initialOperational),
574
+ publicSafe: true,
575
+ };
576
+ const operationId = `elbf_${epicOperationalHash('p224-backfill-operation-id-v1', body).slice(0, 32)}`;
577
+ const planWithoutDigest = { ...body, operationId, idempotencyKey: operationId };
578
+ return { ...planWithoutDigest, planDigest: epicOperationalHash('p224-backfill-plan-v1', planWithoutDigest) };
579
+ }
580
+ function collectPublicSources(preview) {
581
+ return preview.publicSources;
582
+ }
583
+ function writeBackfillBackup(preview, plan) {
584
+ const runtimeDir = join(preview.commonDir, GIT_LIFECYCLE_RUNTIME_DIRECTORY);
585
+ ensurePrivateRuntimeDirectory(runtimeDir, preview.commonDir);
586
+ const backupsDir = join(runtimeDir, 'backups');
587
+ ensurePrivateRuntimeDirectory(backupsDir, runtimeDir);
588
+ const payload = {
589
+ schemaVersion: BACKUP_SCHEMA,
590
+ operationId: plan.operationId,
591
+ plan,
592
+ runtimeRaw: preview.runtimeRaw,
593
+ trackedSourceRaw: preview.tracked.raw,
594
+ trackedTargetRaw: preview.trackedTargetRaw,
595
+ trackedMigration: preview.trackedMigration,
596
+ initialOperational: preview.initialOperational,
597
+ sourceBindings: preview.sourceBindings,
598
+ physicalBranch: preview.physicalBranch,
599
+ };
600
+ const manifest = {
601
+ ...payload,
602
+ manifestDigest: epicOperationalHash('p224-backfill-backup-v1', payload),
603
+ };
604
+ const path = join(backupsDir, `${plan.operationId}.backfill.json`);
605
+ const raw = `${JSON.stringify(manifest, null, 2)}\n`;
606
+ if (existsSync(path)) {
607
+ const existing = parseBackup(readFileSync(path, 'utf8'));
608
+ if (existing.manifestDigest !== manifest.manifestDigest)
609
+ throw new Error('epic-operational-backup-failed');
610
+ return existing;
611
+ }
612
+ const fd = openSync(path, 'wx', 0o600);
613
+ try {
614
+ writeFileSync(fd, raw);
615
+ fsyncSync(fd);
616
+ }
617
+ finally {
618
+ closeSync(fd);
619
+ }
620
+ fsyncDirectory(backupsDir);
621
+ return manifest;
622
+ }
623
+ function readBackfillBackup(projectRoot, backupRef, expectedDigest, plan) {
624
+ if (!/^backups\/elbf_[a-f0-9]{32}\.backfill\.json$/.test(backupRef) || !HASH.test(expectedDigest)) {
625
+ throw new Error('epic-operational-backup-failed');
626
+ }
627
+ const commonDir = resolveCommonDir(projectRoot);
628
+ const runtimeDir = join(commonDir, GIT_LIFECYCLE_RUNTIME_DIRECTORY);
629
+ const path = join(runtimeDir, backupRef);
630
+ assertPrivateEntry(runtimeDir, 'directory');
631
+ assertPrivateEntry(path, 'file');
632
+ const backup = parseBackup(readFileSync(path, 'utf8'));
633
+ if (backup.manifestDigest !== expectedDigest
634
+ || (plan && (backup.plan.planDigest !== plan.planDigest || backup.operationId !== plan.operationId))) {
635
+ throw new Error('epic-operational-backup-failed');
636
+ }
637
+ return backup;
638
+ }
639
+ function parseBackup(raw) {
640
+ let parsed;
641
+ try {
642
+ parsed = JSON.parse(raw);
643
+ }
644
+ catch {
645
+ throw new Error('epic-operational-backup-failed');
646
+ }
647
+ const item = record(parsed);
648
+ if (!item || item.schemaVersion !== BACKUP_SCHEMA || !SAFE_ID.test(String(item.operationId))
649
+ || !record(item.plan) || !record(item.initialOperational) || !Array.isArray(item.sourceBindings)
650
+ || typeof item.trackedSourceRaw !== 'string' || typeof item.trackedTargetRaw !== 'string'
651
+ || typeof item.physicalBranch !== 'string' || !HASH.test(String(item.manifestDigest))) {
652
+ throw new Error('epic-operational-backup-failed');
653
+ }
654
+ const { manifestDigest, ...payload } = item;
655
+ if (manifestDigest !== epicOperationalHash('p224-backfill-backup-v1', payload)) {
656
+ throw new Error('epic-operational-backup-failed');
657
+ }
658
+ assertPlan(item.plan);
659
+ assertEpicOperationalState(item.initialOperational);
660
+ return item;
661
+ }
662
+ function backfillJournal(plan, backup, phase) {
663
+ return {
664
+ schemaVersion: 'EpicOperationalJournal/1.0',
665
+ kind: 'backfill',
666
+ operationId: plan.operationId,
667
+ idempotencyKey: plan.idempotencyKey,
668
+ payloadHash: plan.planDigest,
669
+ identityHash: plan.identity.identityHash,
670
+ expectedRevision: plan.expectedRevision,
671
+ expectedWriterEpoch: plan.expectedWriterEpoch,
672
+ preimageDigest: backup.manifestDigest,
673
+ backupManifestRef: `backups/${plan.operationId}.backfill.json`,
674
+ phase,
675
+ projectionStatuses: { trackedState: 'pending', sqlite: 'pending' },
676
+ reasonCodes: ['epic-operational-backup-verified'],
677
+ };
678
+ }
679
+ function backfillReceipt(plan, backup, trackedPostDigest, appliedRevision) {
680
+ return Object.freeze({
681
+ schemaVersion: 'EpicLifecycleBackfillReceipt/1.0',
682
+ operationId: plan.operationId,
683
+ idempotencyKey: plan.idempotencyKey,
684
+ payloadHash: plan.planDigest,
685
+ planDigest: plan.planDigest,
686
+ sourceInventoryDigest: plan.sourceInventoryDigest,
687
+ preimageDigest: plan.trackedStateMigration.sourceByteDigest,
688
+ postimageDigest: trackedPostDigest,
689
+ authorityPostimageDigest: plan.authorityPostimageDigest,
690
+ backupManifestDigest: backup.manifestDigest,
691
+ identityHash: plan.identity.identityHash,
692
+ appliedRevision,
693
+ writerEpoch: plan.nextWriterEpoch,
694
+ stateMigrationOperationId: plan.trackedStateMigration.operationId,
695
+ outcome: 'applied',
696
+ reasonCodes: ['epic-operational-backfill-applied', 'epic-operational-backup-verified'],
697
+ publicSafe: true,
698
+ });
699
+ }
700
+ function verifyBackupSources(backup, expectedTrackedDigest) {
701
+ verifyPrivateSources(backup.sourceBindings, expectedTrackedDigest);
702
+ }
703
+ function verifyBackupSourcesOneOf(backup, expectedTrackedDigests) {
704
+ for (const binding of backup.sourceBindings) {
705
+ if (binding.kind === 'tracked-state') {
706
+ const source = readBoundedRaw(binding.path);
707
+ if (!expectedTrackedDigests.includes(source.digest))
708
+ throw new Error('epic-operational-source-raced');
709
+ continue;
710
+ }
711
+ if (binding.presence === 'missing') {
712
+ if (existsSync(binding.path))
713
+ throw new Error('epic-operational-source-raced');
714
+ continue;
715
+ }
716
+ if (readBoundedRaw(binding.path).digest !== binding.digest) {
717
+ throw new Error('epic-operational-source-raced');
718
+ }
719
+ }
720
+ }
721
+ function verifyPrivateSources(bindings, expectedTrackedDigest) {
722
+ for (const binding of bindings) {
723
+ if (binding.kind === 'tracked-state') {
724
+ const source = readBoundedRaw(binding.path);
725
+ if (source.digest !== expectedTrackedDigest)
726
+ throw new Error('epic-operational-source-raced');
727
+ continue;
728
+ }
729
+ if (binding.presence === 'missing') {
730
+ if (existsSync(binding.path))
731
+ throw new Error('epic-operational-source-raced');
732
+ continue;
733
+ }
734
+ const source = readBoundedRaw(binding.path);
735
+ if (source.digest !== binding.digest)
736
+ throw new Error('epic-operational-source-raced');
737
+ }
738
+ }
739
+ async function markBackfillAborted(projectRoot, document, operational, plan) {
740
+ if (operational.activeJournal?.phase !== 'prepared')
741
+ return;
742
+ await mutateEpicOperationalState(projectRoot, {
743
+ identityHash: plan.identity.identityHash,
744
+ expectedRevision: document.revision,
745
+ expectedWriterEpoch: plan.nextWriterEpoch,
746
+ }, (state) => {
747
+ state.activeJournal = {
748
+ ...state.activeJournal,
749
+ phase: 'aborted-before-authority',
750
+ reasonCodes: [...state.activeJournal.reasonCodes, 'epic-operational-source-raced'],
751
+ };
752
+ });
753
+ }
754
+ function inspectLegacyJson(path, kind, worktreeFingerprint, validate) {
755
+ const fingerprint = epicOperationalHash('p224-backfill-source-v1', { kind, worktreeFingerprint });
756
+ if (!existsSync(path)) {
757
+ return {
758
+ source: missingSource(kind, fingerprint),
759
+ binding: { path, presence: 'missing', digest: null, kind },
760
+ parsed: null,
761
+ };
762
+ }
763
+ try {
764
+ const raw = readBoundedRaw(path);
765
+ const parsed = validate(JSON.parse(raw.raw));
766
+ return {
767
+ source: sourceFromParsed(kind, fingerprint, raw.raw, parsed),
768
+ binding: { path, presence: 'present', digest: raw.digest, kind },
769
+ parsed,
770
+ };
771
+ }
772
+ catch (error) {
773
+ const reason = error instanceof Error && error.message.startsWith('epic-operational-')
774
+ ? error.message : 'epic-operational-source-corrupt';
775
+ return {
776
+ source: blockedSource(kind, fingerprint, reason.endsWith('unsafe') ? 'unsafe' : 'corrupt', reason),
777
+ binding: { path, presence: 'present', digest: safeFileDigest(path), kind },
778
+ parsed: null,
779
+ };
780
+ }
781
+ }
782
+ function strictCounters(value) {
783
+ const item = record(value);
784
+ if (!item || !['1.1', '1.2'].includes(String(item.version))
785
+ || !Number.isFinite(item.storiesRunToday) || Number(item.storiesRunToday) < 0
786
+ || !Number.isFinite(item.iterationsToday) || Number(item.iterationsToday) < 0
787
+ || !Number.isFinite(item.tokensUsedToday) || Number(item.tokensUsedToday) < 0
788
+ || !Number.isFinite(item.totalRunsAllTime) || Number(item.totalRunsAllTime) < 0
789
+ || !Number.isFinite(item.totalStoriesAllTime) || Number(item.totalStoriesAllTime) < 0
790
+ || !(item.currentSession === null || record(item.currentSession))
791
+ || !Array.isArray(item.pendingContinuationAuthorities ?? [])) {
792
+ throw new Error('epic-operational-source-corrupt');
793
+ }
794
+ return item;
795
+ }
796
+ function strictLock(value) {
797
+ const item = record(value);
798
+ if (!item || !Number.isSafeInteger(item.pid) || Number(item.pid) < 0
799
+ || typeof item.hostname !== 'string' || typeof item.startedAt !== 'string'
800
+ || typeof item.sessionId !== 'string' || item.sessionId.length > 128
801
+ || !Array.isArray(item.epicQueue) || !item.epicQueue.every((entry) => typeof entry === 'string')
802
+ || typeof item.currentEpicId !== 'string' || typeof item.ttlExpiresAt !== 'string') {
803
+ throw new Error('epic-operational-source-corrupt');
804
+ }
805
+ return item;
806
+ }
807
+ function sourceFromRaw(kind, identityPath, raw, role) {
808
+ const fingerprint = sourceFingerprint(identityPath, kind);
809
+ if (raw === null)
810
+ return missingSource(kind, fingerprint);
811
+ let parsed;
812
+ try {
813
+ parsed = JSON.parse(raw);
814
+ }
815
+ catch {
816
+ return blockedSource(kind, fingerprint, 'corrupt', 'epic-operational-source-corrupt');
817
+ }
818
+ const source = sourceFromParsed(kind, fingerprint, raw, parsed);
819
+ return { ...source, role, status: role === 'projection' ? 'projection' : 'candidate' };
820
+ }
821
+ function sourceFromParsed(kind, fingerprint, raw, parsed) {
822
+ const item = record(parsed);
823
+ return {
824
+ schemaVersion: 'EpicLifecycleBackfillSource/1.0',
825
+ kind,
826
+ sourceFingerprint: fingerprint,
827
+ presence: 'present',
828
+ byteLength: Buffer.byteLength(raw, 'utf8'),
829
+ byteDigest: sha256(raw),
830
+ canonicalDigest: epicOperationalHash('p224-backfill-source-canonical-v1', parsed),
831
+ sourceSchema: typeof item?.schemaVersion === 'string' || typeof item?.schemaVersion === 'number'
832
+ ? String(item.schemaVersion) : typeof item?.version === 'string' ? item.version : 'legacy',
833
+ status: kind.includes('lock') ? 'activity' : kind === 'tracked-state' || kind === 'sqlite-projection' ? 'projection' : 'candidate',
834
+ role: kind.includes('lock') ? 'activity-signal' : kind === 'tracked-state' || kind === 'sqlite-projection'
835
+ ? 'projection' : 'authority-candidate',
836
+ reasonCodes: ['epic-operational-source-complete'],
837
+ publicSafe: true,
838
+ };
839
+ }
840
+ function missingSource(kind, fingerprint) {
841
+ return {
842
+ schemaVersion: 'EpicLifecycleBackfillSource/1.0',
843
+ kind,
844
+ sourceFingerprint: fingerprint,
845
+ presence: 'missing',
846
+ byteLength: null,
847
+ byteDigest: null,
848
+ canonicalDigest: null,
849
+ sourceSchema: null,
850
+ status: 'absent',
851
+ role: kind.includes('lock') ? 'activity-signal' : kind === 'tracked-state' || kind === 'sqlite-projection'
852
+ ? 'projection' : 'authority-candidate',
853
+ reasonCodes: ['epic-operational-source-missing'],
854
+ publicSafe: true,
855
+ };
856
+ }
857
+ function blockedSource(kind, fingerprint, presence, reason) {
858
+ return {
859
+ schemaVersion: 'EpicLifecycleBackfillSource/1.0',
860
+ kind,
861
+ sourceFingerprint: fingerprint,
862
+ presence,
863
+ byteLength: null,
864
+ byteDigest: null,
865
+ canonicalDigest: null,
866
+ sourceSchema: null,
867
+ status: 'blocked',
868
+ role: kind.includes('lock') ? 'activity-signal' : kind === 'tracked-state' ? 'projection' : 'authority-candidate',
869
+ reasonCodes: [reason],
870
+ publicSafe: true,
871
+ };
872
+ }
873
+ function listWorktrees(projectRoot, expectedCommonDir) {
874
+ const raw = execFileSync('git', ['worktree', 'list', '--porcelain', '-z'], {
875
+ cwd: projectRoot,
876
+ encoding: 'utf8',
877
+ maxBuffer: 4 * 1024 * 1024,
878
+ });
879
+ const paths = raw.split('\0')
880
+ .filter((token) => token.startsWith('worktree '))
881
+ .map((token) => token.slice('worktree '.length));
882
+ if (paths.length === 0 || paths.length > CHECKOUT_CAP)
883
+ throw new Error('epic-operational-checkout-capacity');
884
+ return paths.map((path) => {
885
+ const canonical = realpathSync(path);
886
+ const common = resolveCommonDir(canonical);
887
+ if (common !== expectedCommonDir)
888
+ throw new Error('epic-operational-foreign-common-dir');
889
+ const info = lstatSync(canonical);
890
+ if (!info.isDirectory() || info.isSymbolicLink())
891
+ throw new Error('epic-operational-path-unsafe');
892
+ return {
893
+ path: canonical,
894
+ fingerprint: epicOperationalHash('p224-backfill-worktree-v1', {
895
+ repository: epicOperationalHash('p224-backfill-common-v1', expectedCommonDir),
896
+ dev: Number(info.dev),
897
+ ino: Number(info.ino),
898
+ }),
899
+ };
900
+ }).sort((left, right) => left.fingerprint.localeCompare(right.fingerprint));
901
+ }
902
+ function resolveCommonDir(projectRoot) {
903
+ const raw = execFileSync('git', ['rev-parse', '--git-common-dir'], {
904
+ cwd: projectRoot,
905
+ encoding: 'utf8',
906
+ stdio: ['ignore', 'pipe', 'ignore'],
907
+ }).trim();
908
+ const absolute = isAbsolute(raw) ? resolve(raw) : resolve(projectRoot, raw);
909
+ const canonical = realpathSync(absolute);
910
+ const info = lstatSync(canonical);
911
+ if (canonical !== absolute || !info.isDirectory() || info.isSymbolicLink()) {
912
+ throw new Error('epic-operational-path-unsafe');
913
+ }
914
+ return canonical;
915
+ }
916
+ function resolveRuntimePath(commonDir) {
917
+ const current = join(commonDir, GIT_LIFECYCLE_RUNTIME_DIRECTORY, GIT_LIFECYCLE_RUNTIME_FILE);
918
+ if (existsSync(current))
919
+ return current;
920
+ return join(commonDir, 'neocortex-yoloop-runtime.json');
921
+ }
922
+ function readBoundedRaw(path) {
923
+ const info = lstatSync(path);
924
+ if (!info.isFile() || info.isSymbolicLink())
925
+ throw new Error('epic-operational-path-unsafe');
926
+ if (typeof process.getuid === 'function' && info.uid !== process.getuid())
927
+ throw new Error('epic-operational-path-unsafe');
928
+ if (info.size > STATE_MIGRATION_INPUT_LIMIT_BYTES)
929
+ throw new Error('epic-operational-source-oversized');
930
+ const raw = readFileSync(path, 'utf8');
931
+ if (Buffer.byteLength(raw, 'utf8') !== info.size)
932
+ throw new Error('epic-operational-source-raced');
933
+ return { raw, digest: sha256(raw) };
934
+ }
935
+ function safeFileDigest(path) {
936
+ try {
937
+ return readBoundedRaw(path).digest;
938
+ }
939
+ catch {
940
+ return null;
941
+ }
942
+ }
943
+ function sourceFingerprint(path, kind) {
944
+ let physical = { presence: 'missing' };
945
+ if (existsSync(path)) {
946
+ const info = lstatSync(path);
947
+ physical = { dev: Number(info.dev), ino: Number(info.ino), type: info.isDirectory() ? 'directory' : 'file' };
948
+ }
949
+ return epicOperationalHash('p224-backfill-source-v1', { kind, physical });
950
+ }
951
+ function findTrackedEpic(state, epicId) {
952
+ const epics = state.epics;
953
+ if (Array.isArray(epics))
954
+ return epics.map(record).find((epic) => epic?.id === epicId);
955
+ return record(epics)?.[epicId];
956
+ }
957
+ function hasTrackedProgress(state) {
958
+ const stories = state.stories;
959
+ const values = Array.isArray(stories) ? stories : Object.values(record(stories) ?? {});
960
+ return values.some((value) => {
961
+ const story = record(value);
962
+ return Boolean(story && ((Array.isArray(story.steps_completed) && story.steps_completed.length > 0)
963
+ || story.checkpoint || story.branch_name || (typeof story.status === 'string' && !['backlog', 'planned'].includes(story.status))));
964
+ });
965
+ }
966
+ function isLiveLease(value, nowIso) {
967
+ const lease = record(value);
968
+ if (!lease)
969
+ return false;
970
+ const expires = Date.parse(String(lease.expiresAt ?? ''));
971
+ return !Number.isFinite(expires) || expires > Date.parse(nowIso);
972
+ }
973
+ function verifyPhysicalBranch(projectRoot, expected) {
974
+ const current = execFileSync('git', ['branch', '--show-current'], { cwd: projectRoot, encoding: 'utf8' }).trim();
975
+ if (current !== expected)
976
+ throw new Error('epic-operational-source-raced');
977
+ }
978
+ function ensurePrivateRuntimeDirectory(path, parent) {
979
+ if (!existsSync(path)) {
980
+ mkdirSync(path, { mode: 0o700 });
981
+ fsyncDirectory(parent);
982
+ }
983
+ assertPrivateEntry(path, 'directory');
984
+ if (realpathSync(path) !== resolve(path) || relative(resolve(parent), resolve(path)).startsWith('..')) {
985
+ throw new Error('epic-operational-path-unsafe');
986
+ }
987
+ }
988
+ function assertPrivateEntry(path, kind) {
989
+ const info = lstatSync(path);
990
+ if (info.isSymbolicLink() || (kind === 'file' ? !info.isFile() : !info.isDirectory())
991
+ || (typeof process.getuid === 'function' && info.uid !== process.getuid())
992
+ || (process.platform !== 'win32' && (info.mode & 0o077) !== 0)) {
993
+ throw new Error('epic-operational-path-unsafe');
994
+ }
995
+ }
996
+ function fsyncDirectory(path) {
997
+ if (process.platform === 'win32')
998
+ return;
999
+ const fd = openSync(path, 'r');
1000
+ try {
1001
+ fsyncSync(fd);
1002
+ }
1003
+ finally {
1004
+ closeSync(fd);
1005
+ }
1006
+ }
1007
+ function validateOptions(options) {
1008
+ if (!/^P[1-9][0-9]*$/.test(options.epicId)
1009
+ || !/^[A-Za-z0-9][A-Za-z0-9._/-]{0,239}$/.test(options.executionBranch)
1010
+ || options.executionBranch.includes('..') || options.executionBranch.includes('//')
1011
+ || !HASH.test(options.graphRevision) || !HASH.test(options.originBindingDigest)
1012
+ || (options.managedClients?.length ?? 0) > 128
1013
+ || options.managedClients?.some((client) => !SAFE_ID.test(client.clientId)
1014
+ || !Number.isSafeInteger(client.writerEpoch) || client.writerEpoch < 0
1015
+ || client.capabilities.length > 16)) {
1016
+ throw new Error('epic-operational-preview-input-invalid');
1017
+ }
1018
+ }
1019
+ function assertPlan(plan) {
1020
+ if (plan.schemaVersion !== 'EpicLifecycleBackfillPlan/1.0'
1021
+ || !/^elbf_[a-f0-9]{32}$/.test(plan.operationId)
1022
+ || plan.idempotencyKey !== plan.operationId
1023
+ || !HASH.test(plan.repositoryFingerprint) || !HASH.test(plan.planDigest)
1024
+ || !HASH.test(plan.sourceInventoryDigest) || !HASH.test(plan.requiredSourceDigest)
1025
+ || computePlanDigest(plan) !== plan.planDigest
1026
+ || plan.sourceInventory.length > CHECKOUT_CAP * 2 + 3
1027
+ || plan.blockers.some((reason) => !/^[a-z0-9][a-z0-9-]{0,79}$/.test(reason))) {
1028
+ throw new Error('epic-operational-plan-invalid');
1029
+ }
1030
+ }
1031
+ function computePlanDigest(plan) {
1032
+ const { planDigest: _planDigest, ...body } = plan;
1033
+ return epicOperationalHash('p224-backfill-plan-v1', body);
1034
+ }
1035
+ function compareSources(left, right) {
1036
+ return `${left.kind}:${left.sourceFingerprint}`.localeCompare(`${right.kind}:${right.sourceFingerprint}`);
1037
+ }
1038
+ function isSourceRace(error) {
1039
+ return error instanceof Error && [
1040
+ 'epic-operational-source-raced',
1041
+ 'epic-operational-branch-mismatch',
1042
+ 'epic-operational-writer-active',
1043
+ 'epic-operational-legacy-writer-unfenced',
1044
+ ].includes(error.message);
1045
+ }
1046
+ function record(value) {
1047
+ return value && typeof value === 'object' && !Array.isArray(value)
1048
+ ? value : undefined;
1049
+ }
1050
+ function sha256(value) {
1051
+ return createHash('sha256').update(value).digest('hex');
1052
+ }
1053
+ function deepFreeze(value) {
1054
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
1055
+ Object.freeze(value);
1056
+ for (const child of Object.values(value))
1057
+ deepFreeze(child);
1058
+ }
1059
+ return value;
1060
+ }