codex-workflow-v2 2.0.0-beta.14 → 2.0.0-beta.14.1
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 +3 -3
- package/dist/reviewer-runtime-build.json +24 -20
- package/dist/src/alpha6/adoption.d.ts +1 -1
- package/dist/src/alpha6/adoption.js +26 -8
- package/dist/src/alpha6/adoption.js.map +1 -1
- package/dist/src/alpha6/corrective-decision-boundary.js +9 -3
- package/dist/src/alpha6/corrective-decision-boundary.js.map +1 -1
- package/dist/src/alpha6/remediation.d.ts +2 -0
- package/dist/src/alpha6/remediation.js +68 -2
- package/dist/src/alpha6/remediation.js.map +1 -1
- package/dist/src/alpha7/autonomy.d.ts +9 -0
- package/dist/src/alpha7/autonomy.js +40 -1
- package/dist/src/alpha7/autonomy.js.map +1 -1
- package/dist/src/beta1/project-transaction.js +14 -4
- package/dist/src/beta1/project-transaction.js.map +1 -1
- package/dist/src/cli-actions.d.ts +3 -3
- package/dist/src/cli-actions.js +5 -0
- package/dist/src/cli-actions.js.map +1 -1
- package/dist/src/cli.js +31 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +19 -2
- package/dist/src/delegation.d.ts +5 -1
- package/dist/src/delegation.js +4 -0
- package/dist/src/delegation.js.map +1 -1
- package/dist/src/failed-checkpoint-update.d.ts +69 -0
- package/dist/src/failed-checkpoint-update.js +171 -0
- package/dist/src/failed-checkpoint-update.js.map +1 -0
- package/dist/src/lifecycle/corrective-replan-credential-core.d.ts +2 -0
- package/dist/src/lifecycle/corrective-replan-credential-core.js +45 -9
- package/dist/src/lifecycle/corrective-replan-credential-core.js.map +1 -1
- package/dist/src/lifecycle/corrective-replan.js +12 -8
- package/dist/src/lifecycle/corrective-replan.js.map +1 -1
- package/dist/src/lifecycle/semantic-registry.js +4 -2
- package/dist/src/lifecycle/semantic-registry.js.map +1 -1
- package/dist/src/observed-routes.js +5 -0
- package/dist/src/observed-routes.js.map +1 -1
- package/dist/src/state/corrective-replan-executor.js +1 -0
- package/dist/src/state/corrective-replan-executor.js.map +1 -1
- package/dist/src/state/corrective-replan-public.d.ts +9 -1
- package/dist/src/state/corrective-replan-public.js +49 -7
- package/dist/src/state/corrective-replan-public.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.js +1 -1
- package/dist/src/workflow-blocker-route.js.map +1 -1
- package/dist/src/workflow.d.ts +40 -1
- package/dist/src/workflow.js +203 -33
- package/dist/src/workflow.js.map +1 -1
- package/docs/autonomy-guardrails.md +4 -4
- package/docs/development-flow.md +8 -6
- package/docs/project-memory.md +2 -2
- package/docs/release.md +7 -1
- package/docs/updating-existing-project.md +37 -0
- package/package.json +1 -1
- package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-workflow-gateway/references/chat-dispatch.md +17 -5
- package/plugins/codex-workflow-gateway/references/protocol.md +39 -43
- package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +13 -3
- package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +18 -33
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +15 -10
- package/references/state-machine.md +3 -1
- package/roles/delivery-coordinator.md +31 -11
- package/roles/scope-lead.md +1 -1
- package/roles/technical-planner.md +4 -1
- package/schemas/adoption-posture-event.schema.json +11 -0
- package/schemas/authorization-event.schema.json +1 -1
- package/schemas/corrective-decision-event.schema.json +23 -3
- package/schemas/corrective-replan-credentials.private.schema.json +8 -5
- package/schemas/corrective-replan-execution-event.private.schema.json +9 -0
- package/schemas/corrective-replan-public.schema.json +2 -1
- package/schemas/delegation-grant.schema.json +1 -1
- package/schemas/task.schema.json +18 -0
- package/schemas/transition-definition.schema.json +4 -0
- package/src/alpha6/adoption.ts +30 -7
- package/src/alpha6/corrective-decision-boundary.ts +9 -3
- package/src/alpha6/remediation.ts +69 -2
- package/src/alpha7/autonomy.ts +39 -1
- package/src/beta1/project-transaction.ts +13 -4
- package/src/cli-actions.ts +5 -0
- package/src/cli.ts +33 -0
- package/src/contracts.ts +19 -1
- package/src/delegation.ts +5 -1
- package/src/failed-checkpoint-update.ts +203 -0
- package/src/lifecycle/corrective-replan-credential-core.ts +39 -10
- package/src/lifecycle/corrective-replan.ts +12 -8
- package/src/lifecycle/semantic-registry.ts +4 -2
- package/src/observed-routes.ts +5 -0
- package/src/state/corrective-replan-executor.ts +1 -0
- package/src/state/corrective-replan-public.ts +50 -6
- package/src/version.ts +1 -1
- package/src/workflow-blocker-route.ts +1 -1
- package/src/workflow.ts +192 -22
|
@@ -13,9 +13,11 @@ import {
|
|
|
13
13
|
} from '../alpha6/remediation.js';
|
|
14
14
|
import { pathAllowed, validatePlan } from '../domain/validation.js';
|
|
15
15
|
import { WorkflowError } from '../errors.js';
|
|
16
|
+
import { assertDelegationCanAuthorize } from '../delegation.js';
|
|
16
17
|
import { hashKnowledgeMap } from '../memory.js';
|
|
17
18
|
import { changedFiles, headCommit } from '../repository.js';
|
|
18
19
|
import { hashDirtyWorktree } from '../alpha6/downstream-proof.js';
|
|
20
|
+
import { readFailedCheckpointDirtyTransport } from '../failed-checkpoint-update.js';
|
|
19
21
|
import { PACKAGE_VERSION } from '../version.js';
|
|
20
22
|
import { GuardedTransitionCatalog } from '../lifecycle/catalog.js';
|
|
21
23
|
import { correctiveReplanBindingManifest } from '../lifecycle/corrective-replan-binding-manifest.js';
|
|
@@ -92,6 +94,7 @@ export interface CorrectiveReplanValidationEvidence {
|
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
export interface CorrectiveReplanPublicCandidate {
|
|
97
|
+
readonly delegation?: { readonly grantId: string; readonly actor: string };
|
|
95
98
|
readonly plan: TaskPlanInput;
|
|
96
99
|
readonly validation: CorrectiveReplanValidationEvidence;
|
|
97
100
|
readonly riskAudit: CorrectiveReplanRiskAuditInput;
|
|
@@ -99,7 +102,7 @@ export interface CorrectiveReplanPublicCandidate {
|
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
export interface CorrectiveReplanHumanGate {
|
|
102
|
-
readonly kind: 'corrective-replan-human-gate';
|
|
105
|
+
readonly kind: 'corrective-replan-human-gate' | 'corrective-replan-delegated-binding';
|
|
103
106
|
readonly version: 1;
|
|
104
107
|
readonly taskId: string;
|
|
105
108
|
readonly preparedAtMs: number;
|
|
@@ -136,6 +139,7 @@ export interface CorrectiveReplanPublicCredential {
|
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
export interface CorrectiveReplanPublicController {
|
|
142
|
+
authorizeDelegated(input: { taskId: string; candidate: CorrectiveReplanPublicCandidate }): CorrectiveReplanPublicCredential;
|
|
139
143
|
prepare(input: { taskId: string; candidate: CorrectiveReplanPublicCandidate }): CorrectiveReplanHumanGateResult;
|
|
140
144
|
confirm(input: {
|
|
141
145
|
taskId: string;
|
|
@@ -192,6 +196,7 @@ export function createCorrectiveReplanPublicController(options: ControllerOption
|
|
|
192
196
|
const prepare = (input: { taskId: string; candidate: CorrectiveReplanPublicCandidate }): CorrectiveReplanHumanGateResult => {
|
|
193
197
|
const owned = ownRequest(input);
|
|
194
198
|
assertPublicCandidate(owned.candidate);
|
|
199
|
+
if (owned.candidate.delegation) throw new WorkflowError('INVALID_ARGUMENT', 'Use corrective-replan-authorize for delegated candidates.');
|
|
195
200
|
const preparedAtMs = now().getTime();
|
|
196
201
|
const chain = buildChain(options, owned.taskId, owned.candidate, preparedAtMs);
|
|
197
202
|
return assertCorrectiveReplanPublicOutput('humanGateResult', deepFreeze({
|
|
@@ -209,6 +214,7 @@ export function createCorrectiveReplanPublicController(options: ControllerOption
|
|
|
209
214
|
}): CorrectiveReplanPublicCredential => {
|
|
210
215
|
const owned = ownRequest(input);
|
|
211
216
|
assertPublicCandidate(owned.candidate);
|
|
217
|
+
if (owned.candidate.delegation) throw new WorkflowError('INVALID_ARGUMENT', 'Delegated candidates cannot use Human confirmation.');
|
|
212
218
|
assertGateTime(owned.gate, now().getTime());
|
|
213
219
|
const chain = buildChain(options, owned.taskId, owned.candidate, owned.gate.preparedAtMs);
|
|
214
220
|
if (!same(chain.gate, owned.gate)) throw new WorkflowError('STATE_CONFLICT', 'Corrective-replan Human gate is stale or malformed.');
|
|
@@ -237,6 +243,17 @@ export function createCorrectiveReplanPublicController(options: ControllerOption
|
|
|
237
243
|
}));
|
|
238
244
|
};
|
|
239
245
|
|
|
246
|
+
const authorizeDelegated = (input: { taskId: string; candidate: CorrectiveReplanPublicCandidate }): CorrectiveReplanPublicCredential => {
|
|
247
|
+
const owned = ownRequest(input); assertPublicCandidate(owned.candidate);
|
|
248
|
+
if (!owned.candidate.delegation) throw new WorkflowError('INVALID_ARGUMENT', 'Delegated corrective replan requires an explicit grant and actor.');
|
|
249
|
+
const chain = buildChain(options, owned.taskId, owned.candidate, now().getTime());
|
|
250
|
+
const confirmation = accepted(chain.kernel.confirmDelegated({ challenge: chain.challenge }), 'Delegated authorization was rejected.');
|
|
251
|
+
const receipt = accepted(chain.kernel.issueReceipt({ planValidation: chain.planValidation, confirmation: confirmation.handle }), 'Delegated receipt issuance was rejected.');
|
|
252
|
+
const event = accepted(chain.kernel.materializeStoredEvent({ receipt: receipt.handle }), 'Plan Validation event materialization was rejected.');
|
|
253
|
+
return assertCorrectiveReplanPublicOutput('credential', deepFreeze({ kind: 'corrective-replan-public-credential', version: 1,
|
|
254
|
+
gate: chain.gate, receipt: receipt.receipt as JsonRecord, planValidationEvent: event.event as JsonRecord }));
|
|
255
|
+
};
|
|
256
|
+
|
|
240
257
|
const execute = (input: {
|
|
241
258
|
taskId: string;
|
|
242
259
|
candidate: CorrectiveReplanPublicCandidate;
|
|
@@ -292,7 +309,7 @@ export function createCorrectiveReplanPublicController(options: ControllerOption
|
|
|
292
309
|
return assertCorrectiveReplanPublicOutput('executionResult', executor(owned.taskId, inertKernel()).recover({ taskId: owned.taskId, transactionId: owned.transactionId }));
|
|
293
310
|
};
|
|
294
311
|
|
|
295
|
-
return deepFreeze({ prepare, confirm, execute, recover });
|
|
312
|
+
return deepFreeze({ prepare, confirm, authorizeDelegated, execute, recover });
|
|
296
313
|
}
|
|
297
314
|
|
|
298
315
|
function assertCanonicalAdmission(
|
|
@@ -397,13 +414,15 @@ function buildChain(
|
|
|
397
414
|
} },
|
|
398
415
|
});
|
|
399
416
|
const validation = accepted(kernel.issuePlanValidation({ taskId, draft: validationDraft(initialSnapshot, candidate.validation) }), 'Plan Validation issuance was rejected.');
|
|
400
|
-
const challenge = accepted(
|
|
417
|
+
const challenge = accepted(candidate.delegation
|
|
418
|
+
? kernel.issueDelegatedChallenge({ planValidation: validation.handle })
|
|
419
|
+
: kernel.issueHumanChallenge({ planValidation: validation.handle }), 'Authorization binding issuance was rejected.');
|
|
401
420
|
const delivery = record(challenge.delivery);
|
|
402
421
|
const binding = record(validation.binding);
|
|
403
422
|
const attestation = record(validation.attestation);
|
|
404
423
|
const values = record(binding.values);
|
|
405
424
|
const gateBase = {
|
|
406
|
-
kind: 'corrective-replan-human-gate' as const,
|
|
425
|
+
kind: candidate.delegation ? 'corrective-replan-delegated-binding' as const : 'corrective-replan-human-gate' as const,
|
|
407
426
|
version: 1 as const,
|
|
408
427
|
taskId,
|
|
409
428
|
preparedAtMs,
|
|
@@ -439,11 +458,11 @@ function confirmChain(
|
|
|
439
458
|
const chain = buildChain(options, taskId, candidate, credential.gate.preparedAtMs, previousEventHash);
|
|
440
459
|
if (!same(chain.gate, credential.gate)) throw new WorkflowError('STATE_CONFLICT', 'Corrective-replan Human gate no longer matches current state.');
|
|
441
460
|
const receiptCandidate = record(credential.receipt);
|
|
442
|
-
const confirmedAtMs = number(get(receiptCandidate, '/human/confirmedAtMs'));
|
|
461
|
+
const confirmedAtMs = number(get(receiptCandidate, candidate.delegation ? '/delegated/authorizedAtMs' : '/human/confirmedAtMs'));
|
|
443
462
|
const issuedAtMs = number(receiptCandidate.issuedAtMs);
|
|
444
463
|
if (!Number.isSafeInteger(confirmedAtMs) || confirmedAtMs !== issuedAtMs) throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-replan receipt time is invalid.');
|
|
445
464
|
chain.clock.set(confirmedAtMs);
|
|
446
|
-
const confirmation = accepted(chain.kernel.confirmHuman({
|
|
465
|
+
const confirmation = accepted(candidate.delegation ? chain.kernel.confirmDelegated({ challenge: chain.challenge }) : chain.kernel.confirmHuman({
|
|
447
466
|
challenge: chain.challenge,
|
|
448
467
|
confirmation: {
|
|
449
468
|
kind: 'corrective-replan-human-confirmation', version: 1,
|
|
@@ -494,6 +513,7 @@ function trustedSnapshot(
|
|
|
494
513
|
}
|
|
495
514
|
const task = options.store.readTask(options.projectId, taskId);
|
|
496
515
|
validateCandidate(task, candidate);
|
|
516
|
+
const delegatedAuthority = candidate.delegation ? delegatedReplanAuthority(options, task, candidate) : null;
|
|
497
517
|
const taskRoot = options.store.taskRoot(options.projectId, taskId);
|
|
498
518
|
if (options.store.hashArtifact(taskRoot, 'brief.md') !== task.briefHash
|
|
499
519
|
|| !task.planHash || options.store.hashArtifact(taskRoot, 'plan.md') !== task.planHash) {
|
|
@@ -537,6 +557,7 @@ function trustedSnapshot(
|
|
|
537
557
|
const remediationTail = matchingRemediations.at(-1);
|
|
538
558
|
if (!remediationTail || matchingRemediations.length !== posture.attemptCount) throw new WorkflowError('STATE_CORRUPT', 'Corrective-replan remediation tail is ambiguous.');
|
|
539
559
|
assertCorrectiveReplanKnowledgeTransition(
|
|
560
|
+
options.store,
|
|
540
561
|
options.repositoryRoot,
|
|
541
562
|
task,
|
|
542
563
|
posture.stepId,
|
|
@@ -601,6 +622,7 @@ function trustedSnapshot(
|
|
|
601
622
|
});
|
|
602
623
|
return {
|
|
603
624
|
snapshotVersion: 2,
|
|
625
|
+
...(delegatedAuthority ? { delegatedAuthority } : {}),
|
|
604
626
|
bindingManifest: correctiveReplanBindingManifest,
|
|
605
627
|
worktree,
|
|
606
628
|
baseValues,
|
|
@@ -625,6 +647,7 @@ function trustedSnapshot(
|
|
|
625
647
|
}
|
|
626
648
|
|
|
627
649
|
function assertCorrectiveReplanKnowledgeTransition(
|
|
650
|
+
store: FileStateStore,
|
|
628
651
|
repositoryRoot: string,
|
|
629
652
|
task: TaskState,
|
|
630
653
|
stepId: string,
|
|
@@ -634,6 +657,13 @@ function assertCorrectiveReplanKnowledgeTransition(
|
|
|
634
657
|
targetKnowledgeHash: string,
|
|
635
658
|
): void {
|
|
636
659
|
if (task.knowledgeMapRevision === targetKnowledgeRevision && task.knowledgeMapHash === targetKnowledgeHash) return;
|
|
660
|
+
const terminal = readRemediationEvents(store, task).filter(event => event.stepId === stepId).at(-1);
|
|
661
|
+
if (terminal?.failureEvidence.dirtyWorktreeHash) {
|
|
662
|
+
const transported = readFailedCheckpointDirtyTransport(store, repositoryRoot, task, stepId,
|
|
663
|
+
terminal.failureEvidence.completionCommit, terminal.failureEvidence.dirtyWorktreeHash);
|
|
664
|
+
if (transported && transported.targetHead === gitHead
|
|
665
|
+
&& remediationEventIds.includes(terminal.eventId)) return;
|
|
666
|
+
}
|
|
637
667
|
const compatibility = [...(task.dependencyProvenanceRecoveries ?? [])].reverse().find((record) =>
|
|
638
668
|
record.commitSha === gitHead
|
|
639
669
|
&& record.packageVersion === PACKAGE_VERSION
|
|
@@ -712,6 +742,20 @@ function guardedStepWorktreeSnapshot(
|
|
|
712
742
|
};
|
|
713
743
|
}
|
|
714
744
|
|
|
745
|
+
function delegatedReplanAuthority(options: ControllerOptions, task: TaskState, candidate: CorrectiveReplanPublicCandidate): JsonRecord {
|
|
746
|
+
const request = candidate.delegation!;
|
|
747
|
+
const grant = options.store.readDelegation(options.projectId, request.grantId);
|
|
748
|
+
const evidence = assertDelegationCanAuthorize(grant, 'task.execution_authorize', request.actor, task, (options.now ?? (() => new Date()))());
|
|
749
|
+
// These are public actor identifiers, not user confirmation or vault credentials.
|
|
750
|
+
const identityHash = (actor: string) => createHash('sha256').update(actor).digest('hex');
|
|
751
|
+
if (candidate.authority.actorIdentityHash !== identityHash(grant.delegate)
|
|
752
|
+
|| candidate.authority.humanPrincipalIdentityHash !== identityHash(grant.principal))
|
|
753
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Delegated corrective actor/principal hashes must match the exact grant actors (SHA256 UTF-8).');
|
|
754
|
+
if (candidate.plan.objective !== task.planObjective)
|
|
755
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Delegated corrective replan cannot change the approved objective; return to Discovery.');
|
|
756
|
+
return { ...evidence, expiresAt: grant.expiresAt };
|
|
757
|
+
}
|
|
758
|
+
|
|
715
759
|
function validateCandidate(task: TaskState, candidate: CorrectiveReplanPublicCandidate): void {
|
|
716
760
|
validatePlan(candidate.plan);
|
|
717
761
|
for (const requirement of task.requirements) if (!candidate.plan.requirements.includes(requirement)) {
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const PACKAGE_NAME = 'codex-workflow-v2';
|
|
2
|
-
export const PACKAGE_VERSION = '2.0.0-beta.14';
|
|
2
|
+
export const PACKAGE_VERSION = '2.0.0-beta.14.1';
|
|
@@ -79,7 +79,7 @@ export function withWorkflowBlockerRoute(next: ObjectValue, packageVersion: stri
|
|
|
79
79
|
projectName: 'codex-workflow-v2',
|
|
80
80
|
chatType: 'workflow-blocker',
|
|
81
81
|
title: patchVersion,
|
|
82
|
-
|
|
82
|
+
modelSelection: 'destination-default',
|
|
83
83
|
requiredPacketFields: [
|
|
84
84
|
'blockerId', 'packageVersion', 'sourceProject', 'sourceThreadId', 'source',
|
|
85
85
|
'freshStatusHash', 'freshNextHash', 'repositoryState', 'expectedContinuation',
|
package/src/workflow.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { assessNavigationDirtyCarryover } from './alpha6/navigation-dirty-carryover.js';
|
|
2
2
|
import { navigationUpdateSource, assertNavigationSourceInstalled, inspectNavigationTransport, type NavigationUpdateReceipt } from './navigation-update.js';
|
|
3
|
+
import { failedCheckpointUpdateSource, assertFailedCheckpointInstalled, inspectFailedCheckpointTransport, readFailedCheckpointDirtyTransport,
|
|
4
|
+
FAILED_CHECKPOINT_SOURCE, FAILED_CHECKPOINT_TARGET, type FailedCheckpointUpdateReceipt } from './failed-checkpoint-update.js';
|
|
3
5
|
import { verifyNavigationUpdateArtifact } from './navigation-update-artifact.js';
|
|
4
6
|
import type { CheckExecutionOptions } from './checks/runner.js';
|
|
5
7
|
import { taskSyncChecks, taskSyncChecksHash, assertNoPendingBaseSyncValidation, assessPendingBaseSyncValidation, assertPendingBaseSyncValidationReady } from './checks/task-sync.js';
|
|
@@ -159,6 +161,7 @@ import {
|
|
|
159
161
|
} from './alpha6/root-cause-replan-carryover.js';
|
|
160
162
|
import {
|
|
161
163
|
appendCorrectiveDecisionEvent,
|
|
164
|
+
firstFailurePlanningObstructionEvidence,
|
|
162
165
|
appendPlanIntegrityRecoveryDecision,
|
|
163
166
|
assertCorrectiveAuditorIndependence,
|
|
164
167
|
assertGuardedRemediationAttemptAllowed,
|
|
@@ -226,6 +229,8 @@ import {
|
|
|
226
229
|
} from './alpha6/review.js';
|
|
227
230
|
import {
|
|
228
231
|
applyMilestoneAutonomyContract,
|
|
232
|
+
bindMilestoneAutonomyToGrant,
|
|
233
|
+
milestoneSemanticScopeHash,
|
|
229
234
|
assertAutonomousPlanEvolution,
|
|
230
235
|
prepareMilestoneAutonomyContract,
|
|
231
236
|
readMilestoneAutonomyEvents,
|
|
@@ -568,6 +573,10 @@ export class WorkflowService {
|
|
|
568
573
|
};
|
|
569
574
|
}
|
|
570
575
|
|
|
576
|
+
authorizeDelegatedCorrectiveReplan(repository: string, taskId: string, candidate: CorrectiveReplanPublicCandidate): CorrectiveReplanPublicCredential {
|
|
577
|
+
return this.correctiveReplanController(repository).authorizeDelegated({ taskId, candidate });
|
|
578
|
+
}
|
|
579
|
+
|
|
571
580
|
prepareCorrectiveReplan(
|
|
572
581
|
repository: string,
|
|
573
582
|
taskId: string,
|
|
@@ -2022,6 +2031,19 @@ export class WorkflowService {
|
|
|
2022
2031
|
}
|
|
2023
2032
|
}
|
|
2024
2033
|
|
|
2034
|
+
bindMilestoneAutonomy(repository: string, milestoneId: string, expectedRevision: number,
|
|
2035
|
+
actor: string, grantId: string): ActiveMilestoneAutonomy {
|
|
2036
|
+
const { identity, locks } = this.#mutationContext(repository);
|
|
2037
|
+
const lease = locks.acquire(milestoneId, 'workflow-core:autonomy-binding');
|
|
2038
|
+
try {
|
|
2039
|
+
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
2040
|
+
assertExpectedRevision(milestone, expectedRevision);
|
|
2041
|
+
this.requireActiveKnowledgeMap(identity.repositoryRoot, identity.projectId);
|
|
2042
|
+
return bindMilestoneAutonomyToGrant({ store: this.store, projectId: identity.projectId,
|
|
2043
|
+
milestone, actor, grantId, now: this.now() });
|
|
2044
|
+
} finally { locks.release(milestoneId, lease.token); }
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2025
2047
|
evolveMilestonePlanAutonomously(
|
|
2026
2048
|
repository: string,
|
|
2027
2049
|
milestoneId: string,
|
|
@@ -2824,7 +2846,7 @@ export class WorkflowService {
|
|
|
2824
2846
|
const { identity } = this.#mutationContext(repository);
|
|
2825
2847
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
2826
2848
|
assertExpectedRevision(task, expectedRevision);
|
|
2827
|
-
const contextBlocker = this.taskContextChangeBlocker(task, 'rebind');
|
|
2849
|
+
const contextBlocker = this.taskContextChangeBlocker(task, 'rebind', identity.repositoryRoot);
|
|
2828
2850
|
// Preserve Plan-corruption precedence over the active-Step rejection.
|
|
2829
2851
|
if (contextBlocker && !contextBlocker.activeStep) throw contextBlocker.error;
|
|
2830
2852
|
if (
|
|
@@ -2935,7 +2957,7 @@ export class WorkflowService {
|
|
|
2935
2957
|
const currentMap = this.store.readKnowledgeMap(identity.projectId);
|
|
2936
2958
|
assertExpectedRevision(task, expectedTaskRevision);
|
|
2937
2959
|
assertExpectedRevision(currentMap, expectedKnowledgeMapRevision);
|
|
2938
|
-
const contextBlocker = this.taskContextChangeBlocker(task, 'refresh');
|
|
2960
|
+
const contextBlocker = this.taskContextChangeBlocker(task, 'refresh', identity.repositoryRoot);
|
|
2939
2961
|
if (contextBlocker) throw contextBlocker.error;
|
|
2940
2962
|
|
|
2941
2963
|
const inspectedMap = inspectKnowledgeMap(
|
|
@@ -2943,8 +2965,12 @@ export class WorkflowService {
|
|
|
2943
2965
|
scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now()),
|
|
2944
2966
|
);
|
|
2945
2967
|
const transitions: string[] = [];
|
|
2946
|
-
const
|
|
2947
|
-
const
|
|
2968
|
+
const retainedAudits = JSON.stringify(readPlanRiskAuditEvents(this.store, task));
|
|
2969
|
+
const retainedRemediation = JSON.stringify(readRemediationEvents(this.store, task));
|
|
2970
|
+
const transport = this.registeredPlanningContextTransport(task, identity.repositoryRoot);
|
|
2971
|
+
const planningRecovery = transport ? null : readFailedStepPlanningRecovery(this.store, task);
|
|
2972
|
+
const planningOnly = Boolean(planningRecovery || transport);
|
|
2973
|
+
const currentPlanRiskAudit = transport ? null : planningRecovery?.current ?? readCurrentPlanRiskAudit(this.store, task);
|
|
2948
2974
|
const currentPlanArtifactHash = task.planHash
|
|
2949
2975
|
? this.store.hashArtifact(this.store.taskRoot(identity.projectId, task.id), 'plan.md')
|
|
2950
2976
|
: null;
|
|
@@ -2954,7 +2980,7 @@ export class WorkflowService {
|
|
|
2954
2980
|
task,
|
|
2955
2981
|
currentPlanArtifactHash,
|
|
2956
2982
|
);
|
|
2957
|
-
if (
|
|
2983
|
+
if (planningOnly && refreshAssessment.mode !== 'delegated-content-only') {
|
|
2958
2984
|
throw new WorkflowError('TRANSITION_BLOCKED', 'Stopped planning context refresh permits content-only Knowledge drift.');
|
|
2959
2985
|
}
|
|
2960
2986
|
|
|
@@ -3041,7 +3067,7 @@ export class WorkflowService {
|
|
|
3041
3067
|
}
|
|
3042
3068
|
transitions.push('task knowledge-rebind');
|
|
3043
3069
|
// A stopped Plan may regain its Knowledge binding, never execution authority.
|
|
3044
|
-
if (!
|
|
3070
|
+
if (!planningOnly) refreshedTask = this.authorizeTask(
|
|
3045
3071
|
repository,
|
|
3046
3072
|
taskId,
|
|
3047
3073
|
refreshedTask.revision,
|
|
@@ -3051,9 +3077,9 @@ export class WorkflowService {
|
|
|
3051
3077
|
: 'Delegated content-only context refresh.',
|
|
3052
3078
|
delegationGrantId,
|
|
3053
3079
|
);
|
|
3054
|
-
if (!
|
|
3080
|
+
if (!planningOnly) transitions.push('task execution authorize');
|
|
3055
3081
|
return {
|
|
3056
|
-
authority:
|
|
3082
|
+
authority: planningOnly ? 'planning-only' as const : 'execution' as const,
|
|
3057
3083
|
mode: refreshAssessment.mode,
|
|
3058
3084
|
transitions,
|
|
3059
3085
|
addedSources: refreshAssessment.addedSources,
|
|
@@ -3063,10 +3089,21 @@ export class WorkflowService {
|
|
|
3063
3089
|
},
|
|
3064
3090
|
validate: (result) => {
|
|
3065
3091
|
const currentTask = this.store.readTask(identity.projectId, task.id);
|
|
3066
|
-
if (currentTask.revision !== result.task.revision || currentTask.status !== (
|
|
3092
|
+
if (currentTask.revision !== result.task.revision || currentTask.status !== (planningOnly ? 'awaiting_execution_authorization' : 'ready')) {
|
|
3067
3093
|
throw new WorkflowError('STATE_CORRUPT', 'Context refresh transaction did not commit the expected Task posture.');
|
|
3068
3094
|
}
|
|
3069
|
-
if (
|
|
3095
|
+
if (transport) {
|
|
3096
|
+
if (!readFailedCheckpointDirtyTransport(this.store, identity.repositoryRoot, currentTask, transport.stepId, transport.sourceHead, transport.sourceDirtyHash)) {
|
|
3097
|
+
throw new WorkflowError('STATE_CORRUPT', 'Transport context proof disappeared.');
|
|
3098
|
+
}
|
|
3099
|
+
if (JSON.stringify(currentTask.steps) !== JSON.stringify(task.steps)
|
|
3100
|
+
|| JSON.stringify(currentTask.authorizations) !== JSON.stringify(task.authorizations)
|
|
3101
|
+
|| JSON.stringify(readPlanRiskAuditEvents(this.store, currentTask)) !== retainedAudits
|
|
3102
|
+
|| JSON.stringify(readRemediationEvents(this.store, currentTask)) !== retainedRemediation) {
|
|
3103
|
+
throw new WorkflowError('STATE_CORRUPT', 'Transport context refresh changed retained evidence.');
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
if (planningRecovery && !transport) {
|
|
3070
3107
|
const rebound = readFailedStepPlanningRecovery(this.store, currentTask);
|
|
3071
3108
|
if (!rebound || rebound.baseline.eventHash !== planningRecovery.baseline.eventHash
|
|
3072
3109
|
|| JSON.stringify(currentTask.steps) !== JSON.stringify(task.steps)
|
|
@@ -5400,6 +5437,7 @@ export class WorkflowService {
|
|
|
5400
5437
|
repository: string,
|
|
5401
5438
|
actor: string,
|
|
5402
5439
|
confirmationCode: string,
|
|
5440
|
+
delegationGrantId: string | null = null,
|
|
5403
5441
|
): AdoptionPostureEvent {
|
|
5404
5442
|
const context = this.#mutationContext(repository);
|
|
5405
5443
|
const tasks = context.store.listTasks(context.identity.projectId);
|
|
@@ -5420,6 +5458,7 @@ export class WorkflowService {
|
|
|
5420
5458
|
},
|
|
5421
5459
|
actor,
|
|
5422
5460
|
confirmationCode,
|
|
5461
|
+
delegationGrantId,
|
|
5423
5462
|
);
|
|
5424
5463
|
}
|
|
5425
5464
|
|
|
@@ -5525,6 +5564,51 @@ export class WorkflowService {
|
|
|
5525
5564
|
return { snapshot, task, root: snapshot.identity.repositoryRoot };
|
|
5526
5565
|
}
|
|
5527
5566
|
|
|
5567
|
+
failedCheckpointUpdateSourcePreflight(repository: string, taskId: string, tarball: string) {
|
|
5568
|
+
try {
|
|
5569
|
+
const { task, root } = this.navigationUpdateBoundary(repository, taskId);
|
|
5570
|
+
assertFailedCheckpointInstalled(root, task, FAILED_CHECKPOINT_SOURCE);
|
|
5571
|
+
const receipt = failedCheckpointUpdateSource(root, this.store, task, verifyNavigationUpdateArtifact(tarball));
|
|
5572
|
+
return { eligible: true, blockers: [] as string[], receipt };
|
|
5573
|
+
} catch (error) {
|
|
5574
|
+
return { eligible: false, blockers: [error instanceof Error ? error.message : String(error)], receipt: null };
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
|
|
5578
|
+
failedCheckpointUpdateTransportPreflight(repository: string, taskId: string, tarball: string, receipt: FailedCheckpointUpdateReceipt) {
|
|
5579
|
+
try {
|
|
5580
|
+
const { task, root } = this.navigationUpdateBoundary(repository, taskId);
|
|
5581
|
+
const proof = inspectFailedCheckpointTransport(root, this.store, task, receipt,
|
|
5582
|
+
verifyNavigationUpdateArtifact(tarball, path.join(root, 'node_modules/codex-workflow-v2')));
|
|
5583
|
+
return { eligible: true, blockers: [] as string[], proof,
|
|
5584
|
+
action: 'update failed-checkpoint-recover', expectedRevision: task.revision, actor: receipt.actor };
|
|
5585
|
+
} catch (error) {
|
|
5586
|
+
return { eligible: false, blockers: [error instanceof Error ? error.message : String(error)], proof: null };
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
|
|
5590
|
+
recoverFailedCheckpointUpdate(repository: string, taskId: string, expectedRevision: number,
|
|
5591
|
+
tarball: string, receipt: FailedCheckpointUpdateReceipt, actor: string, reason: string): TaskState {
|
|
5592
|
+
this.#mutationContext(repository);
|
|
5593
|
+
const { task, root } = this.navigationUpdateBoundary(repository, taskId);
|
|
5594
|
+
assertExpectedRevision(task, expectedRevision);
|
|
5595
|
+
if (!actor.trim() || actor !== receipt?.actor || !reason.trim()) {
|
|
5596
|
+
throw new WorkflowError('INVALID_ARGUMENT', 'Failed-checkpoint update requires the original claimant and an explicit reason.');
|
|
5597
|
+
}
|
|
5598
|
+
const proof = inspectFailedCheckpointTransport(root, this.store, task, receipt,
|
|
5599
|
+
verifyNavigationUpdateArtifact(tarball, path.join(root, 'node_modules/codex-workflow-v2')));
|
|
5600
|
+
const updated: TaskState = { ...task, status: 'awaiting_execution_authorization',
|
|
5601
|
+
systemCommits: [...task.systemCommits, proof.target.commitSha],
|
|
5602
|
+
dependencyProvenanceRecoveries: [...(task.dependencyProvenanceRecoveries ?? []), {
|
|
5603
|
+
...proof.target, packageName: PACKAGE_NAME, files: ['package-lock.json', 'package.json'], actor,
|
|
5604
|
+
reason: `${reason.trim()} [failed-checkpoint-update ${receipt.artifact.sha256}]`,
|
|
5605
|
+
recordedAt: this.now().toISOString(), failedCheckpoint: proof.binding,
|
|
5606
|
+
}],
|
|
5607
|
+
};
|
|
5608
|
+
validateTaskHistory(root, updated);
|
|
5609
|
+
return this.store.writeTask(updated, expectedRevision);
|
|
5610
|
+
}
|
|
5611
|
+
|
|
5528
5612
|
navigationUpdateSourcePreflight(repository: string, taskId: string, tarball: string) {
|
|
5529
5613
|
try {
|
|
5530
5614
|
const { task, root } = this.navigationUpdateBoundary(repository, taskId);
|
|
@@ -6061,6 +6145,7 @@ export class WorkflowService {
|
|
|
6061
6145
|
milestonesById,
|
|
6062
6146
|
current,
|
|
6063
6147
|
inspected,
|
|
6148
|
+
identity.repositoryRoot,
|
|
6064
6149
|
);
|
|
6065
6150
|
if (refreshOwner) {
|
|
6066
6151
|
throw new WorkflowError(
|
|
@@ -6259,7 +6344,16 @@ export class WorkflowService {
|
|
|
6259
6344
|
currentEventType: status.adoption.currentPosture?.eventType ?? null,
|
|
6260
6345
|
};
|
|
6261
6346
|
if (status.adoption.status !== 'applied') {
|
|
6347
|
+
const adoptionApprovals = this.listDelegations(status.projectId).flatMap(grant => {
|
|
6348
|
+
try { return [assertDelegationCanAuthorize(grant, 'project.adopt', grant.delegate,
|
|
6349
|
+
{ kind: 'project', id: status.projectId }, this.now())]; }
|
|
6350
|
+
catch (error) {
|
|
6351
|
+
if (error instanceof WorkflowError && error.code === 'TRANSITION_BLOCKED') return [];
|
|
6352
|
+
throw error;
|
|
6353
|
+
}
|
|
6354
|
+
});
|
|
6262
6355
|
return {
|
|
6356
|
+
...(adoptionApprovals.length ? { delegatedApprovalOptions: adoptionApprovals } : {}),
|
|
6263
6357
|
scope: 'project',
|
|
6264
6358
|
projectId: status.projectId,
|
|
6265
6359
|
status: status.adoption.status,
|
|
@@ -6268,7 +6362,7 @@ export class WorkflowService {
|
|
|
6268
6362
|
observation: status.observation,
|
|
6269
6363
|
navigation: status.adoption.safe
|
|
6270
6364
|
? {
|
|
6271
|
-
kind: 'human-gate',
|
|
6365
|
+
kind: adoptionApprovals.length ? 'delegated' : 'human-gate',
|
|
6272
6366
|
transitionId: null,
|
|
6273
6367
|
cataloged: false,
|
|
6274
6368
|
command: 'state adoption-apply',
|
|
@@ -6290,6 +6384,36 @@ export class WorkflowService {
|
|
|
6290
6384
|
}
|
|
6291
6385
|
const withAdoption = (value: Record<string, unknown>): Record<string, unknown> => ({
|
|
6292
6386
|
...value,
|
|
6387
|
+
delegatedMilestoneAutonomyOptions: status.milestones.flatMap(milestone => {
|
|
6388
|
+
if (!['active', 'awaiting_execution_authorization'].includes(milestone.status) || !milestone.planHash) return [];
|
|
6389
|
+
const previousContract = readMilestoneAutonomyEvents(this.store, status.projectId, milestone.id).at(-1);
|
|
6390
|
+
if (previousContract) {
|
|
6391
|
+
if (previousContract.semanticScopeHash !== milestoneSemanticScopeHash(milestone)) return [];
|
|
6392
|
+
try {
|
|
6393
|
+
const previousGrant = this.store.readDelegation(status.projectId, previousContract.delegationGrantId);
|
|
6394
|
+
if (previousGrant.status === 'active' && Date.parse(previousGrant.expiresAt) > this.now().getTime()) return [];
|
|
6395
|
+
} catch (error) { if (!(error instanceof WorkflowError) || error.code !== 'NOT_FOUND') throw error; }
|
|
6396
|
+
}
|
|
6397
|
+
try { this.requireActiveKnowledgeMap(repository, status.projectId); }
|
|
6398
|
+
catch (error) {
|
|
6399
|
+
if (error instanceof WorkflowError) return [];
|
|
6400
|
+
throw error;
|
|
6401
|
+
}
|
|
6402
|
+
return this.listDelegations(status.projectId).flatMap(grant => {
|
|
6403
|
+
if (grant.scope.kind !== 'milestone' || grant.scope.id !== milestone.id
|
|
6404
|
+
|| !['task.execution_authorize', 'task.final_accept', 'milestone.execution_authorize', 'milestone.final_accept']
|
|
6405
|
+
.every(transition => grant.transitions.some(value => value === transition))) return [];
|
|
6406
|
+
try {
|
|
6407
|
+
assertDelegationCanAuthorize(grant, 'milestone.execution_authorize', grant.delegate, milestone, this.now());
|
|
6408
|
+
return [{ action: 'milestone autonomy-bind', milestoneId: milestone.id,
|
|
6409
|
+
expectedRevision: milestone.revision, actor: grant.delegate, delegationGrantId: grant.id,
|
|
6410
|
+
issuesGrant: false, requiresExactHumanConfirmation: false }];
|
|
6411
|
+
} catch (error) {
|
|
6412
|
+
if (error instanceof WorkflowError && error.code === 'TRANSITION_BLOCKED') return [];
|
|
6413
|
+
throw error;
|
|
6414
|
+
}
|
|
6415
|
+
});
|
|
6416
|
+
}),
|
|
6293
6417
|
...(status.milestones.some((milestone) => milestone.status === 'active' && milestone.planHash)
|
|
6294
6418
|
? { activeMilestoneAutonomyOptions: status.milestones
|
|
6295
6419
|
.filter((milestone) => milestone.status === 'active' && milestone.planHash)
|
|
@@ -6414,7 +6538,7 @@ export class WorkflowService {
|
|
|
6414
6538
|
if (approved?.status === 'active' && approved.approval) {
|
|
6415
6539
|
const inspected = inspectCurrentKnowledgeMap();
|
|
6416
6540
|
if (inspected.status === 'stale') prioritizedRefreshTask = this.findDelegatedContextRefreshOwner(
|
|
6417
|
-
status.projectId, status.tasks, milestonesById, approved, inspected)?.task ?? null;
|
|
6541
|
+
status.projectId, status.tasks, milestonesById, approved, inspected, repositoryRoot)?.task ?? null;
|
|
6418
6542
|
}
|
|
6419
6543
|
}
|
|
6420
6544
|
const activeTask = requestedTask
|
|
@@ -6466,6 +6590,17 @@ export class WorkflowService {
|
|
|
6466
6590
|
dependencyProvenanceRecovery: dependencyRecovery,
|
|
6467
6591
|
});
|
|
6468
6592
|
}
|
|
6593
|
+
if (PACKAGE_VERSION === FAILED_CHECKPOINT_TARGET && activeTask.status === 'awaiting_execution_authorization'
|
|
6594
|
+
&& activeTask.workspaceOwner === 'external' && activeTask.steps.some(step => step.status === 'failed')) {
|
|
6595
|
+
const transport = inspectBoundedWorkflowDependencyCommit(repositoryRoot, headCommit(repositoryRoot));
|
|
6596
|
+
if (transport?.packageVersion === FAILED_CHECKPOINT_TARGET && !activeTask.systemCommits.includes(transport.commitSha)) {
|
|
6597
|
+
return withAdoption({ scope: 'task', id: activeTask.id, revision: activeTask.revision,
|
|
6598
|
+
action: 'update failed-checkpoint-transport-preflight',
|
|
6599
|
+
requiredInputs: ['--tarball <exact target artifact>', '--file <source receipt>'],
|
|
6600
|
+
reason: 'Verify the original failed-checkpoint receipt, native failure and retained product bytes before registering the dependency transport.',
|
|
6601
|
+
});
|
|
6602
|
+
}
|
|
6603
|
+
}
|
|
6469
6604
|
const navigationSourceHead = activeTask.authorizations.filter(item => item.kind === 'execution' && item.decision === 'approved').at(-1)?.headCommit;
|
|
6470
6605
|
if (activeTask.status === 'ready' && navigationSourceHead
|
|
6471
6606
|
&& activeTask.dependencyProvenanceRecoveries?.some(item => item.planIntegrity?.mode === 'navigation-fix-dirty-carryover')) {
|
|
@@ -6807,6 +6942,7 @@ export class WorkflowService {
|
|
|
6807
6942
|
activeTask,
|
|
6808
6943
|
knowledgeMap,
|
|
6809
6944
|
refreshAssessment.mode,
|
|
6945
|
+
repositoryRoot,
|
|
6810
6946
|
)
|
|
6811
6947
|
: [];
|
|
6812
6948
|
if (requestedTask && action === 'project-memory reconcile' && contextRefreshOptions.length === 0) {
|
|
@@ -6816,6 +6952,7 @@ export class WorkflowService {
|
|
|
6816
6952
|
milestonesById,
|
|
6817
6953
|
storedKnowledgeMap,
|
|
6818
6954
|
knowledgeMap,
|
|
6955
|
+
repositoryRoot,
|
|
6819
6956
|
);
|
|
6820
6957
|
if (refreshOwner && refreshOwner.task.id !== requestedTask.id) {
|
|
6821
6958
|
throw new WorkflowError(
|
|
@@ -6847,7 +6984,7 @@ export class WorkflowService {
|
|
|
6847
6984
|
contextRefresh: {
|
|
6848
6985
|
action: 'task context-refresh',
|
|
6849
6986
|
mode: refreshAssessment!.mode,
|
|
6850
|
-
authority: readFailedStepPlanningRecovery(this.store, activeTask) ? 'planning-only' : 'execution',
|
|
6987
|
+
authority: (this.registeredPlanningContextTransport(activeTask, repositoryRoot) || readFailedStepPlanningRecovery(this.store, activeTask)) ? 'planning-only' : 'execution',
|
|
6851
6988
|
addedSources: refreshAssessment!.addedSources,
|
|
6852
6989
|
options: contextRefreshOptions,
|
|
6853
6990
|
},
|
|
@@ -7511,12 +7648,13 @@ export class WorkflowService {
|
|
|
7511
7648
|
: {};
|
|
7512
7649
|
const correctiveReplanOverlay = (posture: Readonly<CorrectiveReplanPosture>): Record<string, unknown> => {
|
|
7513
7650
|
const evaluation = evaluateTaskCorrectiveReplanAvailability(task, posture);
|
|
7651
|
+
const replanDelegations = this.delegatedApprovalOptions(task.projectId, 'task.execution_authorize', task);
|
|
7514
7652
|
const writerLease = inspectRawWriterLease(this.store.lockRoot(task.projectId), task.id, this.store.root);
|
|
7515
7653
|
const command = handoffPosture.state === 'claimed'
|
|
7516
7654
|
? 'task corrective-yield'
|
|
7517
7655
|
: writerLease
|
|
7518
7656
|
? 'doctor'
|
|
7519
|
-
: 'task corrective-replan-prepare';
|
|
7657
|
+
: replanDelegations.length ? 'task corrective-replan-authorize' : 'task corrective-replan-prepare';
|
|
7520
7658
|
if (evaluation.allowed && handoffPosture.state === 'none' && writerLease) {
|
|
7521
7659
|
const navigation = correctiveReplanNavigation(evaluation);
|
|
7522
7660
|
return {
|
|
@@ -7547,16 +7685,19 @@ export class WorkflowService {
|
|
|
7547
7685
|
return evaluation.allowed
|
|
7548
7686
|
? {
|
|
7549
7687
|
action: command,
|
|
7688
|
+
...(replanDelegations.length ? { delegatedApprovalOptions: replanDelegations, correctiveReplanAuthority: { mode: 'delegated', candidateField: 'delegation', actorHash: 'sha256-utf8-grant-delegate', principalHash: 'sha256-utf8-grant-principal', grantTransition: 'task.execution_authorize' } } : {}),
|
|
7550
7689
|
blockedAction: 'task plan-set',
|
|
7551
7690
|
stepId: posture.stepId,
|
|
7552
|
-
navigation: { ...correctiveReplanNavigation(evaluation), command },
|
|
7691
|
+
navigation: { ...correctiveReplanNavigation(evaluation), kind: replanDelegations.length ? 'executable' : 'human-gate', command },
|
|
7553
7692
|
correctiveDecisionGate: {
|
|
7554
7693
|
attemptCount: posture.attemptCount,
|
|
7555
7694
|
decision: posture.correctiveDecision?.decision ?? null,
|
|
7556
7695
|
decisionEventId: posture.correctiveDecision?.eventId ?? null,
|
|
7557
7696
|
requiredAction: handoffPosture.state === 'claimed'
|
|
7558
7697
|
? 'Yield the exact C1 claim and writer lease before preparing replacement authority.'
|
|
7559
|
-
:
|
|
7698
|
+
: replanDelegations.length
|
|
7699
|
+
? 'Prepare an independently validated same-scope candidate with the eligible grant and actor; authorize it without Human confirmation, then execute its exact delegated receipt.'
|
|
7700
|
+
: 'No eligible execution grant: request issuance/expansion of grant for delegate mode; the explicit Human preparation route remains available outside delegate mode.',
|
|
7560
7701
|
},
|
|
7561
7702
|
}
|
|
7562
7703
|
: {
|
|
@@ -8114,12 +8255,14 @@ export class WorkflowService {
|
|
|
8114
8255
|
// fix review. Advertise the existing append-only decision route without turning
|
|
8115
8256
|
// every ordinary repair into a mandatory replan or rewriting that review.
|
|
8116
8257
|
const correctiveDecisionOrdinal = failedStepRemediations.length + 1;
|
|
8258
|
+
const firstFailurePlanningObstruction = planningRecovery && failedStep
|
|
8259
|
+
? firstFailurePlanningObstructionEvidence(this.store, task, failedStep.id) : null;
|
|
8117
8260
|
const optionalCorrectiveDecision = (result.action === 'task run' || planningRecovery !== null)
|
|
8118
8261
|
&& (['needs_fix', 'ready'].includes(task.status) || planningRecovery !== null)
|
|
8119
8262
|
&& failedStep && task.planHash && planRiskAudit
|
|
8120
8263
|
&& planRiskAudit.reviewRequiredStepIds.includes(failedStep.id)
|
|
8121
8264
|
&& !task.steps.some(step => step.status === 'in_progress')
|
|
8122
|
-
&& failedStepRemediations.length >= 2
|
|
8265
|
+
&& (failedStepRemediations.length >= 2 || firstFailurePlanningObstruction !== null)
|
|
8123
8266
|
&& handoffPosture.state !== 'pending'
|
|
8124
8267
|
&& !readCorrectiveDecisionEvents(this.store, task).some(event =>
|
|
8125
8268
|
event.stepId === failedStep.id && event.triggeringAttemptCount === correctiveDecisionOrdinal)
|
|
@@ -8139,12 +8282,13 @@ export class WorkflowService {
|
|
|
8139
8282
|
recommendedAuditor: defaultCorrectiveAuditorActor(task.id),
|
|
8140
8283
|
auditorMustDifferFromLatestStrictReviewer: true,
|
|
8141
8284
|
requiredActor: handoffPosture.state === 'claimed' ? handoffPosture.claimant : null,
|
|
8142
|
-
decisions:
|
|
8285
|
+
decisions: firstFailurePlanningObstruction ? ['replan-required']
|
|
8286
|
+
: planningRecovery ? ['replan-required', 'split-required', 'stop-escalate']
|
|
8143
8287
|
: ['continue-fix', 'replan-required', 'split-required', 'stop-escalate'],
|
|
8144
8288
|
authority: 'none',
|
|
8145
8289
|
preservesOriginalReview: true,
|
|
8146
8290
|
humanApprovalRequiredForDecision: false,
|
|
8147
|
-
|
|
8291
|
+
replanRequiresBoundAuthorization: true,
|
|
8148
8292
|
requiredAction: 'Only for newly assessed evidence: obtain an independent corrective audit, refresh next and existing writer authority, then record the decision. This option does not authorize Plan replacement or extra writes.',
|
|
8149
8293
|
}
|
|
8150
8294
|
: null;
|
|
@@ -8194,7 +8338,28 @@ export class WorkflowService {
|
|
|
8194
8338
|
};
|
|
8195
8339
|
}
|
|
8196
8340
|
|
|
8197
|
-
private
|
|
8341
|
+
private registeredPlanningContextTransport(task: TaskState, repositoryRoot?: string) {
|
|
8342
|
+
const failedTransport = repositoryRoot
|
|
8343
|
+
&& task.status === 'awaiting_execution_authorization'
|
|
8344
|
+
&& !task.steps.some(step => step.status === 'in_progress')
|
|
8345
|
+
&& !readPlanRiskAuditEvents(this.store, task).some(event => event.planHash === task.planHash && event.decision !== 'stop-escalate')
|
|
8346
|
+
&& readCorrectiveDecisionEvents(this.store, task).length === 0
|
|
8347
|
+
? task.dependencyProvenanceRecoveries?.find(record => record.failedCheckpoint
|
|
8348
|
+
&& task.steps.some(step => step.id === record.failedCheckpoint!.stepId && step.status === 'failed'))
|
|
8349
|
+
: null;
|
|
8350
|
+
if (failedTransport?.failedCheckpoint && repositoryRoot) {
|
|
8351
|
+
const binding = failedTransport.failedCheckpoint;
|
|
8352
|
+
// Historical transports remain readable after later legitimate progress. Validate
|
|
8353
|
+
// their artifact first, but require current dirty bytes only for this exact checkpoint.
|
|
8354
|
+
readVerifiedPlanArtifact(this.store.taskRoot(task.projectId, task.id), binding.sourcePlanHash, task.planHash!);
|
|
8355
|
+
if (binding.sourcePlanHash !== task.planHash) return null;
|
|
8356
|
+
if (readFailedCheckpointDirtyTransport(this.store, repositoryRoot, task,
|
|
8357
|
+
binding.stepId, binding.sourceHead, binding.sourceDirtyHash)) return binding;
|
|
8358
|
+
}
|
|
8359
|
+
return null;
|
|
8360
|
+
}
|
|
8361
|
+
|
|
8362
|
+
private taskContextChangeBlocker(task: TaskState, operation: 'rebind' | 'refresh', repositoryRoot?: string): { error: WorkflowError; activeStep: boolean } | null {
|
|
8198
8363
|
const blocked = (error: WorkflowError) => ({ error, activeStep: false });
|
|
8199
8364
|
if (readCurrentPreExecutionReplan(this.store, task)) {
|
|
8200
8365
|
return blocked(new WorkflowError(
|
|
@@ -8213,6 +8378,7 @@ export class WorkflowService {
|
|
|
8213
8378
|
},
|
|
8214
8379
|
));
|
|
8215
8380
|
}
|
|
8381
|
+
if (repositoryRoot && this.registeredPlanningContextTransport(task, repositoryRoot)) return null;
|
|
8216
8382
|
const posture = this.taskTerminalCorrectivePosture(task);
|
|
8217
8383
|
if (posture) {
|
|
8218
8384
|
return blocked(new WorkflowError(
|
|
@@ -8330,10 +8496,12 @@ export class WorkflowService {
|
|
|
8330
8496
|
task: TaskState,
|
|
8331
8497
|
knowledgeMap: ProjectKnowledgeMap,
|
|
8332
8498
|
mode: DelegatedKnowledgeRefreshMode,
|
|
8499
|
+
repositoryRoot?: string,
|
|
8333
8500
|
): Array<DelegatedApprovalOption & { transitions: DelegationTransition[] }> {
|
|
8334
8501
|
let planningRecovery;
|
|
8335
8502
|
try {
|
|
8336
|
-
planningRecovery =
|
|
8503
|
+
planningRecovery = this.registeredPlanningContextTransport(task, repositoryRoot)
|
|
8504
|
+
?? readFailedStepPlanningRecovery(this.store, task);
|
|
8337
8505
|
if (!planningRecovery) readCurrentPlanRiskAudit(this.store, task);
|
|
8338
8506
|
} catch (error) {
|
|
8339
8507
|
if (error instanceof WorkflowError && error.code === 'TRANSITION_BLOCKED') return [];
|
|
@@ -8391,6 +8559,7 @@ export class WorkflowService {
|
|
|
8391
8559
|
milestonesById: Map<string, MilestoneState>,
|
|
8392
8560
|
approved: ProjectKnowledgeMap,
|
|
8393
8561
|
inspected: ProjectKnowledgeMap,
|
|
8562
|
+
repositoryRoot?: string,
|
|
8394
8563
|
): {
|
|
8395
8564
|
task: TaskState;
|
|
8396
8565
|
assessment: DelegatedKnowledgeRefreshAssessment;
|
|
@@ -8414,8 +8583,9 @@ export class WorkflowService {
|
|
|
8414
8583
|
task,
|
|
8415
8584
|
inspected,
|
|
8416
8585
|
assessment.mode,
|
|
8586
|
+
repositoryRoot,
|
|
8417
8587
|
);
|
|
8418
|
-
if (options.length > 0 && this.taskContextChangeBlocker(task, 'refresh') === null) return { task, assessment, options };
|
|
8588
|
+
if (options.length > 0 && this.taskContextChangeBlocker(task, 'refresh', repositoryRoot) === null) return { task, assessment, options };
|
|
8419
8589
|
}
|
|
8420
8590
|
return null;
|
|
8421
8591
|
}
|