@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.
- package/dist/release-source.json +1 -1
- package/dist/sbom.cdx.json +1216 -1210
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/package.json +2 -2
- package/packages/client/dist/adapters/adapter-registry.js +1 -108
- package/packages/client/dist/adapters/antigravity-adapter.js +2 -77
- package/packages/client/dist/adapters/claude-code-adapter.js +3 -79
- package/packages/client/dist/adapters/codex-adapter.js +2 -80
- package/packages/client/dist/adapters/cursor-adapter.js +4 -115
- package/packages/client/dist/adapters/gemini-adapter.js +2 -71
- package/packages/client/dist/adapters/grok-adapter.js +2 -42
- package/packages/client/dist/adapters/index.js +1 -22
- package/packages/client/dist/adapters/platform-detector.js +1 -212
- package/packages/client/dist/adapters/target-adapter.js +0 -12
- package/packages/client/dist/adapters/vscode-adapter.js +2 -72
- package/packages/client/dist/agent/refresh-stubs.js +2 -417
- package/packages/client/dist/agent/update-agent-yaml.js +1 -102
- package/packages/client/dist/agent/update-description.js +1 -251
- package/packages/client/dist/cache/crypto-utils.js +1 -78
- package/packages/client/dist/cache/encrypted-cache.d.ts +27 -0
- package/packages/client/dist/cache/encrypted-cache.js +1 -108
- package/packages/client/dist/cache/in-memory-asset-cache.js +1 -73
- package/packages/client/dist/cache/index.js +1 -13
- package/packages/client/dist/cache/protected-pi-boundary.js +1 -44
- package/packages/client/dist/checkpoint/checkpoint-client-reader.js +2 -127
- package/packages/client/dist/checkpoint/index.js +1 -11
- package/packages/client/dist/checkpoint/shared-checkpoint-types.js +1 -32
- package/packages/client/dist/cli.js +5 -358
- package/packages/client/dist/commands/activate.js +8 -394
- package/packages/client/dist/commands/architecture-answers.js +7 -274
- package/packages/client/dist/commands/architecture-memory-terminal.js +4 -253
- package/packages/client/dist/commands/architecture-non-ui-continuation.js +1 -100
- package/packages/client/dist/commands/architecture-root-binding.js +1 -51
- package/packages/client/dist/commands/cache-status.js +2 -112
- package/packages/client/dist/commands/config.js +1 -88
- package/packages/client/dist/commands/doctor.js +2 -635
- package/packages/client/dist/commands/invoke.d.ts +38 -1
- package/packages/client/dist/commands/invoke.js +143 -7200
- package/packages/client/dist/commands/legacy-epic-terminal.js +1 -71
- package/packages/client/dist/commands/legacy-story-branch.js +1 -96
- package/packages/client/dist/commands/legacy-story-pr.js +1 -276
- package/packages/client/dist/commands/mutation-qa-checkpoint.js +5 -285
- package/packages/client/dist/commands/mutation-qa-gate.js +1 -54
- package/packages/client/dist/commands/mutation-qa-git.js +1 -41
- package/packages/client/dist/commands/mutation-qa-policy.js +1 -102
- package/packages/client/dist/commands/mutation-qa-run.js +3 -219
- package/packages/client/dist/commands/mutation-qa-verdict.js +1 -149
- package/packages/client/dist/commands/mutation-qa.js +1 -220
- package/packages/client/dist/commands/native-agent.js +2 -136
- package/packages/client/dist/commands/planning-diagnosis.js +1 -43
- package/packages/client/dist/commands/planning-generation.js +1 -53
- package/packages/client/dist/commands/planning-memory.js +1 -40
- package/packages/client/dist/commands/planning-research.js +1 -42
- package/packages/client/dist/commands/private-session.js +1 -75
- package/packages/client/dist/commands/property-qa.js +1 -139
- package/packages/client/dist/commands/qa-control.js +1 -16
- package/packages/client/dist/commands/qa-scan-browser-launch.js +1 -55
- package/packages/client/dist/commands/qa-scan-browser-route.js +1 -28
- package/packages/client/dist/commands/qa-scan-capture.js +5 -663
- package/packages/client/dist/commands/qa-scan-discovery.js +3 -376
- package/packages/client/dist/commands/qa-scan-review.js +5 -826
- package/packages/client/dist/commands/qa-synthetic-backend-capture.js +1 -266
- package/packages/client/dist/commands/qa-synthetic-backend-review.js +1 -297
- package/packages/client/dist/commands/qa-synthetic-backend.js +2 -340
- package/packages/client/dist/commands/qa-synthetic-broker-port.js +1 -85
- package/packages/client/dist/commands/qa-synthetic-browser.js +1 -39
- package/packages/client/dist/commands/refactor-characterization.js +21 -671
- package/packages/client/dist/commands/refactor-commit.js +13 -399
- package/packages/client/dist/commands/refactor-control.js +3 -142
- package/packages/client/dist/commands/refactor-cpp-assertions.js +3 -93
- package/packages/client/dist/commands/refactor-review.js +5 -283
- package/packages/client/dist/commands/refactor-root.js +7 -588
- package/packages/client/dist/commands/refresh-memory.js +3 -156
- package/packages/client/dist/commands/shared-architecture-non-ui.js +1 -24
- package/packages/client/dist/commands/story-checkpoint.js +1 -171
- package/packages/client/dist/config/host-auth-broker.d.ts +31 -0
- package/packages/client/dist/config/host-auth-broker.js +1 -0
- package/packages/client/dist/config/host-auth-reference.d.ts +32 -0
- package/packages/client/dist/config/host-auth-reference.js +1 -0
- package/packages/client/dist/config/profile-directory.js +1 -13
- package/packages/client/dist/config/resolver-selection.js +1 -278
- package/packages/client/dist/config/secure-config.js +12 -281
- package/packages/client/dist/config.js +2 -223
- package/packages/client/dist/constants.js +1 -25
- package/packages/client/dist/context/context-collector.js +2 -222
- package/packages/client/dist/context/context-sanitizer.js +1 -145
- package/packages/client/dist/continuity/autonomous-governance.js +1 -285
- package/packages/client/dist/continuity/continuity-client-state-store.js +1 -1587
- package/packages/client/dist/continuity/invoke-hooks.js +1 -129
- package/packages/client/dist/continuity/migrations/001-initial-schema.js +2 -12
- package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +2 -12
- package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +2 -12
- package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +2 -12
- package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +2 -12
- package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +2 -10
- package/packages/client/dist/continuity/native-agent-store.js +1 -69
- package/packages/client/dist/continuity/native-agent-types.d.ts +11 -0
- package/packages/client/dist/continuity/native-agent-types.js +1 -90
- package/packages/client/dist/continuity/outbox.js +1 -117
- package/packages/client/dist/continuity/runner-bootstrap-policy.js +1 -172
- package/packages/client/dist/continuity/sqlite-store.js +81 -2612
- package/packages/client/dist/effects/invoke-effect-gateways.js +1 -62
- package/packages/client/dist/errors/error-messages.d.ts +3 -0
- package/packages/client/dist/errors/error-messages.js +2 -228
- package/packages/client/dist/errors/frontmatter-diagnostics.js +1 -125
- package/packages/client/dist/evidence/collector.js +1 -347
- package/packages/client/dist/evidence/index.js +1 -1
- package/packages/client/dist/evidence/types.js +0 -2
- package/packages/client/dist/fs/atomic-state-replace.js +1 -152
- package/packages/client/dist/generated/command-registry.generated.js +1 -4076
- package/packages/client/dist/generated/public-projections.generated.js +1 -149
- package/packages/client/dist/git/direct-yolo-c01-retry.js +4 -138
- package/packages/client/dist/git/direct-yolo-checkpoint.d.ts +11 -1
- package/packages/client/dist/git/direct-yolo-checkpoint.js +4 -1234
- package/packages/client/dist/git/epic-operational-state.js +2 -672
- package/packages/client/dist/git/git-lifecycle-step-ids.js +1 -14
- package/packages/client/dist/git/git-lifecycle-store.d.ts +23 -0
- package/packages/client/dist/git/git-lifecycle-store.js +10 -1981
- package/packages/client/dist/git/legacy-story-branch-checkpoint.js +9 -259
- package/packages/client/dist/git/legacy-story-branch-setup.js +4 -348
- package/packages/client/dist/git/legacy-story-pr.js +70 -3538
- package/packages/client/dist/git/secure-filesystem-boundary.js +5 -182
- package/packages/client/dist/graph-retrieval/pre-command-hook.js +1 -284
- package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +1 -237
- package/packages/client/dist/i18n/first-run.js +2 -60
- package/packages/client/dist/index.d.ts +2 -0
- package/packages/client/dist/index.js +1 -47
- package/packages/client/dist/invocation/invoke-input.js +1 -24
- package/packages/client/dist/license/host-auth-recovery.d.ts +158 -0
- package/packages/client/dist/license/host-auth-recovery.js +1 -0
- package/packages/client/dist/license/index.js +1 -5
- package/packages/client/dist/license/license-client.d.ts +10 -1
- package/packages/client/dist/license/license-client.js +1 -304
- package/packages/client/dist/machine/fingerprint.js +2 -163
- package/packages/client/dist/machine/index.js +1 -5
- package/packages/client/dist/memory/project-memory-path-scope.js +1 -62
- package/packages/client/dist/memory/project-memory-writer.js +40 -1007
- package/packages/client/dist/memory/shared-project-memory-types.js +2 -253
- package/packages/client/dist/migrations/epic-lifecycle-backfill.js +3 -1060
- package/packages/client/dist/migrations/project-identity-capability.js +1 -50
- package/packages/client/dist/migrations/project-identity.js +7 -493
- package/packages/client/dist/migrations/state-json.js +4 -393
- package/packages/client/dist/policy/architecture-policy.js +2 -184
- package/packages/client/dist/policy/index.js +1 -7
- package/packages/client/dist/policy/shared-policy-types.js +0 -38
- package/packages/client/dist/policy/subagent-approval-policy.js +1 -55
- package/packages/client/dist/private-execution/clean-baseline.js +2 -279
- package/packages/client/dist/private-execution/codex-executor.js +4 -574
- package/packages/client/dist/private-execution/codex-host-runtime.js +1 -137
- package/packages/client/dist/private-execution/codex-lifecycle-guard.js +1 -97
- package/packages/client/dist/private-execution/codex-scoped-permissions.js +1 -128
- package/packages/client/dist/private-execution/delivery.js +13 -410
- package/packages/client/dist/private-execution/diagnosis-root-review.js +2 -294
- package/packages/client/dist/private-execution/frozen-handoff.js +6 -19
- package/packages/client/dist/private-execution/implementation-validation.js +2 -106
- package/packages/client/dist/private-execution/isolated-admission.js +23 -1432
- package/packages/client/dist/private-execution/isolated-session.js +5 -725
- package/packages/client/dist/private-execution/isolated-workspace.js +1 -122
- package/packages/client/dist/private-execution/pact-consumer-runtime.js +3 -409
- package/packages/client/dist/private-execution/pact-test-source.js +1 -258
- package/packages/client/dist/private-execution/pact-toolchain.js +3 -290
- package/packages/client/dist/private-execution/pact-write-set.js +1 -172
- package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +5 -406
- package/packages/client/dist/private-execution/planning-generation-checkpoint.js +5 -462
- package/packages/client/dist/private-execution/planning-memory-checkpoint.js +3 -139
- package/packages/client/dist/private-execution/planning-research-checkpoint.js +4 -325
- package/packages/client/dist/private-execution/private-report-store.js +1 -42
- package/packages/client/dist/private-execution/property-test-candidate.js +1 -103
- package/packages/client/dist/private-execution/property-test-choices.js +3 -147
- package/packages/client/dist/private-execution/property-test-plan.js +7 -271
- package/packages/client/dist/private-execution/property-test-review.js +4 -258
- package/packages/client/dist/private-execution/property-test-runtime.js +9 -372
- package/packages/client/dist/private-execution/property-test-source.js +1 -139
- package/packages/client/dist/private-execution/root-checkpoint.js +1 -80
- package/packages/client/dist/private-execution/root-memory-git-evidence.js +3 -135
- package/packages/client/dist/private-execution/root-memory-recovery.js +1 -93
- package/packages/client/dist/private-execution/root-memory-request.js +1 -29
- package/packages/client/dist/private-execution/root-memory-scope.js +1 -178
- package/packages/client/dist/private-execution/root-memory-writer.js +1 -102
- package/packages/client/dist/private-execution/session-lease.js +1 -63
- package/packages/client/dist/private-execution/shared-private-admission-types.js +1 -50
- package/packages/client/dist/private-execution/shared-project-validation.js +1 -66
- package/packages/client/dist/private-execution/shared-root-memory-types.js +1 -61
- package/packages/client/dist/private-execution/ux-source-validation.js +1 -372
- package/packages/client/dist/private-execution/ux-write-set.js +2 -191
- package/packages/client/dist/registry/registry-reconciliation.js +1 -234
- package/packages/client/dist/resilience/circuit-breaker.js +1 -170
- package/packages/client/dist/resilience/degradation-manager.js +1 -164
- package/packages/client/dist/resilience/freshness-indicator.js +1 -100
- package/packages/client/dist/resilience/index.js +1 -8
- package/packages/client/dist/resilience/recovery-detector.js +1 -74
- package/packages/client/dist/resolvers/asset-resolver.js +0 -13
- package/packages/client/dist/resolvers/local-resolver.js +8 -255
- package/packages/client/dist/resolvers/remote-resolver.js +1 -233
- package/packages/client/dist/runner/binary.js +1 -404
- package/packages/client/dist/runner/cli.js +17 -1571
- package/packages/client/dist/runner/epic-background-dispatcher.d.ts +61 -0
- package/packages/client/dist/runner/epic-background-dispatcher.js +1 -0
- package/packages/client/dist/runner/headless/adapters.js +1 -49
- package/packages/client/dist/runner/headless/background-root.d.ts +65 -0
- package/packages/client/dist/runner/headless/background-root.js +1 -0
- package/packages/client/dist/runner/headless/claude-code-adapter.js +1 -79
- package/packages/client/dist/runner/headless/continuous-story-workspace.js +11 -1628
- package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +1 -69
- package/packages/client/dist/runner/headless/epic-merge-candidate-qa.d.ts +1 -1
- package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +2 -110
- package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.d.ts +2 -0
- package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +10 -1236
- package/packages/client/dist/runner/headless/epic-terminal-git-runner.d.ts +27 -2
- package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +2 -318
- package/packages/client/dist/runner/headless/fake-adapter.js +1 -62
- package/packages/client/dist/runner/headless/managed-environment-cleanup.d.ts +95 -0
- package/packages/client/dist/runner/headless/managed-environment-cleanup.js +1 -0
- package/packages/client/dist/runner/headless/managed-profile.js +1 -132
- package/packages/client/dist/runner/headless/opencode-adapter.js +2 -145
- package/packages/client/dist/runner/headless/prepared-story-bundle.js +1 -43
- package/packages/client/dist/runner/headless/resolver.js +1 -231
- package/packages/client/dist/runner/headless/run-yolo.js +1 -885
- package/packages/client/dist/runner/headless/run-yoloop.d.ts +1 -0
- package/packages/client/dist/runner/headless/run-yoloop.js +2 -2574
- package/packages/client/dist/runner/headless/types.js +2 -574
- package/packages/client/dist/runner/local-console.js +13 -396
- package/packages/client/dist/runner/portal-sync-worker.js +1 -1487
- package/packages/client/dist/runner/project-identity.js +1 -106
- package/packages/client/dist/runner/scheduler.js +7 -995
- package/packages/client/dist/runner-cli.js +2 -18
- package/packages/client/dist/state/invocation-shape.js +1 -13
- package/packages/client/dist/state/project-state-snapshot.d.ts +7 -0
- package/packages/client/dist/state/project-state-snapshot.js +1 -1731
- package/packages/client/dist/state/state-json-repair.js +3 -785
- package/packages/client/dist/state/structural-dag-projection.js +1 -118
- package/packages/client/dist/telemetry/index.js +1 -5
- package/packages/client/dist/telemetry/offline-queue.js +1 -131
- package/packages/client/dist/tier/index.js +1 -5
- package/packages/client/dist/tier/tier-aware-client.js +1 -405
- package/packages/client/dist/types/index.js +1 -38
- package/packages/client/dist/version.js +1 -27
- package/packages/client/dist/yolo/change-evidence-collector.d.ts +21 -1
- package/packages/client/dist/yolo/change-evidence-collector.js +1 -126
- package/packages/client/dist/yolo/invoke-envelope-capability.js +1 -62
- package/packages/client/dist/yolo/spec-envelope-collector.js +5 -184
- package/packages/client/dist/yoloop/discovery-hook.js +2 -300
- package/packages/client/dist/yoloop/index.js +1 -8
- package/packages/client/dist/yoloop/invoke-hooks.js +12 -669
- package/packages/client/dist/yoloop/shared-discover-epics.js +1 -1007
- package/packages/client/dist/yoloop/shared-yoloop-types.js +1 -33
- package/packages/client/dist/yoloop/target-normalization.js +1 -180
- package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1920
- package/packages/shared/dist/invocation/explicit-intent-recovery.d.ts +83 -0
- package/packages/shared/dist/invocation/explicit-intent-recovery.js +187 -0
- package/packages/shared/dist/invocation/index.d.ts +7 -0
- package/packages/shared/dist/invocation/index.js +22 -0
- package/packages/shared/dist/yolo/change-evidence.js +1 -1
- package/packages/shared/dist/yoloop/epic-terminal-git.js +144 -2
- package/targets-stubs/antigravity/gemini.md +1 -1
- package/targets-stubs/antigravity/skill/SKILL.md +1 -1
- package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex-root.md +2 -2
- package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex.md +2 -2
- package/targets-stubs/codex/AGENTS.md +24 -21
- package/targets-stubs/codex/README.md +6 -1
- package/targets-stubs/codex/neocortex.toml +7 -2
- package/targets-stubs/cursor/agent.md +2 -2
- package/targets-stubs/gemini-cli/agent.md +2 -2
- package/targets-stubs/opencode/neocortex-root.md +16 -1
- package/targets-stubs/opencode/neocortex.md +6 -0
- package/targets-stubs/vscode/neocortex.agent.md +2 -2
|
@@ -1,1981 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
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};
|