codex-workflow-v2 2.0.0-beta.12.6 → 2.0.0-beta.12.8
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 +21 -3
- package/dist/src/alpha6/downstream-proof.d.ts +35 -1
- package/dist/src/alpha6/downstream-proof.js +310 -2
- 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 +38 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +30 -1
- package/dist/src/dependency-provenance.d.ts +2 -2
- package/dist/src/dependency-provenance.js +26 -13
- package/dist/src/dependency-provenance.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 +9 -2
- package/dist/src/workflow.js +380 -33
- package/dist/src/workflow.js.map +1 -1
- package/docs/autonomy-guardrails.md +7 -0
- package/docs/development-flow.md +9 -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 +25 -5
- package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +22 -7
- package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +33 -25
- package/docs/release.md +9 -1
- package/docs/updating-existing-project.md +24 -0
- package/docs/validation-report.md +88 -96
- package/package.json +2 -1
- package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-workflow-gateway/references/protocol.md +6 -0
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +25 -0
- 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 +34 -0
package/dist/src/workflow.js
CHANGED
|
@@ -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));
|
|
@@ -2635,16 +2733,23 @@ export class WorkflowService {
|
|
|
2635
2733
|
assertCorrectiveAuditorIndependence(this.store, task, stepId, correctiveAudit.auditor);
|
|
2636
2734
|
return appendCorrectiveDecisionEvent(this.store, task, stepId, correctiveAudit, planRiskAudit, task.planHash, this.now());
|
|
2637
2735
|
}
|
|
2638
|
-
setTaskResult(repository, taskId, expectedRevision, summary, limitations) {
|
|
2639
|
-
const
|
|
2736
|
+
setTaskResult(repository, taskId, expectedRevision, summary, limitations, writerToken, actor) {
|
|
2737
|
+
const context = this.#mutationContext(repository);
|
|
2738
|
+
const { identity } = context;
|
|
2640
2739
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
2641
2740
|
assertExpectedRevision(task, expectedRevision);
|
|
2642
2741
|
if (task.status !== 'validating' || task.review.status !== 'passed') {
|
|
2643
2742
|
throw new WorkflowError('TRANSITION_BLOCKED', 'Result requires a passing independent review.');
|
|
2644
2743
|
}
|
|
2645
2744
|
this.assertReviewFresh(identity.repositoryRoot, task);
|
|
2745
|
+
const lease = context.locks.heartbeat(taskId, writerToken);
|
|
2746
|
+
this.assertTaskMutationAllowedByHandoff(task, actor ?? null, lease.token);
|
|
2646
2747
|
const root = this.store.taskRoot(task.projectId, task.id);
|
|
2647
2748
|
const stagedResult = this.store.stageArtifact(root, 'result.md', renderResult(task, summary, limitations));
|
|
2749
|
+
// Human final acceptance is an intentionally unbounded pause. Release the
|
|
2750
|
+
// execution lease before entering that gate so elapsed human time cannot
|
|
2751
|
+
// turn a successful Task into a repository-wide stale-lock repair route.
|
|
2752
|
+
context.locks.release(taskId, lease.token);
|
|
2648
2753
|
const saved = this.store.writeTask({ ...task, status: 'awaiting_final_acceptance', resultHash: stagedResult.hash, blockReason: null }, expectedRevision);
|
|
2649
2754
|
this.store.publishArtifact(stagedResult);
|
|
2650
2755
|
return saved;
|
|
@@ -2944,13 +3049,117 @@ export class WorkflowService {
|
|
|
2944
3049
|
invalidatedStepIds: assessment.invalidatedSteps.map((step) => step.stepId),
|
|
2945
3050
|
});
|
|
2946
3051
|
}
|
|
3052
|
+
downstreamProofReplanUpdatePreflight(repository, taskId) {
|
|
3053
|
+
const snapshot = this.observationSnapshot(repository);
|
|
3054
|
+
if (snapshot.observation.kind !== 'assessment') {
|
|
3055
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan update preflight requires a coherent observation boundary.', {
|
|
3056
|
+
observation: snapshot.observation,
|
|
3057
|
+
});
|
|
3058
|
+
}
|
|
3059
|
+
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
3060
|
+
if (!task)
|
|
3061
|
+
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
3062
|
+
const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
|
|
3063
|
+
const assessment = assessDownstreamProofReplanRecovery(this.store, snapshot.identity.repositoryRoot, task, { currentPlanRiskAuditExists });
|
|
3064
|
+
const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
|
|
3065
|
+
const sourceVersion = '2.0.0-beta.12.6';
|
|
3066
|
+
const versionSurfaces = {
|
|
3067
|
+
declared: versions.declared,
|
|
3068
|
+
locked: versions.locked,
|
|
3069
|
+
installed: versions.installed,
|
|
3070
|
+
currentBranch: versions.currentBranch,
|
|
3071
|
+
baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
|
|
3072
|
+
};
|
|
3073
|
+
const blockers = [
|
|
3074
|
+
...assessment.blockers,
|
|
3075
|
+
...(String(PACKAGE_VERSION) === sourceVersion ? ['Recovery runner must be newer than the beta.12.6 source package.'] : []),
|
|
3076
|
+
...Object.entries(versionSurfaces)
|
|
3077
|
+
.filter(([, version]) => version !== sourceVersion)
|
|
3078
|
+
.map(([surface]) => `${surface} Workflow version must equal the stranded source ${sourceVersion}.`),
|
|
3079
|
+
...(snapshot.observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
|
|
3080
|
+
...(snapshot.observation.pendingProjectTransactions.length > 0
|
|
3081
|
+
|| snapshot.observation.pendingMilestoneTransactions.length > 0
|
|
3082
|
+
|| snapshot.observation.pendingTaskTransactions.length > 0
|
|
3083
|
+
? ['No pending Workflow transaction may exist.'] : []),
|
|
3084
|
+
...(snapshot.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
|
|
3085
|
+
...(snapshot.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
|
|
3086
|
+
];
|
|
3087
|
+
return {
|
|
3088
|
+
readOnly: true,
|
|
3089
|
+
eligible: assessment.applicable && assessment.eligible && blockers.length === 0,
|
|
3090
|
+
action: assessment.applicable && assessment.eligible && blockers.length === 0
|
|
3091
|
+
? 'update downstream-proof-replan-transport'
|
|
3092
|
+
: null,
|
|
3093
|
+
projectId: task.projectId,
|
|
3094
|
+
taskId: task.id,
|
|
3095
|
+
taskRevision: task.revision,
|
|
3096
|
+
sourceVersion,
|
|
3097
|
+
targetVersion: PACKAGE_VERSION,
|
|
3098
|
+
sourceHeadCommit: assessment.invalidation?.preservedHeadCommit ?? null,
|
|
3099
|
+
taskBranch: task.taskBranch,
|
|
3100
|
+
baseBranch: task.baseBranch,
|
|
3101
|
+
dirtyFiles: assessment.compatibility?.dirtyFiles ?? [],
|
|
3102
|
+
dirtyWorktreeHash: assessment.compatibility?.dirtyWorktreeHash ?? null,
|
|
3103
|
+
requiredTransport: [
|
|
3104
|
+
'Commit only package.json and package-lock.json on the active Milestone base.',
|
|
3105
|
+
'Commit only the same dependency files on the Task branch while preserving all product bytes.',
|
|
3106
|
+
'Install the exact target package, then follow fresh next to dependency registration.',
|
|
3107
|
+
],
|
|
3108
|
+
blockers,
|
|
3109
|
+
};
|
|
3110
|
+
}
|
|
3111
|
+
downstreamProofReplanDependencyRecoveryPreflight(repository, taskId) {
|
|
3112
|
+
const snapshot = this.observationSnapshot(repository);
|
|
3113
|
+
if (snapshot.observation.kind !== 'assessment') {
|
|
3114
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan dependency recovery requires a coherent observation boundary.', {
|
|
3115
|
+
observation: snapshot.observation,
|
|
3116
|
+
});
|
|
3117
|
+
}
|
|
3118
|
+
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
3119
|
+
if (!task)
|
|
3120
|
+
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
3121
|
+
let candidateParent = null;
|
|
3122
|
+
try {
|
|
3123
|
+
candidateParent = runGit(snapshot.identity.repositoryRoot, ['rev-parse', 'HEAD^']);
|
|
3124
|
+
}
|
|
3125
|
+
catch {
|
|
3126
|
+
candidateParent = null;
|
|
3127
|
+
}
|
|
3128
|
+
const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
|
|
3129
|
+
const assessment = candidateParent
|
|
3130
|
+
? assessDownstreamProofReplanRecovery(this.store, snapshot.identity.repositoryRoot, task, {
|
|
3131
|
+
historyBoundary: 'HEAD^',
|
|
3132
|
+
preservedHeadCommit: candidateParent,
|
|
3133
|
+
currentPlanRiskAuditExists,
|
|
3134
|
+
})
|
|
3135
|
+
: null;
|
|
3136
|
+
if (!assessment?.applicable || !assessment.eligible || !assessment.compatibility) {
|
|
3137
|
+
const ordinary = this.inspectDependencyProvenanceCandidate(snapshot, task);
|
|
3138
|
+
return {
|
|
3139
|
+
...ordinary,
|
|
3140
|
+
eligible: false,
|
|
3141
|
+
action: null,
|
|
3142
|
+
activeDownstreamProof: null,
|
|
3143
|
+
downstreamProofReplan: null,
|
|
3144
|
+
blockers: [...new Set([
|
|
3145
|
+
...ordinary.blockers,
|
|
3146
|
+
...(assessment?.blockers ?? []),
|
|
3147
|
+
'The Task must preserve the exact beta.12.6 downstream-proof invalidation and accidental knowledge-rebind boundary.',
|
|
3148
|
+
])],
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
return this.inspectDependencyProvenanceCandidate(snapshot, task, null, assessment.compatibility);
|
|
3152
|
+
}
|
|
2947
3153
|
recoverDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
|
|
2948
3154
|
return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, false);
|
|
2949
3155
|
}
|
|
2950
3156
|
recoverActiveDownstreamProofDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
|
|
2951
3157
|
return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, true);
|
|
2952
3158
|
}
|
|
2953
|
-
|
|
3159
|
+
recoverDownstreamProofReplanDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
|
|
3160
|
+
return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, 'downstream-replan');
|
|
3161
|
+
}
|
|
3162
|
+
recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, recoveryMode) {
|
|
2954
3163
|
const normalizedActor = actor.trim();
|
|
2955
3164
|
const normalizedReason = reason.trim();
|
|
2956
3165
|
if (!normalizedActor)
|
|
@@ -2962,7 +3171,9 @@ export class WorkflowService {
|
|
|
2962
3171
|
const currentHead = headCommit(context.identity.repositoryRoot);
|
|
2963
3172
|
const prior = current.dependencyProvenanceRecoveries?.find((record) => record.commitSha === currentHead
|
|
2964
3173
|
&& record.packageVersion === PACKAGE_VERSION
|
|
2965
|
-
&&
|
|
3174
|
+
&& (recoveryMode === 'downstream-replan'
|
|
3175
|
+
? Boolean(record.downstreamProofReplan)
|
|
3176
|
+
: Boolean(record.activeDownstreamProof) === recoveryMode));
|
|
2966
3177
|
if (prior && current.systemCommits.includes(currentHead))
|
|
2967
3178
|
return current;
|
|
2968
3179
|
assertExpectedRevision(current, expectedRevision);
|
|
@@ -2970,9 +3181,11 @@ export class WorkflowService {
|
|
|
2970
3181
|
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
2971
3182
|
if (!task)
|
|
2972
3183
|
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
2973
|
-
const preflight =
|
|
2974
|
-
? this.
|
|
2975
|
-
:
|
|
3184
|
+
const preflight = recoveryMode === 'downstream-replan'
|
|
3185
|
+
? this.downstreamProofReplanDependencyRecoveryPreflight(repository, taskId)
|
|
3186
|
+
: recoveryMode
|
|
3187
|
+
? this.activeDownstreamProofDependencyRecoveryPreflight(repository, taskId)
|
|
3188
|
+
: this.inspectDependencyProvenanceCandidate(snapshot, task);
|
|
2976
3189
|
if (!preflight.eligible || !preflight.commitSha || !preflight.parentCommitSha) {
|
|
2977
3190
|
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'The bounded dependency provenance recovery preflight failed.', {
|
|
2978
3191
|
taskId,
|
|
@@ -2997,6 +3210,9 @@ export class WorkflowService {
|
|
|
2997
3210
|
...(preflight.activeDownstreamProof
|
|
2998
3211
|
? { activeDownstreamProof: preflight.activeDownstreamProof }
|
|
2999
3212
|
: {}),
|
|
3213
|
+
...(preflight.downstreamProofReplan
|
|
3214
|
+
? { downstreamProofReplan: preflight.downstreamProofReplan }
|
|
3215
|
+
: {}),
|
|
3000
3216
|
},
|
|
3001
3217
|
],
|
|
3002
3218
|
}, expectedRevision);
|
|
@@ -3426,6 +3642,22 @@ export class WorkflowService {
|
|
|
3426
3642
|
},
|
|
3427
3643
|
});
|
|
3428
3644
|
}
|
|
3645
|
+
const downstreamProofReplanDependencyRecovery = this.downstreamProofReplanDependencyRecoveryPreflight(repositoryRoot, activeTask.id);
|
|
3646
|
+
if (downstreamProofReplanDependencyRecovery.eligible) {
|
|
3647
|
+
return withAdoption({
|
|
3648
|
+
scope: 'task',
|
|
3649
|
+
id: activeTask.id,
|
|
3650
|
+
status: activeTask.status,
|
|
3651
|
+
action: 'update downstream-proof-replan-dependency-recover',
|
|
3652
|
+
revision: activeTask.revision,
|
|
3653
|
+
dependencyProvenanceRecovery: downstreamProofReplanDependencyRecovery,
|
|
3654
|
+
compatibilityBoundary: {
|
|
3655
|
+
preservesWorktree: true,
|
|
3656
|
+
prerequisite: 'A newer package preflight approved the exact beta.12.6 stranded replan transport before dependency-only commits were created.',
|
|
3657
|
+
continuation: 'Register this dependency commit, then run task downstream-proof-replan-recover.',
|
|
3658
|
+
},
|
|
3659
|
+
});
|
|
3660
|
+
}
|
|
3429
3661
|
const activeTaskMilestone = activeTask.milestoneId
|
|
3430
3662
|
? milestonesById.get(activeTask.milestoneId) ?? null
|
|
3431
3663
|
: null;
|
|
@@ -3449,36 +3681,109 @@ export class WorkflowService {
|
|
|
3449
3681
|
},
|
|
3450
3682
|
}
|
|
3451
3683
|
: value);
|
|
3684
|
+
const currentPlanRiskAuditExists = activeTask.planHash
|
|
3685
|
+
? readPlanRiskAuditEvents(this.store, activeTask).some((event) => event.taskRevision === activeTask.revision && event.planHash === activeTask.planHash)
|
|
3686
|
+
: false;
|
|
3687
|
+
const downstreamReplanRecovery = assessDownstreamProofReplanRecovery(this.store, repositoryRoot, activeTask, { currentPlanRiskAuditExists });
|
|
3688
|
+
if (downstreamReplanRecovery.applicable) {
|
|
3689
|
+
const environmentBlockers = [
|
|
3690
|
+
...(!downstreamReplanRecovery.dependencyRecoveryCommit
|
|
3691
|
+
? ['The exact beta.12.7 dependency compatibility must be registered before Task recovery.']
|
|
3692
|
+
: []),
|
|
3693
|
+
...(status.observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
|
|
3694
|
+
...(status.observation.pendingProjectTransactions.length > 0
|
|
3695
|
+
|| status.observation.pendingMilestoneTransactions.length > 0
|
|
3696
|
+
|| status.observation.pendingTaskTransactions.length > 0
|
|
3697
|
+
? ['No pending Workflow transaction may exist.'] : []),
|
|
3698
|
+
...(status.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
|
|
3699
|
+
...(status.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
|
|
3700
|
+
];
|
|
3701
|
+
return withAdoption(withMilestoneCancellation(downstreamReplanRecovery.eligible && environmentBlockers.length === 0
|
|
3702
|
+
? {
|
|
3703
|
+
scope: 'task',
|
|
3704
|
+
id: activeTask.id,
|
|
3705
|
+
status: activeTask.status,
|
|
3706
|
+
action: 'task downstream-proof-replan-recover',
|
|
3707
|
+
revision: activeTask.revision,
|
|
3708
|
+
downstreamProofReplanRecovery: {
|
|
3709
|
+
state: 'eligible',
|
|
3710
|
+
invalidationEventId: downstreamReplanRecovery.invalidation.eventId,
|
|
3711
|
+
sourcePlanHash: downstreamReplanRecovery.invalidation.planHash,
|
|
3712
|
+
reboundPlanHash: activeTask.planHash,
|
|
3713
|
+
dirtyFiles: downstreamReplanRecovery.compatibility.dirtyFiles,
|
|
3714
|
+
dirtyWorktreeHash: downstreamReplanRecovery.compatibility.dirtyWorktreeHash,
|
|
3715
|
+
dependencyRecoveryCommit: downstreamReplanRecovery.dependencyRecoveryCommit,
|
|
3716
|
+
boundedEffect: 'restore-needs-fix-and-return-same-task-to-plan-set',
|
|
3717
|
+
},
|
|
3718
|
+
}
|
|
3719
|
+
: {
|
|
3720
|
+
scope: 'task',
|
|
3721
|
+
id: activeTask.id,
|
|
3722
|
+
status: activeTask.status,
|
|
3723
|
+
action: 'doctor',
|
|
3724
|
+
blockedAction: 'task downstream-proof-replan-recover',
|
|
3725
|
+
revision: activeTask.revision,
|
|
3726
|
+
downstreamProofReplanRecovery: {
|
|
3727
|
+
state: 'blocked',
|
|
3728
|
+
blockers: [...downstreamReplanRecovery.blockers, ...environmentBlockers],
|
|
3729
|
+
},
|
|
3730
|
+
}));
|
|
3731
|
+
}
|
|
3452
3732
|
let activePlanRiskAudit = null;
|
|
3453
3733
|
let planRiskAuditRecovery = null;
|
|
3454
|
-
|
|
3734
|
+
const requiresReplacementPlan = activeTask.status === 'needs_fix'
|
|
3735
|
+
&& !activeTask.steps.some((step) => step.status === 'failed');
|
|
3736
|
+
if (activeTask.planHash && !requiresReplacementPlan) {
|
|
3455
3737
|
try {
|
|
3456
3738
|
activePlanRiskAudit = readCurrentPlanRiskAudit(this.store, activeTask);
|
|
3457
3739
|
}
|
|
3458
3740
|
catch (error) {
|
|
3459
3741
|
if (!(error instanceof WorkflowError) || error.code !== 'TRANSITION_BLOCKED')
|
|
3460
3742
|
throw error;
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3743
|
+
const blockingDecision = error.details.decision;
|
|
3744
|
+
planRiskAuditRecovery = blockingDecision === 'stop-escalate' || blockingDecision === 'split-required'
|
|
3745
|
+
? {
|
|
3746
|
+
scope: 'task',
|
|
3747
|
+
id: activeTask.id,
|
|
3748
|
+
status: activeTask.status,
|
|
3749
|
+
action: blockingDecision === 'stop-escalate' ? 'task plan-set' : 'doctor',
|
|
3750
|
+
blockedAction: 'task authorize',
|
|
3751
|
+
revision: activeTask.revision,
|
|
3752
|
+
planRiskAuditDecision: {
|
|
3753
|
+
state: 'recorded-blocking',
|
|
3754
|
+
decision: blockingDecision,
|
|
3755
|
+
reason: error.message,
|
|
3756
|
+
requiredAction: blockingDecision === 'stop-escalate'
|
|
3757
|
+
? 'Replace the rejected Step Plan before requesting fresh authorization.'
|
|
3758
|
+
: 'Resolve the required cross-Task split through an explicit Milestone topology decision.',
|
|
3759
|
+
},
|
|
3760
|
+
...(blockingDecision === 'stop-escalate'
|
|
3761
|
+
? { taskPlanContract: this.taskPlanContract(status.projectId, activeTask) }
|
|
3762
|
+
: {}),
|
|
3763
|
+
}
|
|
3764
|
+
: {
|
|
3765
|
+
scope: 'task',
|
|
3766
|
+
id: activeTask.id,
|
|
3767
|
+
status: activeTask.status,
|
|
3768
|
+
action: 'task plan-risk-audit',
|
|
3769
|
+
revision: activeTask.revision,
|
|
3770
|
+
planRiskAuditRecovery: {
|
|
3771
|
+
state: 'required',
|
|
3772
|
+
reason: error.message,
|
|
3773
|
+
currentBriefHash: activeTask.briefHash,
|
|
3774
|
+
currentPlanHash: activeTask.planHash,
|
|
3775
|
+
knowledgeMapRevision: activeTask.knowledgeMapRevision,
|
|
3776
|
+
knowledgeMapHash: activeTask.knowledgeMapHash,
|
|
3777
|
+
},
|
|
3778
|
+
};
|
|
3476
3779
|
}
|
|
3477
3780
|
}
|
|
3478
3781
|
const taskNext = activeTask.status === 'planning'
|
|
3479
3782
|
? null
|
|
3480
|
-
:
|
|
3481
|
-
|
|
3783
|
+
: requiresReplacementPlan
|
|
3784
|
+
? this.nextForTaskWithDelegation(status.projectId, repositoryRoot, activeTask, null)
|
|
3785
|
+
: planRiskAuditRecovery
|
|
3786
|
+
?? this.nextForTaskWithDelegation(status.projectId, repositoryRoot, activeTask, activePlanRiskAudit);
|
|
3482
3787
|
// Active Step work must outrank Project Knowledge refresh/rebind. Both completion and
|
|
3483
3788
|
// strict review are executable while context refresh correctly remains forbidden.
|
|
3484
3789
|
const hardStop = taskNext?.action === 'task corrective-decision'
|
|
@@ -3526,7 +3831,9 @@ export class WorkflowService {
|
|
|
3526
3831
|
? this.store.hashArtifact(this.store.taskRoot(status.projectId, activeTask.id), 'plan.md')
|
|
3527
3832
|
: null)
|
|
3528
3833
|
: null;
|
|
3529
|
-
const contextRefreshOptions = action === 'project-memory reconcile'
|
|
3834
|
+
const contextRefreshOptions = action === 'project-memory reconcile'
|
|
3835
|
+
&& taskNext?.action !== 'task plan-set'
|
|
3836
|
+
&& refreshAssessment?.eligible
|
|
3530
3837
|
? this.delegatedContextRefreshOptions(status.projectId, activeTask, knowledgeMap, refreshAssessment.mode)
|
|
3531
3838
|
: [];
|
|
3532
3839
|
if (requestedTask && action === 'project-memory reconcile' && contextRefreshOptions.length === 0) {
|
|
@@ -3574,6 +3881,9 @@ export class WorkflowService {
|
|
|
3574
3881
|
}));
|
|
3575
3882
|
}
|
|
3576
3883
|
const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
|
|
3884
|
+
if (taskNext?.action === 'task plan-set') {
|
|
3885
|
+
return withAdoption(withMilestoneCancellation(taskNext));
|
|
3886
|
+
}
|
|
3577
3887
|
if (taskCanRebindKnowledge(activeTask) &&
|
|
3578
3888
|
activeTask.planHash &&
|
|
3579
3889
|
(activeTask.knowledgeMapRevision !== knowledgeMap.revision ||
|
|
@@ -3909,6 +4219,12 @@ export class WorkflowService {
|
|
|
3909
4219
|
}
|
|
3910
4220
|
}
|
|
3911
4221
|
const next = this.taskGitNavigationOverride(task) ?? nextForTask(task);
|
|
4222
|
+
const nextStepId = typeof next.stepId === 'string' ? next.stepId : null;
|
|
4223
|
+
const downstreamProofCarryover = next.action === 'task run'
|
|
4224
|
+
&& nextStepId
|
|
4225
|
+
&& changedFiles(repositoryRoot).length > 0
|
|
4226
|
+
? assessDownstreamProofCarryover(this.store, repositoryRoot, task, nextStepId)
|
|
4227
|
+
: null;
|
|
3912
4228
|
let mechanicalFeasibilityOverlay = {};
|
|
3913
4229
|
if (task.status === 'awaiting_execution_authorization' && task.planHash) {
|
|
3914
4230
|
try {
|
|
@@ -4431,6 +4747,29 @@ export class WorkflowService {
|
|
|
4431
4747
|
}
|
|
4432
4748
|
: {}),
|
|
4433
4749
|
...mechanicalFeasibilityOverlay,
|
|
4750
|
+
...(downstreamProofCarryover?.applicable
|
|
4751
|
+
? downstreamProofCarryover.eligible
|
|
4752
|
+
? {
|
|
4753
|
+
downstreamProofCarryover: {
|
|
4754
|
+
state: 'validated',
|
|
4755
|
+
invalidationEventId: downstreamProofCarryover.invalidationEventId,
|
|
4756
|
+
stepId: nextStepId,
|
|
4757
|
+
dirtyFiles: downstreamProofCarryover.dirtyFiles,
|
|
4758
|
+
dirtyWorktreeHash: downstreamProofCarryover.dirtyWorktreeHash,
|
|
4759
|
+
meaning: 'The replacement Step may adopt exactly these preserved uncommitted bytes on task run.',
|
|
4760
|
+
},
|
|
4761
|
+
}
|
|
4762
|
+
: {
|
|
4763
|
+
action: 'doctor',
|
|
4764
|
+
blockedAction: 'task run',
|
|
4765
|
+
downstreamProofCarryover: {
|
|
4766
|
+
state: 'blocked',
|
|
4767
|
+
invalidationEventId: downstreamProofCarryover.invalidationEventId,
|
|
4768
|
+
stepId: nextStepId,
|
|
4769
|
+
blockers: downstreamProofCarryover.blockers,
|
|
4770
|
+
},
|
|
4771
|
+
}
|
|
4772
|
+
: {}),
|
|
4434
4773
|
...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
|
|
4435
4774
|
};
|
|
4436
4775
|
if (handoffPosture.state === 'pending') {
|
|
@@ -4467,6 +4806,12 @@ export class WorkflowService {
|
|
|
4467
4806
|
};
|
|
4468
4807
|
}
|
|
4469
4808
|
assertTaskContextChangeAllowed(task) {
|
|
4809
|
+
if (task.status === 'needs_fix' && !task.steps.some((step) => step.status === 'failed')) {
|
|
4810
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${task.id} requires a replacement Step Plan; Project Knowledge cannot rebind or refresh the stale Plan.`, {
|
|
4811
|
+
taskId: task.id,
|
|
4812
|
+
requiredAction: 'Run task plan-set with the full unfinished remainder before requesting authorization.',
|
|
4813
|
+
});
|
|
4814
|
+
}
|
|
4470
4815
|
const posture = this.taskTerminalCorrectivePosture(task);
|
|
4471
4816
|
if (!posture)
|
|
4472
4817
|
return;
|
|
@@ -4588,6 +4933,7 @@ export class WorkflowService {
|
|
|
4588
4933
|
if (!taskIsActionableFromRepositoryNext(task, milestonesById, tasksById)
|
|
4589
4934
|
|| !task.planHash
|
|
4590
4935
|
|| !taskCanRebindKnowledge(task)
|
|
4936
|
+
|| (task.status === 'needs_fix' && !task.steps.some((step) => step.status === 'failed'))
|
|
4591
4937
|
|| this.taskHasTerminalCorrectiveDecision(task))
|
|
4592
4938
|
continue;
|
|
4593
4939
|
const assessment = assessDelegatedKnowledgeRefresh(approved, inspected, task, this.store.hashArtifact(this.store.taskRoot(projectId, task.id), 'plan.md'));
|
|
@@ -4717,7 +5063,7 @@ export class WorkflowService {
|
|
|
4717
5063
|
},
|
|
4718
5064
|
};
|
|
4719
5065
|
}
|
|
4720
|
-
inspectDependencyProvenanceCandidate(snapshot, task, activeDownstreamProof = null) {
|
|
5066
|
+
inspectDependencyProvenanceCandidate(snapshot, task, activeDownstreamProof = null, downstreamProofReplan = null) {
|
|
4721
5067
|
const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
|
|
4722
5068
|
const now = this.now().getTime();
|
|
4723
5069
|
return inspectDependencyProvenanceRecovery(snapshot.identity.repositoryRoot, task, {
|
|
@@ -4737,7 +5083,7 @@ export class WorkflowService {
|
|
|
4737
5083
|
currentBranch: versions.currentBranch,
|
|
4738
5084
|
baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
|
|
4739
5085
|
},
|
|
4740
|
-
}, activeDownstreamProof);
|
|
5086
|
+
}, activeDownstreamProof, downstreamProofReplan);
|
|
4741
5087
|
}
|
|
4742
5088
|
withTaskNavigationContracts(task, next) {
|
|
4743
5089
|
const action = typeof next.action === 'string' ? next.action : null;
|
|
@@ -4775,7 +5121,7 @@ export class WorkflowService {
|
|
|
4775
5121
|
return activeLeaseOwner;
|
|
4776
5122
|
const authorizationKind = ['task merge', 'task merge-confirm'].includes(action)
|
|
4777
5123
|
? 'final_acceptance'
|
|
4778
|
-
: ['task step-complete', 'task downstream-proof-recover', 'task submit'].includes(action)
|
|
5124
|
+
: ['task step-complete', 'task downstream-proof-recover', 'task submit', 'task result-set'].includes(action)
|
|
4779
5125
|
? 'execution'
|
|
4780
5126
|
: null;
|
|
4781
5127
|
if (!authorizationKind)
|
|
@@ -5563,6 +5909,7 @@ function writerTokenContractForAction(action, claimedC1Posture = false) {
|
|
|
5563
5909
|
'task step-complete',
|
|
5564
5910
|
'task downstream-proof-recover',
|
|
5565
5911
|
'task submit',
|
|
5912
|
+
'task result-set',
|
|
5566
5913
|
'task merge',
|
|
5567
5914
|
'task merge-confirm',
|
|
5568
5915
|
];
|