@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,139 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { closeSync, existsSync, fsyncSync, lstatSync, openSync, readFileSync, readlinkSync, realpathSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
5
|
-
import { join, resolve } from 'node:path';
|
|
6
|
-
import { isDeepStrictEqual } from 'node:util';
|
|
7
|
-
import { canonicalPlanningIntake } from '../../../shared/dist/planning/diagnosis-state.js';
|
|
8
|
-
import { parseRefreshMemoryArgs, refreshProjectMemoryTargets, verifyProjectMemoryTargets } from '../commands/refresh-memory.js';
|
|
9
|
-
import { planningPendingSidecarDigest } from './planning-diagnosis-checkpoint.js';
|
|
10
|
-
import { verifyCommittedPlanningGeneration } from './planning-generation-checkpoint.js';
|
|
11
|
-
const digest = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
12
|
-
const fail = () => new Error('planning-memory-physical-evidence-invalid');
|
|
13
|
-
const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
14
|
-
const git = (root, ...args) => execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 1024 * 1024 }).trim();
|
|
15
|
-
const receiptPath = (epicId) => `.neocortex/planning/epic-${epicId}/memory-acceptance.json`;
|
|
16
|
-
function committed(root, path) {
|
|
17
|
-
const result = spawnSync('git', ['show', `HEAD:${path}`], { cwd: root, encoding: 'buffer', maxBuffer: 1024 * 1024 });
|
|
18
|
-
if (result.status !== 0 || !Buffer.isBuffer(result.stdout))
|
|
19
|
-
throw fail();
|
|
20
|
-
return result.stdout;
|
|
21
|
-
}
|
|
22
|
-
function physical(root, claimId, epicId) {
|
|
23
|
-
if (realpathSync(root) !== root || git(root, 'rev-parse', '--show-toplevel') !== root)
|
|
24
|
-
throw fail();
|
|
25
|
-
const generation = verifyCommittedPlanningGeneration(root, claimId, epicId);
|
|
26
|
-
const options = parseRefreshMemoryArgs(['--project-root', root, '--verify']);
|
|
27
|
-
const memory = verifyProjectMemoryTargets(options);
|
|
28
|
-
const files = [...new Set(refreshProjectMemoryTargets(options).flatMap(r => r.files.map(f => f.relativeFile)))].sort();
|
|
29
|
-
for (const path of files) {
|
|
30
|
-
const file = join(root, path), entry = lstatSync(file);
|
|
31
|
-
const bytes = entry.isSymbolicLink() ? Buffer.from(readlinkSync(file)) : readFileSync(file);
|
|
32
|
-
if (!bytes.equals(committed(root, path)))
|
|
33
|
-
throw fail();
|
|
34
|
-
const mode = git(root, 'ls-tree', 'HEAD', '--', path).split(' ')[0];
|
|
35
|
-
if (entry.isSymbolicLink() ? mode !== '120000' : !entry.isFile() || !['100644', '100755'].includes(mode))
|
|
36
|
-
throw fail();
|
|
37
|
-
}
|
|
38
|
-
if (git(root, 'rev-parse', 'HEAD') !== generation.verifiedHead)
|
|
39
|
-
throw fail();
|
|
40
|
-
return { generation, memory, files };
|
|
41
|
-
}
|
|
42
|
-
function review(value) {
|
|
43
|
-
if (!record(value) || Object.keys(value).sort().join('|') !== [
|
|
44
|
-
'schemaVersion', 'verdict', 'claimId', 'epicId', 'sourceHead', 'generationReceiptId',
|
|
45
|
-
'canonicalSha256', 'projectionSha256', 'fileCount', 'qaEvidenceId', 'artifactSummary',
|
|
46
|
-
].sort().join('|') || value.schemaVersion !== 'PlanningMemoryRootReview/1' || value.verdict !== 'accepted'
|
|
47
|
-
|| typeof value.claimId !== 'string' || !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(value.claimId)
|
|
48
|
-
|| typeof value.epicId !== 'string' || !/^P[1-9][0-9]{0,11}$/.test(value.epicId)
|
|
49
|
-
|| typeof value.sourceHead !== 'string' || !/^[a-f0-9]{40}$/.test(value.sourceHead)
|
|
50
|
-
|| [value.generationReceiptId, value.canonicalSha256, value.projectionSha256].some(v => typeof v !== 'string' || !/^[a-f0-9]{64}$/.test(v))
|
|
51
|
-
|| !Number.isInteger(value.fileCount) || Number(value.fileCount) < 1 || Number(value.fileCount) > 64
|
|
52
|
-
|| typeof value.qaEvidenceId !== 'string' || !/^qa-[a-z0-9-]{8,80}$/.test(value.qaEvidenceId)
|
|
53
|
-
|| typeof value.artifactSummary !== 'string' || !value.artifactSummary.trim())
|
|
54
|
-
throw fail();
|
|
55
|
-
canonicalPlanningIntake({ goal: value.artifactSummary });
|
|
56
|
-
}
|
|
57
|
-
function derive(root, input) {
|
|
58
|
-
review(input);
|
|
59
|
-
const { generation, memory } = physical(root, input.claimId, input.epicId);
|
|
60
|
-
if (generation.physicalAcceptanceReceiptId !== input.generationReceiptId
|
|
61
|
-
|| memory.canonicalSha256 !== input.canonicalSha256 || memory.projectionSha256 !== input.projectionSha256
|
|
62
|
-
|| memory.fileCount !== input.fileCount
|
|
63
|
-
|| spawnSync('git', ['merge-base', '--is-ancestor', input.sourceHead, 'HEAD'], { cwd: root, stdio: 'ignore' }).status !== 0)
|
|
64
|
-
throw fail();
|
|
65
|
-
const fields = { schemaVersion: 'PlanningMemoryPhysicalAcceptance/1',
|
|
66
|
-
claimId: input.claimId, epicId: input.epicId, sourceHead: input.sourceHead,
|
|
67
|
-
generationReceiptId: input.generationReceiptId, stateSha256: generation.stateSha256,
|
|
68
|
-
canonicalSha256: input.canonicalSha256, projectionSha256: input.projectionSha256,
|
|
69
|
-
fileCount: input.fileCount, qaEvidenceId: input.qaEvidenceId, artifactSummary: input.artifactSummary };
|
|
70
|
-
return { ...fields, receiptId: digest(JSON.stringify(fields)), publicSafe: true };
|
|
71
|
-
}
|
|
72
|
-
/** Only a typed independent root review can create this receipt. No canonical state is rewritten. */
|
|
73
|
-
export function sealPlanningMemory(projectRoot, input) {
|
|
74
|
-
review(input);
|
|
75
|
-
const root = resolve(projectRoot), path = receiptPath(input.epicId);
|
|
76
|
-
verifyCommittedPlanningGeneration(root, input.claimId, input.epicId);
|
|
77
|
-
const lockPath = join(root, '.neocortex/state.json.planning.lock'), lock = openSync(lockPath, 'wx', 0o600);
|
|
78
|
-
try {
|
|
79
|
-
const pending = planningPendingSidecarDigest(root), result = derive(root, input);
|
|
80
|
-
if (existsSync(join(root, path))) {
|
|
81
|
-
const entry = lstatSync(join(root, path));
|
|
82
|
-
if (!entry.isFile() || entry.isSymbolicLink() || entry.size > 16384
|
|
83
|
-
|| !isDeepStrictEqual(JSON.parse(readFileSync(join(root, path), 'utf8')), result))
|
|
84
|
-
throw fail();
|
|
85
|
-
return result;
|
|
86
|
-
}
|
|
87
|
-
if (git(root, 'rev-parse', 'HEAD') !== input.sourceHead || git(root, 'diff', '--name-only')
|
|
88
|
-
|| git(root, 'diff', '--cached', '--name-only'))
|
|
89
|
-
throw fail();
|
|
90
|
-
const untracked = git(root, 'ls-files', '--others', '--exclude-standard').split('\n').filter(Boolean);
|
|
91
|
-
if (untracked.some(path => path !== '.neocortex/pending-epic-claims.json' && path !== '.neocortex/state.json.planning.lock'))
|
|
92
|
-
throw fail();
|
|
93
|
-
if (planningPendingSidecarDigest(root) !== pending || !isDeepStrictEqual(derive(root, input), result))
|
|
94
|
-
throw fail();
|
|
95
|
-
const fd = openSync(join(root, path), 'wx', 0o600);
|
|
96
|
-
try {
|
|
97
|
-
writeFileSync(fd, `${JSON.stringify(result, null, 2)}\n`);
|
|
98
|
-
fsyncSync(fd);
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
closeSync(fd);
|
|
102
|
-
}
|
|
103
|
-
const directory = openSync(join(root, '.neocortex/planning', `epic-${input.epicId}`), 'r');
|
|
104
|
-
try {
|
|
105
|
-
fsyncSync(directory);
|
|
106
|
-
}
|
|
107
|
-
finally {
|
|
108
|
-
closeSync(directory);
|
|
109
|
-
}
|
|
110
|
-
return result;
|
|
111
|
-
}
|
|
112
|
-
finally {
|
|
113
|
-
closeSync(lock);
|
|
114
|
-
unlinkSync(lockPath);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
/** Fresh read-only physical verification; metadata, a digest or an old receipt is insufficient. */
|
|
118
|
-
export function verifyCommittedPlanningMemory(projectRoot, claimId, epicId) {
|
|
119
|
-
if (!/^P[1-9][0-9]{0,11}$/.test(epicId))
|
|
120
|
-
throw fail();
|
|
121
|
-
const root = resolve(projectRoot), path = receiptPath(epicId), entry = lstatSync(join(root, path));
|
|
122
|
-
if (!entry.isFile() || entry.isSymbolicLink() || entry.size > 16384)
|
|
123
|
-
throw fail();
|
|
124
|
-
const bytes = readFileSync(join(root, path));
|
|
125
|
-
if (!bytes.equals(committed(root, path)))
|
|
126
|
-
throw fail();
|
|
127
|
-
const receipt = JSON.parse(bytes.toString('utf8'));
|
|
128
|
-
if (!record(receipt) || receipt.claimId !== claimId || receipt.epicId !== epicId)
|
|
129
|
-
throw fail();
|
|
130
|
-
const input = { schemaVersion: 'PlanningMemoryRootReview/1', verdict: 'accepted',
|
|
131
|
-
claimId, epicId, sourceHead: receipt.sourceHead, generationReceiptId: receipt.generationReceiptId,
|
|
132
|
-
canonicalSha256: receipt.canonicalSha256, projectionSha256: receipt.projectionSha256,
|
|
133
|
-
fileCount: receipt.fileCount, qaEvidenceId: receipt.qaEvidenceId, artifactSummary: receipt.artifactSummary };
|
|
134
|
-
review(input);
|
|
135
|
-
const expected = derive(root, input);
|
|
136
|
-
if (!isDeepStrictEqual(receipt, expected))
|
|
137
|
-
throw fail();
|
|
138
|
-
return expected;
|
|
139
|
-
}
|
|
1
|
+
import{execFileSync as q,spawnSync as x}from"node:child_process";import{createHash as A}from"node:crypto";import{closeSync as h,existsSync as $,fsyncSync as H,lstatSync as g,openSync as S,readFileSync as I,readlinkSync as M,realpathSync as D,unlinkSync as F,writeFileSync as N}from"node:fs";import{join as d,resolve as b}from"node:path";import{isDeepStrictEqual as w}from"node:util";import{canonicalPlanningIntake as B}from"../../../shared/dist/planning/diagnosis-state.js";import{parseRefreshMemoryArgs as O,refreshProjectMemoryTargets as v,verifyProjectMemoryTargets as J}from"../commands/refresh-memory.js";import{planningPendingSidecarDigest as P}from"./planning-diagnosis-checkpoint.js";import{verifyCommittedPlanningGeneration as k}from"./planning-generation-checkpoint.js";const L=e=>A("sha256").update(e).digest("hex"),r=()=>new Error("planning-memory-physical-evidence-invalid"),E=e=>!!e&&typeof e=="object"&&!Array.isArray(e),y=(e,...t)=>q("git",t,{cwd:e,encoding:"utf8",stdio:["ignore","pipe","ignore"],maxBuffer:1024*1024}).trim(),R=e=>`.neocortex/planning/epic-${e}/memory-acceptance.json`;function C(e,t){const i=x("git",["show",`HEAD:${t}`],{cwd:e,encoding:"buffer",maxBuffer:1048576});if(i.status!==0||!Buffer.isBuffer(i.stdout))throw r();return i.stdout}function V(e,t,i){if(D(e)!==e||y(e,"rev-parse","--show-toplevel")!==e)throw r();const o=k(e,t,i),c=O(["--project-root",e,"--verify"]),m=J(c),l=[...new Set(v(c).flatMap(n=>n.files.map(s=>s.relativeFile)))].sort();for(const n of l){const s=d(e,n),a=g(s);if(!(a.isSymbolicLink()?Buffer.from(M(s)):I(s)).equals(C(e,n)))throw r();const f=y(e,"ls-tree","HEAD","--",n).split(" ")[0];if(a.isSymbolicLink()?f!=="120000":!a.isFile()||!["100644","100755"].includes(f))throw r()}if(y(e,"rev-parse","HEAD")!==o.verifiedHead)throw r();return{generation:o,memory:m,files:l}}function u(e){if(!E(e)||Object.keys(e).sort().join("|")!==["schemaVersion","verdict","claimId","epicId","sourceHead","generationReceiptId","canonicalSha256","projectionSha256","fileCount","qaEvidenceId","artifactSummary"].sort().join("|")||e.schemaVersion!=="PlanningMemoryRootReview/1"||e.verdict!=="accepted"||typeof e.claimId!="string"||!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(e.claimId)||typeof e.epicId!="string"||!/^P[1-9][0-9]{0,11}$/.test(e.epicId)||typeof e.sourceHead!="string"||!/^[a-f0-9]{40}$/.test(e.sourceHead)||[e.generationReceiptId,e.canonicalSha256,e.projectionSha256].some(t=>typeof t!="string"||!/^[a-f0-9]{64}$/.test(t))||!Number.isInteger(e.fileCount)||Number(e.fileCount)<1||Number(e.fileCount)>64||typeof e.qaEvidenceId!="string"||!/^qa-[a-z0-9-]{8,80}$/.test(e.qaEvidenceId)||typeof e.artifactSummary!="string"||!e.artifactSummary.trim())throw r();B({goal:e.artifactSummary})}function j(e,t){u(t);const{generation:i,memory:o}=V(e,t.claimId,t.epicId);if(i.physicalAcceptanceReceiptId!==t.generationReceiptId||o.canonicalSha256!==t.canonicalSha256||o.projectionSha256!==t.projectionSha256||o.fileCount!==t.fileCount||x("git",["merge-base","--is-ancestor",t.sourceHead,"HEAD"],{cwd:e,stdio:"ignore"}).status!==0)throw r();const c={schemaVersion:"PlanningMemoryPhysicalAcceptance/1",claimId:t.claimId,epicId:t.epicId,sourceHead:t.sourceHead,generationReceiptId:t.generationReceiptId,stateSha256:i.stateSha256,canonicalSha256:t.canonicalSha256,projectionSha256:t.projectionSha256,fileCount:t.fileCount,qaEvidenceId:t.qaEvidenceId,artifactSummary:t.artifactSummary};return{...c,receiptId:L(JSON.stringify(c)),publicSafe:!0}}function Z(e,t){u(t);const i=b(e),o=R(t.epicId);k(i,t.claimId,t.epicId);const c=d(i,".neocortex/state.json.planning.lock"),m=S(c,"wx",384);try{const l=P(i),n=j(i,t);if($(d(i,o))){const f=g(d(i,o));if(!f.isFile()||f.isSymbolicLink()||f.size>16384||!w(JSON.parse(I(d(i,o),"utf8")),n))throw r();return n}if(y(i,"rev-parse","HEAD")!==t.sourceHead||y(i,"diff","--name-only")||y(i,"diff","--cached","--name-only")||y(i,"ls-files","--others","--exclude-standard").split(`
|
|
2
|
+
`).filter(Boolean).some(f=>f!==".neocortex/pending-epic-claims.json"&&f!==".neocortex/state.json.planning.lock")||P(i)!==l||!w(j(i,t),n))throw r();const a=S(d(i,o),"wx",384);try{N(a,`${JSON.stringify(n,null,2)}
|
|
3
|
+
`),H(a)}finally{h(a)}const p=S(d(i,".neocortex/planning",`epic-${t.epicId}`),"r");try{H(p)}finally{h(p)}return n}finally{h(m),F(c)}}function _(e,t,i){if(!/^P[1-9][0-9]{0,11}$/.test(i))throw r();const o=b(e),c=R(i),m=g(d(o,c));if(!m.isFile()||m.isSymbolicLink()||m.size>16384)throw r();const l=I(d(o,c));if(!l.equals(C(o,c)))throw r();const n=JSON.parse(l.toString("utf8"));if(!E(n)||n.claimId!==t||n.epicId!==i)throw r();const s={schemaVersion:"PlanningMemoryRootReview/1",verdict:"accepted",claimId:t,epicId:i,sourceHead:n.sourceHead,generationReceiptId:n.generationReceiptId,canonicalSha256:n.canonicalSha256,projectionSha256:n.projectionSha256,fileCount:n.fileCount,qaEvidenceId:n.qaEvidenceId,artifactSummary:n.artifactSummary};u(s);const a=j(o,s);if(!w(n,a))throw r();return a}export{Z as sealPlanningMemory,_ as verifyCommittedPlanningMemory};
|
|
@@ -1,325 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { isAbsolute, join, resolve } from 'node:path';
|
|
6
|
-
import { isDeepStrictEqual } from 'node:util';
|
|
7
|
-
import { createPlanningReceiptV1 } from '../../../shared/dist/planning/diagnosis-state.js';
|
|
8
|
-
import { acceptedPlanningDiagnosisPredecessor, planningPendingSidecarDigest, planningPhysicalPatchDigest } from './planning-diagnosis-checkpoint.js';
|
|
9
|
-
const HEX = /^[a-f0-9]{64}$/;
|
|
10
|
-
const EPIC = /^P[1-9][0-9]{0,11}$/;
|
|
11
|
-
const CLAIM = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
|
|
12
|
-
const P01_STEP_DIGEST = 'acd455ed0e39d98b90fb57391db75e366e4c5604db043e03a89a20e33d643c3c';
|
|
13
|
-
const P02_STEP_DIGEST = 'fa2194f89c01f07e225fd6f39e98e679f628e072239b6cd0af3ee527f0722518';
|
|
14
|
-
function record(value) {
|
|
15
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
-
}
|
|
17
|
-
function digest(value) {
|
|
18
|
-
return createHash('sha256').update(value).digest('hex');
|
|
19
|
-
}
|
|
20
|
-
function git(root, args, maxBuffer = 128 * 1024) {
|
|
21
|
-
return execFileSync('git', [...args], { cwd: root, encoding: 'utf8',
|
|
22
|
-
stdio: ['ignore', 'pipe', 'ignore'], maxBuffer }).trim();
|
|
23
|
-
}
|
|
24
|
-
function regularBytes(root, path, maxBytes) {
|
|
25
|
-
const entry = lstatSync(join(root, path));
|
|
26
|
-
if (!entry.isFile() || entry.isSymbolicLink() || entry.size < 1 || entry.size > maxBytes) {
|
|
27
|
-
throw new Error('planning-research-physical-artifact-invalid');
|
|
28
|
-
}
|
|
29
|
-
return readFileSync(join(root, path));
|
|
30
|
-
}
|
|
31
|
-
function safePlanningParents(root, epicId) {
|
|
32
|
-
for (const relativePath of ['.neocortex', '.neocortex/planning',
|
|
33
|
-
`.neocortex/planning/epic-${epicId}`]) {
|
|
34
|
-
const entry = lstatSync(join(root, relativePath));
|
|
35
|
-
if (!entry.isDirectory() || entry.isSymbolicLink()
|
|
36
|
-
|| entry.uid !== process.getuid?.()
|
|
37
|
-
|| relativePath === '.neocortex' && (entry.mode & 0o022) !== 0) {
|
|
38
|
-
throw new Error('planning-research-directory-boundary-invalid');
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function stepMatches(value, expected) {
|
|
43
|
-
return typeof value === 'string' && value.length <= 128 && digest(value) === expected;
|
|
44
|
-
}
|
|
45
|
-
function researchDocumentStep(content, epicId) {
|
|
46
|
-
const normalized = content.replace(/\r\n/g, '\n');
|
|
47
|
-
const header = /^---\n([\s\S]{1,8192}?)\n---\n/.exec(normalized)?.[1];
|
|
48
|
-
if (!header)
|
|
49
|
-
return undefined;
|
|
50
|
-
const lines = header.split('\n');
|
|
51
|
-
const scalar = (key) => {
|
|
52
|
-
const found = lines.filter((line) => line.startsWith(`${key}:`));
|
|
53
|
-
return found.length === 1
|
|
54
|
-
? found[0].slice(key.length + 1).trim().replace(/^(?:"(.*)"|'(.*)')$/, '$1$2')
|
|
55
|
-
: undefined;
|
|
56
|
-
};
|
|
57
|
-
const step = scalar('step');
|
|
58
|
-
return scalar('epic_id') === epicId && scalar('status') === 'completed'
|
|
59
|
-
&& stepMatches(step, P02_STEP_DIGEST)
|
|
60
|
-
&& normalized.slice(header.length + 9).trim().length > 0 ? step : undefined;
|
|
61
|
-
}
|
|
62
|
-
function validateReview(value) {
|
|
63
|
-
if (!record(value) || Object.keys(value).sort().join('|') !== [
|
|
64
|
-
'schemaVersion', 'verdict', 'claimId', 'epicId', 'diagnosisReceiptId',
|
|
65
|
-
'intakeRevision', 'sourceHead', 'sourceBranch', 'sourceStateSha256',
|
|
66
|
-
'admission', 'artifactDigests', 'qaEvidenceId', 'artifactSummary',
|
|
67
|
-
].sort().join('|') || value.schemaVersion !== 'PlanningResearchRootReview/1'
|
|
68
|
-
|| value.verdict !== 'accepted' || typeof value.claimId !== 'string' || !CLAIM.test(value.claimId)
|
|
69
|
-
|| typeof value.epicId !== 'string' || !EPIC.test(value.epicId)
|
|
70
|
-
|| typeof value.diagnosisReceiptId !== 'string' || !HEX.test(value.diagnosisReceiptId)
|
|
71
|
-
|| typeof value.intakeRevision !== 'string' || !HEX.test(value.intakeRevision)
|
|
72
|
-
|| typeof value.sourceHead !== 'string' || !/^[a-f0-9]{40}$/.test(value.sourceHead)
|
|
73
|
-
|| typeof value.sourceBranch !== 'string'
|
|
74
|
-
|| !/^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,127}$/.test(value.sourceBranch)
|
|
75
|
-
|| typeof value.sourceStateSha256 !== 'string' || !HEX.test(value.sourceStateSha256)
|
|
76
|
-
|| !record(value.admission) || Object.keys(value.admission).sort().join('|') !== [
|
|
77
|
-
'schemaVersion', 'outcome', 'baseSha', 'executionBranch', 'changedPaths', 'patchSha256',
|
|
78
|
-
].sort().join('|')
|
|
79
|
-
|| value.admission.schemaVersion !== 1 || value.admission.outcome !== 'admitted-unverified'
|
|
80
|
-
|| value.admission.baseSha !== value.sourceHead
|
|
81
|
-
|| value.admission.executionBranch !== value.sourceBranch
|
|
82
|
-
|| typeof value.admission.patchSha256 !== 'string' || !HEX.test(value.admission.patchSha256)
|
|
83
|
-
|| !record(value.artifactDigests)
|
|
84
|
-
|| Object.keys(value.artifactDigests).sort().join('|') !== 'index|research'
|
|
85
|
-
|| typeof value.artifactDigests.index !== 'string' || !HEX.test(value.artifactDigests.index)
|
|
86
|
-
|| typeof value.artifactDigests.research !== 'string' || !HEX.test(value.artifactDigests.research)
|
|
87
|
-
|| typeof value.qaEvidenceId !== 'string' || !/^qa-[a-z0-9-]{8,80}$/.test(value.qaEvidenceId)
|
|
88
|
-
|| typeof value.artifactSummary !== 'string') {
|
|
89
|
-
throw new Error('planning-research-review-invalid');
|
|
90
|
-
}
|
|
91
|
-
createPlanningReceiptV1({ kind: 'research', epicId: value.epicId,
|
|
92
|
-
intakeRevision: value.intakeRevision, predecessorReceiptId: value.diagnosisReceiptId,
|
|
93
|
-
artifactSummary: value.artifactSummary, accepted: true });
|
|
94
|
-
}
|
|
95
|
-
function physicalAcceptance(review, researchReceiptId) {
|
|
96
|
-
const fields = { schemaVersion: 'PlanningResearchPhysicalAcceptance/1',
|
|
97
|
-
claimId: review.claimId, epicId: review.epicId,
|
|
98
|
-
diagnosisReceiptId: review.diagnosisReceiptId, researchReceiptId,
|
|
99
|
-
intakeRevision: review.intakeRevision, qaEvidenceId: review.qaEvidenceId,
|
|
100
|
-
sourceHead: review.sourceHead, sourceStateSha256: review.sourceStateSha256,
|
|
101
|
-
admissionPatchSha256: review.admission.patchSha256,
|
|
102
|
-
artifactDigests: review.artifactDigests };
|
|
103
|
-
return { ...fields, receiptId: digest(JSON.stringify(fields)), publicSafe: true };
|
|
104
|
-
}
|
|
105
|
-
function evidence(root, review, checkPatch, ownTemporaryPath) {
|
|
106
|
-
safePlanningParents(root, review.epicId);
|
|
107
|
-
if (git(root, ['rev-parse', 'HEAD']) !== review.sourceHead
|
|
108
|
-
|| git(root, ['symbolic-ref', '--quiet', '--short', 'HEAD']) !== review.sourceBranch) {
|
|
109
|
-
throw new Error('planning-research-head-conflict');
|
|
110
|
-
}
|
|
111
|
-
const paths = { research: `.neocortex/planning/epic-${review.epicId}/research.md`,
|
|
112
|
-
index: '.neocortex/planning/planning.json' };
|
|
113
|
-
if (!Array.isArray(review.admission.changedPaths)
|
|
114
|
-
|| [...review.admission.changedPaths].sort().join('|') !== Object.values(paths).sort().join('|')
|
|
115
|
-
|| checkPatch && planningPhysicalPatchDigest(root, paths, ownTemporaryPath, true)
|
|
116
|
-
!== review.admission.patchSha256) {
|
|
117
|
-
throw new Error('planning-research-admission-patch-conflict');
|
|
118
|
-
}
|
|
119
|
-
const current = { research: regularBytes(root, paths.research, 256 * 1024),
|
|
120
|
-
index: regularBytes(root, paths.index, 128 * 1024) };
|
|
121
|
-
const researchStep = researchDocumentStep(current.research.toString('utf8'), review.epicId);
|
|
122
|
-
if (digest(current.research) !== review.artifactDigests.research
|
|
123
|
-
|| digest(current.index) !== review.artifactDigests.index
|
|
124
|
-
|| !researchStep) {
|
|
125
|
-
throw new Error('planning-research-artifact-conflict');
|
|
126
|
-
}
|
|
127
|
-
const stateRaw = regularBytes(root, '.neocortex/state.json', 8 * 1024 * 1024);
|
|
128
|
-
const state = JSON.parse(stateRaw.toString('utf8'));
|
|
129
|
-
const predecessor = acceptedPlanningDiagnosisPredecessor(state, review.claimId, review.epicId, !checkPatch);
|
|
130
|
-
if (predecessor.diagnosisReceiptId !== review.diagnosisReceiptId
|
|
131
|
-
|| predecessor.intakeRevision !== review.intakeRevision
|
|
132
|
-
|| checkPatch && digest(stateRaw) !== review.sourceStateSha256) {
|
|
133
|
-
throw new Error('planning-research-state-conflict');
|
|
134
|
-
}
|
|
135
|
-
const epics = state.epics;
|
|
136
|
-
const epic = epics[review.epicId];
|
|
137
|
-
const physical = epic.createEpicPlanningPhysical;
|
|
138
|
-
const acceptedDigests = physical.artifactDigests;
|
|
139
|
-
const prefix = `.neocortex/planning/epic-${review.epicId}`;
|
|
140
|
-
for (const [name, path] of Object.entries({ idea: `${prefix}/idea.json`,
|
|
141
|
-
diagnosis: `${prefix}/diagnosis.md`, enrichment: `${prefix}/enriched-prompt.md` })) {
|
|
142
|
-
if (digest(regularBytes(root, path, name === 'idea' ? 32 * 1024 : 256 * 1024))
|
|
143
|
-
!== acceptedDigests[name])
|
|
144
|
-
throw new Error('planning-research-predecessor-artifact-drift');
|
|
145
|
-
}
|
|
146
|
-
const baseline = spawnSync('git', ['show', `HEAD:${paths.index}`], { cwd: root,
|
|
147
|
-
encoding: 'buffer', maxBuffer: 128 * 1024 });
|
|
148
|
-
if (baseline.status !== 0 || !Buffer.isBuffer(baseline.stdout)
|
|
149
|
-
|| digest(baseline.stdout) !== acceptedDigests.index) {
|
|
150
|
-
throw new Error('planning-research-predecessor-index-drift');
|
|
151
|
-
}
|
|
152
|
-
const prior = JSON.parse(baseline.stdout.toString('utf8'));
|
|
153
|
-
const next = JSON.parse(current.index.toString('utf8'));
|
|
154
|
-
if (!record(prior) || !record(prior.epics) || !record(next) || !record(next.epics)) {
|
|
155
|
-
throw new Error('planning-research-index-invalid');
|
|
156
|
-
}
|
|
157
|
-
const priorEpic = prior.epics[review.epicId];
|
|
158
|
-
if (!record(priorEpic) || priorEpic.id !== review.epicId
|
|
159
|
-
|| priorEpic.idea !== predecessor.rawIntent || priorEpic.status !== 'diagnosed'
|
|
160
|
-
|| !Array.isArray(priorEpic.steps_completed) || priorEpic.steps_completed.length !== 1
|
|
161
|
-
|| !stepMatches(priorEpic.steps_completed[0], P01_STEP_DIGEST)
|
|
162
|
-
|| !isDeepStrictEqual(priorEpic.output_files, { diagnosis: `${prefix}/diagnosis.md` })) {
|
|
163
|
-
throw new Error('planning-research-index-predecessor-invalid');
|
|
164
|
-
}
|
|
165
|
-
const expected = { ...prior, last_scan: next.last_scan, epics: { ...prior.epics,
|
|
166
|
-
[review.epicId]: { ...priorEpic, status: 'researched',
|
|
167
|
-
steps_completed: [...priorEpic.steps_completed, researchStep],
|
|
168
|
-
output_files: { diagnosis: `${prefix}/diagnosis.md`, research: paths.research } } } };
|
|
169
|
-
if (typeof next.last_scan !== 'string' || !Number.isFinite(Date.parse(next.last_scan))
|
|
170
|
-
|| !isDeepStrictEqual(next, expected)) {
|
|
171
|
-
throw new Error('planning-research-index-transition-invalid');
|
|
172
|
-
}
|
|
173
|
-
const receipt = createPlanningReceiptV1({ kind: 'research', epicId: review.epicId,
|
|
174
|
-
intakeRevision: review.intakeRevision, predecessorReceiptId: review.diagnosisReceiptId,
|
|
175
|
-
artifactSummary: review.artifactSummary, accepted: true });
|
|
176
|
-
return { stateRaw, state, receipt };
|
|
177
|
-
}
|
|
178
|
-
function acceptedReplay(root, review) {
|
|
179
|
-
const current = evidence(root, review, false);
|
|
180
|
-
const epics = current.state.epics;
|
|
181
|
-
const epic = epics[review.epicId];
|
|
182
|
-
const checkpoint = epic.createEpicPlanning;
|
|
183
|
-
if (epic.status !== 'researched' || !isDeepStrictEqual(checkpoint.researchReceipt, current.receipt)
|
|
184
|
-
|| !isDeepStrictEqual(epic.createEpicPlanningResearchPhysical, physicalAcceptance(review, current.receipt.receiptId))) {
|
|
185
|
-
throw new Error('planning-research-replay-conflict');
|
|
186
|
-
}
|
|
187
|
-
return current.receipt.receiptId;
|
|
188
|
-
}
|
|
189
|
-
/** State-consistent P02 seal for a future P03 lease; physical files remain a separate check. */
|
|
190
|
-
export function acceptedPlanningResearchPredecessor(stateInput, claimId, epicId) {
|
|
191
|
-
const diagnosis = acceptedPlanningDiagnosisPredecessor(stateInput, claimId, epicId, true);
|
|
192
|
-
const state = stateInput;
|
|
193
|
-
const epic = state.epics[epicId];
|
|
194
|
-
const checkpoint = record(epic?.createEpicPlanning) ? epic.createEpicPlanning : undefined;
|
|
195
|
-
const receipt = record(checkpoint?.researchReceipt) ? checkpoint.researchReceipt : undefined;
|
|
196
|
-
const physical = record(epic?.createEpicPlanningResearchPhysical)
|
|
197
|
-
? epic.createEpicPlanningResearchPhysical : undefined;
|
|
198
|
-
const digests = record(physical?.artifactDigests) ? physical.artifactDigests : undefined;
|
|
199
|
-
if (epic?.status !== 'researched' || !receipt || !physical || !digests
|
|
200
|
-
|| typeof receipt.artifactSummary !== 'string'
|
|
201
|
-
|| !isDeepStrictEqual(receipt, createPlanningReceiptV1({ kind: 'research', epicId,
|
|
202
|
-
intakeRevision: diagnosis.intakeRevision,
|
|
203
|
-
predecessorReceiptId: diagnosis.diagnosisReceiptId,
|
|
204
|
-
artifactSummary: receipt.artifactSummary, accepted: true }))
|
|
205
|
-
|| physical.schemaVersion !== 'PlanningResearchPhysicalAcceptance/1'
|
|
206
|
-
|| physical.claimId !== claimId || physical.epicId !== epicId
|
|
207
|
-
|| physical.diagnosisReceiptId !== diagnosis.diagnosisReceiptId
|
|
208
|
-
|| physical.researchReceiptId !== receipt.receiptId
|
|
209
|
-
|| physical.intakeRevision !== diagnosis.intakeRevision
|
|
210
|
-
|| typeof physical.qaEvidenceId !== 'string'
|
|
211
|
-
|| !/^qa-[a-z0-9-]{8,80}$/.test(physical.qaEvidenceId)
|
|
212
|
-
|| typeof physical.sourceHead !== 'string' || !/^[a-f0-9]{40}$/.test(physical.sourceHead)
|
|
213
|
-
|| typeof physical.sourceStateSha256 !== 'string' || !HEX.test(physical.sourceStateSha256)
|
|
214
|
-
|| typeof physical.admissionPatchSha256 !== 'string' || !HEX.test(physical.admissionPatchSha256)
|
|
215
|
-
|| Object.keys(digests).sort().join('|') !== 'index|research'
|
|
216
|
-
|| typeof digests.index !== 'string' || !HEX.test(digests.index)
|
|
217
|
-
|| typeof digests.research !== 'string' || !HEX.test(digests.research)) {
|
|
218
|
-
throw new Error('planning-generation-predecessor-invalid');
|
|
219
|
-
}
|
|
220
|
-
const fields = { schemaVersion: physical.schemaVersion, claimId, epicId,
|
|
221
|
-
diagnosisReceiptId: diagnosis.diagnosisReceiptId, researchReceiptId: receipt.receiptId,
|
|
222
|
-
intakeRevision: diagnosis.intakeRevision, qaEvidenceId: physical.qaEvidenceId,
|
|
223
|
-
sourceHead: physical.sourceHead, sourceStateSha256: physical.sourceStateSha256,
|
|
224
|
-
admissionPatchSha256: physical.admissionPatchSha256, artifactDigests: digests };
|
|
225
|
-
const expected = { ...fields, receiptId: digest(JSON.stringify(fields)), publicSafe: true };
|
|
226
|
-
if (!isDeepStrictEqual(physical, expected))
|
|
227
|
-
throw new Error('planning-generation-predecessor-invalid');
|
|
228
|
-
return { intakeRevision: diagnosis.intakeRevision,
|
|
229
|
-
diagnosisReceiptId: diagnosis.diagnosisReceiptId, researchReceiptId: receipt.receiptId,
|
|
230
|
-
physicalAcceptanceReceiptId: expected.receiptId };
|
|
231
|
-
}
|
|
232
|
-
/** A root reviewer accepts exact P02 files, then this CAS persists only the research checkpoint. */
|
|
233
|
-
export function sealPlanningResearch(projectRoot, reviewInput) {
|
|
234
|
-
validateReview(reviewInput);
|
|
235
|
-
const review = reviewInput;
|
|
236
|
-
const root = resolve(projectRoot);
|
|
237
|
-
if (realpathSync(root) !== root || git(root, ['rev-parse', '--show-toplevel']) !== root) {
|
|
238
|
-
throw new Error('planning-research-root-invalid');
|
|
239
|
-
}
|
|
240
|
-
safePlanningParents(root, review.epicId);
|
|
241
|
-
const stateDir = join(root, '.neocortex');
|
|
242
|
-
const lockPath = join(stateDir, 'state.json.planning.lock');
|
|
243
|
-
const lock = openSync(lockPath, 'wx', 0o600);
|
|
244
|
-
let temporaryPath;
|
|
245
|
-
try {
|
|
246
|
-
const pendingDigest = planningPendingSidecarDigest(root);
|
|
247
|
-
const currentRaw = regularBytes(root, '.neocortex/state.json', 8 * 1024 * 1024);
|
|
248
|
-
if (digest(currentRaw) !== review.sourceStateSha256) {
|
|
249
|
-
const researchReceiptId = acceptedReplay(root, review);
|
|
250
|
-
return { epicId: review.epicId, researchReceiptId, qaEvidenceId: review.qaEvidenceId };
|
|
251
|
-
}
|
|
252
|
-
const before = evidence(root, review, true);
|
|
253
|
-
const epics = before.state.epics;
|
|
254
|
-
const epic = epics[review.epicId];
|
|
255
|
-
const checkpoint = epic.createEpicPlanning;
|
|
256
|
-
const acceptance = physicalAcceptance(review, before.receipt.receiptId);
|
|
257
|
-
const next = { ...before.state, epics: { ...epics,
|
|
258
|
-
[review.epicId]: { ...epic, status: 'researched',
|
|
259
|
-
createEpicPlanning: { ...checkpoint, researchReceipt: before.receipt },
|
|
260
|
-
createEpicPlanningResearchPhysical: acceptance } } };
|
|
261
|
-
const commonRaw = git(root, ['rev-parse', '--git-common-dir']);
|
|
262
|
-
const commonDir = isAbsolute(commonRaw) ? resolve(commonRaw) : resolve(root, commonRaw);
|
|
263
|
-
if (realpathSync(commonDir) !== commonDir || lstatSync(commonDir).isSymbolicLink()) {
|
|
264
|
-
throw new Error('planning-research-backup-dir-invalid');
|
|
265
|
-
}
|
|
266
|
-
const backupDir = join(commonDir, 'neocortex-planning');
|
|
267
|
-
mkdirSync(backupDir, { recursive: true, mode: 0o700 });
|
|
268
|
-
const backupEntry = lstatSync(backupDir);
|
|
269
|
-
if (!backupEntry.isDirectory() || backupEntry.isSymbolicLink()
|
|
270
|
-
|| backupEntry.uid !== process.getuid?.() || (backupEntry.mode & 0o077) !== 0) {
|
|
271
|
-
throw new Error('planning-research-backup-dir-invalid');
|
|
272
|
-
}
|
|
273
|
-
const suffix = randomUUID();
|
|
274
|
-
const backup = openSync(join(backupDir, `state-backup-${suffix}.json`), 'wx', 0o600);
|
|
275
|
-
try {
|
|
276
|
-
writeFileSync(backup, before.stateRaw);
|
|
277
|
-
fsyncSync(backup);
|
|
278
|
-
}
|
|
279
|
-
finally {
|
|
280
|
-
closeSync(backup);
|
|
281
|
-
}
|
|
282
|
-
const backupDirectory = openSync(backupDir, 'r');
|
|
283
|
-
try {
|
|
284
|
-
fsyncSync(backupDirectory);
|
|
285
|
-
}
|
|
286
|
-
finally {
|
|
287
|
-
closeSync(backupDirectory);
|
|
288
|
-
}
|
|
289
|
-
temporaryPath = join(stateDir, `state.json.planning-tmp-${suffix}`);
|
|
290
|
-
const temporary = openSync(temporaryPath, 'wx', 0o600);
|
|
291
|
-
try {
|
|
292
|
-
writeFileSync(temporary, `${JSON.stringify(next, null, 2)}\n`);
|
|
293
|
-
fsyncSync(temporary);
|
|
294
|
-
}
|
|
295
|
-
finally {
|
|
296
|
-
closeSync(temporary);
|
|
297
|
-
}
|
|
298
|
-
const recheck = evidence(root, review, true, `.neocortex/state.json.planning-tmp-${suffix}`);
|
|
299
|
-
if (!recheck.stateRaw.equals(before.stateRaw))
|
|
300
|
-
throw new Error('planning-research-state-conflict');
|
|
301
|
-
if (planningPendingSidecarDigest(root) !== pendingDigest) {
|
|
302
|
-
throw new Error('planning-research-pending-conflict');
|
|
303
|
-
}
|
|
304
|
-
renameSync(temporaryPath, join(stateDir, 'state.json'));
|
|
305
|
-
temporaryPath = undefined;
|
|
306
|
-
const stateDirectory = openSync(stateDir, 'r');
|
|
307
|
-
try {
|
|
308
|
-
fsyncSync(stateDirectory);
|
|
309
|
-
}
|
|
310
|
-
finally {
|
|
311
|
-
closeSync(stateDirectory);
|
|
312
|
-
}
|
|
313
|
-
return { epicId: review.epicId, researchReceiptId: before.receipt.receiptId,
|
|
314
|
-
qaEvidenceId: review.qaEvidenceId };
|
|
315
|
-
}
|
|
316
|
-
finally {
|
|
317
|
-
if (temporaryPath)
|
|
318
|
-
try {
|
|
319
|
-
unlinkSync(temporaryPath);
|
|
320
|
-
}
|
|
321
|
-
catch { /* preserve primary failure */ }
|
|
322
|
-
closeSync(lock);
|
|
323
|
-
unlinkSync(lockPath);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
1
|
+
import{execFileSync as W,spawnSync as X}from"node:child_process";import{createHash as K,randomUUID as Q}from"node:crypto";import{closeSync as D,fsyncSync as q,lstatSync as A,mkdirSync as Y,openSync as b,readFileSync as v,realpathSync as J,renameSync as ee,unlinkSync as F,writeFileSync as L}from"node:fs";import{isAbsolute as te,join as y,resolve as B}from"node:path";import{isDeepStrictEqual as P}from"node:util";import{createPlanningReceiptV1 as V}from"../../../shared/dist/planning/diagnosis-state.js";import{acceptedPlanningDiagnosisPredecessor as T,planningPendingSidecarDigest as C,planningPhysicalPatchDigest as ie}from"./planning-diagnosis-checkpoint.js";const l=/^[a-f0-9]{64}$/,ne=/^P[1-9][0-9]{0,11}$/,se=/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/,ce="acd455ed0e39d98b90fb57391db75e366e4c5604db043e03a89a20e33d643c3c",re="fa2194f89c01f07e225fd6f39e98e679f628e072239b6cd0af3ee527f0722518";function p(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function u(e){return K("sha256").update(e).digest("hex")}function _(e,t,i=128*1024){return W("git",[...t],{cwd:e,encoding:"utf8",stdio:["ignore","pipe","ignore"],maxBuffer:i}).trim()}function w(e,t,i){const n=A(y(e,t));if(!n.isFile()||n.isSymbolicLink()||n.size<1||n.size>i)throw new Error("planning-research-physical-artifact-invalid");return v(y(e,t))}function G(e,t){for(const i of[".neocortex",".neocortex/planning",`.neocortex/planning/epic-${t}`]){const n=A(y(e,i));if(!n.isDirectory()||n.isSymbolicLink()||n.uid!==process.getuid?.()||i===".neocortex"&&(n.mode&18)!==0)throw new Error("planning-research-directory-boundary-invalid")}}function M(e,t){return typeof e=="string"&&e.length<=128&&u(e)===t}function ae(e,t){const i=e.replace(/\r\n/g,`
|
|
2
|
+
`),n=/^---\n([\s\S]{1,8192}?)\n---\n/.exec(i)?.[1];if(!n)return;const a=n.split(`
|
|
3
|
+
`),r=c=>{const s=a.filter(o=>o.startsWith(`${c}:`));return s.length===1?s[0].slice(c.length+1).trim().replace(/^(?:"(.*)"|'(.*)')$/,"$1$2"):void 0},g=r("step");return r("epic_id")===t&&r("status")==="completed"&&M(g,re)&&i.slice(n.length+9).trim().length>0?g:void 0}function oe(e){if(!p(e)||Object.keys(e).sort().join("|")!==["schemaVersion","verdict","claimId","epicId","diagnosisReceiptId","intakeRevision","sourceHead","sourceBranch","sourceStateSha256","admission","artifactDigests","qaEvidenceId","artifactSummary"].sort().join("|")||e.schemaVersion!=="PlanningResearchRootReview/1"||e.verdict!=="accepted"||typeof e.claimId!="string"||!se.test(e.claimId)||typeof e.epicId!="string"||!ne.test(e.epicId)||typeof e.diagnosisReceiptId!="string"||!l.test(e.diagnosisReceiptId)||typeof e.intakeRevision!="string"||!l.test(e.intakeRevision)||typeof e.sourceHead!="string"||!/^[a-f0-9]{40}$/.test(e.sourceHead)||typeof e.sourceBranch!="string"||!/^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,127}$/.test(e.sourceBranch)||typeof e.sourceStateSha256!="string"||!l.test(e.sourceStateSha256)||!p(e.admission)||Object.keys(e.admission).sort().join("|")!==["schemaVersion","outcome","baseSha","executionBranch","changedPaths","patchSha256"].sort().join("|")||e.admission.schemaVersion!==1||e.admission.outcome!=="admitted-unverified"||e.admission.baseSha!==e.sourceHead||e.admission.executionBranch!==e.sourceBranch||typeof e.admission.patchSha256!="string"||!l.test(e.admission.patchSha256)||!p(e.artifactDigests)||Object.keys(e.artifactDigests).sort().join("|")!=="index|research"||typeof e.artifactDigests.index!="string"||!l.test(e.artifactDigests.index)||typeof e.artifactDigests.research!="string"||!l.test(e.artifactDigests.research)||typeof e.qaEvidenceId!="string"||!/^qa-[a-z0-9-]{8,80}$/.test(e.qaEvidenceId)||typeof e.artifactSummary!="string")throw new Error("planning-research-review-invalid");V({kind:"research",epicId:e.epicId,intakeRevision:e.intakeRevision,predecessorReceiptId:e.diagnosisReceiptId,artifactSummary:e.artifactSummary,accepted:!0})}function U(e,t){const i={schemaVersion:"PlanningResearchPhysicalAcceptance/1",claimId:e.claimId,epicId:e.epicId,diagnosisReceiptId:e.diagnosisReceiptId,researchReceiptId:t,intakeRevision:e.intakeRevision,qaEvidenceId:e.qaEvidenceId,sourceHead:e.sourceHead,sourceStateSha256:e.sourceStateSha256,admissionPatchSha256:e.admission.patchSha256,artifactDigests:e.artifactDigests};return{...i,receiptId:u(JSON.stringify(i)),publicSafe:!0}}function z(e,t,i,n){if(G(e,t.epicId),_(e,["rev-parse","HEAD"])!==t.sourceHead||_(e,["symbolic-ref","--quiet","--short","HEAD"])!==t.sourceBranch)throw new Error("planning-research-head-conflict");const a={research:`.neocortex/planning/epic-${t.epicId}/research.md`,index:".neocortex/planning/planning.json"};if(!Array.isArray(t.admission.changedPaths)||[...t.admission.changedPaths].sort().join("|")!==Object.values(a).sort().join("|")||i&&ie(e,a,n,!0)!==t.admission.patchSha256)throw new Error("planning-research-admission-patch-conflict");const r={research:w(e,a.research,256*1024),index:w(e,a.index,128*1024)},g=ae(r.research.toString("utf8"),t.epicId);if(u(r.research)!==t.artifactDigests.research||u(r.index)!==t.artifactDigests.index||!g)throw new Error("planning-research-artifact-conflict");const c=w(e,".neocortex/state.json",8*1024*1024),s=JSON.parse(c.toString("utf8")),o=T(s,t.claimId,t.epicId,!i);if(o.diagnosisReceiptId!==t.diagnosisReceiptId||o.intakeRevision!==t.intakeRevision||i&&u(c)!==t.sourceStateSha256)throw new Error("planning-research-state-conflict");const j=s.epics[t.epicId].createEpicPlanningPhysical.artifactDigests,I=`.neocortex/planning/epic-${t.epicId}`;for(const[x,$]of Object.entries({idea:`${I}/idea.json`,diagnosis:`${I}/diagnosis.md`,enrichment:`${I}/enriched-prompt.md`}))if(u(w(e,$,x==="idea"?32*1024:256*1024))!==j[x])throw new Error("planning-research-predecessor-artifact-drift");const R=X("git",["show",`HEAD:${a.index}`],{cwd:e,encoding:"buffer",maxBuffer:128*1024});if(R.status!==0||!Buffer.isBuffer(R.stdout)||u(R.stdout)!==j.index)throw new Error("planning-research-predecessor-index-drift");const m=JSON.parse(R.stdout.toString("utf8")),f=JSON.parse(r.index.toString("utf8"));if(!p(m)||!p(m.epics)||!p(f)||!p(f.epics))throw new Error("planning-research-index-invalid");const d=m.epics[t.epicId];if(!p(d)||d.id!==t.epicId||d.idea!==o.rawIntent||d.status!=="diagnosed"||!Array.isArray(d.steps_completed)||d.steps_completed.length!==1||!M(d.steps_completed[0],ce)||!P(d.output_files,{diagnosis:`${I}/diagnosis.md`}))throw new Error("planning-research-index-predecessor-invalid");const E={...m,last_scan:f.last_scan,epics:{...m.epics,[t.epicId]:{...d,status:"researched",steps_completed:[...d.steps_completed,g],output_files:{diagnosis:`${I}/diagnosis.md`,research:a.research}}}};if(typeof f.last_scan!="string"||!Number.isFinite(Date.parse(f.last_scan))||!P(f,E))throw new Error("planning-research-index-transition-invalid");const k=V({kind:"research",epicId:t.epicId,intakeRevision:t.intakeRevision,predecessorReceiptId:t.diagnosisReceiptId,artifactSummary:t.artifactSummary,accepted:!0});return{stateRaw:c,state:s,receipt:k}}function de(e,t){const i=z(e,t,!1),a=i.state.epics[t.epicId],r=a.createEpicPlanning;if(a.status!=="researched"||!P(r.researchReceipt,i.receipt)||!P(a.createEpicPlanningResearchPhysical,U(t,i.receipt.receiptId)))throw new Error("planning-research-replay-conflict");return i.receipt.receiptId}function Ie(e,t,i){const n=T(e,t,i,!0),r=e.epics[i],g=p(r?.createEpicPlanning)?r.createEpicPlanning:void 0,c=p(g?.researchReceipt)?g.researchReceipt:void 0,s=p(r?.createEpicPlanningResearchPhysical)?r.createEpicPlanningResearchPhysical:void 0,o=p(s?.artifactDigests)?s.artifactDigests:void 0;if(r?.status!=="researched"||!c||!s||!o||typeof c.artifactSummary!="string"||!P(c,V({kind:"research",epicId:i,intakeRevision:n.intakeRevision,predecessorReceiptId:n.diagnosisReceiptId,artifactSummary:c.artifactSummary,accepted:!0}))||s.schemaVersion!=="PlanningResearchPhysicalAcceptance/1"||s.claimId!==t||s.epicId!==i||s.diagnosisReceiptId!==n.diagnosisReceiptId||s.researchReceiptId!==c.receiptId||s.intakeRevision!==n.intakeRevision||typeof s.qaEvidenceId!="string"||!/^qa-[a-z0-9-]{8,80}$/.test(s.qaEvidenceId)||typeof s.sourceHead!="string"||!/^[a-f0-9]{40}$/.test(s.sourceHead)||typeof s.sourceStateSha256!="string"||!l.test(s.sourceStateSha256)||typeof s.admissionPatchSha256!="string"||!l.test(s.admissionPatchSha256)||Object.keys(o).sort().join("|")!=="index|research"||typeof o.index!="string"||!l.test(o.index)||typeof o.research!="string"||!l.test(o.research))throw new Error("planning-generation-predecessor-invalid");const h={schemaVersion:s.schemaVersion,claimId:t,epicId:i,diagnosisReceiptId:n.diagnosisReceiptId,researchReceiptId:c.receiptId,intakeRevision:n.intakeRevision,qaEvidenceId:s.qaEvidenceId,sourceHead:s.sourceHead,sourceStateSha256:s.sourceStateSha256,admissionPatchSha256:s.admissionPatchSha256,artifactDigests:o},S={...h,receiptId:u(JSON.stringify(h)),publicSafe:!0};if(!P(s,S))throw new Error("planning-generation-predecessor-invalid");return{intakeRevision:n.intakeRevision,diagnosisReceiptId:n.diagnosisReceiptId,researchReceiptId:c.receiptId,physicalAcceptanceReceiptId:S.receiptId}}function Se(e,t){oe(t);const i=t,n=B(e);if(J(n)!==n||_(n,["rev-parse","--show-toplevel"])!==n)throw new Error("planning-research-root-invalid");G(n,i.epicId);const a=y(n,".neocortex"),r=y(a,"state.json.planning.lock"),g=b(r,"wx",384);let c;try{const s=C(n),o=w(n,".neocortex/state.json",8*1024*1024);if(u(o)!==i.sourceStateSha256){const Z=de(n,i);return{epicId:i.epicId,researchReceiptId:Z,qaEvidenceId:i.qaEvidenceId}}const h=z(n,i,!0),S=h.state.epics,H=S[i.epicId],j=H.createEpicPlanning,I=U(i,h.receipt.receiptId),R={...h.state,epics:{...S,[i.epicId]:{...H,status:"researched",createEpicPlanning:{...j,researchReceipt:h.receipt},createEpicPlanningResearchPhysical:I}}},m=_(n,["rev-parse","--git-common-dir"]),f=te(m)?B(m):B(n,m);if(J(f)!==f||A(f).isSymbolicLink())throw new Error("planning-research-backup-dir-invalid");const d=y(f,"neocortex-planning");Y(d,{recursive:!0,mode:448});const E=A(d);if(!E.isDirectory()||E.isSymbolicLink()||E.uid!==process.getuid?.()||(E.mode&63)!==0)throw new Error("planning-research-backup-dir-invalid");const k=Q(),x=b(y(d,`state-backup-${k}.json`),"wx",384);try{L(x,h.stateRaw),q(x)}finally{D(x)}const $=b(d,"r");try{q($)}finally{D($)}c=y(a,`state.json.planning-tmp-${k}`);const O=b(c,"wx",384);try{L(O,`${JSON.stringify(R,null,2)}
|
|
4
|
+
`),q(O)}finally{D(O)}if(!z(n,i,!0,`.neocortex/state.json.planning-tmp-${k}`).stateRaw.equals(h.stateRaw))throw new Error("planning-research-state-conflict");if(C(n)!==s)throw new Error("planning-research-pending-conflict");ee(c,y(a,"state.json")),c=void 0;const N=b(a,"r");try{q(N)}finally{D(N)}return{epicId:i.epicId,researchReceiptId:h.receipt.receiptId,qaEvidenceId:i.qaEvidenceId}}finally{if(c)try{F(c)}catch{}D(g),F(r)}}export{Ie as acceptedPlanningResearchPredecessor,Se as sealPlanningResearch};
|
|
@@ -1,42 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { assertGitLifecyclePrivateFile, ensureGitLifecyclePrivateChildDirectory, gitLifecycleRuntimeDirectory } from '../git/git-lifecycle-store.js';
|
|
6
|
-
import { anchoredAtomicWriteFile } from '../git/secure-filesystem-boundary.js';
|
|
7
|
-
/** The returned path is local-only; callers must never place report bytes in public metadata. */
|
|
8
|
-
export async function storePrivateUiUxReviewReport(options) {
|
|
9
|
-
try {
|
|
10
|
-
if (options.format !== 'markdown' && options.format !== 'json'
|
|
11
|
-
|| typeof options.material !== 'string'
|
|
12
|
-
|| !/^[a-f0-9]{64}$/.test(options.sha256))
|
|
13
|
-
throw new Error('private-report-input-invalid');
|
|
14
|
-
const bytes = Buffer.from(options.material, 'utf8');
|
|
15
|
-
if (bytes.length === 0 || bytes.length > 1024 * 1024
|
|
16
|
-
|| createHash('sha256').update(bytes).digest('hex') !== options.sha256) {
|
|
17
|
-
throw new Error('private-report-digest-invalid');
|
|
18
|
-
}
|
|
19
|
-
if (options.format === 'json') {
|
|
20
|
-
const value = JSON.parse(options.material);
|
|
21
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
22
|
-
throw new Error('private-report-json-invalid');
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
const runtime = await gitLifecycleRuntimeDirectory(options.projectRoot);
|
|
26
|
-
const parent = await ensureGitLifecyclePrivateChildDirectory(runtime, 'private-ui-ux-reports');
|
|
27
|
-
const name = `${options.sha256}.${options.format === 'json' ? 'json' : 'md'}`;
|
|
28
|
-
const path = join(parent, name);
|
|
29
|
-
const reused = existsSync(path);
|
|
30
|
-
if (!reused)
|
|
31
|
-
await anchoredAtomicWriteFile(parent, name, bytes);
|
|
32
|
-
await assertGitLifecyclePrivateFile(path, parent);
|
|
33
|
-
if (!readFileSync(path).equals(bytes))
|
|
34
|
-
throw new Error('private-report-readback-invalid');
|
|
35
|
-
return Object.freeze({ schemaVersion: 1, kind: 'private-ui-ux-review-report',
|
|
36
|
-
format: options.format, sha256: options.sha256, byteLength: bytes.length,
|
|
37
|
-
path, reused, publicSafe: true });
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
throw new Error('private-execution-report-store-unverified');
|
|
41
|
-
}
|
|
42
|
-
}
|
|
1
|
+
import{createHash as f}from"node:crypto";import{existsSync as m,readFileSync as s}from"node:fs";import{join as h}from"node:path";import{assertGitLifecyclePrivateFile as l,ensureGitLifecyclePrivateChildDirectory as u,gitLifecycleRuntimeDirectory as p}from"../git/git-lifecycle-store.js";import{anchoredAtomicWriteFile as v}from"../git/secure-filesystem-boundary.js";async function x(r){try{if(r.format!=="markdown"&&r.format!=="json"||typeof r.material!="string"||!/^[a-f0-9]{64}$/.test(r.sha256))throw new Error("private-report-input-invalid");const e=Buffer.from(r.material,"utf8");if(e.length===0||e.length>1024*1024||f("sha256").update(e).digest("hex")!==r.sha256)throw new Error("private-report-digest-invalid");if(r.format==="json"){const i=JSON.parse(r.material);if(!i||typeof i!="object"||Array.isArray(i))throw new Error("private-report-json-invalid")}const c=await p(r.projectRoot),a=await u(c,"private-ui-ux-reports"),o=`${r.sha256}.${r.format==="json"?"json":"md"}`,t=h(a,o),n=m(t);if(n||await v(a,o,e),await l(t,a),!s(t).equals(e))throw new Error("private-report-readback-invalid");return Object.freeze({schemaVersion:1,kind:"private-ui-ux-review-report",format:r.format,sha256:r.sha256,byteLength:e.length,path:t,reused:n,publicSafe:!0})}catch{throw new Error("private-execution-report-store-unverified")}}export{x as storePrivateUiUxReviewReport};
|