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,125 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { changedFiles, runGit } from './repository.js';
|
|
3
|
+
|
|
4
|
+
const HEX_64 = /^[a-f0-9]{64}$/;
|
|
5
|
+
|
|
6
|
+
export interface AppOwnedPreflightBase {
|
|
7
|
+
contract: 'codex-workflow-app-owned-preflight-v1';
|
|
8
|
+
authority: 'diagnostic-only';
|
|
9
|
+
owner: 'codex-app';
|
|
10
|
+
packageMutatesAppState: false;
|
|
11
|
+
atomicAppEffectClaimed: false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function watchdogPreflight(input: {
|
|
15
|
+
previousRouteFingerprint: string | null;
|
|
16
|
+
currentRouteFingerprint: string;
|
|
17
|
+
lastSuccessfulPollAt: string | null;
|
|
18
|
+
lastSignificantObservationAt: string | null;
|
|
19
|
+
}): AppOwnedPreflightBase & Record<string, unknown> {
|
|
20
|
+
const valid = HEX_64.test(input.currentRouteFingerprint)
|
|
21
|
+
&& (input.previousRouteFingerprint === null || HEX_64.test(input.previousRouteFingerprint));
|
|
22
|
+
return {
|
|
23
|
+
...appOwnedBase(),
|
|
24
|
+
eligible: valid,
|
|
25
|
+
routeChanged: input.previousRouteFingerprint !== input.currentRouteFingerprint,
|
|
26
|
+
schedulingPerformed: false,
|
|
27
|
+
checkpoint: {
|
|
28
|
+
lastSuccessfulPollAt: input.lastSuccessfulPollAt,
|
|
29
|
+
lastSignificantObservationAt: input.lastSignificantObservationAt,
|
|
30
|
+
},
|
|
31
|
+
blockers: valid ? [] : ['Route fingerprints must be exact SHA-256 values.'],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function coordinatorReplacementPreflight(input: {
|
|
36
|
+
previousCoordinatorTerminal: boolean;
|
|
37
|
+
replacementCoordinatorId: string;
|
|
38
|
+
activeSupervisorIds: string[];
|
|
39
|
+
coreReadbackFingerprint: string;
|
|
40
|
+
}): AppOwnedPreflightBase & Record<string, unknown> {
|
|
41
|
+
const supervisors = [...new Set(input.activeSupervisorIds)];
|
|
42
|
+
const eligible = input.previousCoordinatorTerminal
|
|
43
|
+
&& input.replacementCoordinatorId.trim().length > 0
|
|
44
|
+
&& supervisors.length === 1
|
|
45
|
+
&& supervisors[0] === input.replacementCoordinatorId
|
|
46
|
+
&& HEX_64.test(input.coreReadbackFingerprint);
|
|
47
|
+
return {
|
|
48
|
+
...appOwnedBase(),
|
|
49
|
+
eligibleToRequestReplacement: eligible,
|
|
50
|
+
replacementPerformed: false,
|
|
51
|
+
activeSupervisorIds: supervisors,
|
|
52
|
+
blockers: eligible
|
|
53
|
+
? []
|
|
54
|
+
: ['Terminal predecessor, one replacement supervisor, and exact Core readback are required.'],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function appConsentPreflight(input: {
|
|
59
|
+
requestedTaskId: string;
|
|
60
|
+
presentedTaskId: string | null;
|
|
61
|
+
presentedTaskRevision: number | null;
|
|
62
|
+
consentReceiptHash: string | null;
|
|
63
|
+
}): AppOwnedPreflightBase & Record<string, unknown> {
|
|
64
|
+
const receiptStructurallyValid = input.presentedTaskId === input.requestedTaskId
|
|
65
|
+
&& Number.isInteger(input.presentedTaskRevision)
|
|
66
|
+
&& (input.presentedTaskRevision ?? -1) >= 0
|
|
67
|
+
&& typeof input.consentReceiptHash === 'string'
|
|
68
|
+
&& HEX_64.test(input.consentReceiptHash);
|
|
69
|
+
return {
|
|
70
|
+
...appOwnedBase(),
|
|
71
|
+
receiptStructurallyValid,
|
|
72
|
+
consentGrantedByPackage: false,
|
|
73
|
+
semanticAuthorityEstablished: false,
|
|
74
|
+
requiredAppReadback: receiptStructurallyValid ? 'verify-consent-with-host' : 'obtain-current-consent-receipt',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function worktreeObservationPreflight(
|
|
79
|
+
repositoryRoot: string,
|
|
80
|
+
limit = 32,
|
|
81
|
+
): AppOwnedPreflightBase & Record<string, unknown> {
|
|
82
|
+
const boundedLimit = Number.isInteger(limit) && limit > 0 ? Math.min(limit, 32) : 32;
|
|
83
|
+
const blocks = runGit(repositoryRoot, ['worktree', 'list', '--porcelain'])
|
|
84
|
+
.split(/\n\n+/)
|
|
85
|
+
.filter(Boolean);
|
|
86
|
+
const records = blocks.slice(0, boundedLimit).map((block) => {
|
|
87
|
+
const fields = new Map<string, string>();
|
|
88
|
+
for (const line of block.split('\n')) {
|
|
89
|
+
const separator = line.indexOf(' ');
|
|
90
|
+
if (separator > 0) fields.set(line.slice(0, separator), line.slice(separator + 1));
|
|
91
|
+
}
|
|
92
|
+
const worktree = fields.get('worktree') ?? '';
|
|
93
|
+
const dirty = changedFiles(worktree).sort();
|
|
94
|
+
return {
|
|
95
|
+
worktree,
|
|
96
|
+
head: fields.get('HEAD') ?? null,
|
|
97
|
+
branch: fields.get('branch')?.replace(/^refs\/heads\//, '') ?? null,
|
|
98
|
+
dirtyCount: dirty.length,
|
|
99
|
+
dirtyFingerprint: sha256(dirty.join('\n')),
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
...appOwnedBase(),
|
|
104
|
+
bounded: true,
|
|
105
|
+
limit: boundedLimit,
|
|
106
|
+
truncated: blocks.length > boundedLimit,
|
|
107
|
+
records,
|
|
108
|
+
observationFingerprint: sha256(JSON.stringify(records)),
|
|
109
|
+
worktreeSelectionPerformed: false,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function appOwnedBase(): AppOwnedPreflightBase {
|
|
114
|
+
return {
|
|
115
|
+
contract: 'codex-workflow-app-owned-preflight-v1',
|
|
116
|
+
authority: 'diagnostic-only',
|
|
117
|
+
owner: 'codex-app',
|
|
118
|
+
packageMutatesAppState: false,
|
|
119
|
+
atomicAppEffectClaimed: false,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function sha256(value: string): string {
|
|
124
|
+
return createHash('sha256').update(value).digest('hex');
|
|
125
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import type { TaskState, SidecarEventRecord } from './contracts.js';
|
|
5
|
+
import { requireCurrentStrictStepReview } from './alpha6/review.js';
|
|
6
|
+
import { readCurrentPlanRiskAudit } from './alpha6/plan-risk.js';
|
|
7
|
+
import { canonicalJsonStringify, sha256Hex, readSidecarEvents } from './alpha6/store-sidecars.js';
|
|
8
|
+
import { inspectBoundedWorkflowDependencyCommit } from './dependency-provenance.js';
|
|
9
|
+
import { validateTaskHistory } from './git.js';
|
|
10
|
+
import { currentBranch, headCommit, isClean, runGit } from './repository.js';
|
|
11
|
+
import { FileStateStore } from './state/store.js';
|
|
12
|
+
import { PACKAGE_VERSION } from './version.js';
|
|
13
|
+
|
|
14
|
+
export const PENDING_REVIEW_SOURCE_VERSION: string = '2.0.0-beta.13.1';
|
|
15
|
+
export interface PendingReviewUpdateBinding {
|
|
16
|
+
protocol: 'pending-review-update-v1';
|
|
17
|
+
projectId: string;
|
|
18
|
+
taskId: string;
|
|
19
|
+
taskRevision: number;
|
|
20
|
+
taskBranch: string;
|
|
21
|
+
baseBranch: string;
|
|
22
|
+
baseHead: string;
|
|
23
|
+
planHash: string;
|
|
24
|
+
briefHash: string;
|
|
25
|
+
stepId: string;
|
|
26
|
+
completionCommit: string;
|
|
27
|
+
pendingEventHash: string;
|
|
28
|
+
evidenceHash: string;
|
|
29
|
+
sourceVersion: string;
|
|
30
|
+
targetVersion: string;
|
|
31
|
+
targetRuntimeHash: string;
|
|
32
|
+
}
|
|
33
|
+
export interface PendingReviewUpdateTransport {
|
|
34
|
+
source: PendingReviewUpdateBinding;
|
|
35
|
+
baseTransportCommit: string;
|
|
36
|
+
}
|
|
37
|
+
export const PENDING_REVIEW_UPDATE_SIDECAR = 'pending-review-updates.jsonl';
|
|
38
|
+
export interface PendingReviewUpdateEvent extends SidecarEventRecord, PendingReviewUpdateTransport {
|
|
39
|
+
taskTransportCommit: string;
|
|
40
|
+
}
|
|
41
|
+
function requireCondition(condition: unknown, message: string): asserts condition {
|
|
42
|
+
if (!condition) throw new Error(message);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Source inspection never writes state, refreshes Knowledge, or manufactures pending evidence. */
|
|
46
|
+
export function pendingReviewSourceBinding(
|
|
47
|
+
repository: string, store: FileStateStore, task: TaskState,
|
|
48
|
+
completionCommit = headCommit(repository), baseHead = runGit(repository, ['rev-parse', task.baseBranch]),
|
|
49
|
+
): PendingReviewUpdateBinding {
|
|
50
|
+
requireCondition(task.status === 'in_progress' && task.workspaceOwner === 'local', 'Expected a local in-progress Task.');
|
|
51
|
+
requireCondition(task.taskBranch && currentBranch(repository) === task.taskBranch, 'Expected the exact Task branch.');
|
|
52
|
+
requireCondition(isClean(repository), 'Expected a clean checkout.');
|
|
53
|
+
const active = task.steps.filter((step) => step.status === 'in_progress');
|
|
54
|
+
requireCondition(active.length === 1 && active[0]?.evidence?.commitSha === completionCommit,
|
|
55
|
+
'Exactly one in-progress Step must have completion evidence at the bound product HEAD.');
|
|
56
|
+
const step = active[0]!;
|
|
57
|
+
requireCondition(task.planHash && task.briefHash, 'Missing Plan or Brief binding.');
|
|
58
|
+
const taskRoot = store.taskRoot(task.projectId, task.id);
|
|
59
|
+
requireCondition(sha256Hex(readFileSync(path.join(taskRoot, 'plan.md'), 'utf8')) === task.planHash
|
|
60
|
+
&& sha256Hex(readFileSync(path.join(taskRoot, 'brief.md'), 'utf8')) === task.briefHash,
|
|
61
|
+
'Plan or Brief artifact hash mismatch.');
|
|
62
|
+
const audit = readCurrentPlanRiskAudit(store, task);
|
|
63
|
+
requireCondition(audit?.reviewRequiredStepIds.includes(step.id), 'The current Plan audit must require strict review.');
|
|
64
|
+
const cycle = requireCurrentStrictStepReview(store, task, step.id);
|
|
65
|
+
requireCondition(cycle.resolution === 'pending' && cycle.pendingEvent, 'Expected a hash-valid pending review chain.');
|
|
66
|
+
validateTaskHistory(repository, task, completionCommit);
|
|
67
|
+
for (const revision of [completionCommit, baseHead]) {
|
|
68
|
+
const manifest = JSON.parse(runGit(repository, ['show', `${revision}:package.json`])) as {
|
|
69
|
+
devDependencies?: Record<string, string>; dependencies?: Record<string, string> };
|
|
70
|
+
const lock = JSON.parse(runGit(repository, ['show', `${revision}:package-lock.json`])) as {
|
|
71
|
+
packages?: Record<string, { version?: string; devDependencies?: Record<string, string>; dependencies?: Record<string, string> }> };
|
|
72
|
+
requireCondition(manifest.devDependencies?.['codex-workflow-v2'] === PENDING_REVIEW_SOURCE_VERSION
|
|
73
|
+
&& manifest.dependencies?.['codex-workflow-v2'] === undefined
|
|
74
|
+
&& lock.packages?.['']?.devDependencies?.['codex-workflow-v2'] === PENDING_REVIEW_SOURCE_VERSION
|
|
75
|
+
&& lock.packages?.['']?.dependencies?.['codex-workflow-v2'] === undefined
|
|
76
|
+
&& lock.packages?.['node_modules/codex-workflow-v2']?.version === PENDING_REVIEW_SOURCE_VERSION,
|
|
77
|
+
'Every source manifest and lock root/package entry must bind the exact source devDependency.');
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
protocol: 'pending-review-update-v1', projectId: task.projectId, taskId: task.id,
|
|
81
|
+
taskRevision: task.revision, taskBranch: task.taskBranch, baseBranch: task.baseBranch, baseHead,
|
|
82
|
+
planHash: task.planHash, briefHash: task.briefHash, stepId: step.id, completionCommit,
|
|
83
|
+
pendingEventHash: cycle.pendingEvent.eventHash, evidenceHash: cycle.binding.evidenceHash,
|
|
84
|
+
sourceVersion: PENDING_REVIEW_SOURCE_VERSION, targetVersion: PACKAGE_VERSION,
|
|
85
|
+
targetRuntimeHash: pendingReviewRunnerFingerprint(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function assertPendingReviewTransport(
|
|
90
|
+
repository: string, source: PendingReviewUpdateBinding, taskHead: string, baseHead: string,
|
|
91
|
+
): void {
|
|
92
|
+
requireCondition(source.protocol === 'pending-review-update-v1'
|
|
93
|
+
&& source.sourceVersion === PENDING_REVIEW_SOURCE_VERSION
|
|
94
|
+
&& source.targetVersion === PACKAGE_VERSION && source.targetVersion !== source.sourceVersion
|
|
95
|
+
&& source.targetRuntimeHash === pendingReviewRunnerFingerprint(),
|
|
96
|
+
'Unsupported pending review source/target compatibility.');
|
|
97
|
+
for (const [commit, parent] of [[taskHead, source.completionCommit], [baseHead, source.baseHead]]) {
|
|
98
|
+
const inspected = inspectBoundedWorkflowDependencyCommit(repository, commit!);
|
|
99
|
+
requireCondition(inspected !== null && inspected.parentCommitSha === parent && inspected.packageVersion === source.targetVersion,
|
|
100
|
+
'Expected an exact dependency-only transport on the bound parent.');
|
|
101
|
+
requireCondition(runGit(repository, ['rev-list', '--parents', '-n', '1', commit!]).split(' ').length === 2,
|
|
102
|
+
'Dependency transport must have exactly one parent.');
|
|
103
|
+
const manifest = JSON.parse(runGit(repository, ['show', `${parent}:package.json`])) as { devDependencies?: Record<string, string> };
|
|
104
|
+
requireCondition(manifest.devDependencies?.['codex-workflow-v2'] === source.sourceVersion,
|
|
105
|
+
'Dependency transport parent must retain the exact source pin.');
|
|
106
|
+
}
|
|
107
|
+
requireCondition(runGit(repository, ['rev-parse', source.baseBranch]) === baseHead,
|
|
108
|
+
'The bound base branch moved after transport.');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** This is deliberately only used by external sealed Step review, never generic review/seal logic. */
|
|
112
|
+
export function verifiedPendingReviewTransport(repository: string, task: TaskState, stepId: string, taskRoot: string): PendingReviewUpdateTransport | null {
|
|
113
|
+
if (!existsSync(path.join(taskRoot, PENDING_REVIEW_UPDATE_SIDECAR))) return null;
|
|
114
|
+
const head = headCommit(repository);
|
|
115
|
+
const events = readSidecarEvents<PendingReviewUpdateEvent>(path.join(taskRoot, PENDING_REVIEW_UPDATE_SIDECAR), { trustedRoot: path.resolve(taskRoot, '../../../..') });
|
|
116
|
+
const matching = events.filter((event) => event.taskTransportCommit === head && event.source?.stepId === stepId);
|
|
117
|
+
if (matching.length === 0) return null;
|
|
118
|
+
const store = new FileStateStore({ root: path.resolve(taskRoot, '../../../..') });
|
|
119
|
+
requireCondition(store.taskRoot(task.projectId, task.id) === path.resolve(taskRoot), 'Unexpected Task sidecar root.');
|
|
120
|
+
// Review payloads can carry authorized check-support allowedWrites augmentations.
|
|
121
|
+
// Only canonical stored Step definitions establish the pending evidence hash.
|
|
122
|
+
const canonical = store.readTask(task.projectId, task.id);
|
|
123
|
+
requireCondition(canonical.revision === task.revision && canonical.planHash === task.planHash
|
|
124
|
+
&& canonical.briefHash === task.briefHash
|
|
125
|
+
&& canonicalJsonStringify(canonical.steps.find((step) => step.id === stepId)?.evidence ?? null)
|
|
126
|
+
=== canonicalJsonStringify(task.steps.find((step) => step.id === stepId)?.evidence ?? null),
|
|
127
|
+
'Review payload no longer matches the canonical Task.');
|
|
128
|
+
task = canonical;
|
|
129
|
+
requireCondition(events.length === 1 && matching.length === 1, 'Expected one exact pending review update sidecar.');
|
|
130
|
+
const transport = matching[0]!;
|
|
131
|
+
const records = (task.dependencyProvenanceRecoveries ?? []).filter((record) => record.commitSha === head);
|
|
132
|
+
requireCondition(records.length === 1, 'Missing unique dependency registration for the review bridge.');
|
|
133
|
+
const record = records[0]!;
|
|
134
|
+
const source = transport.source;
|
|
135
|
+
const cycle = requireCurrentStrictStepReview(store, task, stepId);
|
|
136
|
+
requireCondition(cycle.pendingEvent?.eventHash === source.pendingEventHash
|
|
137
|
+
&& cycle.binding.evidenceHash === source.evidenceHash, 'Transported pending review binding changed.');
|
|
138
|
+
const step = task.steps.find((candidate) => candidate.id === stepId);
|
|
139
|
+
requireCondition(source.projectId === task.projectId && source.taskId === task.id
|
|
140
|
+
&& source.taskBranch === task.taskBranch && source.baseBranch === task.baseBranch
|
|
141
|
+
&& currentBranch(repository) === source.taskBranch && isClean(repository)
|
|
142
|
+
&& source.planHash === task.planHash && source.briefHash === task.briefHash
|
|
143
|
+
&& source.completionCommit === step?.evidence?.commitSha
|
|
144
|
+
&& source.taskRevision < task.revision && task.systemCommits.includes(head)
|
|
145
|
+
&& record.parentCommitSha === source.completionCommit && record.packageVersion === source.targetVersion,
|
|
146
|
+
'Pending review transport no longer matches the current Task.');
|
|
147
|
+
assertPendingReviewTransport(repository, source, head, transport.baseTransportCommit);
|
|
148
|
+
validateTaskHistory(repository, task);
|
|
149
|
+
return transport;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Verify the exact external runner's published build bytes without installing or writing. */
|
|
153
|
+
export function pendingReviewRunnerFingerprint(): string {
|
|
154
|
+
const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
155
|
+
const packageRoot = path.resolve(moduleDirectory, '../..');
|
|
156
|
+
const packageContent = readFileSync(path.join(packageRoot, 'package.json'), 'utf8');
|
|
157
|
+
const pkg = JSON.parse(packageContent) as { name: string; version: string };
|
|
158
|
+
const manifest = JSON.parse(readFileSync(path.join(packageRoot, 'dist/reviewer-runtime-build.json'), 'utf8')) as {
|
|
159
|
+
protocol: string; packageName: string; packageVersion: string; packageManifestSha256: string;
|
|
160
|
+
files: Array<{ path: string; sha256: string }>;
|
|
161
|
+
};
|
|
162
|
+
requireCondition(pkg.name === 'codex-workflow-v2' && pkg.version === PACKAGE_VERSION
|
|
163
|
+
&& manifest.protocol === 'codex-workflow-runtime-build-manifest-v1'
|
|
164
|
+
&& manifest.packageName === pkg.name && manifest.packageVersion === pkg.version
|
|
165
|
+
&& manifest.packageManifestSha256 === sha256Hex(packageContent), 'Exact target runner package/build manifest mismatch.');
|
|
166
|
+
requireCondition(['cli.js', 'workflow.js', 'reviewer.js', 'gateway-handshake.js', 'pending-review-update.js']
|
|
167
|
+
.every((name) => manifest.files.some((entry) => entry.path === `dist/src/${name}`)), 'Target runner build entrypoints are incomplete.');
|
|
168
|
+
for (const entry of manifest.files) {
|
|
169
|
+
requireCondition(/^dist\/src\/(?:[a-zA-Z0-9_-]+\/)*[a-zA-Z0-9_-]+\.js$/.test(entry.path)
|
|
170
|
+
&& /^[a-f0-9]{64}$/.test(entry.sha256), 'Invalid target build entry.');
|
|
171
|
+
requireCondition(sha256Hex(readFileSync(path.join(moduleDirectory, entry.path.slice('dist/src/'.length)))) === entry.sha256,
|
|
172
|
+
'Exact target runner runtime bytes differ from build evidence.');
|
|
173
|
+
}
|
|
174
|
+
return sha256Hex(canonicalJsonStringify(manifest));
|
|
175
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { WorkflowError } from './errors.js';
|
|
6
|
+
|
|
7
|
+
export interface RepositoryIdentity {
|
|
8
|
+
projectId: string;
|
|
9
|
+
repositoryRoot: string;
|
|
10
|
+
gitCommonDir: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Bound memory use while allowing complete multi-megabyte review diffs. Overflow is
|
|
14
|
+
// an explicit failure, including for probes that allow ordinary Git exit failures.
|
|
15
|
+
const GIT_MAX_BUFFER = 64 * 1024 * 1024;
|
|
16
|
+
const GIT_DIAGNOSTIC_LIMIT = 4096;
|
|
17
|
+
|
|
18
|
+
export function runGit(repositoryRoot: string, args: string[], allowFailure = false): string {
|
|
19
|
+
const result = spawnSync('git', ['-C', repositoryRoot, ...args], {
|
|
20
|
+
encoding: 'utf8',
|
|
21
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
22
|
+
});
|
|
23
|
+
if (result.error || result.signal || (result.status !== 0 && !allowFailure)) {
|
|
24
|
+
throw new WorkflowError('COMMAND_FAILED', `git ${args.join(' ')} failed`, {
|
|
25
|
+
stdout: (result.stdout ?? '').slice(0, GIT_DIAGNOSTIC_LIMIT).trim(),
|
|
26
|
+
stderr: (result.stderr ?? '').slice(0, GIT_DIAGNOSTIC_LIMIT).trim(),
|
|
27
|
+
stdoutTruncated: (result.stdout?.length ?? 0) > GIT_DIAGNOSTIC_LIMIT,
|
|
28
|
+
stderrTruncated: (result.stderr?.length ?? 0) > GIT_DIAGNOSTIC_LIMIT,
|
|
29
|
+
exitCode: result.status,
|
|
30
|
+
signal: result.signal,
|
|
31
|
+
errorCode: (result.error as NodeJS.ErrnoException | undefined)?.code ?? null,
|
|
32
|
+
maxBufferBytes: GIT_MAX_BUFFER,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return result.stdout.trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveRepositoryIdentity(repository: string): RepositoryIdentity {
|
|
39
|
+
const repositoryRoot = realpathSync(runGit(repository, ['rev-parse', '--show-toplevel']));
|
|
40
|
+
const rawCommonDir = runGit(repositoryRoot, ['rev-parse', '--git-common-dir']);
|
|
41
|
+
const commonCandidate = path.isAbsolute(rawCommonDir)
|
|
42
|
+
? rawCommonDir
|
|
43
|
+
: path.resolve(repositoryRoot, rawCommonDir);
|
|
44
|
+
const gitCommonDir = realpathSync(commonCandidate);
|
|
45
|
+
const projectId = createHash('sha256').update(gitCommonDir).digest('hex').slice(0, 24);
|
|
46
|
+
return { projectId, repositoryRoot, gitCommonDir };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function currentBranch(repositoryRoot: string): string | null {
|
|
50
|
+
const branch = runGit(repositoryRoot, ['branch', '--show-current'], true);
|
|
51
|
+
return branch || null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function headCommit(repositoryRoot: string): string {
|
|
55
|
+
return runGit(repositoryRoot, ['rev-parse', 'HEAD']);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function changedFiles(repositoryRoot: string): string[] {
|
|
59
|
+
const result = spawnSync(
|
|
60
|
+
'git',
|
|
61
|
+
['-C', repositoryRoot, 'status', '--porcelain=v1', '--untracked-files=all'],
|
|
62
|
+
{ encoding: 'utf8' },
|
|
63
|
+
);
|
|
64
|
+
if (result.status !== 0) {
|
|
65
|
+
throw new WorkflowError('COMMAND_FAILED', 'Unable to inspect Git working tree.', {
|
|
66
|
+
stdout: result.stdout.trim(),
|
|
67
|
+
stderr: result.stderr.trim(),
|
|
68
|
+
exitCode: result.status,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const output = result.stdout.trimEnd();
|
|
72
|
+
if (!output) return [];
|
|
73
|
+
return output.split('\n').map((line) => {
|
|
74
|
+
const entry = line.slice(3).trim();
|
|
75
|
+
const renameTarget = entry.includes(' -> ') ? entry.split(' -> ').at(-1) : entry;
|
|
76
|
+
return renameTarget?.replaceAll('\\', '/') ?? entry;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function isClean(repositoryRoot: string): boolean {
|
|
81
|
+
return changedFiles(repositoryRoot).length === 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function gitIsAncestor(repositoryRoot: string, ancestor: string, descendant: string): boolean {
|
|
85
|
+
const result = spawnSync(
|
|
86
|
+
'git',
|
|
87
|
+
['-C', repositoryRoot, 'merge-base', '--is-ancestor', ancestor, descendant],
|
|
88
|
+
{ encoding: 'utf8' },
|
|
89
|
+
);
|
|
90
|
+
if (result.status === 0) return true;
|
|
91
|
+
if (result.status === 1) return false;
|
|
92
|
+
throw new WorkflowError('COMMAND_FAILED', 'Unable to verify Git ancestry.', {
|
|
93
|
+
ancestor,
|
|
94
|
+
descendant,
|
|
95
|
+
stdout: result.stdout.trim(),
|
|
96
|
+
stderr: result.stderr.trim(),
|
|
97
|
+
exitCode: result.status,
|
|
98
|
+
});
|
|
99
|
+
}
|