codex-workflow-v2 2.0.0-beta.12.5 → 2.0.0-beta.12.7
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 +29 -3
- package/dist/src/alpha6/downstream-proof.d.ts +41 -2
- package/dist/src/alpha6/downstream-proof.js +360 -5
- package/dist/src/alpha6/downstream-proof.js.map +1 -1
- package/dist/src/alpha6/plan-risk.js +2 -2
- package/dist/src/alpha6/plan-risk.js.map +1 -1
- package/dist/src/cli.js +57 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +44 -1
- package/dist/src/dependency-provenance.d.ts +9 -2
- package/dist/src/dependency-provenance.js +49 -5
- package/dist/src/dependency-provenance.js.map +1 -1
- package/dist/src/git.d.ts +1 -1
- package/dist/src/git.js +4 -2
- package/dist/src/git.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/workflow.d.ts +11 -1
- package/dist/src/workflow.js +434 -28
- package/dist/src/workflow.js.map +1 -1
- package/docs/autonomy-guardrails.md +7 -0
- package/docs/development-flow.md +16 -0
- 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/sources/codex-workflow-v2-architecture-ru.md +26 -6
- package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +27 -7
- package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +34 -12
- package/docs/release.md +11 -1
- package/docs/updating-existing-project.md +61 -2
- package/docs/validation-report.md +92 -83
- package/package.json +2 -1
- package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-workflow-gateway/references/protocol.md +12 -1
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +33 -1
- package/schemas/downstream-proof-invalidation-event.schema.json +1 -0
- package/schemas/downstream-proof-replan-recovery-event.schema.json +61 -0
- package/schemas/task.schema.json +56 -0
- package/scripts/generate-pdf-docs.py +13 -2
package/dist/src/workflow.js
CHANGED
|
@@ -2,7 +2,7 @@ import { appendFileSync, existsSync, lstatSync, readFileSync, readdirSync } from
|
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { PROTOCOL_VERSION, STATE_SCHEMA_VERSION, } from './contracts.js';
|
|
5
|
-
import { inspectDependencyProvenanceRecovery } from './dependency-provenance.js';
|
|
5
|
+
import { inspectDependencyProvenanceRecovery, } from './dependency-provenance.js';
|
|
6
6
|
import { renderBrief, renderPlan, renderResult } from './artifacts.js';
|
|
7
7
|
import { assertDelegationCanAuthorize, hashDelegationPolicy, prepareDelegationGrantGate, } from './delegation.js';
|
|
8
8
|
import { assessDiscovery, mergeUnique } from './domain/discovery.js';
|
|
@@ -11,9 +11,9 @@ import { WorkflowError } from './errors.js';
|
|
|
11
11
|
import { bindGraphEvidence, createGraphRefreshRequest, fallbackGraphBinding, inspectGraphBinding, validateGraphRequestCurrent, } from './graph.js';
|
|
12
12
|
import { inspectLocalDependency, inspectWorkflowVersions } from './diagnostics.js';
|
|
13
13
|
import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, appendTaskWriterCredentialReplacement, assertTaskClaimAllowed, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, prepareTaskCorrectiveYield, readTaskC1Posture, } from './alpha6/handoff.js';
|
|
14
|
-
import { assessDownstreamProofRecovery, downstreamProofInvalidationSidecar, prepareDownstreamProofInvalidation, readDownstreamProofInvalidations, } from './alpha6/downstream-proof.js';
|
|
14
|
+
import { assessDownstreamProofCarryover, assessDownstreamProofReplanRecovery, assessDownstreamProofRecovery, downstreamProofInvalidationSidecar, downstreamProofReplanRecoverySidecar, prepareDownstreamProofInvalidation, prepareDownstreamProofReplanRecovery, readDownstreamProofInvalidations, readDownstreamProofReplanRecoveries, } from './alpha6/downstream-proof.js';
|
|
15
15
|
import { applyAdoptionPosture, assertAdoptionBaselinePreserved, buildAdoptionPreparation, initializeProjectRegistrationAdoption, readCurrentAdoptionPosture, } from './alpha6/adoption.js';
|
|
16
|
-
import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, readCurrentPlanRiskAudit, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
|
|
16
|
+
import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, readCurrentPlanRiskAudit, readPlanRiskAuditEvents, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
|
|
17
17
|
import { appendCorrectiveDecisionEvent, appendPlanIntegrityRecoveryDecision, appendRemediationEvent, appendRemediationModeRecovery, appendStopEscalateOverride, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, defaultCorrectiveAuditorActor, findCurrentGuardedRemediationPosture, findRemediationModeRecoveryCandidate, findFailedGuardedStepRequiringCorrectiveDecision, readGuardedRemediationPostureForStep, readCorrectiveDecisionEvents, readRemediationEvents, prepareStopEscalateOverride, stopEscalateOverrideDecisionEventIds, } from './alpha6/remediation.js';
|
|
18
18
|
import { assessPlanIntegrityRecovery, defaultPlanIntegrityRecoveryActor, } from './alpha6/plan-integrity.js';
|
|
19
19
|
import { assertMechanicalFeasibilityFresh, evaluateMechanicalFeasibility, hashMechanicalFeasibilityEvidence, } from './alpha6/mechanical-feasibility.js';
|
|
@@ -281,6 +281,92 @@ export class WorkflowService {
|
|
|
281
281
|
invalidation: invalidation.event,
|
|
282
282
|
};
|
|
283
283
|
}
|
|
284
|
+
recoverDownstreamProofReplan(repository, taskId, expectedTaskRevision, actor, reason) {
|
|
285
|
+
const context = this.#mutationContext(repository);
|
|
286
|
+
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
287
|
+
assertExpectedRevision(task, expectedTaskRevision);
|
|
288
|
+
// This repair intentionally runs before Project Knowledge refresh: the exact
|
|
289
|
+
// dependency-only transport can make the current map stale, while the
|
|
290
|
+
// recovery assessment still binds the stranded Task to its recorded rebind.
|
|
291
|
+
const observation = this.observationSnapshot(repository).observation;
|
|
292
|
+
if (observation.kind !== 'assessment') {
|
|
293
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan recovery requires a coherent observation boundary.', {
|
|
294
|
+
observation,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
|
|
298
|
+
const assessment = assessDownstreamProofReplanRecovery(this.store, context.identity.repositoryRoot, task, { currentPlanRiskAuditExists });
|
|
299
|
+
const environmentBlockers = [
|
|
300
|
+
...(!assessment.dependencyRecoveryCommit
|
|
301
|
+
? ['The exact beta.12.7 dependency compatibility must be registered before Task recovery.']
|
|
302
|
+
: []),
|
|
303
|
+
...(observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
|
|
304
|
+
...(observation.pendingProjectTransactions.length > 0
|
|
305
|
+
|| observation.pendingMilestoneTransactions.length > 0
|
|
306
|
+
|| observation.pendingTaskTransactions.length > 0
|
|
307
|
+
? ['No pending Workflow transaction may exist.'] : []),
|
|
308
|
+
...(observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
|
|
309
|
+
...(observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
|
|
310
|
+
];
|
|
311
|
+
if (!assessment.applicable || !assessment.eligible || environmentBlockers.length > 0) {
|
|
312
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Task is not eligible for bounded downstream-proof replan recovery.', {
|
|
313
|
+
taskId,
|
|
314
|
+
blockers: [...assessment.blockers, ...environmentBlockers],
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const now = this.now();
|
|
318
|
+
const resultTask = {
|
|
319
|
+
...task,
|
|
320
|
+
revision: task.revision + 1,
|
|
321
|
+
updatedAt: now.toISOString(),
|
|
322
|
+
status: 'needs_fix',
|
|
323
|
+
authorizations: task.authorizations.map((authorization) => authorization.kind === 'execution' && authorization.decision === 'approved'
|
|
324
|
+
? { ...authorization, decision: 'superseded' }
|
|
325
|
+
: authorization),
|
|
326
|
+
review: emptyReview(),
|
|
327
|
+
resultHash: null,
|
|
328
|
+
evidenceHash: null,
|
|
329
|
+
blockReason: `${assessment.invalidation.activeStepId} downstream-proof recovery requires replacement of the full unfinished Step Plan before execution can resume.`,
|
|
330
|
+
};
|
|
331
|
+
const recovery = prepareDownstreamProofReplanRecovery(this.store, resultTask, assessment, { previousTaskRevision: task.revision, actor, reason }, now);
|
|
332
|
+
const projectRoot = this.store.projectRoot(task.projectId);
|
|
333
|
+
const relative = (absolute) => path.relative(projectRoot, absolute).split(path.sep).join('/');
|
|
334
|
+
const taskRoot = this.store.taskRoot(task.projectId, task.id);
|
|
335
|
+
applyProjectTransaction({
|
|
336
|
+
projectRoot,
|
|
337
|
+
projectId: task.projectId,
|
|
338
|
+
transactionId: `tx-${createEntityId('TASK', now.getTime()).toLowerCase()}`,
|
|
339
|
+
kind: 'downstream-proof-replan-recovery',
|
|
340
|
+
now,
|
|
341
|
+
targets: [
|
|
342
|
+
{
|
|
343
|
+
path: relative(path.join(taskRoot, downstreamProofReplanRecoverySidecar())),
|
|
344
|
+
content: recovery.postimage,
|
|
345
|
+
},
|
|
346
|
+
{ path: relative(path.join(taskRoot, 'state.json')), content: `${JSON.stringify(resultTask, null, 2)}\n` },
|
|
347
|
+
],
|
|
348
|
+
validate: () => {
|
|
349
|
+
const saved = this.store.readTask(task.projectId, task.id);
|
|
350
|
+
const events = readDownstreamProofReplanRecoveries(this.store, saved);
|
|
351
|
+
if (saved.revision !== resultTask.revision
|
|
352
|
+
|| saved.status !== 'needs_fix'
|
|
353
|
+
|| saved.planHash !== task.planHash
|
|
354
|
+
|| events.at(-1)?.eventHash !== recovery.event.eventHash
|
|
355
|
+
|| headCommit(context.identity.repositoryRoot) !== recovery.event.preservedHeadCommit
|
|
356
|
+
|| JSON.stringify(changedFiles(context.identity.repositoryRoot).sort())
|
|
357
|
+
!== JSON.stringify(recovery.event.dirtyFiles)) {
|
|
358
|
+
throw new WorkflowError('STATE_CORRUPT', 'Downstream-proof replan recovery transaction readback failed.', {
|
|
359
|
+
taskId,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
validateTaskHistory(context.identity.repositoryRoot, saved);
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
return {
|
|
366
|
+
task: this.store.readTask(task.projectId, task.id),
|
|
367
|
+
recovery: recovery.event,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
284
370
|
correctiveReplanController(repository) {
|
|
285
371
|
const identity = resolveRepositoryIdentity(repository);
|
|
286
372
|
return createCorrectiveReplanPublicController({
|
|
@@ -1177,7 +1263,10 @@ export class WorkflowService {
|
|
|
1177
1263
|
const overriddenStopDecisionEventIds = stopEscalateOverrideDecisionEventIds(this.store, task);
|
|
1178
1264
|
const currentHardBlockingDecisions = currentPlanCorrectiveDecisions.filter((event) => event.decision === 'split-required'
|
|
1179
1265
|
|| (event.decision === 'stop-escalate' && !overriddenStopDecisionEventIds.has(event.eventId)));
|
|
1180
|
-
const
|
|
1266
|
+
const downstreamReplanRecovered = readDownstreamProofReplanRecoveries(this.store, task)
|
|
1267
|
+
.some((event) => event.resultTaskRevision === task.revision && event.reboundPlanHash === task.planHash);
|
|
1268
|
+
const currentPlanRiskAudit = (task.status === 'needs_fix' || currentReplanDecisions.length > 0)
|
|
1269
|
+
&& !downstreamReplanRecovered
|
|
1181
1270
|
? readCurrentPlanRiskAudit(this.store, task)
|
|
1182
1271
|
: null;
|
|
1183
1272
|
const classifiedCorrectivePosture = currentReplanDecisions.length > 0 && currentPlanRiskAudit
|
|
@@ -1946,9 +2035,18 @@ export class WorkflowService {
|
|
|
1946
2035
|
}
|
|
1947
2036
|
const knowledgeMap = this.assertTaskKnowledgeBinding(context.identity.repositoryRoot, task);
|
|
1948
2037
|
const dirty = changedFiles(context.identity.repositoryRoot);
|
|
1949
|
-
|
|
2038
|
+
const downstreamProofCarryover = step.status === 'planned' && dirty.length > 0
|
|
2039
|
+
? assessDownstreamProofCarryover(this.store, context.identity.repositoryRoot, task, stepId)
|
|
2040
|
+
: null;
|
|
2041
|
+
if (step.status === 'planned'
|
|
2042
|
+
&& dirty.length > 0
|
|
2043
|
+
&& !resumesCorrectiveReplan
|
|
2044
|
+
&& !downstreamProofCarryover?.eligible) {
|
|
1950
2045
|
throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} must start from a clean checkout.`, {
|
|
1951
2046
|
changedFiles: dirty,
|
|
2047
|
+
...(downstreamProofCarryover?.applicable
|
|
2048
|
+
? { downstreamProofCarryoverBlockers: downstreamProofCarryover.blockers }
|
|
2049
|
+
: {}),
|
|
1952
2050
|
});
|
|
1953
2051
|
}
|
|
1954
2052
|
const dirtyOutsideStep = dirty.filter((file) => !pathAllowed(file, step.allowedWrites));
|
|
@@ -2907,7 +3005,154 @@ export class WorkflowService {
|
|
|
2907
3005
|
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
2908
3006
|
return this.inspectDependencyProvenanceCandidate(snapshot, task);
|
|
2909
3007
|
}
|
|
3008
|
+
activeDownstreamProofDependencyRecoveryPreflight(repository, taskId) {
|
|
3009
|
+
const snapshot = this.observationSnapshot(repository);
|
|
3010
|
+
if (snapshot.observation.kind !== 'assessment') {
|
|
3011
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Active downstream-proof dependency recovery requires a coherent observation boundary.', {
|
|
3012
|
+
observation: snapshot.observation,
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
3016
|
+
if (!task)
|
|
3017
|
+
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
3018
|
+
const assessment = assessDownstreamProofRecovery(this.store, snapshot.identity.repositoryRoot, task, { historyBoundary: 'HEAD^', preservedHeadCommit: headCommit(snapshot.identity.repositoryRoot) });
|
|
3019
|
+
if (!assessment.applicable
|
|
3020
|
+
|| !assessment.eligible
|
|
3021
|
+
|| !assessment.activeStepId
|
|
3022
|
+
|| !assessment.dirtyWorktreeHash
|
|
3023
|
+
|| !task.planHash) {
|
|
3024
|
+
const ordinary = this.inspectDependencyProvenanceCandidate(snapshot, task);
|
|
3025
|
+
return {
|
|
3026
|
+
...ordinary,
|
|
3027
|
+
eligible: false,
|
|
3028
|
+
action: null,
|
|
3029
|
+
activeDownstreamProof: null,
|
|
3030
|
+
blockers: [...new Set([
|
|
3031
|
+
...ordinary.blockers,
|
|
3032
|
+
...assessment.blockers,
|
|
3033
|
+
'The Task must have one eligible active downstream-proof predecessor invalidation at the candidate parent boundary.',
|
|
3034
|
+
])],
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
3037
|
+
return this.inspectDependencyProvenanceCandidate(snapshot, task, {
|
|
3038
|
+
activeStepId: assessment.activeStepId,
|
|
3039
|
+
planHash: task.planHash,
|
|
3040
|
+
dirtyFiles: assessment.dirtyFiles,
|
|
3041
|
+
dirtyWorktreeHash: assessment.dirtyWorktreeHash,
|
|
3042
|
+
invalidatedStepIds: assessment.invalidatedSteps.map((step) => step.stepId),
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
3045
|
+
downstreamProofReplanUpdatePreflight(repository, taskId) {
|
|
3046
|
+
const snapshot = this.observationSnapshot(repository);
|
|
3047
|
+
if (snapshot.observation.kind !== 'assessment') {
|
|
3048
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan update preflight requires a coherent observation boundary.', {
|
|
3049
|
+
observation: snapshot.observation,
|
|
3050
|
+
});
|
|
3051
|
+
}
|
|
3052
|
+
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
3053
|
+
if (!task)
|
|
3054
|
+
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
3055
|
+
const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
|
|
3056
|
+
const assessment = assessDownstreamProofReplanRecovery(this.store, snapshot.identity.repositoryRoot, task, { currentPlanRiskAuditExists });
|
|
3057
|
+
const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
|
|
3058
|
+
const sourceVersion = '2.0.0-beta.12.6';
|
|
3059
|
+
const versionSurfaces = {
|
|
3060
|
+
declared: versions.declared,
|
|
3061
|
+
locked: versions.locked,
|
|
3062
|
+
installed: versions.installed,
|
|
3063
|
+
currentBranch: versions.currentBranch,
|
|
3064
|
+
baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
|
|
3065
|
+
};
|
|
3066
|
+
const blockers = [
|
|
3067
|
+
...assessment.blockers,
|
|
3068
|
+
...(String(PACKAGE_VERSION) === sourceVersion ? ['Recovery runner must be newer than the beta.12.6 source package.'] : []),
|
|
3069
|
+
...Object.entries(versionSurfaces)
|
|
3070
|
+
.filter(([, version]) => version !== sourceVersion)
|
|
3071
|
+
.map(([surface]) => `${surface} Workflow version must equal the stranded source ${sourceVersion}.`),
|
|
3072
|
+
...(snapshot.observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
|
|
3073
|
+
...(snapshot.observation.pendingProjectTransactions.length > 0
|
|
3074
|
+
|| snapshot.observation.pendingMilestoneTransactions.length > 0
|
|
3075
|
+
|| snapshot.observation.pendingTaskTransactions.length > 0
|
|
3076
|
+
? ['No pending Workflow transaction may exist.'] : []),
|
|
3077
|
+
...(snapshot.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
|
|
3078
|
+
...(snapshot.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
|
|
3079
|
+
];
|
|
3080
|
+
return {
|
|
3081
|
+
readOnly: true,
|
|
3082
|
+
eligible: assessment.applicable && assessment.eligible && blockers.length === 0,
|
|
3083
|
+
action: assessment.applicable && assessment.eligible && blockers.length === 0
|
|
3084
|
+
? 'update downstream-proof-replan-transport'
|
|
3085
|
+
: null,
|
|
3086
|
+
projectId: task.projectId,
|
|
3087
|
+
taskId: task.id,
|
|
3088
|
+
taskRevision: task.revision,
|
|
3089
|
+
sourceVersion,
|
|
3090
|
+
targetVersion: PACKAGE_VERSION,
|
|
3091
|
+
sourceHeadCommit: assessment.invalidation?.preservedHeadCommit ?? null,
|
|
3092
|
+
taskBranch: task.taskBranch,
|
|
3093
|
+
baseBranch: task.baseBranch,
|
|
3094
|
+
dirtyFiles: assessment.compatibility?.dirtyFiles ?? [],
|
|
3095
|
+
dirtyWorktreeHash: assessment.compatibility?.dirtyWorktreeHash ?? null,
|
|
3096
|
+
requiredTransport: [
|
|
3097
|
+
'Commit only package.json and package-lock.json on the active Milestone base.',
|
|
3098
|
+
'Commit only the same dependency files on the Task branch while preserving all product bytes.',
|
|
3099
|
+
'Install the exact target package, then follow fresh next to dependency registration.',
|
|
3100
|
+
],
|
|
3101
|
+
blockers,
|
|
3102
|
+
};
|
|
3103
|
+
}
|
|
3104
|
+
downstreamProofReplanDependencyRecoveryPreflight(repository, taskId) {
|
|
3105
|
+
const snapshot = this.observationSnapshot(repository);
|
|
3106
|
+
if (snapshot.observation.kind !== 'assessment') {
|
|
3107
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan dependency recovery requires a coherent observation boundary.', {
|
|
3108
|
+
observation: snapshot.observation,
|
|
3109
|
+
});
|
|
3110
|
+
}
|
|
3111
|
+
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
3112
|
+
if (!task)
|
|
3113
|
+
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
3114
|
+
let candidateParent = null;
|
|
3115
|
+
try {
|
|
3116
|
+
candidateParent = runGit(snapshot.identity.repositoryRoot, ['rev-parse', 'HEAD^']);
|
|
3117
|
+
}
|
|
3118
|
+
catch {
|
|
3119
|
+
candidateParent = null;
|
|
3120
|
+
}
|
|
3121
|
+
const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
|
|
3122
|
+
const assessment = candidateParent
|
|
3123
|
+
? assessDownstreamProofReplanRecovery(this.store, snapshot.identity.repositoryRoot, task, {
|
|
3124
|
+
historyBoundary: 'HEAD^',
|
|
3125
|
+
preservedHeadCommit: candidateParent,
|
|
3126
|
+
currentPlanRiskAuditExists,
|
|
3127
|
+
})
|
|
3128
|
+
: null;
|
|
3129
|
+
if (!assessment?.applicable || !assessment.eligible || !assessment.compatibility) {
|
|
3130
|
+
const ordinary = this.inspectDependencyProvenanceCandidate(snapshot, task);
|
|
3131
|
+
return {
|
|
3132
|
+
...ordinary,
|
|
3133
|
+
eligible: false,
|
|
3134
|
+
action: null,
|
|
3135
|
+
activeDownstreamProof: null,
|
|
3136
|
+
downstreamProofReplan: null,
|
|
3137
|
+
blockers: [...new Set([
|
|
3138
|
+
...ordinary.blockers,
|
|
3139
|
+
...(assessment?.blockers ?? []),
|
|
3140
|
+
'The Task must preserve the exact beta.12.6 downstream-proof invalidation and accidental knowledge-rebind boundary.',
|
|
3141
|
+
])],
|
|
3142
|
+
};
|
|
3143
|
+
}
|
|
3144
|
+
return this.inspectDependencyProvenanceCandidate(snapshot, task, null, assessment.compatibility);
|
|
3145
|
+
}
|
|
2910
3146
|
recoverDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
|
|
3147
|
+
return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, false);
|
|
3148
|
+
}
|
|
3149
|
+
recoverActiveDownstreamProofDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
|
|
3150
|
+
return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, true);
|
|
3151
|
+
}
|
|
3152
|
+
recoverDownstreamProofReplanDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
|
|
3153
|
+
return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, 'downstream-replan');
|
|
3154
|
+
}
|
|
3155
|
+
recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, recoveryMode) {
|
|
2911
3156
|
const normalizedActor = actor.trim();
|
|
2912
3157
|
const normalizedReason = reason.trim();
|
|
2913
3158
|
if (!normalizedActor)
|
|
@@ -2917,7 +3162,11 @@ export class WorkflowService {
|
|
|
2917
3162
|
const context = this.#mutationContext(repository);
|
|
2918
3163
|
const current = context.store.readTask(context.identity.projectId, taskId);
|
|
2919
3164
|
const currentHead = headCommit(context.identity.repositoryRoot);
|
|
2920
|
-
const prior = current.dependencyProvenanceRecoveries?.find((record) => record.commitSha === currentHead
|
|
3165
|
+
const prior = current.dependencyProvenanceRecoveries?.find((record) => record.commitSha === currentHead
|
|
3166
|
+
&& record.packageVersion === PACKAGE_VERSION
|
|
3167
|
+
&& (recoveryMode === 'downstream-replan'
|
|
3168
|
+
? Boolean(record.downstreamProofReplan)
|
|
3169
|
+
: Boolean(record.activeDownstreamProof) === recoveryMode));
|
|
2921
3170
|
if (prior && current.systemCommits.includes(currentHead))
|
|
2922
3171
|
return current;
|
|
2923
3172
|
assertExpectedRevision(current, expectedRevision);
|
|
@@ -2925,7 +3174,11 @@ export class WorkflowService {
|
|
|
2925
3174
|
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
2926
3175
|
if (!task)
|
|
2927
3176
|
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
2928
|
-
const preflight =
|
|
3177
|
+
const preflight = recoveryMode === 'downstream-replan'
|
|
3178
|
+
? this.downstreamProofReplanDependencyRecoveryPreflight(repository, taskId)
|
|
3179
|
+
: recoveryMode
|
|
3180
|
+
? this.activeDownstreamProofDependencyRecoveryPreflight(repository, taskId)
|
|
3181
|
+
: this.inspectDependencyProvenanceCandidate(snapshot, task);
|
|
2929
3182
|
if (!preflight.eligible || !preflight.commitSha || !preflight.parentCommitSha) {
|
|
2930
3183
|
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'The bounded dependency provenance recovery preflight failed.', {
|
|
2931
3184
|
taskId,
|
|
@@ -2947,6 +3200,12 @@ export class WorkflowService {
|
|
|
2947
3200
|
reason: normalizedReason,
|
|
2948
3201
|
recordedAt: this.now().toISOString(),
|
|
2949
3202
|
retainedHistoricalCommits: preflight.retainedHistoricalCommits,
|
|
3203
|
+
...(preflight.activeDownstreamProof
|
|
3204
|
+
? { activeDownstreamProof: preflight.activeDownstreamProof }
|
|
3205
|
+
: {}),
|
|
3206
|
+
...(preflight.downstreamProofReplan
|
|
3207
|
+
? { downstreamProofReplan: preflight.downstreamProofReplan }
|
|
3208
|
+
: {}),
|
|
2950
3209
|
},
|
|
2951
3210
|
],
|
|
2952
3211
|
}, expectedRevision);
|
|
@@ -3360,6 +3619,38 @@ export class WorkflowService {
|
|
|
3360
3619
|
dependencyProvenanceRecovery: dependencyRecovery,
|
|
3361
3620
|
});
|
|
3362
3621
|
}
|
|
3622
|
+
const activeDownstreamProofDependencyRecovery = this.activeDownstreamProofDependencyRecoveryPreflight(repositoryRoot, activeTask.id);
|
|
3623
|
+
if (activeDownstreamProofDependencyRecovery.eligible) {
|
|
3624
|
+
return withAdoption({
|
|
3625
|
+
scope: 'task',
|
|
3626
|
+
id: activeTask.id,
|
|
3627
|
+
status: activeTask.status,
|
|
3628
|
+
action: 'update downstream-proof-dependency-recover',
|
|
3629
|
+
revision: activeTask.revision,
|
|
3630
|
+
dependencyProvenanceRecovery: activeDownstreamProofDependencyRecovery,
|
|
3631
|
+
compatibilityBoundary: {
|
|
3632
|
+
preservesWorktree: true,
|
|
3633
|
+
prerequisite: 'The active Milestone base and Task branch already contain only the exact dependency update commits.',
|
|
3634
|
+
continuation: 'Run task downstream-proof-recover after this provenance registration.',
|
|
3635
|
+
},
|
|
3636
|
+
});
|
|
3637
|
+
}
|
|
3638
|
+
const downstreamProofReplanDependencyRecovery = this.downstreamProofReplanDependencyRecoveryPreflight(repositoryRoot, activeTask.id);
|
|
3639
|
+
if (downstreamProofReplanDependencyRecovery.eligible) {
|
|
3640
|
+
return withAdoption({
|
|
3641
|
+
scope: 'task',
|
|
3642
|
+
id: activeTask.id,
|
|
3643
|
+
status: activeTask.status,
|
|
3644
|
+
action: 'update downstream-proof-replan-dependency-recover',
|
|
3645
|
+
revision: activeTask.revision,
|
|
3646
|
+
dependencyProvenanceRecovery: downstreamProofReplanDependencyRecovery,
|
|
3647
|
+
compatibilityBoundary: {
|
|
3648
|
+
preservesWorktree: true,
|
|
3649
|
+
prerequisite: 'A newer package preflight approved the exact beta.12.6 stranded replan transport before dependency-only commits were created.',
|
|
3650
|
+
continuation: 'Register this dependency commit, then run task downstream-proof-replan-recover.',
|
|
3651
|
+
},
|
|
3652
|
+
});
|
|
3653
|
+
}
|
|
3363
3654
|
const activeTaskMilestone = activeTask.milestoneId
|
|
3364
3655
|
? milestonesById.get(activeTask.milestoneId) ?? null
|
|
3365
3656
|
: null;
|
|
@@ -3383,36 +3674,109 @@ export class WorkflowService {
|
|
|
3383
3674
|
},
|
|
3384
3675
|
}
|
|
3385
3676
|
: value);
|
|
3677
|
+
const currentPlanRiskAuditExists = activeTask.planHash
|
|
3678
|
+
? readPlanRiskAuditEvents(this.store, activeTask).some((event) => event.taskRevision === activeTask.revision && event.planHash === activeTask.planHash)
|
|
3679
|
+
: false;
|
|
3680
|
+
const downstreamReplanRecovery = assessDownstreamProofReplanRecovery(this.store, repositoryRoot, activeTask, { currentPlanRiskAuditExists });
|
|
3681
|
+
if (downstreamReplanRecovery.applicable) {
|
|
3682
|
+
const environmentBlockers = [
|
|
3683
|
+
...(!downstreamReplanRecovery.dependencyRecoveryCommit
|
|
3684
|
+
? ['The exact beta.12.7 dependency compatibility must be registered before Task recovery.']
|
|
3685
|
+
: []),
|
|
3686
|
+
...(status.observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
|
|
3687
|
+
...(status.observation.pendingProjectTransactions.length > 0
|
|
3688
|
+
|| status.observation.pendingMilestoneTransactions.length > 0
|
|
3689
|
+
|| status.observation.pendingTaskTransactions.length > 0
|
|
3690
|
+
? ['No pending Workflow transaction may exist.'] : []),
|
|
3691
|
+
...(status.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
|
|
3692
|
+
...(status.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
|
|
3693
|
+
];
|
|
3694
|
+
return withAdoption(withMilestoneCancellation(downstreamReplanRecovery.eligible && environmentBlockers.length === 0
|
|
3695
|
+
? {
|
|
3696
|
+
scope: 'task',
|
|
3697
|
+
id: activeTask.id,
|
|
3698
|
+
status: activeTask.status,
|
|
3699
|
+
action: 'task downstream-proof-replan-recover',
|
|
3700
|
+
revision: activeTask.revision,
|
|
3701
|
+
downstreamProofReplanRecovery: {
|
|
3702
|
+
state: 'eligible',
|
|
3703
|
+
invalidationEventId: downstreamReplanRecovery.invalidation.eventId,
|
|
3704
|
+
sourcePlanHash: downstreamReplanRecovery.invalidation.planHash,
|
|
3705
|
+
reboundPlanHash: activeTask.planHash,
|
|
3706
|
+
dirtyFiles: downstreamReplanRecovery.compatibility.dirtyFiles,
|
|
3707
|
+
dirtyWorktreeHash: downstreamReplanRecovery.compatibility.dirtyWorktreeHash,
|
|
3708
|
+
dependencyRecoveryCommit: downstreamReplanRecovery.dependencyRecoveryCommit,
|
|
3709
|
+
boundedEffect: 'restore-needs-fix-and-return-same-task-to-plan-set',
|
|
3710
|
+
},
|
|
3711
|
+
}
|
|
3712
|
+
: {
|
|
3713
|
+
scope: 'task',
|
|
3714
|
+
id: activeTask.id,
|
|
3715
|
+
status: activeTask.status,
|
|
3716
|
+
action: 'doctor',
|
|
3717
|
+
blockedAction: 'task downstream-proof-replan-recover',
|
|
3718
|
+
revision: activeTask.revision,
|
|
3719
|
+
downstreamProofReplanRecovery: {
|
|
3720
|
+
state: 'blocked',
|
|
3721
|
+
blockers: [...downstreamReplanRecovery.blockers, ...environmentBlockers],
|
|
3722
|
+
},
|
|
3723
|
+
}));
|
|
3724
|
+
}
|
|
3386
3725
|
let activePlanRiskAudit = null;
|
|
3387
3726
|
let planRiskAuditRecovery = null;
|
|
3388
|
-
|
|
3727
|
+
const requiresReplacementPlan = activeTask.status === 'needs_fix'
|
|
3728
|
+
&& !activeTask.steps.some((step) => step.status === 'failed');
|
|
3729
|
+
if (activeTask.planHash && !requiresReplacementPlan) {
|
|
3389
3730
|
try {
|
|
3390
3731
|
activePlanRiskAudit = readCurrentPlanRiskAudit(this.store, activeTask);
|
|
3391
3732
|
}
|
|
3392
3733
|
catch (error) {
|
|
3393
3734
|
if (!(error instanceof WorkflowError) || error.code !== 'TRANSITION_BLOCKED')
|
|
3394
3735
|
throw error;
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3736
|
+
const blockingDecision = error.details.decision;
|
|
3737
|
+
planRiskAuditRecovery = blockingDecision === 'stop-escalate' || blockingDecision === 'split-required'
|
|
3738
|
+
? {
|
|
3739
|
+
scope: 'task',
|
|
3740
|
+
id: activeTask.id,
|
|
3741
|
+
status: activeTask.status,
|
|
3742
|
+
action: blockingDecision === 'stop-escalate' ? 'task plan-set' : 'doctor',
|
|
3743
|
+
blockedAction: 'task authorize',
|
|
3744
|
+
revision: activeTask.revision,
|
|
3745
|
+
planRiskAuditDecision: {
|
|
3746
|
+
state: 'recorded-blocking',
|
|
3747
|
+
decision: blockingDecision,
|
|
3748
|
+
reason: error.message,
|
|
3749
|
+
requiredAction: blockingDecision === 'stop-escalate'
|
|
3750
|
+
? 'Replace the rejected Step Plan before requesting fresh authorization.'
|
|
3751
|
+
: 'Resolve the required cross-Task split through an explicit Milestone topology decision.',
|
|
3752
|
+
},
|
|
3753
|
+
...(blockingDecision === 'stop-escalate'
|
|
3754
|
+
? { taskPlanContract: this.taskPlanContract(status.projectId, activeTask) }
|
|
3755
|
+
: {}),
|
|
3756
|
+
}
|
|
3757
|
+
: {
|
|
3758
|
+
scope: 'task',
|
|
3759
|
+
id: activeTask.id,
|
|
3760
|
+
status: activeTask.status,
|
|
3761
|
+
action: 'task plan-risk-audit',
|
|
3762
|
+
revision: activeTask.revision,
|
|
3763
|
+
planRiskAuditRecovery: {
|
|
3764
|
+
state: 'required',
|
|
3765
|
+
reason: error.message,
|
|
3766
|
+
currentBriefHash: activeTask.briefHash,
|
|
3767
|
+
currentPlanHash: activeTask.planHash,
|
|
3768
|
+
knowledgeMapRevision: activeTask.knowledgeMapRevision,
|
|
3769
|
+
knowledgeMapHash: activeTask.knowledgeMapHash,
|
|
3770
|
+
},
|
|
3771
|
+
};
|
|
3410
3772
|
}
|
|
3411
3773
|
}
|
|
3412
3774
|
const taskNext = activeTask.status === 'planning'
|
|
3413
3775
|
? null
|
|
3414
|
-
:
|
|
3415
|
-
|
|
3776
|
+
: requiresReplacementPlan
|
|
3777
|
+
? this.nextForTaskWithDelegation(status.projectId, repositoryRoot, activeTask, null)
|
|
3778
|
+
: planRiskAuditRecovery
|
|
3779
|
+
?? this.nextForTaskWithDelegation(status.projectId, repositoryRoot, activeTask, activePlanRiskAudit);
|
|
3416
3780
|
// Active Step work must outrank Project Knowledge refresh/rebind. Both completion and
|
|
3417
3781
|
// strict review are executable while context refresh correctly remains forbidden.
|
|
3418
3782
|
const hardStop = taskNext?.action === 'task corrective-decision'
|
|
@@ -3420,6 +3784,7 @@ export class WorkflowService {
|
|
|
3420
3784
|
const terminalCorrectiveDecision = taskNext?.correctiveDecisionGate?.decision;
|
|
3421
3785
|
if (taskNext?.action === 'task step-complete'
|
|
3422
3786
|
|| taskNext?.action === 'task step-review'
|
|
3787
|
+
|| taskNext?.action === 'task downstream-proof-recover'
|
|
3423
3788
|
|| taskNext?.action === 'task remediation-mode-recover'
|
|
3424
3789
|
|| taskNext?.action === 'task plan-integrity-recover'
|
|
3425
3790
|
|| taskNext?.blockedAction === 'task plan-integrity-recover'
|
|
@@ -3459,7 +3824,9 @@ export class WorkflowService {
|
|
|
3459
3824
|
? this.store.hashArtifact(this.store.taskRoot(status.projectId, activeTask.id), 'plan.md')
|
|
3460
3825
|
: null)
|
|
3461
3826
|
: null;
|
|
3462
|
-
const contextRefreshOptions = action === 'project-memory reconcile'
|
|
3827
|
+
const contextRefreshOptions = action === 'project-memory reconcile'
|
|
3828
|
+
&& taskNext?.action !== 'task plan-set'
|
|
3829
|
+
&& refreshAssessment?.eligible
|
|
3463
3830
|
? this.delegatedContextRefreshOptions(status.projectId, activeTask, knowledgeMap, refreshAssessment.mode)
|
|
3464
3831
|
: [];
|
|
3465
3832
|
if (requestedTask && action === 'project-memory reconcile' && contextRefreshOptions.length === 0) {
|
|
@@ -3507,6 +3874,9 @@ export class WorkflowService {
|
|
|
3507
3874
|
}));
|
|
3508
3875
|
}
|
|
3509
3876
|
const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
|
|
3877
|
+
if (taskNext?.action === 'task plan-set') {
|
|
3878
|
+
return withAdoption(withMilestoneCancellation(taskNext));
|
|
3879
|
+
}
|
|
3510
3880
|
if (taskCanRebindKnowledge(activeTask) &&
|
|
3511
3881
|
activeTask.planHash &&
|
|
3512
3882
|
(activeTask.knowledgeMapRevision !== knowledgeMap.revision ||
|
|
@@ -3842,6 +4212,12 @@ export class WorkflowService {
|
|
|
3842
4212
|
}
|
|
3843
4213
|
}
|
|
3844
4214
|
const next = this.taskGitNavigationOverride(task) ?? nextForTask(task);
|
|
4215
|
+
const nextStepId = typeof next.stepId === 'string' ? next.stepId : null;
|
|
4216
|
+
const downstreamProofCarryover = next.action === 'task run'
|
|
4217
|
+
&& nextStepId
|
|
4218
|
+
&& changedFiles(repositoryRoot).length > 0
|
|
4219
|
+
? assessDownstreamProofCarryover(this.store, repositoryRoot, task, nextStepId)
|
|
4220
|
+
: null;
|
|
3845
4221
|
let mechanicalFeasibilityOverlay = {};
|
|
3846
4222
|
if (task.status === 'awaiting_execution_authorization' && task.planHash) {
|
|
3847
4223
|
try {
|
|
@@ -4364,6 +4740,29 @@ export class WorkflowService {
|
|
|
4364
4740
|
}
|
|
4365
4741
|
: {}),
|
|
4366
4742
|
...mechanicalFeasibilityOverlay,
|
|
4743
|
+
...(downstreamProofCarryover?.applicable
|
|
4744
|
+
? downstreamProofCarryover.eligible
|
|
4745
|
+
? {
|
|
4746
|
+
downstreamProofCarryover: {
|
|
4747
|
+
state: 'validated',
|
|
4748
|
+
invalidationEventId: downstreamProofCarryover.invalidationEventId,
|
|
4749
|
+
stepId: nextStepId,
|
|
4750
|
+
dirtyFiles: downstreamProofCarryover.dirtyFiles,
|
|
4751
|
+
dirtyWorktreeHash: downstreamProofCarryover.dirtyWorktreeHash,
|
|
4752
|
+
meaning: 'The replacement Step may adopt exactly these preserved uncommitted bytes on task run.',
|
|
4753
|
+
},
|
|
4754
|
+
}
|
|
4755
|
+
: {
|
|
4756
|
+
action: 'doctor',
|
|
4757
|
+
blockedAction: 'task run',
|
|
4758
|
+
downstreamProofCarryover: {
|
|
4759
|
+
state: 'blocked',
|
|
4760
|
+
invalidationEventId: downstreamProofCarryover.invalidationEventId,
|
|
4761
|
+
stepId: nextStepId,
|
|
4762
|
+
blockers: downstreamProofCarryover.blockers,
|
|
4763
|
+
},
|
|
4764
|
+
}
|
|
4765
|
+
: {}),
|
|
4367
4766
|
...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
|
|
4368
4767
|
};
|
|
4369
4768
|
if (handoffPosture.state === 'pending') {
|
|
@@ -4400,6 +4799,12 @@ export class WorkflowService {
|
|
|
4400
4799
|
};
|
|
4401
4800
|
}
|
|
4402
4801
|
assertTaskContextChangeAllowed(task) {
|
|
4802
|
+
if (task.status === 'needs_fix' && !task.steps.some((step) => step.status === 'failed')) {
|
|
4803
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${task.id} requires a replacement Step Plan; Project Knowledge cannot rebind or refresh the stale Plan.`, {
|
|
4804
|
+
taskId: task.id,
|
|
4805
|
+
requiredAction: 'Run task plan-set with the full unfinished remainder before requesting authorization.',
|
|
4806
|
+
});
|
|
4807
|
+
}
|
|
4403
4808
|
const posture = this.taskTerminalCorrectivePosture(task);
|
|
4404
4809
|
if (!posture)
|
|
4405
4810
|
return;
|
|
@@ -4521,6 +4926,7 @@ export class WorkflowService {
|
|
|
4521
4926
|
if (!taskIsActionableFromRepositoryNext(task, milestonesById, tasksById)
|
|
4522
4927
|
|| !task.planHash
|
|
4523
4928
|
|| !taskCanRebindKnowledge(task)
|
|
4929
|
+
|| (task.status === 'needs_fix' && !task.steps.some((step) => step.status === 'failed'))
|
|
4524
4930
|
|| this.taskHasTerminalCorrectiveDecision(task))
|
|
4525
4931
|
continue;
|
|
4526
4932
|
const assessment = assessDelegatedKnowledgeRefresh(approved, inspected, task, this.store.hashArtifact(this.store.taskRoot(projectId, task.id), 'plan.md'));
|
|
@@ -4650,7 +5056,7 @@ export class WorkflowService {
|
|
|
4650
5056
|
},
|
|
4651
5057
|
};
|
|
4652
5058
|
}
|
|
4653
|
-
inspectDependencyProvenanceCandidate(snapshot, task) {
|
|
5059
|
+
inspectDependencyProvenanceCandidate(snapshot, task, activeDownstreamProof = null, downstreamProofReplan = null) {
|
|
4654
5060
|
const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
|
|
4655
5061
|
const now = this.now().getTime();
|
|
4656
5062
|
return inspectDependencyProvenanceRecovery(snapshot.identity.repositoryRoot, task, {
|
|
@@ -4670,7 +5076,7 @@ export class WorkflowService {
|
|
|
4670
5076
|
currentBranch: versions.currentBranch,
|
|
4671
5077
|
baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
|
|
4672
5078
|
},
|
|
4673
|
-
});
|
|
5079
|
+
}, activeDownstreamProof, downstreamProofReplan);
|
|
4674
5080
|
}
|
|
4675
5081
|
withTaskNavigationContracts(task, next) {
|
|
4676
5082
|
const action = typeof next.action === 'string' ? next.action : null;
|