codex-workflow-v2 2.0.0-beta.13.1 → 2.0.0-beta.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/dist/reviewer-runtime-build.json +15 -11
  3. package/dist/src/cli-actions.d.ts +1 -1
  4. package/dist/src/cli-actions.js +3 -0
  5. package/dist/src/cli-actions.js.map +1 -1
  6. package/dist/src/cli.js +15 -0
  7. package/dist/src/cli.js.map +1 -1
  8. package/dist/src/gateway-handshake.js +1 -0
  9. package/dist/src/gateway-handshake.js.map +1 -1
  10. package/dist/src/observed-routes.js +1 -0
  11. package/dist/src/observed-routes.js.map +1 -1
  12. package/dist/src/pending-review-update.d.ts +49 -0
  13. package/dist/src/pending-review-update.js +132 -0
  14. package/dist/src/pending-review-update.js.map +1 -0
  15. package/dist/src/repository.js +16 -4
  16. package/dist/src/repository.js.map +1 -1
  17. package/dist/src/reviewer.js +4 -1
  18. package/dist/src/reviewer.js.map +1 -1
  19. package/dist/src/version.d.ts +1 -1
  20. package/dist/src/version.js +1 -1
  21. package/dist/src/workflow.d.ts +10 -0
  22. package/dist/src/workflow.js +155 -2
  23. package/dist/src/workflow.js.map +1 -1
  24. package/docs/beta13.2-signal-review-recovery.md +38 -0
  25. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  26. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  27. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  28. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +1 -1
  29. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +2 -2
  30. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +2 -2
  31. package/docs/pending-review-update.md +15 -0
  32. package/docs/release.md +11 -0
  33. package/package.json +1 -1
  34. package/plugins/codex-workflow-gateway/references/protocol.md +7 -0
  35. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +36 -13
@@ -1,3 +1,4 @@
1
+ import { pendingReviewRunnerFingerprint, PENDING_REVIEW_UPDATE_SIDECAR, preparePendingReviewUpdateEvent, PENDING_REVIEW_SOURCE_VERSION, pendingReviewSourceBinding, pendingReviewBindingHash, assertPendingReviewTransport, verifiedPendingReviewTransport } from './pending-review-update.js';
1
2
  import { appendFileSync, existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
2
3
  import { createHash } from 'node:crypto';
3
4
  import path from 'node:path';
@@ -2970,7 +2971,15 @@ export class WorkflowService {
2970
2971
  throw new WorkflowError('TRANSITION_BLOCKED', `External Strict Step Review requires ${stepId} in progress with canonical evidence.`);
2971
2972
  }
2972
2973
  validateTaskHistory(identity.repositoryRoot, task);
2973
- if (!isClean(identity.repositoryRoot) || headCommit(identity.repositoryRoot) !== step.evidence.commitSha) {
2974
+ const transport = verifiedPendingReviewTransport(identity.repositoryRoot, task, stepId, this.store.taskRoot(task.projectId, task.id));
2975
+ if (transport) {
2976
+ const cycle = buildCurrentStrictStepReviewCycle(this.store, task, stepId);
2977
+ if (cycle.resolution !== 'pending' || cycle.pendingEvent?.eventHash !== transport.source.pendingEventHash
2978
+ || cycle.binding.evidenceHash !== transport.source.evidenceHash) {
2979
+ throw new WorkflowError('TRANSITION_BLOCKED', 'The transported strict review must retain its exact pending chain.');
2980
+ }
2981
+ }
2982
+ if (!isClean(identity.repositoryRoot) || (headCommit(identity.repositoryRoot) !== step.evidence.commitSha && !transport)) {
2974
2983
  throw new WorkflowError('GIT_PRECONDITION_FAILED', 'External Strict Step Review requires a clean checkout at the completion commit.');
2975
2984
  }
2976
2985
  const envelope = this.taskContext(repository, taskId, 'independent-reviewer');
@@ -3657,6 +3666,137 @@ export class WorkflowService {
3657
3666
  now: this.now(),
3658
3667
  }, actor, confirmationCode);
3659
3668
  }
