codex-workflow-v2 2.0.0-beta.13.8 → 2.0.0-beta.14
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/README.md +27 -388
- package/dist/reviewer-runtime-build.json +96 -32
- package/dist/src/alpha6/adoption.d.ts +2 -0
- package/dist/src/alpha6/adoption.js +22 -0
- package/dist/src/alpha6/adoption.js.map +1 -1
- package/dist/src/alpha6/captured-check-evidence.d.ts +51 -0
- package/dist/src/alpha6/captured-check-evidence.js +152 -0
- package/dist/src/alpha6/captured-check-evidence.js.map +1 -0
- package/dist/src/alpha6/component-owner.d.ts +15 -1
- package/dist/src/alpha6/component-owner.js +44 -2
- package/dist/src/alpha6/component-owner.js.map +1 -1
- package/dist/src/alpha6/corrective-decision-boundary.d.ts +4 -0
- package/dist/src/alpha6/corrective-decision-boundary.js +47 -0
- package/dist/src/alpha6/corrective-decision-boundary.js.map +1 -0
- package/dist/src/alpha6/downstream-proof.d.ts +1 -25
- package/dist/src/alpha6/downstream-proof.js +0 -171
- package/dist/src/alpha6/downstream-proof.js.map +1 -1
- package/dist/src/alpha6/failed-step-planning-recovery.d.ts +10 -0
- package/dist/src/alpha6/failed-step-planning-recovery.js +93 -0
- package/dist/src/alpha6/failed-step-planning-recovery.js.map +1 -0
- package/dist/src/alpha6/literal-test-invocation.d.ts +2 -0
- package/dist/src/alpha6/literal-test-invocation.js +106 -0
- package/dist/src/alpha6/literal-test-invocation.js.map +1 -0
- package/dist/src/alpha6/milestone.d.ts +2 -0
- package/dist/src/alpha6/milestone.js +10 -1
- package/dist/src/alpha6/milestone.js.map +1 -1
- package/dist/src/alpha6/navigation-dirty-carryover.d.ts +11 -0
- package/dist/src/alpha6/navigation-dirty-carryover.js +130 -0
- package/dist/src/alpha6/navigation-dirty-carryover.js.map +1 -0
- package/dist/src/alpha6/plan-integrity.js +7 -5
- package/dist/src/alpha6/plan-integrity.js.map +1 -1
- package/dist/src/alpha6/plan-risk.d.ts +2 -0
- package/dist/src/alpha6/plan-risk.js +10 -3
- package/dist/src/alpha6/plan-risk.js.map +1 -1
- package/dist/src/alpha6/remediation.d.ts +6 -21
- package/dist/src/alpha6/remediation.js +161 -476
- package/dist/src/alpha6/remediation.js.map +1 -1
- package/dist/src/alpha6/root-cause-replan-carryover.d.ts +4 -1
- package/dist/src/alpha6/root-cause-replan-carryover.js +29 -5
- package/dist/src/alpha6/root-cause-replan-carryover.js.map +1 -1
- package/dist/src/beta1/project-transaction.d.ts +3 -1
- package/dist/src/beta1/project-transaction.js +6 -2
- package/dist/src/beta1/project-transaction.js.map +1 -1
- package/dist/src/checks/runner.d.ts +26 -0
- package/dist/src/checks/runner.js +223 -0
- package/dist/src/checks/runner.js.map +1 -0
- package/dist/src/checks/task-sync.d.ts +10 -0
- package/dist/src/checks/task-sync.js +41 -0
- package/dist/src/checks/task-sync.js.map +1 -0
- package/dist/src/checks/worker.d.ts +1 -0
- package/dist/src/checks/worker.js +154 -0
- package/dist/src/checks/worker.js.map +1 -0
- package/dist/src/cli-actions.d.ts +2 -2
- package/dist/src/cli-actions.js +3 -15
- package/dist/src/cli-actions.js.map +1 -1
- package/dist/src/cli.js +14 -98
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +35 -32
- package/dist/src/dependency-provenance.d.ts +2 -2
- package/dist/src/dependency-provenance.js +12 -69
- package/dist/src/dependency-provenance.js.map +1 -1
- package/dist/src/domain/base-sync-conflict.d.ts +13 -0
- package/dist/src/domain/base-sync-conflict.js +32 -0
- package/dist/src/domain/base-sync-conflict.js.map +1 -0
- package/dist/src/domain/step-start-admission.d.ts +4 -0
- package/dist/src/domain/step-start-admission.js +9 -0
- package/dist/src/domain/step-start-admission.js.map +1 -0
- package/dist/src/domain/virgin-registration.d.ts +3 -0
- package/dist/src/domain/virgin-registration.js +65 -0
- package/dist/src/domain/virgin-registration.js.map +1 -0
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/gateway-handshake.js +0 -8
- package/dist/src/gateway-handshake.js.map +1 -1
- package/dist/src/git.d.ts +3 -1
- package/dist/src/git.js +41 -14
- package/dist/src/git.js.map +1 -1
- package/dist/src/graph.js +25 -3
- package/dist/src/graph.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/lifecycle/corrective-replan.js +3 -3
- package/dist/src/lifecycle/corrective-replan.js.map +1 -1
- package/dist/src/navigation-actions.d.ts +9 -0
- package/dist/src/navigation-actions.js +72 -0
- package/dist/src/navigation-actions.js.map +1 -0
- package/dist/src/navigation-update-artifact.d.ts +5 -0
- package/dist/src/navigation-update-artifact.js +227 -0
- package/dist/src/navigation-update-artifact.js.map +1 -0
- package/dist/src/navigation-update.d.ts +39 -0
- package/dist/src/navigation-update.js +82 -0
- package/dist/src/navigation-update.js.map +1 -0
- package/dist/src/observation.js +52 -29
- package/dist/src/observation.js.map +1 -1
- package/dist/src/observed-routes.js +5 -6
- package/dist/src/observed-routes.js.map +1 -1
- package/dist/src/pending-review-update.d.ts +0 -13
- package/dist/src/pending-review-update.js +1 -6
- package/dist/src/pending-review-update.js.map +1 -1
- package/dist/src/reviewer.d.ts +1 -1
- package/dist/src/reviewer.js +20 -17
- package/dist/src/reviewer.js.map +1 -1
- package/dist/src/runtime.d.ts +3 -0
- package/dist/src/runtime.js +6 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/state/corrective-replan-executor.d.ts +10 -0
- package/dist/src/state/corrective-replan-executor.js +37 -1
- package/dist/src/state/corrective-replan-executor.js.map +1 -1
- package/dist/src/state/corrective-replan-public-schema.js +15 -2
- package/dist/src/state/corrective-replan-public-schema.js.map +1 -1
- package/dist/src/state/corrective-replan-public.js +6 -5
- package/dist/src/state/corrective-replan-public.js.map +1 -1
- package/dist/src/state/corrective-replan-transaction.d.ts +1 -0
- package/dist/src/state/corrective-replan-transaction.js +12 -11
- package/dist/src/state/corrective-replan-transaction.js.map +1 -1
- package/dist/src/state/corrective-yield-executor.js +2 -2
- package/dist/src/state/corrective-yield-executor.js.map +1 -1
- package/dist/src/state/corrective-yield-transaction.d.ts +1 -0
- package/dist/src/state/corrective-yield-transaction.js +11 -10
- package/dist/src/state/corrective-yield-transaction.js.map +1 -1
- package/dist/src/state/lock.d.ts +4 -0
- package/dist/src/state/lock.js +21 -0
- package/dist/src/state/lock.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/src/workflow-blocker-route.d.ts +8 -0
- package/dist/src/workflow-blocker-route.js +106 -0
- package/dist/src/workflow-blocker-route.js.map +1 -0
- package/dist/src/workflow.d.ts +48 -63
- package/dist/src/workflow.js +698 -1470
- package/dist/src/workflow.js.map +1 -1
- package/docs/autonomy-guardrails.md +26 -304
- package/docs/decisions.md +14 -112
- package/docs/development-flow.md +35 -238
- package/docs/project-memory.md +31 -50
- package/docs/release-app-evidence.md +190 -0
- package/docs/release.md +95 -363
- package/docs/updating-existing-project.md +19 -717
- package/package.json +11 -13
- package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +2 -2
- package/plugins/codex-workflow-gateway/references/chat-dispatch.md +73 -197
- package/plugins/codex-workflow-gateway/references/codebase-memory-routing.md +57 -0
- package/plugins/codex-workflow-gateway/references/protocol.md +42 -445
- package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +7 -1
- package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +10 -8
- package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +23 -5
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +66 -740
- package/references/state-machine.md +4 -4
- package/roles/technical-planner.md +1 -1
- package/schemas/corrective-decision-event.schema.json +3 -1
- package/schemas/project-knowledge-map.schema.json +3 -1
- package/schemas/remediation-event.schema.json +10 -1
- package/schemas/task.schema.json +46 -2
- package/schemas/transition-payloads.schema.json +16 -1
- package/src/alpha6/adoption.ts +1123 -0
- package/src/alpha6/captured-check-evidence.ts +132 -0
- package/src/alpha6/check-support-anchor.ts +128 -0
- package/src/alpha6/component-owner.ts +168 -0
- package/src/alpha6/corrective-decision-boundary.ts +39 -0
- package/src/alpha6/downstream-proof.ts +520 -0
- package/src/alpha6/failed-step-planning-recovery.ts +88 -0
- package/src/alpha6/handoff.ts +1443 -0
- package/src/alpha6/journal.ts +473 -0
- package/src/alpha6/literal-test-invocation.ts +84 -0
- package/src/alpha6/mechanical-feasibility.ts +488 -0
- package/src/alpha6/milestone.ts +2192 -0
- package/src/alpha6/navigation-dirty-carryover.ts +154 -0
- package/src/alpha6/npm-check-contract.ts +47 -0
- package/src/alpha6/plan-integrity.ts +298 -0
- package/src/alpha6/plan-risk.ts +1480 -0
- package/src/alpha6/preexecution-replan.ts +187 -0
- package/src/alpha6/remediation-cause.ts +98 -0
- package/src/alpha6/remediation.ts +2438 -0
- package/src/alpha6/review.ts +1198 -0
- package/src/alpha6/root-cause-replan-carryover.ts +491 -0
- package/src/alpha6/store-sidecars.ts +335 -0
- package/src/alpha7/autonomy.ts +411 -0
- package/src/alpha7/corrective-recovery.ts +1332 -0
- package/src/artifacts.ts +130 -0
- package/src/beta1/project-transaction.ts +355 -0
- package/src/change-explanation.ts +153 -0
- package/src/checks/runner.ts +245 -0
- package/src/checks/task-sync.ts +41 -0
- package/src/checks/worker.ts +149 -0
- package/src/cli-actions.ts +107 -0
- package/src/cli.ts +1457 -0
- package/src/contracts.ts +1494 -0
- package/src/credential-output.ts +89 -0
- package/src/credential-transport.ts +215 -0
- package/src/delegation.ts +190 -0
- package/src/dependency-provenance.ts +472 -0
- package/src/diagnostics.ts +93 -0
- package/src/domain/base-sync-conflict.ts +35 -0
- package/src/domain/completed-step-carryover.ts +76 -0
- package/src/domain/discovery.ts +27 -0
- package/src/domain/plan-semantics.ts +58 -0
- package/src/domain/step-start-admission.ts +10 -0
- package/src/domain/validation.ts +177 -0
- package/src/domain/virgin-registration.ts +46 -0
- package/src/errors.ts +24 -0
- package/src/fs-utils.ts +61 -0
- package/src/gateway-handshake.ts +95 -0
- package/src/git.ts +183 -0
- package/src/graph.ts +342 -0
- package/src/historical-step-provenance.ts +136 -0
- package/src/index.ts +23 -0
- package/src/lifecycle/canonical-hash.ts +28 -0
- package/src/lifecycle/catalog.ts +202 -0
- package/src/lifecycle/compiler-inspection.ts +29 -0
- package/src/lifecycle/core-static-readiness.ts +132 -0
- package/src/lifecycle/corrective-replan-authority.ts +136 -0
- package/src/lifecycle/corrective-replan-binding-manifest.ts +51 -0
- package/src/lifecycle/corrective-replan-credential-core.ts +408 -0
- package/src/lifecycle/corrective-replan-credential-schema.ts +54 -0
- package/src/lifecycle/corrective-replan-credentials.ts +48 -0
- package/src/lifecycle/corrective-replan.ts +843 -0
- package/src/lifecycle/evaluator.ts +48 -0
- package/src/lifecycle/fingerprint.ts +488 -0
- package/src/lifecycle/immutable.ts +8 -0
- package/src/lifecycle/implementation-table.ts +118 -0
- package/src/lifecycle/index.ts +8 -0
- package/src/lifecycle/schema-artifact.ts +263 -0
- package/src/lifecycle/semantic-registry.ts +572 -0
- package/src/lifecycle/types.ts +838 -0
- package/src/memory.ts +273 -0
- package/src/migration.ts +161 -0
- package/src/navigation-actions.ts +70 -0
- package/src/navigation-update-artifact.ts +198 -0
- package/src/navigation-update.ts +121 -0
- package/src/observation.ts +225 -0
- package/src/observed-routes.ts +660 -0
- package/src/operational-contract.ts +125 -0
- package/src/pending-review-update.ts +175 -0
- package/src/repository.ts +99 -0
- package/src/reviewer.ts +1879 -0
- package/src/runtime.ts +6 -0
- package/src/state/corrective-replan-executor.ts +818 -0
- package/src/state/corrective-replan-public-schema.ts +83 -0
- package/src/state/corrective-replan-public.ts +908 -0
- package/src/state/corrective-replan-transaction.ts +949 -0
- package/src/state/corrective-yield-executor.ts +327 -0
- package/src/state/corrective-yield-transaction.ts +730 -0
- package/src/state/lock.ts +902 -0
- package/src/state/store.ts +567 -0
- package/src/transition-core.ts +330 -0
- package/src/ulid.ts +24 -0
- package/src/version.ts +2 -0
- package/src/workflow-blocker-route.ts +109 -0
- package/src/workflow.ts +10172 -0
- package/docs/alpha7.1-implementation-brief.md +0 -268
- package/docs/alpha7.2-corrective-context-refresh-brief.md +0 -484
- package/docs/alpha7.2.1-remediation-recovery-brief.md +0 -86
- package/docs/beta1-stabilization-brief.md +0 -165
- package/docs/beta11-plan-integrity-recovery-brief.md +0 -38
- package/docs/beta13.2-signal-review-recovery.md +0 -38
- package/docs/beta2-initial-assembly-navigation-brief.md +0 -616
- package/docs/change-model.md +0 -118
- package/docs/delegated-approval.md +0 -254
- package/docs/lifecycle/state-machine-stabilization.md +0 -641
- package/docs/pdf/README.md +0 -24
- package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
- package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
- package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
- package/docs/pdf/requirements.txt +0 -1
- package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +0 -478
- package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +0 -508
- package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +0 -778
- package/docs/pending-review-update.md +0 -15
- package/docs/problem-briefs/01-pre-implementation-integrity.md +0 -482
- package/docs/problem-briefs/02-minimal-step-integrity.md +0 -411
- package/docs/problem-briefs/03-minimal-agent-context-integrity.md +0 -358
- package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +0 -573
- package/docs/problem-briefs/BRIEF-TEMPLATE.md +0 -56
- package/docs/problem-briefs/README.md +0 -120
- package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +0 -90
- package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +0 -246
- package/docs/split-required-recovery.md +0 -47
- package/docs/stable-release-defect-register.md +0 -730
- package/docs/validation-report.md +0 -182
- package/scripts/generate-pdf-docs.py +0 -524
- package/scripts/run-pdf-docs.mjs +0 -62
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { PlanIntegrityUpdateCompatibility, TaskState } from '../contracts.js';
|
|
2
|
+
import { inspectBoundedWorkflowDependencyCommit } from '../dependency-provenance.js';
|
|
3
|
+
import { assertKnowledgeBindingOnlyPlanChange, readVerifiedPlanArtifact } from '../domain/plan-semantics.js';
|
|
4
|
+
import { pathAllowed } from '../domain/validation.js';
|
|
5
|
+
import { validateTaskHistory } from '../git.js';
|
|
6
|
+
import { changedFiles, currentBranch, gitIsAncestor, headCommit, runGit } from '../repository.js';
|
|
7
|
+
import type { FileStateStore } from '../state/store.js';
|
|
8
|
+
import { hashDirtyWorktree } from './downstream-proof.js';
|
|
9
|
+
import { readTaskC1Posture, readTaskHandoffEvents } from './handoff.js';
|
|
10
|
+
import { assertMechanicalFeasibilityFresh, hashMechanicalFeasibilityEvidence } from './mechanical-feasibility.js';
|
|
11
|
+
import { readCurrentPlanRiskAudit, readPlanRiskAuditEvents } from './plan-risk.js';
|
|
12
|
+
import { verifiedHistoricalStepReviewBindings } from './review.js';
|
|
13
|
+
import { readRemediationEvents } from './remediation.js';
|
|
14
|
+
|
|
15
|
+
export interface NavigationDirtyCarryoverAssessment {
|
|
16
|
+
applicable: boolean;
|
|
17
|
+
eligible: boolean;
|
|
18
|
+
compatibility: PlanIntegrityUpdateCompatibility | null;
|
|
19
|
+
blockers: string[];
|
|
20
|
+
requiredActor?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Validate an existing native checkpoint; this never grants writes or execution authority. */
|
|
24
|
+
export function assessNavigationDirtyCarryover(
|
|
25
|
+
store: FileStateStore,
|
|
26
|
+
repositoryRoot: string,
|
|
27
|
+
task: TaskState,
|
|
28
|
+
stepId?: string,
|
|
29
|
+
historyBoundary = headCommit(repositoryRoot),
|
|
30
|
+
authorizationBoundary = headCommit(repositoryRoot),
|
|
31
|
+
): NavigationDirtyCarryoverAssessment {
|
|
32
|
+
const records = (task.dependencyProvenanceRecoveries ?? []).filter(record =>
|
|
33
|
+
record.planIntegrity?.mode === 'navigation-fix-dirty-carryover'
|
|
34
|
+
&& (!stepId || record.planIntegrity.stepId === stepId));
|
|
35
|
+
const record = records.at(-1);
|
|
36
|
+
if (!record) return { applicable: false, eligible: false, compatibility: null, blockers: [] };
|
|
37
|
+
const binding = record.planIntegrity!;
|
|
38
|
+
const blockers: string[] = [];
|
|
39
|
+
const require = (condition: unknown, message: string) => { if (!condition) blockers.push(message); };
|
|
40
|
+
let requiredActor: string | undefined;
|
|
41
|
+
try {
|
|
42
|
+
const step = task.steps.find(candidate => candidate.id === binding.stepId);
|
|
43
|
+
require(task.status === 'ready' && task.workspaceOwner === 'local', 'Navigation checkpoint requires a ready local Task.');
|
|
44
|
+
require(task.taskBranch && currentBranch(repositoryRoot) === task.taskBranch, 'Navigation checkpoint requires the owned Task branch.');
|
|
45
|
+
require(step?.status === 'planned' && step.evidence === null, 'The checkpoint Step must remain planned without evidence.');
|
|
46
|
+
require(!task.steps.some(candidate => candidate.status === 'in_progress'), 'No Step may be in progress.');
|
|
47
|
+
require(task.systemCommits.includes(record.commitSha)
|
|
48
|
+
&& (task.dependencyProvenanceRecoveries ?? []).filter(item => item.commitSha === record.commitSha).length === 1
|
|
49
|
+
&& record.packageName === 'codex-workflow-v2'
|
|
50
|
+
&& JSON.stringify(record.files) === JSON.stringify(['package-lock.json', 'package.json']),
|
|
51
|
+
'The checkpoint must have one exact registered Workflow dependency record.');
|
|
52
|
+
const inspected = inspectBoundedWorkflowDependencyCommit(repositoryRoot, record.commitSha);
|
|
53
|
+
require(inspected?.parentCommitSha === record.parentCommitSha && inspected?.packageVersion === record.packageVersion,
|
|
54
|
+
'The checkpoint is not the exact recorded dependency-only commit.');
|
|
55
|
+
require(gitIsAncestor(repositoryRoot, record.commitSha, historyBoundary), 'The checkpoint must precede the assessed history boundary.');
|
|
56
|
+
validateTaskHistory(repositoryRoot, task, historyBoundary);
|
|
57
|
+
const suffix = runGit(repositoryRoot, ['rev-list', '--reverse', `${record.commitSha}..${historyBoundary}`]).split('\n').filter(Boolean);
|
|
58
|
+
let parent = record.commitSha;
|
|
59
|
+
for (const commit of suffix) {
|
|
60
|
+
const inspectedCommit = inspectBoundedWorkflowDependencyCommit(repositoryRoot, commit);
|
|
61
|
+
const matches = (task.dependencyProvenanceRecoveries ?? []).filter(item => item.commitSha === commit);
|
|
62
|
+
require(inspectedCommit?.parentCommitSha === parent && matches.length === 1
|
|
63
|
+
&& matches[0]!.parentCommitSha === parent && matches[0]!.packageVersion === inspectedCommit?.packageVersion
|
|
64
|
+
&& task.systemCommits.includes(commit), 'Checkpoint continuation requires exact registered dependency-only continuity.');
|
|
65
|
+
parent = commit;
|
|
66
|
+
}
|
|
67
|
+
const dirty = changedFiles(repositoryRoot).sort();
|
|
68
|
+
require(dirty.length > 0 && JSON.stringify(dirty) === JSON.stringify([...binding.dirtyFiles].sort())
|
|
69
|
+
&& hashDirtyWorktree(repositoryRoot, dirty) === binding.dirtyWorktreeHash, 'Navigation checkpoint dirty paths or bytes changed.');
|
|
70
|
+
if (step) {
|
|
71
|
+
require(dirty.every(file => pathAllowed(file, step.allowedWrites) && !pathAllowed(file, step.forbiddenScope)),
|
|
72
|
+
'Checkpoint dirty files must remain inside allowedWrites and outside forbiddenScope.');
|
|
73
|
+
require(step.dependencies.every(id => task.steps.find(item => item.id === id)?.status === 'completed'),
|
|
74
|
+
'Checkpoint Step dependencies must be complete.');
|
|
75
|
+
}
|
|
76
|
+
const events = readRemediationEvents(store, task).filter(event => event.stepId === binding.stepId && event.failureKind === 'checks-failed');
|
|
77
|
+
require(events.length > 0 && JSON.stringify(events.map(event => event.eventId)) === JSON.stringify(binding.remediationEventIds),
|
|
78
|
+
'Checkpoint remediation IDs must equal the complete ordered native Step check-failure history.');
|
|
79
|
+
const terminal = events.at(-1);
|
|
80
|
+
require(terminal?.failureEvidence.dirtyWorktreeHash === binding.dirtyWorktreeHash
|
|
81
|
+
&& terminal?.failureEvidence.completionCommit === record.parentCommitSha,
|
|
82
|
+
'The terminal native failure must bind the checkpoint parent and exact dirty bytes.');
|
|
83
|
+
require(terminal && Number.isFinite(Date.parse(record.recordedAt)) && Date.parse(record.recordedAt) >= Date.parse(terminal.recordedAt),
|
|
84
|
+
'The checkpoint timestamp must follow its native failure.');
|
|
85
|
+
const root = store.taskRoot(task.projectId, task.id);
|
|
86
|
+
require(task.planHash && task.briefHash && store.hashArtifact(root, 'brief.md') === task.briefHash,
|
|
87
|
+
'Current Brief authority does not match its artifact.');
|
|
88
|
+
if (!task.planHash) throw new Error('Current Plan is missing.');
|
|
89
|
+
let expected = binding.planHash;
|
|
90
|
+
readVerifiedPlanArtifact(root, expected, task.planHash);
|
|
91
|
+
const planHashes = [expected];
|
|
92
|
+
if (expected !== task.planHash) {
|
|
93
|
+
const start = task.knowledgeRebinds.findIndex(item => item.previousPlanHash === expected);
|
|
94
|
+
require(start >= 0, 'Checkpoint Plan requires an exact Knowledge-rebind chain.');
|
|
95
|
+
let previousTime = Number.NEGATIVE_INFINITY;
|
|
96
|
+
for (const item of start < 0 ? [] : task.knowledgeRebinds.slice(start)) {
|
|
97
|
+
require(item.previousPlanHash === expected && !planHashes.includes(item.reboundPlanHash)
|
|
98
|
+
&& Number.isFinite(Date.parse(item.recordedAt)) && Date.parse(item.recordedAt) > previousTime,
|
|
99
|
+
'Checkpoint Knowledge-rebind chain forks, cycles, or has invalid timestamps.');
|
|
100
|
+
assertKnowledgeBindingOnlyPlanChange(readVerifiedPlanArtifact(root, item.previousPlanHash, task.planHash),
|
|
101
|
+
readVerifiedPlanArtifact(root, item.reboundPlanHash, task.planHash));
|
|
102
|
+
expected = item.reboundPlanHash;
|
|
103
|
+
previousTime = Date.parse(item.recordedAt);
|
|
104
|
+
planHashes.push(expected);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
require(expected === task.planHash, 'Checkpoint Plan chain does not terminate at the current Plan.');
|
|
108
|
+
const audit = readCurrentPlanRiskAudit(store, task);
|
|
109
|
+
require(audit && ['approved', 'approved-with-rationale'].includes(audit.audit.decision), 'Current approved Plan Risk Audit is required.');
|
|
110
|
+
const audits = readPlanRiskAuditEvents(store, task);
|
|
111
|
+
const reviews = verifiedHistoricalStepReviewBindings(store, task);
|
|
112
|
+
for (const completed of task.steps.filter(item => item.status === 'completed')) {
|
|
113
|
+
require(completed.evidence, 'Every completed Step must retain execution evidence.');
|
|
114
|
+
const guarded = audits.some(item => item.stepClassifications.some(classification => classification.stepId === completed.id && classification.reviewRequired));
|
|
115
|
+
if (guarded) require(completed.evidence && reviews.some(review => review.stepId === completed.id && review.commitSha === completed.evidence!.commitSha),
|
|
116
|
+
'Completed guarded Step must retain verified strict-review evidence.');
|
|
117
|
+
}
|
|
118
|
+
const selected = planHashes.map(hash => audits.filter(item => item.planHash === hash));
|
|
119
|
+
require(selected.every(matches => matches.length === 1), 'Checkpoint Plan audit chain is missing or ambiguous.');
|
|
120
|
+
if (selected.every(matches => matches.length === 1)) {
|
|
121
|
+
const chain = selected.map(matches => matches[0]!);
|
|
122
|
+
require(chain.at(-1)?.eventHash === audit?.audit.eventHash, 'Checkpoint audit chain must reach the current audit.');
|
|
123
|
+
chain.forEach((item, index) => {
|
|
124
|
+
require(item.briefHash === task.briefHash, 'Checkpoint audit Brief authority changed.');
|
|
125
|
+
if (index) {
|
|
126
|
+
const before = chain[index - 1]!;
|
|
127
|
+
require(item.previousEventHash === before.eventHash && item.planner === before.planner
|
|
128
|
+
&& item.auditor !== item.planner && item.decision === before.decision
|
|
129
|
+
&& item.splitRationale === before.splitRationale
|
|
130
|
+
&& JSON.stringify(item.stepClassifications) === JSON.stringify(before.stepClassifications),
|
|
131
|
+
'Checkpoint Plan Risk Audit changed beyond its Knowledge binding.');
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const authorization = [...task.authorizations].reverse().find(item => item.kind === 'execution' && item.decision === 'approved');
|
|
136
|
+
require(authorization && authorization.planHash === task.planHash && authorization.briefHash === task.briefHash
|
|
137
|
+
&& authorization.headCommit === authorizationBoundary
|
|
138
|
+
&& gitIsAncestor(repositoryRoot, authorizationBoundary, headCommit(repositoryRoot)), 'Current execution authorization must bind the exact Brief, Plan and HEAD.');
|
|
139
|
+
if (!authorization?.mechanicalFeasibility) throw new Error('Checkpoint requires current mechanical-feasibility evidence.');
|
|
140
|
+
require(authorization.evidenceHash === hashMechanicalFeasibilityEvidence(authorization.mechanicalFeasibility)
|
|
141
|
+
&& authorization.headCommit === authorization.mechanicalFeasibility.evaluatedHead,
|
|
142
|
+
'Mechanical-feasibility evidence hash or HEAD does not match authorization.');
|
|
143
|
+
assertMechanicalFeasibilityFresh(repositoryRoot, task, authorization.mechanicalFeasibility, task.briefHash!, task.planHash);
|
|
144
|
+
const posture = readTaskC1Posture(store, task);
|
|
145
|
+
const claim = readTaskHandoffEvents(store, task).filter(event => event.eventType === 'claim').at(-1);
|
|
146
|
+
requiredActor = claim?.actor;
|
|
147
|
+
require(posture.state === 'claimed' && claim && posture.claimant === claim.actor
|
|
148
|
+
&& planHashes.includes(claim.planHash ?? '') && claim.briefHash === task.briefHash,
|
|
149
|
+
'Checkpoint requires its original actor with a current immutable claimed C1 boundary.');
|
|
150
|
+
} catch (error) { blockers.push(error instanceof Error ? error.message : String(error)); }
|
|
151
|
+
return { applicable: true, eligible: blockers.length === 0,
|
|
152
|
+
compatibility: blockers.length ? null : { ...structuredClone(binding), planHash: task.planHash! },
|
|
153
|
+
blockers: [...new Set(blockers)], ...(requiredActor ? { requiredActor } : {}) };
|
|
154
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
const NPM_RUN_CHECK = /^npm\s+run(?:-script)?\s+([A-Za-z0-9._:-]+)(?:\s+--\s+(.+))?$/;
|
|
4
|
+
const SAFE_FORWARD_ARG = /^--[A-Za-z0-9._:-]+=[^\s]+$/;
|
|
5
|
+
const NODE_AFFECTED_SCRIPT = /^node\s+([A-Za-z0-9._/-]+\.(?:cjs|js|mjs))\s+affected$/;
|
|
6
|
+
const SELECTOR = /^(?:--base|--head|--files)=.+$/;
|
|
7
|
+
|
|
8
|
+
export interface ParsedNpmRunCheck {
|
|
9
|
+
script: string;
|
|
10
|
+
forwardedArguments: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RequiredSelectorContract {
|
|
14
|
+
runnerPath: string;
|
|
15
|
+
selectors: readonly ['--base', '--head', '--files'];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function parseNpmRunCheck(command: string): ParsedNpmRunCheck | null {
|
|
19
|
+
const match = NPM_RUN_CHECK.exec(command.trim());
|
|
20
|
+
if (!match) return null;
|
|
21
|
+
const forwardedArguments = match[2]?.trim().split(/\s+/).filter(Boolean) ?? [];
|
|
22
|
+
if (forwardedArguments.some((argument) => !SAFE_FORWARD_ARG.test(argument))) return null;
|
|
23
|
+
return { script: match[1]!, forwardedArguments };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function inspectRequiredSelectorContract(
|
|
27
|
+
scriptCommand: string,
|
|
28
|
+
readRunner: (runnerPath: string) => string | null,
|
|
29
|
+
): RequiredSelectorContract | null {
|
|
30
|
+
const match = NODE_AFFECTED_SCRIPT.exec(scriptCommand.trim());
|
|
31
|
+
if (!match) return null;
|
|
32
|
+
const runnerPath = match[1]!;
|
|
33
|
+
if (path.posix.isAbsolute(runnerPath)
|
|
34
|
+
|| runnerPath.split('/').some((segment) => segment === '.' || segment === '..')) return null;
|
|
35
|
+
const source = readRunner(runnerPath);
|
|
36
|
+
if (!source
|
|
37
|
+
|| !source.includes('process.argv.slice(3)')
|
|
38
|
+
|| !source.includes('--base')
|
|
39
|
+
|| !source.includes('--head')
|
|
40
|
+
|| !source.includes('--files')
|
|
41
|
+
|| !source.includes('Affected mode requires')) return null;
|
|
42
|
+
return { runnerPath, selectors: ['--base', '--head', '--files'] };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function hasRequiredSelector(arguments_: readonly string[]): boolean {
|
|
46
|
+
return arguments_.some((argument) => SELECTOR.test(argument));
|
|
47
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, lstatSync, readFileSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import type {
|
|
6
|
+
PlanIntegrityConflictEvidence,
|
|
7
|
+
PlanIntegrityConflictEvidenceV2,
|
|
8
|
+
PlanIntegrityConflictEvidenceV3,
|
|
9
|
+
PlanIntegrityConflictEvidenceV4,
|
|
10
|
+
TaskState,
|
|
11
|
+
} from '../contracts.js';
|
|
12
|
+
import { pathAllowed } from '../domain/validation.js';
|
|
13
|
+
import { WorkflowError } from '../errors.js';
|
|
14
|
+
import { hashCanonical } from '../lifecycle/fingerprint.js';
|
|
15
|
+
import { changedFiles, headCommit } from '../repository.js';
|
|
16
|
+
import type { FileStateStore } from '../state/store.js';
|
|
17
|
+
import {
|
|
18
|
+
hasRequiredSelector,
|
|
19
|
+
inspectRequiredSelectorContract,
|
|
20
|
+
parseNpmRunCheck,
|
|
21
|
+
} from './npm-check-contract.js';
|
|
22
|
+
import { inspectCheckSupportAnchors } from './check-support-anchor.js';
|
|
23
|
+
import { inspectFailedCheckComponentOwners, semanticBoundaryHash } from './component-owner.js';
|
|
24
|
+
import {
|
|
25
|
+
readCorrectiveDecisionEvents,
|
|
26
|
+
readRemediationEvents,
|
|
27
|
+
selectPlanIntegrityRemediations,
|
|
28
|
+
} from './remediation.js';
|
|
29
|
+
|
|
30
|
+
const ROOT_MANIFEST = 'package.json' as const;
|
|
31
|
+
const TASK_ID = /^TASK-[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
32
|
+
|
|
33
|
+
export interface PlanIntegrityRecoveryAssessment {
|
|
34
|
+
readonly applicable: boolean;
|
|
35
|
+
readonly eligible: boolean;
|
|
36
|
+
readonly taskId: string;
|
|
37
|
+
readonly taskRevision: number;
|
|
38
|
+
readonly stepId: string;
|
|
39
|
+
readonly planHash: string | null;
|
|
40
|
+
readonly blockers: readonly string[];
|
|
41
|
+
readonly evidence: PlanIntegrityConflictEvidence | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function defaultPlanIntegrityRecoveryActor(taskId: string): string {
|
|
45
|
+
if (!TASK_ID.test(taskId)) {
|
|
46
|
+
throw new WorkflowError('INVALID_ARGUMENT', `Cannot derive a Plan-integrity recovery actor from invalid Task ID ${taskId}.`);
|
|
47
|
+
}
|
|
48
|
+
return `agent:plan-integrity-recovery:${taskId.toLowerCase()}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function assessPlanIntegrityRecovery(
|
|
52
|
+
store: FileStateStore,
|
|
53
|
+
repositoryRoot: string,
|
|
54
|
+
task: TaskState,
|
|
55
|
+
stepId: string,
|
|
56
|
+
): PlanIntegrityRecoveryAssessment {
|
|
57
|
+
const base = {
|
|
58
|
+
taskId: task.id,
|
|
59
|
+
taskRevision: task.revision,
|
|
60
|
+
stepId,
|
|
61
|
+
planHash: task.planHash,
|
|
62
|
+
};
|
|
63
|
+
const step = task.steps.find((candidate) => candidate.id === stepId) ?? null;
|
|
64
|
+
if (!step || task.status !== 'needs_fix' || step.status !== 'failed' || !task.planHash) {
|
|
65
|
+
return { ...base, applicable: false, eligible: false, blockers: [], evidence: null };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const allRemediations = readRemediationEvents(store, task).filter((event) => event.stepId === stepId);
|
|
69
|
+
const decisions = readCorrectiveDecisionEvents(store, task).filter((event) => event.stepId === stepId);
|
|
70
|
+
const remediations = selectPlanIntegrityRemediations(store, task, allRemediations, decisions);
|
|
71
|
+
if (!remediations || remediations.some(event => event.failureKind !== 'checks-failed')) {
|
|
72
|
+
return { ...base, applicable: false, eligible: false, blockers: [], evidence: null };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const manifest = inspectRootManifest(repositoryRoot);
|
|
76
|
+
const runnerBlockers: string[] = [];
|
|
77
|
+
const conflicts: PlanIntegrityConflictEvidenceV2['conflicts'] = [];
|
|
78
|
+
for (const command of step.checks) {
|
|
79
|
+
const invocation = parseNpmRunCheck(command);
|
|
80
|
+
if (!invocation) continue;
|
|
81
|
+
const scriptCommand = manifest.scripts.get(invocation.script) ?? null;
|
|
82
|
+
if (scriptCommand === null) {
|
|
83
|
+
if (pathAllowed(ROOT_MANIFEST, step.allowedWrites)) continue;
|
|
84
|
+
conflicts.push({
|
|
85
|
+
reasonCode: 'MISSING_NPM_SCRIPT' as const,
|
|
86
|
+
script: invocation.script,
|
|
87
|
+
command: command.trim(),
|
|
88
|
+
scriptCommand: null,
|
|
89
|
+
runnerPath: null,
|
|
90
|
+
runnerHash: null,
|
|
91
|
+
});
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const runnerInspections = new Map<string, ReturnType<typeof inspectRepositoryFile>>();
|
|
95
|
+
const selectorContract = inspectRequiredSelectorContract(scriptCommand, (runnerPath) => {
|
|
96
|
+
const inspection = inspectRepositoryFile(repositoryRoot, runnerPath);
|
|
97
|
+
runnerInspections.set(runnerPath, inspection);
|
|
98
|
+
return inspection.content;
|
|
99
|
+
});
|
|
100
|
+
if (!selectorContract || hasRequiredSelector(invocation.forwardedArguments)) continue;
|
|
101
|
+
const runnerInspection = runnerInspections.get(selectorContract.runnerPath) ?? null;
|
|
102
|
+
if (runnerInspection?.blocker) runnerBlockers.push(runnerInspection.blocker);
|
|
103
|
+
if (pathAllowed(ROOT_MANIFEST, step.allowedWrites)
|
|
104
|
+
|| pathAllowed(selectorContract.runnerPath, step.allowedWrites)) continue;
|
|
105
|
+
conflicts.push({
|
|
106
|
+
reasonCode: 'REQUIRED_SELECTOR_MISSING' as const,
|
|
107
|
+
script: invocation.script,
|
|
108
|
+
command: command.trim(),
|
|
109
|
+
scriptCommand,
|
|
110
|
+
runnerPath: selectorContract.runnerPath,
|
|
111
|
+
runnerHash: runnerInspection?.hash ?? null,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
conflicts.sort((left, right) => left.command.localeCompare(right.command) || left.reasonCode.localeCompare(right.reasonCode));
|
|
115
|
+
const supportConflicts: PlanIntegrityConflictEvidenceV3['conflicts'] = conflicts.length === 0
|
|
116
|
+
? inspectCheckSupportAnchors({
|
|
117
|
+
step,
|
|
118
|
+
scripts: manifest.scripts,
|
|
119
|
+
readFile: (relativePath) => {
|
|
120
|
+
const inspected = inspectRepositoryFile(repositoryRoot, relativePath);
|
|
121
|
+
return { content: inspected.content, hash: inspected.hash };
|
|
122
|
+
},
|
|
123
|
+
pathExists: (relativePath) => gitPathExists(repositoryRoot, headCommit(repositoryRoot), relativePath),
|
|
124
|
+
}).map((conflict) => ({
|
|
125
|
+
...conflict,
|
|
126
|
+
expectedOutputPaths: [...conflict.expectedOutputPaths],
|
|
127
|
+
anchoredMigrationNames: [...conflict.anchoredMigrationNames],
|
|
128
|
+
}))
|
|
129
|
+
: [];
|
|
130
|
+
const componentOwnerConflicts: PlanIntegrityConflictEvidenceV4['conflicts'] =
|
|
131
|
+
conflicts.length === 0 && supportConflicts.length === 0
|
|
132
|
+
? inspectFailedCheckComponentOwners({
|
|
133
|
+
step,
|
|
134
|
+
scripts: manifest.scripts,
|
|
135
|
+
isTracked: (relativePath) => gitPathExists(repositoryRoot, headCommit(repositoryRoot), relativePath),
|
|
136
|
+
readFile: (relativePath) => {
|
|
137
|
+
const inspected = inspectRepositoryFile(repositoryRoot, relativePath);
|
|
138
|
+
return { content: inspected.content, hash: inspected.hash };
|
|
139
|
+
},
|
|
140
|
+
}).map(({ causeFingerprint: _causeFingerprint, ...conflict }) => conflict)
|
|
141
|
+
: [];
|
|
142
|
+
if (conflicts.length === 0 && supportConflicts.length === 0 && componentOwnerConflicts.length === 0) {
|
|
143
|
+
return { ...base, applicable: false, eligible: false, blockers: [], evidence: null };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (remediations.length !== allRemediations.length && conflicts.length > 0)
|
|
147
|
+
return { ...base, applicable: false, eligible: false, blockers: [], evidence: null };
|
|
148
|
+
|
|
149
|
+
const files = [...changedFiles(repositoryRoot)].sort();
|
|
150
|
+
const outsideAllowedWrites = files.filter((file) => !pathAllowed(file, step.allowedWrites));
|
|
151
|
+
const forbiddenChanges = files.filter((file) => pathAllowed(file, step.forbiddenScope));
|
|
152
|
+
const blockers = [
|
|
153
|
+
...(manifest.blocker ? [manifest.blocker] : []),
|
|
154
|
+
...runnerBlockers,
|
|
155
|
+
...(outsideAllowedWrites.length > 0
|
|
156
|
+
? [`Changed files outside ${stepId} allowedWrites: ${outsideAllowedWrites.join(', ')}`]
|
|
157
|
+
: []),
|
|
158
|
+
...(forbiddenChanges.length > 0
|
|
159
|
+
? [`Changed files intersect ${stepId} forbiddenScope: ${forbiddenChanges.join(', ')}`]
|
|
160
|
+
: []),
|
|
161
|
+
...supportConflicts.flatMap((conflict) => pathAllowed(conflict.supportPath, step.forbiddenScope)
|
|
162
|
+
? [`Check-support authority ${conflict.supportPath} intersects ${stepId} forbiddenScope.`]
|
|
163
|
+
: []),
|
|
164
|
+
...componentOwnerConflicts.flatMap((conflict) => pathAllowed(conflict.ownerPath, step.forbiddenScope)
|
|
165
|
+
? [`Component owner ${conflict.ownerPath} intersects ${stepId} forbiddenScope.`]
|
|
166
|
+
: []),
|
|
167
|
+
];
|
|
168
|
+
const stepDefinitionHash = hashCanonical({
|
|
169
|
+
id: step.id,
|
|
170
|
+
title: step.title,
|
|
171
|
+
objective: step.objective,
|
|
172
|
+
requirements: step.requirements,
|
|
173
|
+
dependencies: step.dependencies,
|
|
174
|
+
expectedOutputs: step.expectedOutputs,
|
|
175
|
+
allowedWrites: step.allowedWrites,
|
|
176
|
+
forbiddenScope: step.forbiddenScope,
|
|
177
|
+
checks: step.checks,
|
|
178
|
+
});
|
|
179
|
+
const remediationBindings = remediations.map((event) => ({
|
|
180
|
+
eventId: event.eventId,
|
|
181
|
+
eventHash: event.eventHash,
|
|
182
|
+
attemptOrdinal: event.attemptOrdinal,
|
|
183
|
+
}));
|
|
184
|
+
const evidenceBase = supportConflicts.length > 0 ? {
|
|
185
|
+
kind: 'check-support-anchor-outside-allowed-writes' as const,
|
|
186
|
+
taskRevision: task.revision,
|
|
187
|
+
remediationEvents: remediationBindings,
|
|
188
|
+
manifestPath: ROOT_MANIFEST,
|
|
189
|
+
manifestHash: manifest.hash,
|
|
190
|
+
gitHead: headCommit(repositoryRoot),
|
|
191
|
+
stepDefinitionHash,
|
|
192
|
+
conflicts: supportConflicts,
|
|
193
|
+
scopeExtensions: [...new Set(supportConflicts.map((conflict) => conflict.supportPath))].sort(),
|
|
194
|
+
changedFiles: files,
|
|
195
|
+
} : componentOwnerConflicts.length > 0 ? {
|
|
196
|
+
kind: 'failed-check-component-owner-outside-allowed-writes' as const,
|
|
197
|
+
taskRevision: task.revision,
|
|
198
|
+
remediationEvents: remediationBindings,
|
|
199
|
+
gitHead: headCommit(repositoryRoot),
|
|
200
|
+
stepDefinitionHash,
|
|
201
|
+
semanticBoundaryHash: semanticBoundaryHash(task),
|
|
202
|
+
failedCheckCauseFingerprint: step.failurePause!.failedChecks!.find((failure) =>
|
|
203
|
+
componentOwnerConflicts.some((conflict) => conflict.command === failure.command))!.causeFingerprint,
|
|
204
|
+
conflicts: componentOwnerConflicts,
|
|
205
|
+
scopeExtensions: [...new Set(componentOwnerConflicts.map((conflict) => conflict.ownerPath))].sort(),
|
|
206
|
+
changedFiles: files,
|
|
207
|
+
} : {
|
|
208
|
+
kind: 'non-executable-npm-check-outside-allowed-writes' as const,
|
|
209
|
+
taskRevision: task.revision,
|
|
210
|
+
remediationEvents: remediationBindings,
|
|
211
|
+
manifestPath: ROOT_MANIFEST,
|
|
212
|
+
manifestHash: manifest.hash,
|
|
213
|
+
gitHead: headCommit(repositoryRoot),
|
|
214
|
+
stepDefinitionHash,
|
|
215
|
+
conflicts,
|
|
216
|
+
changedFiles: files,
|
|
217
|
+
};
|
|
218
|
+
const evidence: PlanIntegrityConflictEvidence = {
|
|
219
|
+
...evidenceBase,
|
|
220
|
+
conflictHash: hashCanonical({
|
|
221
|
+
domain: supportConflicts.length > 0
|
|
222
|
+
? 'codex-workflow/plan-integrity-conflict/v3'
|
|
223
|
+
: componentOwnerConflicts.length > 0
|
|
224
|
+
? 'codex-workflow/plan-integrity-conflict/v4'
|
|
225
|
+
: 'codex-workflow/plan-integrity-conflict/v2',
|
|
226
|
+
taskId: task.id,
|
|
227
|
+
planHash: task.planHash,
|
|
228
|
+
stepId,
|
|
229
|
+
evidence: evidenceBase,
|
|
230
|
+
}),
|
|
231
|
+
};
|
|
232
|
+
return {
|
|
233
|
+
...base,
|
|
234
|
+
applicable: true,
|
|
235
|
+
eligible: blockers.length === 0,
|
|
236
|
+
blockers,
|
|
237
|
+
evidence,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function gitPathExists(repositoryRoot: string, commit: string, relativePath: string): boolean {
|
|
242
|
+
const result = spawnSync('git', ['-C', repositoryRoot, 'cat-file', '-e', `${commit}:${relativePath}`], {
|
|
243
|
+
encoding: 'utf8',
|
|
244
|
+
});
|
|
245
|
+
if (result.status === 0) return true;
|
|
246
|
+
if (result.status === 128) return false;
|
|
247
|
+
throw new WorkflowError('COMMAND_FAILED', `Unable to inspect ${relativePath} at ${commit}.`, {
|
|
248
|
+
stderr: result.stderr.trim(),
|
|
249
|
+
exitCode: result.status,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function inspectRootManifest(repositoryRoot: string): {
|
|
254
|
+
readonly scripts: ReadonlyMap<string, string>;
|
|
255
|
+
readonly hash: string | null;
|
|
256
|
+
readonly blocker: string | null;
|
|
257
|
+
} {
|
|
258
|
+
const file = path.join(repositoryRoot, ROOT_MANIFEST);
|
|
259
|
+
if (!existsSync(file)) return { scripts: new Map(), hash: null, blocker: null };
|
|
260
|
+
const stat = lstatSync(file);
|
|
261
|
+
if (!stat.isFile()) {
|
|
262
|
+
return { scripts: new Map(), hash: null, blocker: 'Root package.json is not a regular file.' };
|
|
263
|
+
}
|
|
264
|
+
const bytes = readFileSync(file);
|
|
265
|
+
const hash = createHash('sha256').update(bytes).digest('hex');
|
|
266
|
+
try {
|
|
267
|
+
const parsed = JSON.parse(bytes.toString('utf8')) as { scripts?: unknown };
|
|
268
|
+
if (parsed.scripts === undefined) return { scripts: new Map(), hash, blocker: null };
|
|
269
|
+
if (!parsed.scripts || typeof parsed.scripts !== 'object' || Array.isArray(parsed.scripts)) {
|
|
270
|
+
return { scripts: new Map(), hash, blocker: 'Root package.json scripts field is malformed.' };
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
scripts: new Map(Object.entries(parsed.scripts)
|
|
274
|
+
.filter((entry): entry is [string, string] => typeof entry[1] === 'string')),
|
|
275
|
+
hash,
|
|
276
|
+
blocker: null,
|
|
277
|
+
};
|
|
278
|
+
} catch {
|
|
279
|
+
return { scripts: new Map(), hash, blocker: 'Root package.json is not valid JSON.' };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function inspectRepositoryFile(repositoryRoot: string, relativePath: string): {
|
|
284
|
+
content: string | null;
|
|
285
|
+
hash: string | null;
|
|
286
|
+
blocker: string | null;
|
|
287
|
+
} {
|
|
288
|
+
const file = path.join(repositoryRoot, relativePath);
|
|
289
|
+
if (!existsSync(file)) return { content: null, hash: null, blocker: `${relativePath} is missing.` };
|
|
290
|
+
const stat = lstatSync(file);
|
|
291
|
+
if (!stat.isFile()) return { content: null, hash: null, blocker: `${relativePath} is not a regular file.` };
|
|
292
|
+
const bytes = readFileSync(file);
|
|
293
|
+
return {
|
|
294
|
+
content: bytes.toString('utf8'),
|
|
295
|
+
hash: createHash('sha256').update(bytes).digest('hex'),
|
|
296
|
+
blocker: null,
|
|
297
|
+
};
|
|
298
|
+
}
|