3669
+ pendingReviewUpdatePreflight(repository, taskId) {
3670
+ const snapshot = this.observationSnapshot(repository);
3671
+ const blockers = observationPreflightBlockers(snapshot.observation);
3672
+ try {
3673
+ pendingReviewRunnerFingerprint();
3674
+ }
3675
+ catch (error) {
3676
+ blockers.push(error instanceof Error ? error.message : String(error));
3677
+ }
3678
+ const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
3679
+ let binding = null;
3680
+ if (PACKAGE_VERSION === PENDING_REVIEW_SOURCE_VERSION)
3681
+ blockers.push('The exact target runner must be newer than the supported source.');
3682
+ if (!task)
3683
+ blockers.push('Task not found.');
3684
+ if (task) {
3685
+ try {
3686
+ binding = pendingReviewSourceBinding(snapshot.identity.repositoryRoot, this.store, task);
3687
+ }
3688
+ catch (error) {
3689
+ blockers.push(error instanceof Error ? error.message : String(error));
3690
+ }
3691
+ const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
3692
+ if ([versions.declared, versions.locked, versions.installed, versions.currentBranch,
3693
+ ...versions.milestoneBases.map((base) => base.version)].some((version) => version !== PENDING_REVIEW_SOURCE_VERSION)) {
3694
+ blockers.push('All source version surfaces must equal the exact supported beta.13.1.');
3695
+ }
3696
+ }
3697
+ const otherRunningSteps = snapshot.tasks.flatMap((candidate) => candidate.steps.filter((step) => step.status === 'in_progress' && (candidate.id !== taskId || step.id !== binding?.stepId)));
3698
+ if (otherRunningSteps.length)
3699
+ blockers.push('No other Step may be running.');
3700
+ const stale = snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) <= this.now().getTime());
3701
+ if (snapshot.leases.some((lease) => !stale.includes(lease)))
3702
+ blockers.push('No live writer lease may exist.');
3703
+ if (stale.some((lease) => lease.entityId !== taskId))
3704
+ blockers.push('No unrelated stale writer lease may exist.');
3705
+ const staleLeaseRepair = blockers.length === 0 && binding
3706
+ ? stale.map((lease) => ({ action: 'locks repair', entityId: lease.entityId })) : [];
3707
+ if (stale.length)
3708
+ blockers.push('Repair the exact stale Task lease with the installed source locks repair, then rerun source preflight.');
3709
+ return { readOnly: true, eligible: blockers.length === 0 && binding !== null, binding,
3710
+ bindingHash: binding ? pendingReviewBindingHash(binding) : null, blockers, staleLeaseRepair };
3711
+ }
3712
+ pendingReviewDependencyPreflight(repository, taskId, source) {
3713
+ const snapshot = this.observationSnapshot(repository);
3714
+ const blockers = observationPreflightBlockers(snapshot.observation);
3715
+ try {
3716
+ pendingReviewRunnerFingerprint();
3717
+ }
3718
+ catch (error) {
3719
+ blockers.push(error instanceof Error ? error.message : String(error));
3720
+ }
3721
+ const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
3722
+ const taskHead = headCommit(snapshot.identity.repositoryRoot);
3723
+ let baseTransportCommit = null;
3724
+ if (snapshot.leases.length)
3725
+ blockers.push('No live or stale writer lease may exist.');
3726
+ if (!task)
3727
+ blockers.push('Task not found.');
3728
+ if (task && existsSync(path.join(this.store.taskRoot(task.projectId, task.id), PENDING_REVIEW_UPDATE_SIDECAR)))
3729
+ blockers.push('A pending review update is already recorded.');
3730
+ if (task) {
3731
+ try {
3732
+ // Receipt fields are claims, not authority. Reconstruct every source binding from
3733
+ // canonical Task/sidecars and the exact parents of the candidate Git commits.
3734
+ const parent = runGit(repository, ['rev-parse', 'HEAD^']);
3735
+ baseTransportCommit = runGit(repository, ['rev-parse', task.baseBranch]);
3736
+ const baseParent = runGit(repository, ['rev-parse', `${baseTransportCommit}^`]);
3737
+ const reconstructed = pendingReviewSourceBinding(repository, this.store, task, parent, baseParent);
3738
+ if (canonicalJsonStringify(reconstructed) !== canonicalJsonStringify(source)) {
3739
+ throw new Error('Source receipt does not match reconstructed Task/revision/Plan/HEAD/pending bindings.');
3740
+ }
3741
+ assertPendingReviewTransport(repository, reconstructed, taskHead, baseTransportCommit);
3742
+ if (snapshot.tasks.some((candidate) => candidate.steps.some((step) => step.status === 'in_progress' && (candidate.id !== taskId || step.id !== source.stepId)))) {
3743
+ throw new Error('No other Step may be running.');
3744
+ }
3745
+ const versions = inspectWorkflowVersions(repository, [task.baseBranch]);
3746
+ if ([versions.runtime, versions.declared, versions.locked, versions.installed, versions.currentBranch,
3747
+ ...versions.milestoneBases.map((base) => base.version)].some((version) => version !== PACKAGE_VERSION)) {
3748
+ throw new Error('All target version surfaces must equal the exact target runner.');
3749
+ }
3750
+ }
3751
+ catch (error) {
3752
+ blockers.push(error instanceof Error ? error.message : String(error));
3753
+ }
3754
+ }
3755
+ return { readOnly: true, eligible: blockers.length === 0, blockers, taskHead, baseTransportCommit };
3756
+ }
3757
+ recoverPendingReviewDependency(repository, taskId, expectedRevision, source, actor, reason) {
3758
+ if (!actor.trim() || !reason.trim())
3759
+ throw new WorkflowError('INVALID_ARGUMENT', 'Actor and reason are required.');
3760
+ const initial = this.pendingReviewDependencyPreflight(repository, taskId, source);
3761
+ if (!initial.eligible)
3762
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Pending review dependency preflight failed.', initial);
3763
+ const context = this.#mutationContext(repository);
3764
+ const task = this.store.readTask(context.identity.projectId, taskId);
3765
+ assertExpectedRevision(task, expectedRevision);
3766
+ const preflight = this.pendingReviewDependencyPreflight(repository, taskId, source);
3767
+ if (!preflight.eligible || !preflight.baseTransportCommit) {
3768
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Pending review dependency preflight changed.', preflight);
3769
+ }
3770
+ const now = this.now();
3771
+ const nextTask = { ...task, revision: task.revision + 1, updatedAt: now.toISOString(),
3772
+ systemCommits: [...task.systemCommits, preflight.taskHead],
3773
+ dependencyProvenanceRecoveries: [...(task.dependencyProvenanceRecoveries ?? []), {
3774
+ commitSha: preflight.taskHead, parentCommitSha: source.completionCommit,
3775
+ packageName: PACKAGE_NAME, packageVersion: PACKAGE_VERSION, files: ['package-lock.json', 'package.json'],
3776
+ actor: actor.trim(), reason: reason.trim(), recordedAt: now.toISOString(),
3777
+ }] };
3778
+ const projectRoot = this.store.projectRoot(task.projectId);
3779
+ const event = preparePendingReviewUpdateEvent({ source, baseTransportCommit: preflight.baseTransportCommit }, preflight.taskHead, now.toISOString());
3780
+ applyProjectTransaction({ projectRoot, projectId: task.projectId,
3781
+ transactionId: `tx-${createEntityId('TASK', now.getTime()).toLowerCase()}`,
3782
+ kind: 'pending-review-dependency-recovery', now,
3783
+ targets: [{ path: path.relative(projectRoot, path.join(this.store.taskRoot(task.projectId, task.id), 'state.json')).split(path.sep).join('/'),
3784
+ content: `${JSON.stringify(nextTask, null, 2)}\n` },
3785
+ { path: path.relative(projectRoot, path.join(this.store.taskRoot(task.projectId, task.id), PENDING_REVIEW_UPDATE_SIDECAR)).split(path.sep).join('/'),
3786
+ content: `${canonicalJsonStringify(event)}\n` }],
3787
+ validate: () => {
3788
+ const saved = this.store.readTask(task.projectId, task.id);
3789
+ if (saved.revision !== nextTask.revision || !verifiedPendingReviewTransport(repository, saved, source.stepId, this.store.taskRoot(task.projectId, task.id))) {
3790
+ throw new WorkflowError('STATE_CORRUPT', 'Pending review dependency recovery readback failed.');
3791
+ }
3792
+ const cycle = buildCurrentStrictStepReviewCycle(this.store, saved, source.stepId);
3793
+ if (cycle.resolution !== 'pending' || cycle.pendingEvent?.eventHash !== source.pendingEventHash) {
3794
+ throw new WorkflowError('STATE_CORRUPT', 'Pending review changed during dependency recovery.');
3795
+ }
3796
+ },
3797
+ });
3798
+ return this.store.readTask(task.projectId, task.id);
3799
+ }
3660
3800
  updatePreflight(repository) {
3661
3801
  const snapshot = this.observationSnapshot(repository);
3662
3802
  const tasks = snapshot.tasks;
@@ -4748,6 +4888,19 @@ export class WorkflowService {
4748
4888
  ?? prioritizedRemediationTask
4749
4889
  ?? status.tasks.find((task) => taskIsActionableFromRepositoryNext(task, milestonesById, tasksById));
4750
4890
  if (activeTask) {
4891
+ let pendingReviewDependencyRecovery = null;
4892
+ try {
4893
+ const reconstructed = pendingReviewSourceBinding(repositoryRoot, this.store, activeTask, runGit(repositoryRoot, ['rev-parse', 'HEAD^']), runGit(repositoryRoot, ['rev-parse', `${activeTask.baseBranch}^`]));
4894
+ pendingReviewDependencyRecovery = this.pendingReviewDependencyPreflight(repositoryRoot, activeTask.id, reconstructed);
4895
+ }
4896
+ catch { /* A nonmatching shape follows ordinary navigation without a compatibility bypass. */ }
4897
+ if (pendingReviewDependencyRecovery?.eligible) {
4898
+ return withAdoption({ scope: 'task', id: activeTask.id, status: activeTask.status,
4899
+ action: 'update pending-review-dependency-recover', revision: activeTask.revision,
4900
+ pendingReviewDependencyRecovery,
4901
+ sourceReceiptRequired: true,
4902
+ });
4903
+ }
4751
4904
  const historicalStepProvenanceRecovery = this.historicalStepProvenanceRecoveryPreflight(repositoryRoot, activeTask.id);
4752
4905
  if (historicalStepProvenanceRecovery.eligible) {
4753
4906
  return withAdoption({
@@ -5092,7 +5245,7 @@ export class WorkflowService {
5092
5245
  || taskNext?.action === 'task downstream-proof-recover'
5093
5246
  || taskNext?.action === 'task remediation-mode-recover'
5094
5247
  || (taskNext?.action === 'task writer-credential-replace'
5095
- && taskNext?.blockedAction === 'task remediation-mode-recover')
5248
+ && ['task step-complete', 'task step-review', 'task remediation-mode-recover'].includes(String(taskNext?.blockedAction)))
5096
5249
  || taskNext?.action === 'task plan-integrity-recover'
5097
5250
  || taskNext?.blockedAction === 'task plan-integrity-recover'
5098
5251
  || taskNext?.action === 'task corrective-yield'