codex-workflow-v2 2.0.0-beta.2 → 2.0.0-beta.5
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 +35 -2
- package/dist/src/alpha6/adoption.js +13 -3
- package/dist/src/alpha6/adoption.js.map +1 -1
- package/dist/src/alpha6/handoff.d.ts +23 -0
- package/dist/src/alpha6/handoff.js +221 -28
- package/dist/src/alpha6/handoff.js.map +1 -1
- package/dist/src/alpha6/milestone.d.ts +2 -0
- package/dist/src/alpha6/milestone.js +15 -0
- package/dist/src/alpha6/milestone.js.map +1 -1
- package/dist/src/alpha6/plan-risk.d.ts +1 -0
- package/dist/src/alpha6/plan-risk.js +15 -7
- package/dist/src/alpha6/plan-risk.js.map +1 -1
- package/dist/src/alpha6/review.d.ts +1 -0
- package/dist/src/alpha6/review.js +9 -0
- package/dist/src/alpha6/review.js.map +1 -1
- package/dist/src/cli.js +253 -67
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +111 -5
- package/dist/src/contracts.js +1 -1
- package/dist/src/credential-output.d.ts +10 -0
- package/dist/src/credential-output.js +73 -0
- package/dist/src/credential-output.js.map +1 -0
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/lifecycle/canonical-hash.d.ts +6 -0
- package/dist/src/lifecycle/canonical-hash.js +24 -0
- package/dist/src/lifecycle/canonical-hash.js.map +1 -0
- package/dist/src/lifecycle/catalog.d.ts +26 -0
- package/dist/src/lifecycle/catalog.js +208 -0
- package/dist/src/lifecycle/catalog.js.map +1 -0
- package/dist/src/lifecycle/compiler-inspection.d.ts +48 -0
- package/dist/src/lifecycle/compiler-inspection.js +29 -0
- package/dist/src/lifecycle/compiler-inspection.js.map +1 -0
- package/dist/src/lifecycle/core-static-readiness.d.ts +6 -0
- package/dist/src/lifecycle/core-static-readiness.js +130 -0
- package/dist/src/lifecycle/core-static-readiness.js.map +1 -0
- package/dist/src/lifecycle/corrective-replan-authority.d.ts +53 -0
- package/dist/src/lifecycle/corrective-replan-authority.js +139 -0
- package/dist/src/lifecycle/corrective-replan-authority.js.map +1 -0
- package/dist/src/lifecycle/corrective-replan-binding-manifest.d.ts +21 -0
- package/dist/src/lifecycle/corrective-replan-binding-manifest.js +35 -0
- package/dist/src/lifecycle/corrective-replan-binding-manifest.js.map +1 -0
- package/dist/src/lifecycle/corrective-replan-credential-core.d.ts +86 -0
- package/dist/src/lifecycle/corrective-replan-credential-core.js +563 -0
- package/dist/src/lifecycle/corrective-replan-credential-core.js.map +1 -0
- package/dist/src/lifecycle/corrective-replan-credential-schema.d.ts +17 -0
- package/dist/src/lifecycle/corrective-replan-credential-schema.js +44 -0
- package/dist/src/lifecycle/corrective-replan-credential-schema.js.map +1 -0
- package/dist/src/lifecycle/corrective-replan-credentials.d.ts +47 -0
- package/dist/src/lifecycle/corrective-replan-credentials.js +103 -0
- package/dist/src/lifecycle/corrective-replan-credentials.js.map +1 -0
- package/dist/src/lifecycle/corrective-replan.d.ts +64 -0
- package/dist/src/lifecycle/corrective-replan.js +548 -0
- package/dist/src/lifecycle/corrective-replan.js.map +1 -0
- package/dist/src/lifecycle/evaluator.d.ts +2 -0
- package/dist/src/lifecycle/evaluator.js +35 -0
- package/dist/src/lifecycle/evaluator.js.map +1 -0
- package/dist/src/lifecycle/fingerprint.d.ts +31 -0
- package/dist/src/lifecycle/fingerprint.js +388 -0
- package/dist/src/lifecycle/fingerprint.js.map +1 -0
- package/dist/src/lifecycle/immutable.d.ts +1 -0
- package/dist/src/lifecycle/immutable.js +10 -0
- package/dist/src/lifecycle/immutable.js.map +1 -0
- package/dist/src/lifecycle/implementation-table.d.ts +29 -0
- package/dist/src/lifecycle/implementation-table.js +87 -0
- package/dist/src/lifecycle/implementation-table.js.map +1 -0
- package/dist/src/lifecycle/index.d.ts +4 -0
- package/dist/src/lifecycle/index.js +5 -0
- package/dist/src/lifecycle/index.js.map +1 -0
- package/dist/src/lifecycle/schema-artifact.d.ts +12 -0
- package/dist/src/lifecycle/schema-artifact.js +214 -0
- package/dist/src/lifecycle/schema-artifact.js.map +1 -0
- package/dist/src/lifecycle/semantic-registry.d.ts +6 -0
- package/dist/src/lifecycle/semantic-registry.js +489 -0
- package/dist/src/lifecycle/semantic-registry.js.map +1 -0
- package/dist/src/lifecycle/types.d.ts +718 -0
- package/dist/src/lifecycle/types.js +4 -0
- package/dist/src/lifecycle/types.js.map +1 -0
- package/dist/src/observation.d.ts +20 -0
- package/dist/src/observation.js +171 -0
- package/dist/src/observation.js.map +1 -0
- package/dist/src/reviewer.d.ts +39 -1
- package/dist/src/reviewer.js +179 -43
- package/dist/src/reviewer.js.map +1 -1
- package/dist/src/state/corrective-replan-executor.d.ts +64 -0
- package/dist/src/state/corrective-replan-executor.js +608 -0
- package/dist/src/state/corrective-replan-executor.js.map +1 -0
- package/dist/src/state/corrective-replan-public-schema.d.ts +3 -0
- package/dist/src/state/corrective-replan-public-schema.js +43 -0
- package/dist/src/state/corrective-replan-public-schema.js.map +1 -0
- package/dist/src/state/corrective-replan-public.d.ts +95 -0
- package/dist/src/state/corrective-replan-public.js +665 -0
- package/dist/src/state/corrective-replan-public.js.map +1 -0
- package/dist/src/state/corrective-replan-transaction.d.ts +160 -0
- package/dist/src/state/corrective-replan-transaction.js +730 -0
- package/dist/src/state/corrective-replan-transaction.js.map +1 -0
- package/dist/src/state/corrective-yield-executor.d.ts +31 -0
- package/dist/src/state/corrective-yield-executor.js +236 -0
- package/dist/src/state/corrective-yield-executor.js.map +1 -0
- package/dist/src/state/corrective-yield-transaction.d.ts +97 -0
- package/dist/src/state/corrective-yield-transaction.js +591 -0
- package/dist/src/state/corrective-yield-transaction.js.map +1 -0
- package/dist/src/state/lock.d.ts +76 -3
- package/dist/src/state/lock.js +611 -30
- package/dist/src/state/lock.js.map +1 -1
- package/dist/src/state/store.d.ts +3 -0
- package/dist/src/state/store.js +65 -12
- package/dist/src/state/store.js.map +1 -1
- package/dist/src/transition-core.d.ts +67 -0
- package/dist/src/transition-core.js +318 -0
- package/dist/src/transition-core.js.map +1 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/workflow.d.ts +62 -25
- package/dist/src/workflow.js +892 -183
- package/dist/src/workflow.js.map +1 -1
- package/docs/lifecycle/state-machine-stabilization.md +641 -0
- package/docs/release.md +6 -2
- package/docs/updating-existing-project.md +9 -0
- package/package.json +4 -1
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +36 -0
- package/schemas/adoption-posture-event.schema.json +1 -1
- package/schemas/context-envelope.schema.json +1 -1
- package/schemas/corrective-replan-credentials.private.schema.json +98 -0
- package/schemas/corrective-replan-execution-event.private.schema.json +53 -0
- package/schemas/corrective-replan-public.schema.json +142 -0
- package/schemas/corrective-replan-task-transaction.private.schema.json +151 -0
- package/schemas/corrective-yield-task-transaction.private.schema.json +98 -0
- package/schemas/remediation-event.schema.json +11 -1
- package/schemas/task-handoff-event.schema.json +1 -1
- package/schemas/transition-definition.schema.json +2609 -0
- package/schemas/transition-payloads.schema.json +140 -0
package/dist/src/workflow.js
CHANGED
|
@@ -9,24 +9,32 @@ import { pathAllowed, validatePlan } from './domain/validation.js';
|
|
|
9
9
|
import { WorkflowError } from './errors.js';
|
|
10
10
|
import { bindGraphEvidence, createGraphRefreshRequest, fallbackGraphBinding, inspectGraphBinding, validateGraphRequestCurrent, } from './graph.js';
|
|
11
11
|
import { inspectLocalDependency, inspectWorkflowVersions } from './diagnostics.js';
|
|
12
|
-
import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, readTaskC1Posture, } from './alpha6/handoff.js';
|
|
12
|
+
import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, appendTaskWriterCredentialReplacement, assertTaskClaimAllowed, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, readTaskC1Posture, } from './alpha6/handoff.js';
|
|
13
13
|
import { applyAdoptionPosture, assertAdoptionBaselinePreserved, buildAdoptionPreparation, initializeProjectRegistrationAdoption, readCurrentAdoptionPosture, } from './alpha6/adoption.js';
|
|
14
14
|
import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, readCurrentPlanRiskAudit, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
|
|
15
15
|
import { appendCorrectiveDecisionEvent, appendRemediationEvent, appendRemediationModeRecovery, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, defaultCorrectiveAuditorActor, findCurrentGuardedRemediationPosture, findRemediationModeRecoveryCandidate, findFailedGuardedStepRequiringCorrectiveDecision, readGuardedRemediationPostureForStep, readCorrectiveDecisionEvents, readRemediationEvents, } from './alpha6/remediation.js';
|
|
16
|
-
import { assessMilestoneInitialAssembly, applyMilestoneScopeChangeTransaction, assertMilestoneMembershipIntegrity, assertMilestoneScopeChangeStatusAllowed, normalizeMilestonePlan, prepareMilestoneScopeChangeCandidate, prepareMilestoneScopeChangeBundle, readMilestoneForScopeChange, readMilestoneWithIntegrity, } from './alpha6/milestone.js';
|
|
16
|
+
import { assessMilestoneInitialAssembly, applyMilestoneScopeChangeTransaction, assertMilestoneMembershipIntegrity, assertMilestoneScopeChangeStatusAllowed, inspectMilestoneWithIntegrity, normalizeMilestonePlan, prepareMilestoneScopeChangeCandidate, prepareMilestoneScopeChangeBundle, readMilestoneForScopeChange, readMilestoneWithIntegrity, } from './alpha6/milestone.js';
|
|
17
17
|
import { applyProjectTransaction, recoverProjectTransactions, runRollbackProjectTransaction, } from './beta1/project-transaction.js';
|
|
18
|
-
import { buildCurrentStrictStepReviewCycle, ensurePendingStrictStepReview, isStepReviewRequired, recordStrictStepReview, } from './alpha6/review.js';
|
|
18
|
+
import { buildCurrentStrictStepReviewCycle, countUnverifiedStrictStepReviews, ensurePendingStrictStepReview, isStepReviewRequired, recordStrictStepReview, } from './alpha6/review.js';
|
|
19
19
|
import { applyMilestoneAutonomyContract, assertAutonomousPlanEvolution, prepareMilestoneAutonomyContract, readMilestoneAutonomyEvents, requireActiveMilestoneAutonomy, } from './alpha7/autonomy.js';
|
|
20
20
|
import { appendCorrectiveDecisionRecovery, readValidatedCorrectiveDecisionRecoveryForNavigation, requireCorrectiveDecisionRecoveryForRun, } from './alpha7/corrective-recovery.js';
|
|
21
21
|
import { applyKnowledgeSelections, hashKnowledgeMap, inspectKnowledgeMap, normalizeRelativePath, reconcileKnowledgeMap, scanProjectKnowledge, selectKnowledgeSources, } from './memory.js';
|
|
22
22
|
import { canonicalJsonStringify, sha256Hex } from './alpha6/store-sidecars.js';
|
|
23
|
+
import { ensureDirectory } from './fs-utils.js';
|
|
23
24
|
import { commitStep, mergeTaskBranch, runChecks, startLocalTaskBranch, syncBaseIntoTask, validateTaskHistory, } from './git.js';
|
|
24
25
|
import { changedFiles, currentBranch, gitIsAncestor, headCommit, isClean, resolveRepositoryIdentity, runGit, } from './repository.js';
|
|
25
|
-
import { launchStrictStepReviewer } from './reviewer.js';
|
|
26
|
-
import { WriterLockManager } from './state/lock.js';
|
|
26
|
+
import { launchStrictStepReviewer, prepareExternalStrictStepReviewPacket, prepareExternalStrictTaskReviewPacket, strictReviewInputHash, validateExternalStrictStepReviewInput, validateExternalStrictTaskReviewInput, } from './reviewer.js';
|
|
27
|
+
import { listCoreTaskOperationMutexes, inspectWriterLease as inspectRawWriterLease, toPublicWriterLease, WriterLockManager, } from './state/lock.js';
|
|
27
28
|
import { FileStateStore } from './state/store.js';
|
|
29
|
+
import { inspectCorrectiveReplanTaskTransactions } from './state/corrective-replan-transaction.js';
|
|
30
|
+
import { inspectCorrectiveYieldTaskTransactions } from './state/corrective-yield-transaction.js';
|
|
31
|
+
import { createCorrectiveYieldExecutor } from './state/corrective-yield-executor.js';
|
|
32
|
+
import { createCorrectiveReplanPublicController, } from './state/corrective-replan-public.js';
|
|
33
|
+
import { inspectWorkflowObservationBoundary, } from './observation.js';
|
|
28
34
|
import { createEntityId } from './ulid.js';
|
|
29
35
|
import { PACKAGE_VERSION } from './version.js';
|
|
36
|
+
import { correctiveReplanBlockedNavigation, correctiveReplanNavigation, evaluateTaskCorrectiveReplanAvailability, evaluateTaskCorrectiveReplanCandidate, identifyCorrectiveReplanPosture, prepareCorrectiveReplanCandidate, TASK_CORRECTIVE_REPLAN_TRANSITION_ID, } from './lifecycle/corrective-replan.js';
|
|
37
|
+
const MAX_STRICT_REVIEW_INFRASTRUCTURE_FAILURES = 2;
|
|
30
38
|
export class WorkflowService {
|
|
31
39
|
store;
|
|
32
40
|
now;
|
|
@@ -34,13 +42,42 @@ export class WorkflowService {
|
|
|
34
42
|
this.store = store;
|
|
35
43
|
this.now = now;
|
|
36
44
|
}
|
|
37
|
-
|
|
45
|
+
#mutationContext(repository) {
|
|
38
46
|
const identity = resolveRepositoryIdentity(repository);
|
|
39
47
|
const projectRoot = this.store.projectRoot(identity.projectId);
|
|
48
|
+
const replanTransactions = inspectCorrectiveReplanTaskTransactions(projectRoot, this.store.root);
|
|
49
|
+
const yieldTransactions = inspectCorrectiveYieldTaskTransactions(projectRoot, this.store.root);
|
|
50
|
+
const taskTransactions = {
|
|
51
|
+
corrupt: [...replanTransactions.corrupt, ...yieldTransactions.corrupt],
|
|
52
|
+
pending: [...replanTransactions.pending, ...yieldTransactions.pending],
|
|
53
|
+
};
|
|
54
|
+
if (taskTransactions.corrupt.length > 0) {
|
|
55
|
+
throw new WorkflowError('STATE_CORRUPT', 'A Task transaction journal failed integrity validation.', {
|
|
56
|
+
transactions: taskTransactions.corrupt,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (taskTransactions.pending.length > 0) {
|
|
60
|
+
throw new WorkflowError('TASK_RECOVERY_REQUIRED', 'A Task transaction requires explicit recovery.', {
|
|
61
|
+
taskIds: [...new Set(taskTransactions.pending.map((transaction) => transaction.taskId))].sort(),
|
|
62
|
+
transactionIds: taskTransactions.pending.map((transaction) => transaction.transactionId).sort(),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const coreTaskOperations = listCoreTaskOperationMutexes(this.store.lockRoot(identity.projectId), this.store.root);
|
|
66
|
+
if (coreTaskOperations.length > 0) {
|
|
67
|
+
throw new WorkflowError('LOCKED', 'A Core Task operation owns the project mutation boundary.', {
|
|
68
|
+
taskIds: coreTaskOperations.map((operation) => operation.entityId).sort(),
|
|
69
|
+
operationIds: coreTaskOperations.map((operation) => operation.operationId).sort(),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
40
72
|
recoverProjectTransactions(projectRoot, identity.projectId);
|
|
41
73
|
const projectFile = path.join(projectRoot, 'project.json');
|
|
42
74
|
const projectAlreadyRegistered = existsSync(projectFile);
|
|
43
75
|
if (!projectAlreadyRegistered) {
|
|
76
|
+
// The registration posture is a sidecar under the project root. A completely
|
|
77
|
+
// fresh CODEX_WORKFLOW_STATE_HOME has no project directory yet, so establish
|
|
78
|
+
// only that directory before writing the posture; project.json remains the
|
|
79
|
+
// final registration record written below.
|
|
80
|
+
ensureDirectory(projectRoot);
|
|
44
81
|
initializeProjectRegistrationAdoption(this.store, {
|
|
45
82
|
projectId: identity.projectId,
|
|
46
83
|
repositoryIdentity: identity,
|
|
@@ -54,17 +91,103 @@ export class WorkflowService {
|
|
|
54
91
|
return {
|
|
55
92
|
identity,
|
|
56
93
|
store: this.store,
|
|
57
|
-
locks: new WriterLockManager(this.store.lockRoot(identity.projectId), this.now),
|
|
94
|
+
locks: new WriterLockManager(this.store.lockRoot(identity.projectId), this.now, this.store.root),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
inspectWriterLease(repository, entityId) {
|
|
98
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
99
|
+
const lease = inspectRawWriterLease(this.store.lockRoot(identity.projectId), entityId, this.store.root);
|
|
100
|
+
return lease ? this.publicWriterLeaseDiagnostic(lease) : null;
|
|
101
|
+
}
|
|
102
|
+
repairStaleWriterLease(repository, entityId) {
|
|
103
|
+
const context = this.#mutationContext(repository);
|
|
104
|
+
const lease = context.locks.repairStale(entityId);
|
|
105
|
+
return {
|
|
106
|
+
...this.publicWriterLeaseDiagnostic(lease),
|
|
107
|
+
repaired: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
prepareCorrectiveReplan(repository, taskId, candidate) {
|
|
111
|
+
return this.correctiveReplanController(repository).prepare({ taskId, candidate });
|
|
112
|
+
}
|
|
113
|
+
confirmCorrectiveReplan(repository, taskId, candidate, gate, confirmationCode) {
|
|
114
|
+
return this.correctiveReplanController(repository).confirm({ taskId, candidate, gate, confirmationCode });
|
|
115
|
+
}
|
|
116
|
+
executeCorrectiveReplan(repository, taskId, candidate, credential) {
|
|
117
|
+
return this.correctiveReplanController(repository).execute({ taskId, candidate, credential });
|
|
118
|
+
}
|
|
119
|
+
recoverCorrectiveReplan(repository, taskId, transactionId) {
|
|
120
|
+
return this.correctiveReplanController(repository).recover({ taskId, transactionId });
|
|
121
|
+
}
|
|
122
|
+
executeCorrectiveYield(repository, taskId, expectedTaskRevision, actor, writerToken, reason) {
|
|
123
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
124
|
+
return createCorrectiveYieldExecutor({
|
|
125
|
+
store: this.store,
|
|
126
|
+
projectId: identity.projectId,
|
|
127
|
+
now: this.now,
|
|
128
|
+
}).execute({
|
|
129
|
+
projectId: identity.projectId,
|
|
130
|
+
taskId,
|
|
131
|
+
expectedTaskRevision,
|
|
132
|
+
actor,
|
|
133
|
+
writerToken,
|
|
134
|
+
reason,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
recoverCorrectiveYield(repository, taskId, transactionId) {
|
|
138
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
139
|
+
return createCorrectiveYieldExecutor({
|
|
140
|
+
store: this.store,
|
|
141
|
+
projectId: identity.projectId,
|
|
142
|
+
now: this.now,
|
|
143
|
+
}).recover({ taskId, transactionId });
|
|
144
|
+
}
|
|
145
|
+
correctiveReplanController(repository) {
|
|
146
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
147
|
+
return createCorrectiveReplanPublicController({
|
|
148
|
+
store: this.store,
|
|
149
|
+
projectId: identity.projectId,
|
|
150
|
+
repositoryRoot: identity.repositoryRoot,
|
|
151
|
+
now: this.now,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
observationSnapshot(repository) {
|
|
155
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
156
|
+
const boundary = inspectWorkflowObservationBoundary({
|
|
157
|
+
store: this.store,
|
|
158
|
+
identity,
|
|
159
|
+
now: this.now(),
|
|
160
|
+
});
|
|
161
|
+
if (boundary.observation.kind !== 'assessment') {
|
|
162
|
+
return {
|
|
163
|
+
...boundary,
|
|
164
|
+
discoveries: [],
|
|
165
|
+
tasks: [],
|
|
166
|
+
milestones: [],
|
|
167
|
+
delegations: [],
|
|
168
|
+
adoption: null,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const tasks = this.store.listTasks(identity.projectId);
|
|
172
|
+
const rawMilestones = this.store.listMilestones(identity.projectId);
|
|
173
|
+
const adoption = this.inspectAdoptionPreparation(identity, tasks, rawMilestones, boundary.leases, boundary.currentAdoptionPosture);
|
|
174
|
+
return {
|
|
175
|
+
...boundary,
|
|
176
|
+
discoveries: this.store.listDiscoveries(identity.projectId),
|
|
177
|
+
tasks,
|
|
178
|
+
milestones: this.listMilestonesObserved(identity.projectId, rawMilestones, adoption.currentPosture),
|
|
179
|
+
delegations: this.store.listDelegations(identity.projectId),
|
|
180
|
+
adoption,
|
|
58
181
|
};
|
|
59
182
|
}
|
|
60
183
|
prepareDelegation(repository, input) {
|
|
61
|
-
const { identity } = this
|
|
184
|
+
const { identity } = this.#mutationContext(repository);
|
|
62
185
|
const gate = prepareDelegationGrantGate(identity.projectId, input, this.now());
|
|
63
186
|
this.assertDelegationScopeExists(identity.projectId, gate.policy);
|
|
64
187
|
return gate;
|
|
65
188
|
}
|
|
66
189
|
grantDelegation(repository, input, confirmationCode) {
|
|
67
|
-
const { identity } = this
|
|
190
|
+
const { identity } = this.#mutationContext(repository);
|
|
68
191
|
const gate = prepareDelegationGrantGate(identity.projectId, input, this.now());
|
|
69
192
|
this.assertDelegationScopeExists(identity.projectId, gate.policy);
|
|
70
193
|
if (confirmationCode.trim() !== gate.confirmationCode) {
|
|
@@ -100,7 +223,7 @@ export class WorkflowService {
|
|
|
100
223
|
}, null);
|
|
101
224
|
}
|
|
102
225
|
revokeDelegation(repository, grantId, expectedRevision, actor, reason) {
|
|
103
|
-
const { identity } = this
|
|
226
|
+
const { identity } = this.#mutationContext(repository);
|
|
104
227
|
const grant = this.store.readDelegation(identity.projectId, grantId);
|
|
105
228
|
assertExpectedRevision(grant, expectedRevision);
|
|
106
229
|
if (grant.status !== 'active') {
|
|
@@ -124,7 +247,7 @@ export class WorkflowService {
|
|
|
124
247
|
startDiscovery(repository, input) {
|
|
125
248
|
if (!input.goal.trim())
|
|
126
249
|
throw new WorkflowError('INVALID_ARGUMENT', 'Discovery requires a substantive goal.');
|
|
127
|
-
const { identity } = this
|
|
250
|
+
const { identity } = this.#mutationContext(repository);
|
|
128
251
|
const timestamp = this.now().toISOString();
|
|
129
252
|
const initial = {
|
|
130
253
|
schemaVersion: STATE_SCHEMA_VERSION,
|
|
@@ -152,7 +275,7 @@ export class WorkflowService {
|
|
|
152
275
|
return this.store.writeDiscovery(initial, null);
|
|
153
276
|
}
|
|
154
277
|
updateDiscovery(repository, discoveryId, expectedRevision, patch) {
|
|
155
|
-
const { identity } = this
|
|
278
|
+
const { identity } = this.#mutationContext(repository);
|
|
156
279
|
const current = this.store.readDiscovery(identity.projectId, discoveryId);
|
|
157
280
|
if (current.status === 'materialized' || current.status === 'abandoned') {
|
|
158
281
|
throw new WorkflowError('TRANSITION_BLOCKED', `Discovery ${discoveryId} is ${current.status}.`);
|
|
@@ -176,7 +299,7 @@ export class WorkflowService {
|
|
|
176
299
|
return this.store.writeDiscovery(updated, expectedRevision);
|
|
177
300
|
}
|
|
178
301
|
materializeTask(repository, discoveryId, expectedRevision, title, baseBranch = 'main', milestoneId = null) {
|
|
179
|
-
const { identity } = this
|
|
302
|
+
const { identity } = this.#mutationContext(repository);
|
|
180
303
|
const discovery = this.store.readDiscovery(identity.projectId, discoveryId);
|
|
181
304
|
assertExpectedRevision(discovery, expectedRevision);
|
|
182
305
|
const readiness = assessDiscovery(discovery);
|
|
@@ -253,7 +376,7 @@ export class WorkflowService {
|
|
|
253
376
|
return { discovery: savedDiscovery, task: savedTask, taskRoot };
|
|
254
377
|
}
|
|
255
378
|
replaceSplitRequiredTask(repository, oldTaskId, expectedOldTaskRevision, discoveryId, expectedDiscoveryRevision, expectedMilestoneRevision, title, actor) {
|
|
256
|
-
const { identity, locks } = this
|
|
379
|
+
const { identity, locks } = this.#mutationContext(repository);
|
|
257
380
|
const oldTask = this.store.readTask(identity.projectId, oldTaskId);
|
|
258
381
|
assertExpectedRevision(oldTask, expectedOldTaskRevision);
|
|
259
382
|
if (!oldTask.milestoneId) {
|
|
@@ -504,7 +627,7 @@ export class WorkflowService {
|
|
|
504
627
|
};
|
|
505
628
|
}
|
|
506
629
|
materializeMilestone(repository, discoveryId, expectedRevision, title, baseBranch = 'main') {
|
|
507
|
-
const { identity } = this
|
|
630
|
+
const { identity } = this.#mutationContext(repository);
|
|
508
631
|
const discovery = this.store.readDiscovery(identity.projectId, discoveryId);
|
|
509
632
|
assertExpectedRevision(discovery, expectedRevision);
|
|
510
633
|
const readiness = assessDiscovery(discovery);
|
|
@@ -552,7 +675,7 @@ export class WorkflowService {
|
|
|
552
675
|
};
|
|
553
676
|
}
|
|
554
677
|
setMilestonePlan(repository, milestoneId, expectedRevision, plan) {
|
|
555
|
-
const { identity } = this
|
|
678
|
+
const { identity } = this.#mutationContext(repository);
|
|
556
679
|
const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
|
|
557
680
|
assertExpectedRevision(milestone, expectedRevision);
|
|
558
681
|
if (!['planning', 'awaiting_execution_authorization', 'active', 'blocked'].includes(milestone.status)) {
|
|
@@ -624,7 +747,7 @@ export class WorkflowService {
|
|
|
624
747
|
return saved;
|
|
625
748
|
}
|
|
626
749
|
prepareMilestoneScopeChange(repository, milestoneId, expectedRevision, plan, actor) {
|
|
627
|
-
const { identity } = this
|
|
750
|
+
const { identity } = this.#mutationContext(repository);
|
|
628
751
|
const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
|
|
629
752
|
assertExpectedRevision(milestone, expectedRevision);
|
|
630
753
|
assertMilestoneScopeChangeStatusAllowed(milestone);
|
|
@@ -641,7 +764,7 @@ export class WorkflowService {
|
|
|
641
764
|
};
|
|
642
765
|
}
|
|
643
766
|
applyMilestoneScopeChange(repository, milestoneId, expectedRevision, plan, actor, confirmationCode) {
|
|
644
|
-
const { identity } = this
|
|
767
|
+
const { identity } = this.#mutationContext(repository);
|
|
645
768
|
const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
|
|
646
769
|
assertExpectedRevision(milestone, expectedRevision);
|
|
647
770
|
assertMilestoneScopeChangeStatusAllowed(milestone);
|
|
@@ -664,7 +787,7 @@ export class WorkflowService {
|
|
|
664
787
|
});
|
|
665
788
|
}
|
|
666
789
|
prepareMilestoneAutonomy(repository, milestoneId, expectedRevision, principal, delegate, expiresAt) {
|
|
667
|
-
const { identity } = this
|
|
790
|
+
const { identity } = this.#mutationContext(repository);
|
|
668
791
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
669
792
|
assertExpectedRevision(milestone, expectedRevision);
|
|
670
793
|
return prepareMilestoneAutonomyContract({
|
|
@@ -677,7 +800,7 @@ export class WorkflowService {
|
|
|
677
800
|
});
|
|
678
801
|
}
|
|
679
802
|
grantMilestoneAutonomy(repository, milestoneId, expectedRevision, principal, delegate, expiresAt, confirmationCode) {
|
|
680
|
-
const { identity } = this
|
|
803
|
+
const { identity } = this.#mutationContext(repository);
|
|
681
804
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
682
805
|
assertExpectedRevision(milestone, expectedRevision);
|
|
683
806
|
return applyMilestoneAutonomyContract({
|
|
@@ -692,7 +815,7 @@ export class WorkflowService {
|
|
|
692
815
|
});
|
|
693
816
|
}
|
|
694
817
|
evolveMilestonePlanAutonomously(repository, milestoneId, expectedRevision, plan, actor) {
|
|
695
|
-
const { identity } = this
|
|
818
|
+
const { identity } = this.#mutationContext(repository);
|
|
696
819
|
const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
|
|
697
820
|
assertExpectedRevision(milestone, expectedRevision);
|
|
698
821
|
assertMilestoneScopeChangeStatusAllowed(milestone);
|
|
@@ -722,7 +845,7 @@ export class WorkflowService {
|
|
|
722
845
|
});
|
|
723
846
|
}
|
|
724
847
|
authorizeMilestone(repository, milestoneId, expectedRevision, actor, reason = '', delegationGrantId = null) {
|
|
725
|
-
const { identity } = this
|
|
848
|
+
const { identity } = this.#mutationContext(repository);
|
|
726
849
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
727
850
|
assertExpectedRevision(milestone, expectedRevision);
|
|
728
851
|
if (milestone.status !== 'awaiting_execution_authorization' || !milestone.planHash) {
|
|
@@ -751,7 +874,7 @@ export class WorkflowService {
|
|
|
751
874
|
return this.store.writeMilestone({ ...milestone, status: 'active', planHash, authorizations: [...milestone.authorizations, event] }, expectedRevision);
|
|
752
875
|
}
|
|
753
876
|
validateMilestone(repository, milestoneId, expectedRevision) {
|
|
754
|
-
const { identity } = this
|
|
877
|
+
const { identity } = this.#mutationContext(repository);
|
|
755
878
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
756
879
|
assertExpectedRevision(milestone, expectedRevision);
|
|
757
880
|
if (!['active', 'awaiting_final_acceptance'].includes(milestone.status)) {
|
|
@@ -814,7 +937,7 @@ export class WorkflowService {
|
|
|
814
937
|
return saved;
|
|
815
938
|
}
|
|
816
939
|
acceptMilestone(repository, milestoneId, expectedRevision, actor, confirmationCode, delegationGrantId = null) {
|
|
817
|
-
const { identity } = this
|
|
940
|
+
const { identity } = this.#mutationContext(repository);
|
|
818
941
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
819
942
|
assertExpectedRevision(milestone, expectedRevision);
|
|
820
943
|
if (milestone.status !== 'awaiting_final_acceptance' || !milestone.resultHash || !milestone.evidenceHash) {
|
|
@@ -853,7 +976,7 @@ export class WorkflowService {
|
|
|
853
976
|
resultHash,
|
|
854
977
|
evidenceHash,
|
|
855
978
|
headCommit: currentHead,
|
|
856
|
-
reason:
|
|
979
|
+
reason: 'Milestone final acceptance confirmed against the current validated state.',
|
|
857
980
|
authorizationMode: delegation ? 'delegated' : 'human',
|
|
858
981
|
...(delegation ? { delegation } : {}),
|
|
859
982
|
};
|
|
@@ -869,7 +992,7 @@ export class WorkflowService {
|
|
|
869
992
|
cancelMilestone(repository, milestoneId, expectedRevision, reason) {
|
|
870
993
|
if (!reason.trim())
|
|
871
994
|
throw new WorkflowError('INVALID_ARGUMENT', 'Milestone cancellation requires a reason.');
|
|
872
|
-
const { identity } = this
|
|
995
|
+
const { identity } = this.#mutationContext(repository);
|
|
873
996
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
874
997
|
assertExpectedRevision(milestone, expectedRevision);
|
|
875
998
|
if (['accepted', 'cancelled'].includes(milestone.status)) {
|
|
@@ -879,21 +1002,54 @@ export class WorkflowService {
|
|
|
879
1002
|
}
|
|
880
1003
|
setTaskPlan(repository, taskId, expectedRevision, plan, correctiveAudit = null, planRiskAudit = null) {
|
|
881
1004
|
validatePlan(plan);
|
|
882
|
-
const { identity } = this
|
|
1005
|
+
const { identity } = this.#mutationContext(repository);
|
|
883
1006
|
const knowledgeMap = this.assertKnowledgeMapBinding(identity.repositoryRoot, identity.projectId, plan.knowledgeMapRevision, plan.knowledgeMapHash);
|
|
884
1007
|
this.validateKnowledgeTargets(knowledgeMap, plan);
|
|
885
1008
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
886
1009
|
assertExpectedRevision(task, expectedRevision);
|
|
887
|
-
|
|
888
|
-
throw new WorkflowError('TRANSITION_BLOCKED', `Cannot update Plan while Task is ${task.status}.`);
|
|
889
|
-
}
|
|
1010
|
+
const correctiveDecisionEvents = task.planHash ? readCorrectiveDecisionEvents(this.store, task) : [];
|
|
890
1011
|
const currentPlanCorrectiveDecisions = task.planHash
|
|
891
|
-
?
|
|
1012
|
+
? correctiveDecisionEvents.filter((event) => event.planHash === task.planHash)
|
|
892
1013
|
: [];
|
|
893
1014
|
const currentReplanDecisions = currentPlanCorrectiveDecisions.filter((event) => event.decision === 'replan-required');
|
|
894
1015
|
const currentContinueFixDecisions = currentPlanCorrectiveDecisions.filter((event) => event.decision === 'continue-fix');
|
|
895
1016
|
const currentHardBlockingDecisions = currentPlanCorrectiveDecisions.filter((event) => event.decision === 'split-required' || event.decision === 'stop-escalate');
|
|
896
|
-
const
|
|
1017
|
+
const currentPlanRiskAudit = task.status === 'needs_fix' || currentReplanDecisions.length > 0
|
|
1018
|
+
? readCurrentPlanRiskAudit(this.store, task)
|
|
1019
|
+
: null;
|
|
1020
|
+
const classifiedCorrectivePosture = currentReplanDecisions.length > 0 && currentPlanRiskAudit
|
|
1021
|
+
? findCurrentGuardedRemediationPosture(this.store, task, currentPlanRiskAudit)
|
|
1022
|
+
: null;
|
|
1023
|
+
const correctiveReplanPosture = classifiedCorrectivePosture?.posture === 'replan-required'
|
|
1024
|
+
? classifiedCorrectivePosture
|
|
1025
|
+
: currentPlanRiskAudit
|
|
1026
|
+
? identifyCorrectiveReplanPosture(task, currentPlanRiskAudit.reviewRequiredStepIds, correctiveDecisionEvents, readRemediationEvents(this.store, task))
|
|
1027
|
+
: null;
|
|
1028
|
+
const correctiveReplanAvailability = correctiveReplanPosture?.posture === 'replan-required'
|
|
1029
|
+
? evaluateTaskCorrectiveReplanAvailability(task, correctiveReplanPosture)
|
|
1030
|
+
: null;
|
|
1031
|
+
if (currentReplanDecisions.length > 0) {
|
|
1032
|
+
if (!correctiveReplanAvailability?.allowed) {
|
|
1033
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Transition ${TASK_CORRECTIVE_REPLAN_TRANSITION_ID} is blocked for Task ${task.id}.`, {
|
|
1034
|
+
transitionId: TASK_CORRECTIVE_REPLAN_TRANSITION_ID,
|
|
1035
|
+
posture: correctiveReplanPosture?.posture ?? null,
|
|
1036
|
+
evaluationPhase: correctiveReplanAvailability?.phase ?? 'availability',
|
|
1037
|
+
blockingGuardIds: correctiveReplanAvailability?.blockingGuardIds ?? [],
|
|
1038
|
+
pendingGuardIds: correctiveReplanAvailability?.pendingGuardIds ?? [],
|
|
1039
|
+
guardResults: correctiveReplanAvailability?.guardResults ?? [],
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Legacy task plan-set cannot execute ${TASK_CORRECTIVE_REPLAN_TRANSITION_ID}.`, {
|
|
1043
|
+
taskId: task.id,
|
|
1044
|
+
transitionId: TASK_CORRECTIVE_REPLAN_TRANSITION_ID,
|
|
1045
|
+
requiredAction: readTaskC1Posture(this.store, task).state === 'claimed'
|
|
1046
|
+
? 'Run task corrective-yield first, then use task corrective-replan-prepare.'
|
|
1047
|
+
: 'Use task corrective-replan-prepare, confirm, and execute.',
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
else if (!['planning', 'awaiting_execution_authorization', 'needs_fix', 'blocked'].includes(task.status)) {
|
|
1051
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Cannot update Plan while Task is ${task.status}.`);
|
|
1052
|
+
}
|
|
897
1053
|
if (currentHardBlockingDecisions.length > 0) {
|
|
898
1054
|
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${task.id} has current corrective decisions that prohibit normal plan updates.`, {
|
|
899
1055
|
taskId: task.id,
|
|
@@ -923,9 +1079,6 @@ export class WorkflowService {
|
|
|
923
1079
|
})),
|
|
924
1080
|
});
|
|
925
1081
|
}
|
|
926
|
-
const currentPlanRiskAudit = task.status === 'needs_fix'
|
|
927
|
-
? readCurrentPlanRiskAudit(this.store, task)
|
|
928
|
-
: null;
|
|
929
1082
|
const failedReviewAttempts = countFailedReviewAttempts(this.store.taskRoot(identity.projectId, taskId));
|
|
930
1083
|
if (task.status === 'needs_fix' && task.review.status === 'failed' && failedReviewAttempts >= 2) {
|
|
931
1084
|
validateCorrectivePlanAudit(task, correctiveAudit, failedReviewAttempts);
|
|
@@ -968,7 +1121,7 @@ export class WorkflowService {
|
|
|
968
1121
|
}
|
|
969
1122
|
}
|
|
970
1123
|
const root = this.store.taskRoot(identity.projectId, taskId);
|
|
971
|
-
const
|
|
1124
|
+
const preparedPlan = prepareCorrectiveReplanCandidate(task, plan);
|
|
972
1125
|
const authorizations = task.authorizations.map((authorization) => authorization.kind === 'execution' && authorization.decision === 'approved'
|
|
973
1126
|
? { ...authorization, decision: 'superseded' }
|
|
974
1127
|
: authorization);
|
|
@@ -985,12 +1138,17 @@ export class WorkflowService {
|
|
|
985
1138
|
plannedSteps.every((step) => step.status === 'completed')) {
|
|
986
1139
|
throw new WorkflowError('TRANSITION_BLOCKED', 'A failed review requires at least one new or changed remediation Step.');
|
|
987
1140
|
}
|
|
988
|
-
if (
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1141
|
+
if (correctiveReplanPosture?.posture === 'replan-required') {
|
|
1142
|
+
const executionEvaluation = evaluateTaskCorrectiveReplanCandidate(task, correctiveReplanPosture, preparedPlan);
|
|
1143
|
+
if (!executionEvaluation.allowed) {
|
|
1144
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Transition ${TASK_CORRECTIVE_REPLAN_TRANSITION_ID} rejected the candidate Plan for Task ${task.id}.`, {
|
|
1145
|
+
transitionId: TASK_CORRECTIVE_REPLAN_TRANSITION_ID,
|
|
1146
|
+
evaluationPhase: executionEvaluation.phase,
|
|
1147
|
+
blockingGuardIds: executionEvaluation.blockingGuardIds,
|
|
1148
|
+
pendingGuardIds: executionEvaluation.pendingGuardIds,
|
|
1149
|
+
guardResults: executionEvaluation.guardResults,
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
994
1152
|
}
|
|
995
1153
|
if (guardedRemediationGate && currentReplanDecisions.length === 0 && !plannedSteps.some((step) => step.id === guardedRemediationGate.stepId)) {
|
|
996
1154
|
throw new WorkflowError('TRANSITION_BLOCKED', `Continue-fix must retain guarded Step ${guardedRemediationGate.stepId}; removing or renaming it would reset the remediation breaker.`, {
|
|
@@ -998,14 +1156,10 @@ export class WorkflowService {
|
|
|
998
1156
|
stepId: guardedRemediationGate.stepId,
|
|
999
1157
|
});
|
|
1000
1158
|
}
|
|
1001
|
-
if (correctiveAudit && guardedRemediationGate && currentReplanDecisions.length === 0 && currentPlanRiskAudit) {
|
|
1002
|
-
assertCorrectiveAuditorIndependence(this.store, task, guardedRemediationGate.stepId, correctiveAudit.auditor);
|
|
1003
|
-
appendCorrectiveDecisionEvent(this.store, task, guardedRemediationGate.stepId, correctiveAudit, currentPlanRiskAudit, stagedPlan.hash, this.now());
|
|
1004
|
-
}
|
|
1005
1159
|
const candidate = {
|
|
1006
1160
|
...task,
|
|
1007
1161
|
status: 'awaiting_execution_authorization',
|
|
1008
|
-
planHash:
|
|
1162
|
+
planHash: preparedPlan.planHash,
|
|
1009
1163
|
planObjective: plan.objective,
|
|
1010
1164
|
requirements: plan.requirements,
|
|
1011
1165
|
acceptance: plan.acceptance,
|
|
@@ -1025,6 +1179,18 @@ export class WorkflowService {
|
|
|
1025
1179
|
};
|
|
1026
1180
|
if (planRiskAudit)
|
|
1027
1181
|
validatePlanRiskAuditCandidate(this.store, candidate, planRiskAudit);
|
|
1182
|
+
const stagedPlan = this.store.stageArtifact(root, 'plan.md', preparedPlan.planBytes);
|
|
1183
|
+
if (stagedPlan.hash !== preparedPlan.planHash) {
|
|
1184
|
+
throw new WorkflowError('STATE_CORRUPT', 'Prepared candidate Plan hash changed during staging.', {
|
|
1185
|
+
taskId: task.id,
|
|
1186
|
+
preparedPlanHash: preparedPlan.planHash,
|
|
1187
|
+
stagedPlanHash: stagedPlan.hash,
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
if (correctiveAudit && guardedRemediationGate && currentReplanDecisions.length === 0 && currentPlanRiskAudit) {
|
|
1191
|
+
assertCorrectiveAuditorIndependence(this.store, task, guardedRemediationGate.stepId, correctiveAudit.auditor);
|
|
1192
|
+
appendCorrectiveDecisionEvent(this.store, task, guardedRemediationGate.stepId, correctiveAudit, currentPlanRiskAudit, preparedPlan.planHash, this.now());
|
|
1193
|
+
}
|
|
1028
1194
|
const saved = this.store.writeTask(candidate, expectedRevision);
|
|
1029
1195
|
this.store.publishArtifact(stagedPlan);
|
|
1030
1196
|
if (planRiskAudit)
|
|
@@ -1035,7 +1201,7 @@ export class WorkflowService {
|
|
|
1035
1201
|
return saved;
|
|
1036
1202
|
}
|
|
1037
1203
|
rebindTaskKnowledge(repository, taskId, expectedRevision) {
|
|
1038
|
-
const { identity } = this
|
|
1204
|
+
const { identity } = this.#mutationContext(repository);
|
|
1039
1205
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1040
1206
|
assertExpectedRevision(task, expectedRevision);
|
|
1041
1207
|
this.assertTaskContextChangeAllowed(task);
|
|
@@ -1120,7 +1286,7 @@ export class WorkflowService {
|
|
|
1120
1286
|
if (!delegationGrantId) {
|
|
1121
1287
|
throw new WorkflowError('TRANSITION_BLOCKED', 'Task context refresh combines approval transitions and therefore requires an explicit delegated approval grant.');
|
|
1122
1288
|
}
|
|
1123
|
-
const { identity } = this
|
|
1289
|
+
const { identity } = this.#mutationContext(repository);
|
|
1124
1290
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1125
1291
|
const currentMap = this.store.readKnowledgeMap(identity.projectId);
|
|
1126
1292
|
assertExpectedRevision(task, expectedTaskRevision);
|
|
@@ -1211,7 +1377,7 @@ export class WorkflowService {
|
|
|
1211
1377
|
});
|
|
1212
1378
|
}
|
|
1213
1379
|
authorizeTask(repository, taskId, expectedRevision, actor, reason = '', delegationGrantId = null) {
|
|
1214
|
-
const { identity } = this
|
|
1380
|
+
const { identity } = this.#mutationContext(repository);
|
|
1215
1381
|
this.readCurrentAdoptionPostureStrict(identity.projectId);
|
|
1216
1382
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1217
1383
|
assertExpectedRevision(task, expectedRevision);
|
|
@@ -1241,7 +1407,7 @@ export class WorkflowService {
|
|
|
1241
1407
|
return this.store.writeTask({ ...task, status: 'ready', briefHash, planHash, authorizations: [...task.authorizations, event] }, expectedRevision);
|
|
1242
1408
|
}
|
|
1243
1409
|
recordPlanRiskAudit(repository, taskId, expectedRevision, input) {
|
|
1244
|
-
const { identity } = this
|
|
1410
|
+
const { identity } = this.#mutationContext(repository);
|
|
1245
1411
|
this.readCurrentAdoptionPostureStrict(identity.projectId);
|
|
1246
1412
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1247
1413
|
assertExpectedRevision(task, expectedRevision);
|
|
@@ -1249,7 +1415,7 @@ export class WorkflowService {
|
|
|
1249
1415
|
return appendCurrentPlanRiskAudit(this.store, task, input, this.now());
|
|
1250
1416
|
}
|
|
1251
1417
|
showTaskHandoff(repository, taskId) {
|
|
1252
|
-
const { identity } = this
|
|
1418
|
+
const { identity } = this.#mutationContext(repository);
|
|
1253
1419
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1254
1420
|
return {
|
|
1255
1421
|
task,
|
|
@@ -1257,8 +1423,8 @@ export class WorkflowService {
|
|
|
1257
1423
|
claimTokenAvailable: false,
|
|
1258
1424
|
};
|
|
1259
1425
|
}
|
|
1260
|
-
handoffTask(repository, taskId, expectedRevision, actor, targetActor, reason, writerToken = null, grantId = null, expiresAt = null) {
|
|
1261
|
-
const context = this
|
|
1426
|
+
handoffTask(repository, taskId, expectedRevision, actor, targetActor, reason, writerToken = null, grantId = null, expiresAt = null, replacePending = false) {
|
|
1427
|
+
const context = this.#mutationContext(repository);
|
|
1262
1428
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1263
1429
|
assertExpectedRevision(task, expectedRevision);
|
|
1264
1430
|
const normalizedActor = actor.trim();
|
|
@@ -1316,6 +1482,7 @@ export class WorkflowService {
|
|
|
1316
1482
|
expectedNext: String(nextForTask(task).action ?? 'task show'),
|
|
1317
1483
|
grantId,
|
|
1318
1484
|
expiresAt: expiresAt ?? new Date(this.now().getTime() + 24 * 60 * 60 * 1000).toISOString(),
|
|
1485
|
+
replacePending,
|
|
1319
1486
|
}, this.now());
|
|
1320
1487
|
releaseLease();
|
|
1321
1488
|
return { task, event: prepared.event, bundle: prepared.bundle };
|
|
@@ -1327,15 +1494,15 @@ export class WorkflowService {
|
|
|
1327
1494
|
}
|
|
1328
1495
|
}
|
|
1329
1496
|
claimTask(repository, taskId, expectedRevision, actor, claimToken, writerToken = null) {
|
|
1330
|
-
const context = this
|
|
1497
|
+
const context = this.#mutationContext(repository);
|
|
1331
1498
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1332
1499
|
assertExpectedRevision(task, expectedRevision);
|
|
1333
1500
|
const normalizedActor = actor.trim();
|
|
1334
1501
|
if (!normalizedActor) {
|
|
1335
1502
|
throw new WorkflowError('INVALID_ARGUMENT', 'Claim actor is required.');
|
|
1336
1503
|
}
|
|
1337
|
-
const pendingHandoff =
|
|
1338
|
-
if (pendingHandoff.
|
|
1504
|
+
const pendingHandoff = assertTaskClaimAllowed(this.store, task, normalizedActor, claimToken, this.now());
|
|
1505
|
+
if (pendingHandoff.latestEvent.grantId) {
|
|
1339
1506
|
this.assertHandoffGrant(context.identity.projectId, task, normalizedActor, pendingHandoff.latestEvent.grantId);
|
|
1340
1507
|
}
|
|
1341
1508
|
const existingLease = context.locks.inspect(taskId);
|
|
@@ -1368,7 +1535,14 @@ export class WorkflowService {
|
|
|
1368
1535
|
}
|
|
1369
1536
|
try {
|
|
1370
1537
|
const event = appendTaskClaim(this.store, task, actor, claimToken, lease.token, this.now());
|
|
1371
|
-
return {
|
|
1538
|
+
return {
|
|
1539
|
+
task,
|
|
1540
|
+
event,
|
|
1541
|
+
lease: toPublicWriterLease(lease, this.now()),
|
|
1542
|
+
writerCredential: acquiredHere
|
|
1543
|
+
? { disposition: 'issued', token: lease.token }
|
|
1544
|
+
: { disposition: 'renewed' },
|
|
1545
|
+
};
|
|
1372
1546
|
}
|
|
1373
1547
|
catch (error) {
|
|
1374
1548
|
if (acquiredHere)
|
|
@@ -1376,8 +1550,45 @@ export class WorkflowService {
|
|
|
1376
1550
|
throw error;
|
|
1377
1551
|
}
|
|
1378
1552
|
}
|
|
1553
|
+
replaceClaimedWriterCredential(repository, taskId, expectedRevision, actor) {
|
|
1554
|
+
const context = this.#mutationContext(repository);
|
|
1555
|
+
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1556
|
+
assertExpectedRevision(task, expectedRevision);
|
|
1557
|
+
const normalizedActor = actor.trim();
|
|
1558
|
+
if (!normalizedActor)
|
|
1559
|
+
throw new WorkflowError('INVALID_ARGUMENT', 'Writer credential replacement actor is required.');
|
|
1560
|
+
const posture = readTaskC1Posture(this.store, task);
|
|
1561
|
+
const navigation = this.taskGitNavigationOverride(task) ?? nextForTask(task);
|
|
1562
|
+
const action = String(navigation.action);
|
|
1563
|
+
const currentLease = context.locks.inspect(taskId);
|
|
1564
|
+
const writerTokenContract = writerTokenContractForAction(action, posture.state === 'claimed');
|
|
1565
|
+
const requiredActor = writerTokenContract
|
|
1566
|
+
? this.writerCredentialRecoveryActor(task, action, posture.state === 'claimed' ? posture.claimant : null, currentLease?.owner ?? null)
|
|
1567
|
+
: null;
|
|
1568
|
+
if (!requiredActor || requiredActor !== normalizedActor) {
|
|
1569
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} writer credential replacement requires the exact advertised recovery actor.`, {
|
|
1570
|
+
taskId,
|
|
1571
|
+
posture: posture.state,
|
|
1572
|
+
claimant: posture.state === 'claimed' ? posture.claimant : null,
|
|
1573
|
+
actor: normalizedActor,
|
|
1574
|
+
requiredActor,
|
|
1575
|
+
nextAction: action,
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
const lease = currentLease
|
|
1579
|
+
? context.locks.replaceCredential(taskId, normalizedActor)
|
|
1580
|
+
: context.locks.acquire(taskId, normalizedActor);
|
|
1581
|
+
if (posture.state === 'claimed') {
|
|
1582
|
+
appendTaskWriterCredentialReplacement(this.store, task, normalizedActor, lease.token, this.now());
|
|
1583
|
+
}
|
|
1584
|
+
return {
|
|
1585
|
+
task,
|
|
1586
|
+
lease: toPublicWriterLease(lease, this.now()),
|
|
1587
|
+
writerCredential: { disposition: 'issued', token: lease.token },
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1379
1590
|
handbackTask(repository, taskId, expectedRevision, actor, reason, writerToken = null, limitations = [], recommendedNext = 'Coordinator reviews the terminal Task result and recorded evidence.') {
|
|
1380
|
-
const context = this
|
|
1591
|
+
const context = this.#mutationContext(repository);
|
|
1381
1592
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1382
1593
|
assertExpectedRevision(task, expectedRevision);
|
|
1383
1594
|
const lease = task.status === 'merged' || task.status === 'cancelled'
|
|
@@ -1389,7 +1600,7 @@ export class WorkflowService {
|
|
|
1389
1600
|
return { task, event };
|
|
1390
1601
|
}
|
|
1391
1602
|
startTask(repository, taskId, expectedRevision, workspaceOwner) {
|
|
1392
|
-
const { identity } = this
|
|
1603
|
+
const { identity } = this.#mutationContext(repository);
|
|
1393
1604
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1394
1605
|
assertExpectedRevision(task, expectedRevision);
|
|
1395
1606
|
this.requireExecutionAdoptionPosture(identity, taskId);
|
|
@@ -1441,7 +1652,7 @@ export class WorkflowService {
|
|
|
1441
1652
|
return this.store.writeTask({ ...task, baseCommit, taskBranch, workspaceOwner, blockReason: null }, expectedRevision);
|
|
1442
1653
|
}
|
|
1443
1654
|
runStep(repository, taskId, stepId, expectedRevision, actor, writerToken) {
|
|
1444
|
-
const context = this
|
|
1655
|
+
const context = this.#mutationContext(repository);
|
|
1445
1656
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1446
1657
|
assertExpectedRevision(task, expectedRevision);
|
|
1447
1658
|
this.requireExecutionAdoptionPosture(context.identity, taskId);
|
|
@@ -1470,6 +1681,7 @@ export class WorkflowService {
|
|
|
1470
1681
|
throw new WorkflowError('TRANSITION_BLOCKED', `${stepId} cannot start from ${step.status}.`);
|
|
1471
1682
|
}
|
|
1472
1683
|
const planRiskAudit = readCurrentPlanRiskAudit(this.store, task);
|
|
1684
|
+
let resumesCorrectiveReplan = false;
|
|
1473
1685
|
if (remediationHistoryForStep.length > 0 && (!planRiskAudit || !isStepReviewRequired(task, stepId, planRiskAudit.reviewRequiredStepIds))) {
|
|
1474
1686
|
throw new WorkflowError('TRANSITION_BLOCKED', `Step ${stepId} has remediation history but is no longer marked for guarded review by the current Plan Risk Audit; execution is blocked until the audit and Step posture are reconciled.`, {
|
|
1475
1687
|
taskId,
|
|
@@ -1484,20 +1696,21 @@ export class WorkflowService {
|
|
|
1484
1696
|
&& guardedPosture.correctiveDecision.planHash !== task.planHash
|
|
1485
1697
|
? guardedPosture.correctiveDecision
|
|
1486
1698
|
: null;
|
|
1487
|
-
const recovery = staleDecision
|
|
1699
|
+
const recovery = staleDecision?.decision === 'continue-fix'
|
|
1488
1700
|
? requireCorrectiveDecisionRecoveryForRun(this.store, task, stepId, actor, writerToken)
|
|
1489
1701
|
: null;
|
|
1490
|
-
assertGuardedRemediationAttemptAllowed(this.store, task, stepId, planRiskAudit, recovery?.correctiveDecisionEventId ?? null);
|
|
1702
|
+
const gate = assertGuardedRemediationAttemptAllowed(this.store, task, stepId, planRiskAudit, recovery?.correctiveDecisionEventId ?? null);
|
|
1703
|
+
resumesCorrectiveReplan = gate.posture === 'replan-ready';
|
|
1491
1704
|
}
|
|
1492
1705
|
const knowledgeMap = this.assertTaskKnowledgeBinding(context.identity.repositoryRoot, task);
|
|
1493
1706
|
const dirty = changedFiles(context.identity.repositoryRoot);
|
|
1494
|
-
if (step.status === 'planned' && dirty.length > 0) {
|
|
1707
|
+
if (step.status === 'planned' && dirty.length > 0 && !resumesCorrectiveReplan) {
|
|
1495
1708
|
throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} must start from a clean checkout.`, {
|
|
1496
1709
|
changedFiles: dirty,
|
|
1497
1710
|
});
|
|
1498
1711
|
}
|
|
1499
1712
|
const dirtyOutsideStep = dirty.filter((file) => !pathAllowed(file, step.allowedWrites));
|
|
1500
|
-
if (step.status === 'failed' && dirtyOutsideStep.length > 0) {
|
|
1713
|
+
if ((step.status === 'failed' || resumesCorrectiveReplan) && dirtyOutsideStep.length > 0) {
|
|
1501
1714
|
throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} retry has changes outside allowedWrites.`, {
|
|
1502
1715
|
changedFiles: dirtyOutsideStep,
|
|
1503
1716
|
});
|
|
@@ -1555,7 +1768,8 @@ export class WorkflowService {
|
|
|
1555
1768
|
}
|
|
1556
1769
|
return {
|
|
1557
1770
|
task: saved,
|
|
1558
|
-
lease,
|
|
1771
|
+
lease: toPublicWriterLease(lease, this.now()),
|
|
1772
|
+
writerCredential: { disposition: acquiredHere ? 'issued' : 'renewed' },
|
|
1559
1773
|
envelope: {
|
|
1560
1774
|
protocolVersion: PROTOCOL_VERSION,
|
|
1561
1775
|
packageVersion: PACKAGE_VERSION,
|
|
@@ -1591,14 +1805,14 @@ export class WorkflowService {
|
|
|
1591
1805
|
taskId,
|
|
1592
1806
|
stepId,
|
|
1593
1807
|
expectedRevision: saved.revision,
|
|
1594
|
-
writerToken: lease.token,
|
|
1808
|
+
writerToken: acquiredHere ? lease.token : null,
|
|
1595
1809
|
knowledgeMap: knowledgeSummary(knowledgeMap),
|
|
1596
1810
|
knowledgeSources: selectKnowledgeSources(knowledgeMap, 'worker', task.knowledgeTargets.map((target) => target.path)),
|
|
1597
1811
|
},
|
|
1598
1812
|
};
|
|
1599
1813
|
}
|
|
1600
1814
|
taskContext(repository, taskId, role) {
|
|
1601
|
-
const { identity } = this
|
|
1815
|
+
const { identity } = this.#mutationContext(repository);
|
|
1602
1816
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1603
1817
|
const historicalStrictReview = role === 'independent-reviewer'
|
|
1604
1818
|
&& task.steps.some((step) => step.status === 'in_progress' && step.evidence !== null);
|
|
@@ -1677,7 +1891,7 @@ export class WorkflowService {
|
|
|
1677
1891
|
};
|
|
1678
1892
|
}
|
|
1679
1893
|
completeStep(repository, taskId, stepId, expectedRevision, writerToken, notes, actor) {
|
|
1680
|
-
const context = this
|
|
1894
|
+
const context = this.#mutationContext(repository);
|
|
1681
1895
|
const lease = context.locks.heartbeat(taskId, writerToken);
|
|
1682
1896
|
this.readCurrentAdoptionPostureStrict(context.identity.projectId);
|
|
1683
1897
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
@@ -1763,7 +1977,7 @@ export class WorkflowService {
|
|
|
1763
1977
|
return this.store.writeTask({ ...task, status: 'in_progress', steps, blockReason: null }, expectedRevision);
|
|
1764
1978
|
}
|
|
1765
1979
|
reviewStep(repository, taskId, stepId, expectedRevision, runner, actor, writerToken = null) {
|
|
1766
|
-
const context = this
|
|
1980
|
+
const context = this.#mutationContext(repository);
|
|
1767
1981
|
this.readCurrentAdoptionPostureStrict(context.identity.projectId);
|
|
1768
1982
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1769
1983
|
assertExpectedRevision(task, expectedRevision);
|
|
@@ -1819,10 +2033,18 @@ export class WorkflowService {
|
|
|
1819
2033
|
resolution: currentCycle.resolution,
|
|
1820
2034
|
});
|
|
1821
2035
|
}
|
|
1822
|
-
|
|
2036
|
+
const infrastructureFailures = countUnverifiedStrictStepReviews(this.store, task, stepId);
|
|
2037
|
+
if (infrastructureFailures >= MAX_STRICT_REVIEW_INFRASTRUCTURE_FAILURES) {
|
|
2038
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Strict Step Review exhausted its bounded infrastructure retries.', {
|
|
2039
|
+
taskId: task.id,
|
|
2040
|
+
stepId,
|
|
2041
|
+
infrastructureFailures,
|
|
2042
|
+
requiredAction: 'Use an external sealed reviewer or run doctor; do not start another local reviewer process.',
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
1823
2045
|
}
|
|
1824
2046
|
else if (currentCycle.resolution !== 'pending') {
|
|
1825
|
-
if (currentCycle.resolution === 'failed'
|
|
2047
|
+
if (currentCycle.resolution === 'failed') {
|
|
1826
2048
|
if (!currentCycle.reviewRecordedEvent || !currentCycle.reviewerAttestationEvent) {
|
|
1827
2049
|
throw new WorkflowError('STATE_CORRUPT', 'Strict Step Review recovery requires exact review and attestation sidecars.', {
|
|
1828
2050
|
taskId: task.id,
|
|
@@ -1830,7 +2052,7 @@ export class WorkflowService {
|
|
|
1830
2052
|
resolution: currentCycle.resolution,
|
|
1831
2053
|
});
|
|
1832
2054
|
}
|
|
1833
|
-
appendRemediationEvent(this.store, task, stepId,
|
|
2055
|
+
appendRemediationEvent(this.store, task, stepId, 'review-failed', {
|
|
1834
2056
|
completionCommit: currentCycle.reviewRecordedEvent.completionCommit,
|
|
1835
2057
|
checksHash: currentCycle.reviewRecordedEvent.checksHash,
|
|
1836
2058
|
reviewEventHash: currentCycle.reviewRecordedEvent.eventHash,
|
|
@@ -1848,7 +2070,7 @@ export class WorkflowService {
|
|
|
1848
2070
|
reviewedCommit: launch.reviewedCommit,
|
|
1849
2071
|
isolationResult: launch.verifiedIsolation ? 'verified' : 'unverified',
|
|
1850
2072
|
}, this.now());
|
|
1851
|
-
if (recorded.resolution === 'failed'
|
|
2073
|
+
if (recorded.resolution === 'failed') {
|
|
1852
2074
|
const reviewRecordedEvent = recorded.reviewRecordedEvent;
|
|
1853
2075
|
const reviewerAttestationEvent = recorded.reviewerAttestationEvent;
|
|
1854
2076
|
if (!reviewRecordedEvent || !reviewerAttestationEvent) {
|
|
@@ -1858,7 +2080,7 @@ export class WorkflowService {
|
|
|
1858
2080
|
resolution: recorded.resolution,
|
|
1859
2081
|
});
|
|
1860
2082
|
}
|
|
1861
|
-
appendRemediationEvent(this.store, task, stepId,
|
|
2083
|
+
appendRemediationEvent(this.store, task, stepId, 'review-failed', {
|
|
1862
2084
|
completionCommit: reviewRecordedEvent.completionCommit,
|
|
1863
2085
|
checksHash: reviewRecordedEvent.checksHash,
|
|
1864
2086
|
reviewEventHash: reviewRecordedEvent.eventHash,
|
|
@@ -1867,8 +2089,132 @@ export class WorkflowService {
|
|
|
1867
2089
|
}
|
|
1868
2090
|
return this.store.writeTask(applyStrictStepReviewResolution(task, stepId, recorded.resolution, launch.review.summary), expectedRevision);
|
|
1869
2091
|
}
|
|
2092
|
+
prepareExternalStepReview(repository, taskId, stepId, expectedRevision) {
|
|
2093
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
2094
|
+
const task = this.store.readTask(identity.projectId, taskId);
|
|
2095
|
+
assertExpectedRevision(task, expectedRevision);
|
|
2096
|
+
if (task.status !== 'in_progress' && task.status !== 'blocked') {
|
|
2097
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `External Strict Step Review cannot run while task ${taskId} is ${task.status}.`);
|
|
2098
|
+
}
|
|
2099
|
+
const planRiskAudit = readCurrentPlanRiskAudit(this.store, task);
|
|
2100
|
+
if (!planRiskAudit || !isStepReviewRequired(task, stepId, planRiskAudit.reviewRequiredStepIds)) {
|
|
2101
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Step ${stepId} is not marked for strict review by the current Plan Risk Audit.`);
|
|
2102
|
+
}
|
|
2103
|
+
const step = requireStep(task, stepId);
|
|
2104
|
+
if (step.status !== 'in_progress' || !step.evidence) {
|
|
2105
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `External Strict Step Review requires ${stepId} in progress with canonical evidence.`);
|
|
2106
|
+
}
|
|
2107
|
+
validateTaskHistory(identity.repositoryRoot, task);
|
|
2108
|
+
if (!isClean(identity.repositoryRoot) || headCommit(identity.repositoryRoot) !== step.evidence.commitSha) {
|
|
2109
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'External Strict Step Review requires a clean checkout at the completion commit.');
|
|
2110
|
+
}
|
|
2111
|
+
const envelope = this.taskContext(repository, taskId, 'independent-reviewer');
|
|
2112
|
+
return prepareExternalStrictStepReviewPacket(identity.repositoryRoot, task, this.store.taskRoot(task.projectId, task.id), envelope, stepId, step.evidence);
|
|
2113
|
+
}
|
|
2114
|
+
recordExternalStepReview(repository, taskId, stepId, expectedRevision, input, actor, writerToken = null) {
|
|
2115
|
+
const context = this.#mutationContext(repository);
|
|
2116
|
+
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
2117
|
+
assertExpectedRevision(task, expectedRevision);
|
|
2118
|
+
this.proveOptionalWriterLeaseToken(context, taskId, writerToken);
|
|
2119
|
+
this.assertTaskMutationAllowedByHandoff(task, actor, writerToken);
|
|
2120
|
+
const packet = this.prepareExternalStepReview(repository, taskId, stepId, expectedRevision);
|
|
2121
|
+
let review;
|
|
2122
|
+
try {
|
|
2123
|
+
review = validateExternalStrictStepReviewInput(input, packet);
|
|
2124
|
+
}
|
|
2125
|
+
catch (error) {
|
|
2126
|
+
throw new WorkflowError('INVALID_ARGUMENT', error instanceof Error ? error.message : 'Invalid external strict review.');
|
|
2127
|
+
}
|
|
2128
|
+
if (review.reviewer.trim() === actor.trim()) {
|
|
2129
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'The independent Step reviewer must differ from the lifecycle mutation actor.');
|
|
2130
|
+
}
|
|
2131
|
+
const currentCycle = buildCurrentStrictStepReviewCycle(this.store, task, stepId);
|
|
2132
|
+
if (task.status === 'blocked' && currentCycle.resolution !== 'unverified') {
|
|
2133
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'External sealed review rescue requires an unverified prior review cycle.', {
|
|
2134
|
+
taskId,
|
|
2135
|
+
stepId,
|
|
2136
|
+
resolution: currentCycle.resolution,
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
const infrastructureFailures = countUnverifiedStrictStepReviews(this.store, task, stepId);
|
|
2140
|
+
if (infrastructureFailures > MAX_STRICT_REVIEW_INFRASTRUCTURE_FAILURES) {
|
|
2141
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'External sealed review rescue exceeded its bounded infrastructure history.', {
|
|
2142
|
+
taskId,
|
|
2143
|
+
stepId,
|
|
2144
|
+
infrastructureFailures,
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
ensurePendingStrictStepReview(this.store, task, stepId, this.now());
|
|
2148
|
+
const recorded = recordStrictStepReview(this.store, task, stepId, {
|
|
2149
|
+
review,
|
|
2150
|
+
reviewerOutputHash: strictReviewInputHash(review),
|
|
2151
|
+
reviewedCommit: packet.reviewedCommit,
|
|
2152
|
+
isolationResult: 'verified',
|
|
2153
|
+
}, this.now());
|
|
2154
|
+
if (recorded.resolution === 'failed') {
|
|
2155
|
+
appendRemediationEvent(this.store, task, stepId, 'review-failed', {
|
|
2156
|
+
completionCommit: recorded.reviewRecordedEvent.completionCommit,
|
|
2157
|
+
checksHash: recorded.reviewRecordedEvent.checksHash,
|
|
2158
|
+
reviewEventHash: recorded.reviewRecordedEvent.eventHash,
|
|
2159
|
+
reviewerAttestationHash: recorded.reviewerAttestationEvent.eventHash,
|
|
2160
|
+
}, readCurrentPlanRiskAudit(this.store, task), this.now());
|
|
2161
|
+
}
|
|
2162
|
+
const saved = this.store.writeTask(applyStrictStepReviewResolution(task, stepId, recorded.resolution, review.summary), expectedRevision);
|
|
2163
|
+
return {
|
|
2164
|
+
mode: 'external-sealed',
|
|
2165
|
+
packetHash: packet.packetHash,
|
|
2166
|
+
repositorySealHash: packet.repositorySealHash,
|
|
2167
|
+
reviewerThreadId: input.reviewerThreadId,
|
|
2168
|
+
task: saved,
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
prepareExternalTaskReview(repository, taskId, expectedRevision) {
|
|
2172
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
2173
|
+
const task = this.store.readTask(identity.projectId, taskId);
|
|
2174
|
+
assertExpectedRevision(task, expectedRevision);
|
|
2175
|
+
const retryingUnverified = task.status === 'blocked' && task.review.status === 'unverified';
|
|
2176
|
+
if (task.status !== 'validating' && !retryingUnverified) {
|
|
2177
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `External Strict Task Review cannot run while task ${taskId} is ${task.status}.`);
|
|
2178
|
+
}
|
|
2179
|
+
validateTaskHistory(identity.repositoryRoot, task);
|
|
2180
|
+
assertTaskBranch(identity.repositoryRoot, task);
|
|
2181
|
+
if (!isClean(identity.repositoryRoot)) {
|
|
2182
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'External Strict Task Review requires a clean Task checkout.');
|
|
2183
|
+
}
|
|
2184
|
+
const taskRoot = this.store.taskRoot(task.projectId, task.id);
|
|
2185
|
+
if (!task.evidenceHash || this.store.hashArtifact(taskRoot, 'evidence.json') !== task.evidenceHash) {
|
|
2186
|
+
throw new WorkflowError('STATE_CORRUPT', 'External Strict Task Review requires the exact submitted evidence artifact.');
|
|
2187
|
+
}
|
|
2188
|
+
return prepareExternalStrictTaskReviewPacket(identity.repositoryRoot, task, taskRoot, this.taskContext(repository, taskId, 'independent-reviewer'));
|
|
2189
|
+
}
|
|
2190
|
+
recordExternalTaskReview(repository, taskId, expectedRevision, input, actor, writerToken = null) {
|
|
2191
|
+
const context = this.#mutationContext(repository);
|
|
2192
|
+
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
2193
|
+
assertExpectedRevision(task, expectedRevision);
|
|
2194
|
+
this.proveOptionalWriterLeaseToken(context, taskId, writerToken);
|
|
2195
|
+
this.assertTaskMutationAllowedByHandoff(task, actor, writerToken);
|
|
2196
|
+
const packet = this.prepareExternalTaskReview(repository, taskId, expectedRevision);
|
|
2197
|
+
let review;
|
|
2198
|
+
try {
|
|
2199
|
+
review = validateExternalStrictTaskReviewInput(input, packet);
|
|
2200
|
+
}
|
|
2201
|
+
catch (error) {
|
|
2202
|
+
throw new WorkflowError('INVALID_ARGUMENT', error instanceof Error ? error.message : 'Invalid external strict Task review.');
|
|
2203
|
+
}
|
|
2204
|
+
if (review.reviewer.trim() === actor.trim()) {
|
|
2205
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'The independent Task reviewer must differ from the lifecycle mutation actor.');
|
|
2206
|
+
}
|
|
2207
|
+
const saved = this.recordReview(repository, taskId, expectedRevision, review, actor, writerToken);
|
|
2208
|
+
return {
|
|
2209
|
+
mode: 'external-sealed',
|
|
2210
|
+
packetHash: packet.packetHash,
|
|
2211
|
+
repositorySealHash: packet.repositorySealHash,
|
|
2212
|
+
reviewerThreadId: input.reviewerThreadId,
|
|
2213
|
+
task: saved,
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
1870
2216
|
submitTask(repository, taskId, expectedRevision, writerToken, actor) {
|
|
1871
|
-
const context = this
|
|
2217
|
+
const context = this.#mutationContext(repository);
|
|
1872
2218
|
const lease = context.locks.heartbeat(taskId, writerToken);
|
|
1873
2219
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1874
2220
|
assertExpectedRevision(task, expectedRevision);
|
|
@@ -1900,11 +2246,11 @@ export class WorkflowService {
|
|
|
1900
2246
|
return saved;
|
|
1901
2247
|
}
|
|
1902
2248
|
recordReview(repository, taskId, expectedRevision, review, actor, writerToken = null) {
|
|
1903
|
-
const { identity } = this
|
|
2249
|
+
const { identity } = this.#mutationContext(repository);
|
|
1904
2250
|
this.readCurrentAdoptionPostureStrict(identity.projectId);
|
|
1905
2251
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1906
2252
|
assertExpectedRevision(task, expectedRevision);
|
|
1907
|
-
this.proveOptionalWriterLeaseToken(this
|
|
2253
|
+
this.proveOptionalWriterLeaseToken(this.#mutationContext(repository), taskId, writerToken);
|
|
1908
2254
|
this.assertTaskMutationAllowedByHandoff(task, actor ?? null, writerToken);
|
|
1909
2255
|
const retryingUnverified = task.status === 'blocked' && task.review.status === 'unverified';
|
|
1910
2256
|
if (task.status !== 'validating' && !retryingUnverified) {
|
|
@@ -1942,7 +2288,7 @@ export class WorkflowService {
|
|
|
1942
2288
|
return saved;
|
|
1943
2289
|
}
|
|
1944
2290
|
recoverStepCorrectiveDecision(repository, taskId, stepId, expectedRevision, actor, writerToken) {
|
|
1945
|
-
const context = this
|
|
2291
|
+
const context = this.#mutationContext(repository);
|
|
1946
2292
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1947
2293
|
assertExpectedRevision(task, expectedRevision);
|
|
1948
2294
|
this.requireExecutionAdoptionPosture(context.identity, taskId);
|
|
@@ -1954,18 +2300,18 @@ export class WorkflowService {
|
|
|
1954
2300
|
return appendCorrectiveDecisionRecovery(this.store, task, stepId, expectedRevision, actor, writerToken, this.now());
|
|
1955
2301
|
}
|
|
1956
2302
|
recoverRemediationMode(repository, taskId, stepId, expectedRevision, actor, writerToken) {
|
|
1957
|
-
const context = this
|
|
2303
|
+
const context = this.#mutationContext(repository);
|
|
1958
2304
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1959
2305
|
assertExpectedRevision(task, expectedRevision);
|
|
1960
2306
|
this.requireExecutionAdoptionPosture(context.identity, taskId);
|
|
1961
2307
|
return appendRemediationModeRecovery(this.store, task, stepId, expectedRevision, actor, writerToken, this.now());
|
|
1962
2308
|
}
|
|
1963
2309
|
recordStepCorrectiveDecision(repository, taskId, stepId, expectedRevision, correctiveAudit, actor, writerToken = null) {
|
|
1964
|
-
const { identity } = this
|
|
2310
|
+
const { identity } = this.#mutationContext(repository);
|
|
1965
2311
|
this.readCurrentAdoptionPostureStrict(identity.projectId);
|
|
1966
2312
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1967
2313
|
assertExpectedRevision(task, expectedRevision);
|
|
1968
|
-
this.proveOptionalWriterLeaseToken(this
|
|
2314
|
+
this.proveOptionalWriterLeaseToken(this.#mutationContext(repository), taskId, writerToken);
|
|
1969
2315
|
this.assertTaskMutationAllowedByHandoff(task, actor ?? null, writerToken);
|
|
1970
2316
|
if (!['ready', 'needs_fix', 'blocked'].includes(task.status)) {
|
|
1971
2317
|
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} cannot record a corrective decision while ${task.status}.`);
|
|
@@ -2007,7 +2353,7 @@ export class WorkflowService {
|
|
|
2007
2353
|
return appendCorrectiveDecisionEvent(this.store, task, stepId, correctiveAudit, planRiskAudit, task.planHash, this.now());
|
|
2008
2354
|
}
|
|
2009
2355
|
setTaskResult(repository, taskId, expectedRevision, summary, limitations) {
|
|
2010
|
-
const { identity } = this
|
|
2356
|
+
const { identity } = this.#mutationContext(repository);
|
|
2011
2357
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
2012
2358
|
assertExpectedRevision(task, expectedRevision);
|
|
2013
2359
|
if (task.status !== 'validating' || task.review.status !== 'passed') {
|
|
@@ -2021,7 +2367,7 @@ export class WorkflowService {
|
|
|
2021
2367
|
return saved;
|
|
2022
2368
|
}
|
|
2023
2369
|
acceptTask(repository, taskId, expectedRevision, actor, delegationGrantId = null) {
|
|
2024
|
-
const { identity } = this
|
|
2370
|
+
const { identity } = this.#mutationContext(repository);
|
|
2025
2371
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
2026
2372
|
assertExpectedRevision(task, expectedRevision);
|
|
2027
2373
|
if (task.status !== 'awaiting_final_acceptance' || !task.resultHash || !task.evidenceHash) {
|
|
@@ -2059,7 +2405,7 @@ export class WorkflowService {
|
|
|
2059
2405
|
}, expectedRevision);
|
|
2060
2406
|
}
|
|
2061
2407
|
syncTaskBase(repository, taskId, expectedRevision) {
|
|
2062
|
-
const { identity } = this
|
|
2408
|
+
const { identity } = this.#mutationContext(repository);
|
|
2063
2409
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
2064
2410
|
assertExpectedRevision(task, expectedRevision);
|
|
2065
2411
|
if (task.status !== 'merging' || task.workspaceOwner !== 'local' || !task.taskBranch || !task.baseCommit) {
|
|
@@ -2111,7 +2457,7 @@ export class WorkflowService {
|
|
|
2111
2457
|
return saved;
|
|
2112
2458
|
}
|
|
2113
2459
|
mergeTask(repository, taskId, expectedRevision, writerToken, actor) {
|
|
2114
|
-
const context = this
|
|
2460
|
+
const context = this.#mutationContext(repository);
|
|
2115
2461
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
2116
2462
|
assertExpectedRevision(task, expectedRevision);
|
|
2117
2463
|
const lease = context.locks.heartbeat(taskId, writerToken);
|
|
@@ -2133,7 +2479,7 @@ export class WorkflowService {
|
|
|
2133
2479
|
return saved;
|
|
2134
2480
|
}
|
|
2135
2481
|
confirmExternalMerge(repository, taskId, expectedRevision, writerToken, actor) {
|
|
2136
|
-
const context = this
|
|
2482
|
+
const context = this.#mutationContext(repository);
|
|
2137
2483
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
2138
2484
|
assertExpectedRevision(task, expectedRevision);
|
|
2139
2485
|
const lease = context.locks.heartbeat(taskId, writerToken);
|
|
@@ -2150,25 +2496,22 @@ export class WorkflowService {
|
|
|
2150
2496
|
return saved;
|
|
2151
2497
|
}
|
|
2152
2498
|
status(repository) {
|
|
2153
|
-
const
|
|
2154
|
-
const tasks = this.store.listTasks(identity.projectId);
|
|
2155
|
-
const rawMilestones = this.store.listMilestones(identity.projectId);
|
|
2156
|
-
const adoption = this.inspectAdoptionPreparation(identity, tasks, rawMilestones);
|
|
2157
|
-
const milestones = this.listMilestonesCurrent(identity.projectId, adoption.currentPosture);
|
|
2499
|
+
const snapshot = this.observationSnapshot(repository);
|
|
2158
2500
|
return {
|
|
2159
|
-
projectId: identity.projectId,
|
|
2160
|
-
discoveries:
|
|
2161
|
-
tasks,
|
|
2162
|
-
milestones,
|
|
2163
|
-
milestoneInitialAssembly: milestones
|
|
2164
|
-
.map((milestone) => this.describeMilestoneInitialAssembly(identity.projectId, milestone, tasks))
|
|
2501
|
+
projectId: snapshot.identity.projectId,
|
|
2502
|
+
discoveries: snapshot.discoveries,
|
|
2503
|
+
tasks: snapshot.tasks,
|
|
2504
|
+
milestones: snapshot.milestones,
|
|
2505
|
+
milestoneInitialAssembly: snapshot.milestones
|
|
2506
|
+
.map((milestone) => this.describeMilestoneInitialAssembly(snapshot.identity.projectId, milestone, snapshot.tasks))
|
|
2165
2507
|
.filter((value) => value !== null),
|
|
2166
|
-
delegations:
|
|
2167
|
-
adoption,
|
|
2508
|
+
delegations: snapshot.delegations,
|
|
2509
|
+
adoption: snapshot.adoption,
|
|
2510
|
+
observation: snapshot.observation,
|
|
2168
2511
|
};
|
|
2169
2512
|
}
|
|
2170
2513
|
recoverMilestoneTransactions(repository) {
|
|
2171
|
-
const { identity } = this
|
|
2514
|
+
const { identity } = this.#mutationContext(repository);
|
|
2172
2515
|
const milestones = this.listMilestonesCurrent(identity.projectId, readCurrentAdoptionPosture(this.store, identity.projectId));
|
|
2173
2516
|
return {
|
|
2174
2517
|
projectId: identity.projectId,
|
|
@@ -2176,11 +2519,11 @@ export class WorkflowService {
|
|
|
2176
2519
|
};
|
|
2177
2520
|
}
|
|
2178
2521
|
prepareAdoption(repository) {
|
|
2179
|
-
const { identity } = this
|
|
2522
|
+
const { identity } = this.#mutationContext(repository);
|
|
2180
2523
|
return this.inspectAdoptionPreparation(identity, this.store.listTasks(identity.projectId), this.store.listMilestones(identity.projectId));
|
|
2181
2524
|
}
|
|
2182
2525
|
applyAdoption(repository, actor, confirmationCode) {
|
|
2183
|
-
const context = this
|
|
2526
|
+
const context = this.#mutationContext(repository);
|
|
2184
2527
|
const tasks = context.store.listTasks(context.identity.projectId);
|
|
2185
2528
|
const milestones = context.store.listMilestones(context.identity.projectId);
|
|
2186
2529
|
return applyAdoptionPosture(this.store, {
|
|
@@ -2197,19 +2540,19 @@ export class WorkflowService {
|
|
|
2197
2540
|
}, actor, confirmationCode);
|
|
2198
2541
|
}
|
|
2199
2542
|
updatePreflight(repository) {
|
|
2200
|
-
const
|
|
2201
|
-
const tasks =
|
|
2202
|
-
const milestones =
|
|
2203
|
-
const dirty = changedFiles(
|
|
2543
|
+
const snapshot = this.observationSnapshot(repository);
|
|
2544
|
+
const tasks = snapshot.tasks;
|
|
2545
|
+
const milestones = snapshot.milestones;
|
|
2546
|
+
const dirty = changedFiles(snapshot.identity.repositoryRoot);
|
|
2204
2547
|
const runningSteps = tasks.flatMap((task) => task.steps
|
|
2205
2548
|
.filter((step) => step.status === 'in_progress')
|
|
2206
2549
|
.map((step) => ({ taskId: task.id, stepId: step.id })));
|
|
2207
|
-
const leases =
|
|
2550
|
+
const leases = snapshot.leases;
|
|
2208
2551
|
const now = this.now().getTime();
|
|
2209
2552
|
const activeLeases = leases.filter((lease) => Date.parse(lease.expiresAt) > now);
|
|
2210
2553
|
const staleLeases = leases.filter((lease) => Date.parse(lease.expiresAt) <= now);
|
|
2211
2554
|
const activeMilestones = milestones.filter((milestone) => !['accepted', 'cancelled'].includes(milestone.status));
|
|
2212
|
-
const inspectedVersions = inspectWorkflowVersions(
|
|
2555
|
+
const inspectedVersions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, activeMilestones.map((milestone) => milestone.baseBranch));
|
|
2213
2556
|
const milestoneBases = inspectedVersions.milestoneBases.map((base) => ({
|
|
2214
2557
|
...base,
|
|
2215
2558
|
milestoneIds: activeMilestones.filter((milestone) => milestone.baseBranch === base.branch).map((milestone) => milestone.id),
|
|
@@ -2236,31 +2579,20 @@ export class WorkflowService {
|
|
|
2236
2579
|
...(activeLeases.length > 0 ? ['At least one writer lease is active.'] : []),
|
|
2237
2580
|
...(staleLeases.length > 0 ? ['At least one stale writer lease requires explicit repair.'] : []),
|
|
2238
2581
|
...versionBlockers,
|
|
2582
|
+
...observationPreflightBlockers(snapshot.observation),
|
|
2239
2583
|
];
|
|
2240
|
-
const adoption = buildAdoptionPreparation({
|
|
2241
|
-
projectId: context.identity.projectId,
|
|
2242
|
-
repositoryIdentity: context.identity,
|
|
2243
|
-
packageVersion: PACKAGE_VERSION,
|
|
2244
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
2245
|
-
stateSchemaVersion: STATE_SCHEMA_VERSION,
|
|
2246
|
-
tasks,
|
|
2247
|
-
milestones,
|
|
2248
|
-
sidecarInventory: this.collectAdoptionSidecarInventory(context.identity.projectId, tasks, milestones),
|
|
2249
|
-
blockers,
|
|
2250
|
-
now: this.now(),
|
|
2251
|
-
}, readCurrentAdoptionPosture(this.store, context.identity.projectId));
|
|
2252
2584
|
return {
|
|
2253
2585
|
safe: blockers.length === 0,
|
|
2254
2586
|
packageVersion: PACKAGE_VERSION,
|
|
2255
2587
|
protocolVersion: PROTOCOL_VERSION,
|
|
2256
2588
|
stateSchemaVersion: STATE_SCHEMA_VERSION,
|
|
2257
|
-
projectId:
|
|
2258
|
-
repositoryRoot:
|
|
2589
|
+
projectId: snapshot.identity.projectId,
|
|
2590
|
+
repositoryRoot: snapshot.identity.repositoryRoot,
|
|
2259
2591
|
clean: dirty.length === 0,
|
|
2260
2592
|
changedFiles: dirty,
|
|
2261
2593
|
runningSteps,
|
|
2262
|
-
activeLeases,
|
|
2263
|
-
staleLeases,
|
|
2594
|
+
activeLeases: activeLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
|
|
2595
|
+
staleLeases: staleLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
|
|
2264
2596
|
blockers,
|
|
2265
2597
|
workflowVersions: {
|
|
2266
2598
|
runtime: inspectedVersions.runtime,
|
|
@@ -2276,11 +2608,12 @@ export class WorkflowService {
|
|
|
2276
2608
|
? null
|
|
2277
2609
|
: 'Land only package.json/package-lock.json dependency changes on every active Milestone base; do not transport unrelated product commits.',
|
|
2278
2610
|
},
|
|
2279
|
-
adoption,
|
|
2611
|
+
adoption: snapshot.adoption,
|
|
2612
|
+
observation: snapshot.observation,
|
|
2280
2613
|
};
|
|
2281
2614
|
}
|
|
2282
2615
|
alpha6StrictReviewRescuePreflight(repository) {
|
|
2283
|
-
const context = this
|
|
2616
|
+
const context = this.#mutationContext(repository);
|
|
2284
2617
|
const dependency = inspectLocalDependency(context.identity.repositoryRoot);
|
|
2285
2618
|
const tasks = context.store.listTasks(context.identity.projectId);
|
|
2286
2619
|
const leases = context.locks.list();
|
|
@@ -2375,15 +2708,15 @@ export class WorkflowService {
|
|
|
2375
2708
|
projectId: context.identity.projectId,
|
|
2376
2709
|
repositoryRoot: context.identity.repositoryRoot,
|
|
2377
2710
|
clean: dirty.length === 0,
|
|
2378
|
-
activeLeases,
|
|
2379
|
-
staleLeases,
|
|
2711
|
+
activeLeases: activeLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
|
|
2712
|
+
staleLeases: staleLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
|
|
2380
2713
|
candidate,
|
|
2381
2714
|
blockers,
|
|
2382
2715
|
requiredActions,
|
|
2383
2716
|
};
|
|
2384
2717
|
}
|
|
2385
2718
|
scanKnowledgeMap(repository) {
|
|
2386
|
-
const { identity } = this
|
|
2719
|
+
const { identity } = this.#mutationContext(repository);
|
|
2387
2720
|
const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
|
|
2388
2721
|
try {
|
|
2389
2722
|
const current = this.store.readKnowledgeMap(identity.projectId);
|
|
@@ -2396,11 +2729,11 @@ export class WorkflowService {
|
|
|
2396
2729
|
}
|
|
2397
2730
|
}
|
|
2398
2731
|
showKnowledgeMap(repository) {
|
|
2399
|
-
const { identity } = this
|
|
2732
|
+
const { identity } = this.#mutationContext(repository);
|
|
2400
2733
|
return this.store.readKnowledgeMap(identity.projectId);
|
|
2401
2734
|
}
|
|
2402
2735
|
knowledgeMapStatus(repository) {
|
|
2403
|
-
const { identity } = this
|
|
2736
|
+
const { identity } = this.#mutationContext(repository);
|
|
2404
2737
|
const current = this.store.readKnowledgeMap(identity.projectId);
|
|
2405
2738
|
const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
|
|
2406
2739
|
return inspectKnowledgeMap(current, scanned);
|
|
@@ -2409,7 +2742,7 @@ export class WorkflowService {
|
|
|
2409
2742
|
return this.approveKnowledgeMapForTaskRefresh(repository, expectedRevision, selections, actor, delegationGrantId, null);
|
|
2410
2743
|
}
|
|
2411
2744
|
approveKnowledgeMapForTaskRefresh(repository, expectedRevision, selections, actor, delegationGrantId, autonomyTaskId) {
|
|
2412
|
-
const { identity } = this
|
|
2745
|
+
const { identity } = this.#mutationContext(repository);
|
|
2413
2746
|
const current = this.store.readKnowledgeMap(identity.projectId);
|
|
2414
2747
|
assertExpectedRevision(current, expectedRevision);
|
|
2415
2748
|
const inspected = inspectKnowledgeMap(current, scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now()));
|
|
@@ -2426,43 +2759,35 @@ export class WorkflowService {
|
|
|
2426
2759
|
return this.store.writeKnowledgeMap(approved, expectedRevision);
|
|
2427
2760
|
}
|
|
2428
2761
|
reconcileKnowledgeMap(repository, expectedRevision, allowAtomicTaskRefresh = false) {
|
|
2429
|
-
const { identity } = this
|
|
2762
|
+
const { identity } = this.#mutationContext(repository);
|
|
2430
2763
|
const current = this.store.readKnowledgeMap(identity.projectId);
|
|
2431
2764
|
assertExpectedRevision(current, expectedRevision);
|
|
2432
2765
|
const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
|
|
2433
2766
|
if (!allowAtomicTaskRefresh && current.status === 'active' && current.approval) {
|
|
2434
2767
|
const milestonesById = new Map(this.listMilestonesCurrent(identity.projectId).map((milestone) => [milestone.id, milestone]));
|
|
2435
|
-
const
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
const assessment = assessDelegatedKnowledgeRefresh(current, inspected, activeTask, this.store.hashArtifact(this.store.taskRoot(identity.projectId, activeTask.id), 'plan.md'));
|
|
2443
|
-
if (assessment.eligible
|
|
2444
|
-
&& this.delegatedContextRefreshOptions(identity.projectId, activeTask, inspected, assessment.mode).length > 0) {
|
|
2445
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Standalone Project Knowledge reconcile would strand an authorized Task context refresh.', {
|
|
2446
|
-
taskId: activeTask.id,
|
|
2447
|
-
requiredAction: 'Run the exact task context-refresh action returned by next.',
|
|
2448
|
-
});
|
|
2449
|
-
}
|
|
2768
|
+
const inspected = inspectKnowledgeMap(current, scanned);
|
|
2769
|
+
const refreshOwner = this.findDelegatedContextRefreshOwner(identity.projectId, this.store.listTasks(identity.projectId), milestonesById, current, inspected);
|
|
2770
|
+
if (refreshOwner) {
|
|
2771
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Standalone Project Knowledge reconcile would strand an authorized Task context refresh.', {
|
|
2772
|
+
taskId: refreshOwner.task.id,
|
|
2773
|
+
requiredAction: 'Run the exact task context-refresh action returned by next.',
|
|
2774
|
+
});
|
|
2450
2775
|
}
|
|
2451
2776
|
}
|
|
2452
2777
|
return this.store.writeKnowledgeMap(reconcileKnowledgeMap(current, scanned), expectedRevision);
|
|
2453
2778
|
}
|
|
2454
2779
|
graphRefreshRequest(repository, graphKind, mode = 'moderate') {
|
|
2455
|
-
const { identity } = this
|
|
2780
|
+
const { identity } = this.#mutationContext(repository);
|
|
2456
2781
|
return createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
|
|
2457
2782
|
}
|
|
2458
2783
|
graphStatus(repository, graphKind, mode = 'moderate') {
|
|
2459
|
-
const { identity } = this
|
|
2784
|
+
const { identity } = this.#mutationContext(repository);
|
|
2460
2785
|
const request = createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
|
|
2461
2786
|
const binding = this.readGraphBindingOrNull(identity.projectId, graphKind);
|
|
2462
2787
|
return { request, binding: binding ? inspectGraphBinding(binding, request) : null };
|
|
2463
2788
|
}
|
|
2464
2789
|
bindGraph(repository, expectedRevision, request, evidence) {
|
|
2465
|
-
const { identity } = this
|
|
2790
|
+
const { identity } = this.#mutationContext(repository);
|
|
2466
2791
|
const currentRequest = createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, request.graphKind, request.mode, this.now());
|
|
2467
2792
|
validateGraphRequestCurrent(request, currentRequest);
|
|
2468
2793
|
const existing = this.readGraphBindingOrNull(identity.projectId, request.graphKind);
|
|
@@ -2470,13 +2795,78 @@ export class WorkflowService {
|
|
|
2470
2795
|
return this.store.writeGraphBinding(binding, expectedRevision);
|
|
2471
2796
|
}
|
|
2472
2797
|
recordGraphFallback(repository, graphKind, mode, expectedRevision, reason) {
|
|
2473
|
-
const { identity } = this
|
|
2798
|
+
const { identity } = this.#mutationContext(repository);
|
|
2474
2799
|
const request = createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
|
|
2475
2800
|
const existing = this.readGraphBindingOrNull(identity.projectId, graphKind);
|
|
2476
2801
|
return this.store.writeGraphBinding(fallbackGraphBinding(identity.projectId, request, reason, existing, this.now()), expectedRevision);
|
|
2477
2802
|
}
|
|
2478
|
-
next(repository) {
|
|
2803
|
+
next(repository, taskId = null) {
|
|
2479
2804
|
const status = this.status(repository);
|
|
2805
|
+
if (status.observation.kind === 'blocked') {
|
|
2806
|
+
return {
|
|
2807
|
+
scope: 'project',
|
|
2808
|
+
projectId: status.projectId,
|
|
2809
|
+
status: 'observation-blocked',
|
|
2810
|
+
action: 'blocked',
|
|
2811
|
+
observation: status.observation,
|
|
2812
|
+
navigation: {
|
|
2813
|
+
kind: 'blocked',
|
|
2814
|
+
reasonCodes: status.observation.reasonCodes,
|
|
2815
|
+
availableRepairTransitionId: null,
|
|
2816
|
+
},
|
|
2817
|
+
};
|
|
2818
|
+
}
|
|
2819
|
+
if (status.observation.kind === 'repair') {
|
|
2820
|
+
const repair = status.observation.repair;
|
|
2821
|
+
return {
|
|
2822
|
+
scope: 'project',
|
|
2823
|
+
projectId: status.projectId,
|
|
2824
|
+
status: 'repair-required',
|
|
2825
|
+
action: repair?.command ?? 'blocked',
|
|
2826
|
+
observation: status.observation,
|
|
2827
|
+
navigation: {
|
|
2828
|
+
kind: repair ? 'repair' : 'blocked',
|
|
2829
|
+
transitionId: null,
|
|
2830
|
+
availableRepairTransitionId: null,
|
|
2831
|
+
...(repair ? {
|
|
2832
|
+
command: repair.command,
|
|
2833
|
+
cataloged: repair.cataloged,
|
|
2834
|
+
...('available' in repair ? { available: repair.available } : {}),
|
|
2835
|
+
bindings: repair.command === 'milestone recover'
|
|
2836
|
+
? {
|
|
2837
|
+
projectId: repair.projectId,
|
|
2838
|
+
milestoneIds: repair.milestoneIds,
|
|
2839
|
+
transactionIds: repair.transactionIds,
|
|
2840
|
+
}
|
|
2841
|
+
: 'taskIds' in repair
|
|
2842
|
+
? {
|
|
2843
|
+
projectId: repair.projectId,
|
|
2844
|
+
taskIds: repair.taskIds,
|
|
2845
|
+
transactionIds: repair.transactionIds,
|
|
2846
|
+
}
|
|
2847
|
+
: {
|
|
2848
|
+
projectId: repair.projectId,
|
|
2849
|
+
entityIds: repair.entityIds,
|
|
2850
|
+
},
|
|
2851
|
+
} : {}),
|
|
2852
|
+
reasonCodes: status.observation.reasonCodes,
|
|
2853
|
+
},
|
|
2854
|
+
};
|
|
2855
|
+
}
|
|
2856
|
+
if (!status.adoption) {
|
|
2857
|
+
return {
|
|
2858
|
+
scope: 'project',
|
|
2859
|
+
projectId: status.projectId,
|
|
2860
|
+
status: 'observation-blocked',
|
|
2861
|
+
action: 'blocked',
|
|
2862
|
+
observation: status.observation,
|
|
2863
|
+
navigation: {
|
|
2864
|
+
kind: 'blocked',
|
|
2865
|
+
reasonCodes: status.observation.reasonCodes,
|
|
2866
|
+
availableRepairTransitionId: null,
|
|
2867
|
+
},
|
|
2868
|
+
};
|
|
2869
|
+
}
|
|
2480
2870
|
const adoptionInfo = {
|
|
2481
2871
|
status: status.adoption.status,
|
|
2482
2872
|
safe: status.adoption.safe,
|
|
@@ -2494,19 +2884,105 @@ export class WorkflowService {
|
|
|
2494
2884
|
status: status.adoption.status,
|
|
2495
2885
|
action: status.adoption.safe ? 'state adoption-apply' : 'restore safe boundary, then state adoption-prepare',
|
|
2496
2886
|
adoption: adoptionInfo,
|
|
2887
|
+
observation: status.observation,
|
|
2888
|
+
navigation: status.adoption.safe
|
|
2889
|
+
? {
|
|
2890
|
+
kind: 'human-gate',
|
|
2891
|
+
transitionId: null,
|
|
2892
|
+
cataloged: false,
|
|
2893
|
+
command: 'state adoption-apply',
|
|
2894
|
+
bindings: {
|
|
2895
|
+
projectId: status.projectId,
|
|
2896
|
+
confirmationCode: status.adoption.confirmationCode,
|
|
2897
|
+
confirmationCodeBindingHash: status.adoption.confirmationCodeBindingHash,
|
|
2898
|
+
actorRequired: true,
|
|
2899
|
+
},
|
|
2900
|
+
}
|
|
2901
|
+
: {
|
|
2902
|
+
kind: 'blocked',
|
|
2903
|
+
transitionId: null,
|
|
2904
|
+
availableRepairTransitionId: null,
|
|
2905
|
+
reasonCodes: ['ADOPTION_MISSING'],
|
|
2906
|
+
blockers: status.adoption.blockers,
|
|
2907
|
+
},
|
|
2497
2908
|
};
|
|
2498
2909
|
}
|
|
2499
|
-
const withAdoption = (value) => ({
|
|
2910
|
+
const withAdoption = (value) => ({
|
|
2911
|
+
...value,
|
|
2912
|
+
adoption: adoptionInfo,
|
|
2913
|
+
observation: status.observation,
|
|
2914
|
+
});
|
|
2500
2915
|
const repositoryRoot = resolveRepositoryIdentity(repository).repositoryRoot;
|
|
2501
2916
|
const milestonesById = new Map(status.milestones.map((milestone) => [milestone.id, milestone]));
|
|
2502
|
-
const
|
|
2917
|
+
const requestedTask = taskId === null
|
|
2918
|
+
? null
|
|
2919
|
+
: status.tasks.find((task) => task.id === taskId) ?? null;
|
|
2920
|
+
if (taskId !== null && !requestedTask) {
|
|
2921
|
+
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
2922
|
+
}
|
|
2923
|
+
if (requestedTask && !taskIsActionableFromRepositoryNext(requestedTask, milestonesById)) {
|
|
2924
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${requestedTask.id} is not actionable from repository next.`, {
|
|
2925
|
+
taskId: requestedTask.id,
|
|
2926
|
+
taskStatus: requestedTask.status,
|
|
2927
|
+
milestoneId: requestedTask.milestoneId,
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
const activeTask = requestedTask
|
|
2931
|
+
?? status.tasks.find((task) => taskIsActionableFromRepositoryNext(task, milestonesById));
|
|
2503
2932
|
if (activeTask) {
|
|
2504
|
-
const
|
|
2505
|
-
?
|
|
2933
|
+
const activeTaskMilestone = activeTask.milestoneId
|
|
2934
|
+
? milestonesById.get(activeTask.milestoneId) ?? null
|
|
2506
2935
|
: null;
|
|
2936
|
+
const milestoneCancellationSafe = Boolean(activeTaskMilestone
|
|
2937
|
+
&& !activeTask.steps.some((step) => step.status === 'in_progress')
|
|
2938
|
+
&& status.observation.leases.length === 0
|
|
2939
|
+
&& status.observation.coreTaskOperations.length === 0
|
|
2940
|
+
&& status.observation.pendingProjectTransactions.length === 0
|
|
2941
|
+
&& status.observation.pendingMilestoneTransactions.length === 0
|
|
2942
|
+
&& status.observation.pendingTaskTransactions.length === 0
|
|
2943
|
+
&& status.observation.corruptTaskTransactions.length === 0);
|
|
2944
|
+
const withMilestoneCancellation = (value) => (milestoneCancellationSafe
|
|
2945
|
+
? {
|
|
2946
|
+
...value,
|
|
2947
|
+
milestoneCancellationOption: {
|
|
2948
|
+
command: 'milestone cancel',
|
|
2949
|
+
milestoneId: activeTaskMilestone.id,
|
|
2950
|
+
expectedRevision: activeTaskMilestone.revision,
|
|
2951
|
+
reasonRequired: true,
|
|
2952
|
+
taskDisposition: 'retained-historical',
|
|
2953
|
+
},
|
|
2954
|
+
}
|
|
2955
|
+
: value);
|
|
2956
|
+
let activePlanRiskAudit = null;
|
|
2957
|
+
let planRiskAuditRecovery = null;
|
|
2958
|
+
if (activeTask.planHash) {
|
|
2959
|
+
try {
|
|
2960
|
+
activePlanRiskAudit = readCurrentPlanRiskAudit(this.store, activeTask);
|
|
2961
|
+
}
|
|
2962
|
+
catch (error) {
|
|
2963
|
+
if (!(error instanceof WorkflowError) || error.code !== 'TRANSITION_BLOCKED')
|
|
2964
|
+
throw error;
|
|
2965
|
+
planRiskAuditRecovery = {
|
|
2966
|
+
scope: 'task',
|
|
2967
|
+
id: activeTask.id,
|
|
2968
|
+
status: activeTask.status,
|
|
2969
|
+
action: 'task plan-risk-audit',
|
|
2970
|
+
revision: activeTask.revision,
|
|
2971
|
+
planRiskAuditRecovery: {
|
|
2972
|
+
state: 'required',
|
|
2973
|
+
reason: error.message,
|
|
2974
|
+
currentBriefHash: activeTask.briefHash,
|
|
2975
|
+
currentPlanHash: activeTask.planHash,
|
|
2976
|
+
knowledgeMapRevision: activeTask.knowledgeMapRevision,
|
|
2977
|
+
knowledgeMapHash: activeTask.knowledgeMapHash,
|
|
2978
|
+
},
|
|
2979
|
+
};
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2507
2982
|
const taskNext = activeTask.status === 'planning'
|
|
2508
2983
|
? null
|
|
2509
|
-
:
|
|
2984
|
+
: planRiskAuditRecovery
|
|
2985
|
+
?? this.nextForTaskWithDelegation(status.projectId, activeTask, activePlanRiskAudit);
|
|
2510
2986
|
// Active Step work must outrank Project Knowledge refresh/rebind. Both completion and
|
|
2511
2987
|
// strict review are executable while context refresh correctly remains forbidden.
|
|
2512
2988
|
const hardStop = taskNext?.action === 'task corrective-decision'
|
|
@@ -2518,12 +2994,27 @@ export class WorkflowService {
|
|
|
2518
2994
|
|| hardStop
|
|
2519
2995
|
|| terminalCorrectiveDecision === 'split-required'
|
|
2520
2996
|
|| terminalCorrectiveDecision === 'stop-escalate') {
|
|
2521
|
-
return withAdoption(taskNext);
|
|
2997
|
+
return withAdoption(withMilestoneCancellation(taskNext));
|
|
2522
2998
|
}
|
|
2523
2999
|
if (!taskCanRebindKnowledge(activeTask) && activeTask.status !== 'planning') {
|
|
2524
|
-
return withAdoption(taskNext);
|
|
3000
|
+
return withAdoption(withMilestoneCancellation(taskNext));
|
|
3001
|
+
}
|
|
3002
|
+
let knowledgeMap;
|
|
3003
|
+
try {
|
|
3004
|
+
knowledgeMap = this.knowledgeMapStatus(repository);
|
|
3005
|
+
}
|
|
3006
|
+
catch (error) {
|
|
3007
|
+
if (!(error instanceof WorkflowError) || error.code !== 'NOT_FOUND')
|
|
3008
|
+
throw error;
|
|
3009
|
+
return withAdoption(withMilestoneCancellation({
|
|
3010
|
+
scope: 'project-memory',
|
|
3011
|
+
status: 'missing',
|
|
3012
|
+
action: 'project-memory scan',
|
|
3013
|
+
stalenessOriginTaskId: activeTask.id,
|
|
3014
|
+
authorizesTask: false,
|
|
3015
|
+
provenanceNotice: 'This Task identifier is diagnostic provenance only; the Project Knowledge action does not authorize or resume it.',
|
|
3016
|
+
}));
|
|
2525
3017
|
}
|
|
2526
|
-
const knowledgeMap = this.knowledgeMapStatus(repository);
|
|
2527
3018
|
if (knowledgeMap.status !== 'active' || !knowledgeMap.approval) {
|
|
2528
3019
|
const action = knowledgeMap.status === 'stale' ? 'project-memory reconcile' : 'project-memory approve';
|
|
2529
3020
|
const delegatedApprovalOptions = action === 'project-memory approve'
|
|
@@ -2539,7 +3030,17 @@ export class WorkflowService {
|
|
|
2539
3030
|
const contextRefreshOptions = action === 'project-memory reconcile' && refreshAssessment?.eligible
|
|
2540
3031
|
? this.delegatedContextRefreshOptions(status.projectId, activeTask, knowledgeMap, refreshAssessment.mode)
|
|
2541
3032
|
: [];
|
|
2542
|
-
|
|
3033
|
+
if (requestedTask && action === 'project-memory reconcile' && contextRefreshOptions.length === 0) {
|
|
3034
|
+
const refreshOwner = this.findDelegatedContextRefreshOwner(status.projectId, status.tasks, milestonesById, storedKnowledgeMap, knowledgeMap);
|
|
3035
|
+
if (refreshOwner && refreshOwner.task.id !== requestedTask.id) {
|
|
3036
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${requestedTask.id} cannot be selected while Task ${refreshOwner.task.id} requires an atomic context refresh.`, {
|
|
3037
|
+
taskId: requestedTask.id,
|
|
3038
|
+
blockingTaskId: refreshOwner.task.id,
|
|
3039
|
+
requiredAction: 'Run repository next and its exact task context-refresh action first.',
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
return withAdoption(withMilestoneCancellation({
|
|
2543
3044
|
scope: contextRefreshOptions.length > 0 ? 'task' : 'project-memory',
|
|
2544
3045
|
status: knowledgeMap.status,
|
|
2545
3046
|
action: contextRefreshOptions.length > 0 ? 'task context-refresh' : action,
|
|
@@ -2571,14 +3072,14 @@ export class WorkflowService {
|
|
|
2571
3072
|
},
|
|
2572
3073
|
}
|
|
2573
3074
|
: {}),
|
|
2574
|
-
});
|
|
3075
|
+
}));
|
|
2575
3076
|
}
|
|
2576
3077
|
const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
|
|
2577
3078
|
if (taskCanRebindKnowledge(activeTask) &&
|
|
2578
3079
|
activeTask.planHash &&
|
|
2579
3080
|
(activeTask.knowledgeMapRevision !== knowledgeMap.revision ||
|
|
2580
3081
|
activeTask.knowledgeMapHash !== knowledgeMapHash)) {
|
|
2581
|
-
return withAdoption({
|
|
3082
|
+
return withAdoption(withMilestoneCancellation({
|
|
2582
3083
|
scope: 'task',
|
|
2583
3084
|
id: activeTask.id,
|
|
2584
3085
|
status: activeTask.status,
|
|
@@ -2586,9 +3087,9 @@ export class WorkflowService {
|
|
|
2586
3087
|
revision: activeTask.revision,
|
|
2587
3088
|
knowledgeMapRevision: knowledgeMap.revision,
|
|
2588
3089
|
knowledgeMapHash,
|
|
2589
|
-
});
|
|
3090
|
+
}));
|
|
2590
3091
|
}
|
|
2591
|
-
return withAdoption(taskNext ?? this.nextForTaskWithDelegation(status.projectId, activeTask, null));
|
|
3092
|
+
return withAdoption(withMilestoneCancellation(taskNext ?? this.nextForTaskWithDelegation(status.projectId, activeTask, null)));
|
|
2592
3093
|
}
|
|
2593
3094
|
const activeMilestone = status.milestones.find((milestone) => !['accepted', 'cancelled'].includes(milestone.status));
|
|
2594
3095
|
if (activeMilestone) {
|
|
@@ -2667,6 +3168,19 @@ export class WorkflowService {
|
|
|
2667
3168
|
status: activeMilestone.status,
|
|
2668
3169
|
action: nextForMilestone(activeMilestone),
|
|
2669
3170
|
revision: activeMilestone.revision,
|
|
3171
|
+
...(activeMilestone.status === 'awaiting_execution_authorization' && activeMilestone.planHash
|
|
3172
|
+
? {
|
|
3173
|
+
milestoneAuthorizationOptions: [
|
|
3174
|
+
{ action: 'milestone authorize', mode: 'direct-human' },
|
|
3175
|
+
{
|
|
3176
|
+
action: 'milestone autonomy-prepare',
|
|
3177
|
+
mode: 'delegated',
|
|
3178
|
+
requiresExactHumanConfirmation: true,
|
|
3179
|
+
maximumDurationHours: 72,
|
|
3180
|
+
},
|
|
3181
|
+
],
|
|
3182
|
+
}
|
|
3183
|
+
: {}),
|
|
2670
3184
|
...(activeMilestone.status === 'active'
|
|
2671
3185
|
? {
|
|
2672
3186
|
completionPolicy: {
|
|
@@ -2823,6 +3337,19 @@ export class WorkflowService {
|
|
|
2823
3337
|
throw error;
|
|
2824
3338
|
}
|
|
2825
3339
|
}
|
|
3340
|
+
let catalogCorrectiveReplanPosture = guardedCorrectivePosture?.posture === 'replan-required'
|
|
3341
|
+
? guardedCorrectivePosture
|
|
3342
|
+
: null;
|
|
3343
|
+
if (!remediationModeRecovery && planRiskAudit && guardedCorrectivePosture === null) {
|
|
3344
|
+
try {
|
|
3345
|
+
catalogCorrectiveReplanPosture = identifyCorrectiveReplanPosture(task, planRiskAudit.reviewRequiredStepIds, readCorrectiveDecisionEvents(this.store, task), readRemediationEvents(this.store, task));
|
|
3346
|
+
}
|
|
3347
|
+
catch (error) {
|
|
3348
|
+
remediationModeRecovery = findRemediationModeRecoveryCandidate(this.store, task);
|
|
3349
|
+
if (!remediationModeRecovery)
|
|
3350
|
+
throw error;
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
2826
3353
|
const next = this.taskGitNavigationOverride(task) ?? nextForTask(task);
|
|
2827
3354
|
const handoffPosture = readTaskC1Posture(this.store, task);
|
|
2828
3355
|
const strictStepReviewStep = planRiskAudit
|
|
@@ -2835,6 +3362,11 @@ export class WorkflowService {
|
|
|
2835
3362
|
return buildCurrentStrictStepReviewCycle(this.store, task, step.id).resolution === 'unverified';
|
|
2836
3363
|
}) ?? null
|
|
2837
3364
|
: null;
|
|
3365
|
+
const strictReviewInfrastructureFailures = strictStepReviewStep
|
|
3366
|
+
? countUnverifiedStrictStepReviews(this.store, task, strictStepReviewStep.id)
|
|
3367
|
+
: 0;
|
|
3368
|
+
const strictTaskReviewRequired = (task.status === 'validating' && task.review.status === 'pending')
|
|
3369
|
+
|| (task.status === 'blocked' && task.review.status === 'unverified');
|
|
2838
3370
|
const handoffOverlay = handoffPosture.state === 'pending'
|
|
2839
3371
|
? {
|
|
2840
3372
|
action: 'task claim',
|
|
@@ -2843,6 +3375,15 @@ export class WorkflowService {
|
|
|
2843
3375
|
state: 'pending',
|
|
2844
3376
|
targetActor: handoffPosture.targetActor,
|
|
2845
3377
|
eventId: handoffPosture.latestEvent.eventId,
|
|
3378
|
+
credentialRecovery: {
|
|
3379
|
+
action: 'task handoff-replace',
|
|
3380
|
+
when: 'claim credential is unavailable',
|
|
3381
|
+
requiredActor: handoffPosture.latestEvent.actor,
|
|
3382
|
+
targetActor: handoffPosture.targetActor,
|
|
3383
|
+
grantId: handoffPosture.latestEvent.grantId,
|
|
3384
|
+
expectedRevision: task.revision,
|
|
3385
|
+
invalidatesPriorCredential: true,
|
|
3386
|
+
},
|
|
2846
3387
|
},
|
|
2847
3388
|
}
|
|
2848
3389
|
: handoffPosture.state === 'claimed'
|
|
@@ -2865,6 +3406,63 @@ export class WorkflowService {
|
|
|
2865
3406
|
},
|
|
2866
3407
|
}
|
|
2867
3408
|
: {};
|
|
3409
|
+
const correctiveReplanOverlay = (posture) => {
|
|
3410
|
+
const evaluation = evaluateTaskCorrectiveReplanAvailability(task, posture);
|
|
3411
|
+
const writerLease = inspectRawWriterLease(this.store.lockRoot(task.projectId), task.id, this.store.root);
|
|
3412
|
+
const command = handoffPosture.state === 'claimed'
|
|
3413
|
+
? 'task corrective-yield'
|
|
3414
|
+
: writerLease
|
|
3415
|
+
? 'doctor'
|
|
3416
|
+
: 'task corrective-replan-prepare';
|
|
3417
|
+
if (evaluation.allowed && handoffPosture.state === 'none' && writerLease) {
|
|
3418
|
+
const navigation = correctiveReplanNavigation(evaluation);
|
|
3419
|
+
return {
|
|
3420
|
+
action: command,
|
|
3421
|
+
blockedAction: 'task corrective-replan-prepare',
|
|
3422
|
+
stepId: posture.stepId,
|
|
3423
|
+
navigation: {
|
|
3424
|
+
kind: 'blocked',
|
|
3425
|
+
transitionId: navigation.transitionId,
|
|
3426
|
+
semanticFingerprint: navigation.semanticFingerprint,
|
|
3427
|
+
command: navigation.command,
|
|
3428
|
+
blockingGuardIds: ['task.corrective-replan.no-writer-lease'],
|
|
3429
|
+
pendingInputGuardIds: navigation.pendingInputGuardIds,
|
|
3430
|
+
reasonCode: 'ACTIVE_WRITER_LEASE',
|
|
3431
|
+
},
|
|
3432
|
+
correctiveDecisionGate: {
|
|
3433
|
+
attemptCount: posture.attemptCount,
|
|
3434
|
+
decision: posture.correctiveDecision?.decision ?? null,
|
|
3435
|
+
decisionEventId: posture.correctiveDecision?.eventId ?? null,
|
|
3436
|
+
requiredAction: 'The active non-C1 writer lease must expire and be explicitly repaired before corrective replan preparation.',
|
|
3437
|
+
writerLease: {
|
|
3438
|
+
owner: writerLease.owner,
|
|
3439
|
+
expiresAt: writerLease.expiresAt,
|
|
3440
|
+
},
|
|
3441
|
+
},
|
|
3442
|
+
};
|
|
3443
|
+
}
|
|
3444
|
+
return evaluation.allowed
|
|
3445
|
+
? {
|
|
3446
|
+
action: command,
|
|
3447
|
+
blockedAction: 'task plan-set',
|
|
3448
|
+
stepId: posture.stepId,
|
|
3449
|
+
navigation: { ...correctiveReplanNavigation(evaluation), command },
|
|
3450
|
+
correctiveDecisionGate: {
|
|
3451
|
+
attemptCount: posture.attemptCount,
|
|
3452
|
+
decision: posture.correctiveDecision?.decision ?? null,
|
|
3453
|
+
decisionEventId: posture.correctiveDecision?.eventId ?? null,
|
|
3454
|
+
requiredAction: handoffPosture.state === 'claimed'
|
|
3455
|
+
? 'Yield the exact C1 claim and writer lease before preparing replacement authority.'
|
|
3456
|
+
: 'Prepare a state-bound candidate, confirm the Human gate, then execute its exact receipt.',
|
|
3457
|
+
},
|
|
3458
|
+
}
|
|
3459
|
+
: {
|
|
3460
|
+
action: 'doctor',
|
|
3461
|
+
blockedAction: 'task plan-set',
|
|
3462
|
+
stepId: posture.stepId,
|
|
3463
|
+
navigation: correctiveReplanBlockedNavigation(evaluation),
|
|
3464
|
+
};
|
|
3465
|
+
};
|
|
2868
3466
|
let guardedCorrectiveOverlay = {};
|
|
2869
3467
|
if (remediationModeRecovery) {
|
|
2870
3468
|
guardedCorrectiveOverlay = {
|
|
@@ -2883,7 +3481,12 @@ export class WorkflowService {
|
|
|
2883
3481
|
},
|
|
2884
3482
|
};
|
|
2885
3483
|
}
|
|
2886
|
-
if (!remediationModeRecovery &&
|
|
3484
|
+
if (!remediationModeRecovery && guardedCorrectivePosture === null && catalogCorrectiveReplanPosture) {
|
|
3485
|
+
guardedCorrectiveOverlay = correctiveReplanOverlay(catalogCorrectiveReplanPosture);
|
|
3486
|
+
}
|
|
3487
|
+
if (!remediationModeRecovery
|
|
3488
|
+
&& guardedCorrectivePosture
|
|
3489
|
+
&& (!strictStepReviewStep || guardedCorrectivePosture.posture === 'replan-required')) {
|
|
2887
3490
|
switch (guardedCorrectivePosture.posture) {
|
|
2888
3491
|
case 'decision-required':
|
|
2889
3492
|
if (guardedCorrectivePosture.correctiveDecision
|
|
@@ -2958,16 +3561,7 @@ export class WorkflowService {
|
|
|
2958
3561
|
};
|
|
2959
3562
|
break;
|
|
2960
3563
|
case 'replan-required':
|
|
2961
|
-
guardedCorrectiveOverlay =
|
|
2962
|
-
action: 'task plan-set',
|
|
2963
|
-
stepId: guardedCorrectivePosture.stepId,
|
|
2964
|
-
correctiveDecisionGate: {
|
|
2965
|
-
attemptCount: guardedCorrectivePosture.attemptCount,
|
|
2966
|
-
decision: guardedCorrectivePosture.correctiveDecision?.decision ?? null,
|
|
2967
|
-
decisionEventId: guardedCorrectivePosture.correctiveDecision?.eventId ?? null,
|
|
2968
|
-
requiredAction: 'Record an actual new Plan posture before another remediation run.',
|
|
2969
|
-
},
|
|
2970
|
-
};
|
|
3564
|
+
guardedCorrectiveOverlay = correctiveReplanOverlay(guardedCorrectivePosture);
|
|
2971
3565
|
break;
|
|
2972
3566
|
case 'split-required':
|
|
2973
3567
|
{
|
|
@@ -3051,6 +3645,30 @@ export class WorkflowService {
|
|
|
3051
3645
|
strictStepReview: {
|
|
3052
3646
|
reviewRequired: true,
|
|
3053
3647
|
completionCommit: strictStepReviewStep.evidence.commitSha,
|
|
3648
|
+
infrastructureFailures: strictReviewInfrastructureFailures,
|
|
3649
|
+
maxLocalInfrastructureFailures: MAX_STRICT_REVIEW_INFRASTRUCTURE_FAILURES,
|
|
3650
|
+
recommendedMode: 'external-sealed',
|
|
3651
|
+
externalSealedReview: {
|
|
3652
|
+
packetCommand: 'task step-review-packet',
|
|
3653
|
+
recordCommand: 'task step-review-record',
|
|
3654
|
+
reviewerMustUseSeparateChat: true,
|
|
3655
|
+
repositorySealRequired: true,
|
|
3656
|
+
},
|
|
3657
|
+
},
|
|
3658
|
+
}
|
|
3659
|
+
: {}),
|
|
3660
|
+
...(strictTaskReviewRequired
|
|
3661
|
+
? {
|
|
3662
|
+
strictTaskReview: {
|
|
3663
|
+
reviewRequired: true,
|
|
3664
|
+
localInfrastructureFailureObserved: task.review.status === 'unverified',
|
|
3665
|
+
recommendedMode: 'external-sealed',
|
|
3666
|
+
externalSealedReview: {
|
|
3667
|
+
packetCommand: 'task review-packet',
|
|
3668
|
+
recordCommand: 'task review-sealed-record',
|
|
3669
|
+
reviewerMustUseSeparateChat: true,
|
|
3670
|
+
repositorySealRequired: true,
|
|
3671
|
+
},
|
|
3054
3672
|
},
|
|
3055
3673
|
}
|
|
3056
3674
|
: {}),
|
|
@@ -3104,6 +3722,12 @@ export class WorkflowService {
|
|
|
3104
3722
|
exactInclusionRequired: true,
|
|
3105
3723
|
briefHash: task.briefHash,
|
|
3106
3724
|
knowledgeMap: knowledgeSummary(map),
|
|
3725
|
+
planRiskAuditContract: {
|
|
3726
|
+
required: true,
|
|
3727
|
+
option: '--risk-audit-file',
|
|
3728
|
+
atomicWithPlanSet: true,
|
|
3729
|
+
plannerAuditorSeparationRequired: true,
|
|
3730
|
+
},
|
|
3107
3731
|
};
|
|
3108
3732
|
}
|
|
3109
3733
|
assertTaskContextChangeAllowed(task) {
|
|
@@ -3183,6 +3807,22 @@ export class WorkflowService {
|
|
|
3183
3807
|
policyHash: grant.policyHash,
|
|
3184
3808
|
}));
|
|
3185
3809
|
}
|
|
3810
|
+
findDelegatedContextRefreshOwner(projectId, tasks, milestonesById, approved, inspected) {
|
|
3811
|
+
for (const task of tasks) {
|
|
3812
|
+
if (!taskIsActionableFromRepositoryNext(task, milestonesById)
|
|
3813
|
+
|| !task.planHash
|
|
3814
|
+
|| !taskCanRebindKnowledge(task)
|
|
3815
|
+
|| this.taskHasTerminalCorrectiveDecision(task))
|
|
3816
|
+
continue;
|
|
3817
|
+
const assessment = assessDelegatedKnowledgeRefresh(approved, inspected, task, this.store.hashArtifact(this.store.taskRoot(projectId, task.id), 'plan.md'));
|
|
3818
|
+
if (!assessment.eligible)
|
|
3819
|
+
continue;
|
|
3820
|
+
const options = this.delegatedContextRefreshOptions(projectId, task, inspected, assessment.mode);
|
|
3821
|
+
if (options.length > 0)
|
|
3822
|
+
return { task, assessment, options };
|
|
3823
|
+
}
|
|
3824
|
+
return null;
|
|
3825
|
+
}
|
|
3186
3826
|
assertPlanBoundedSupportingSourceGrant(projectId, grantId, actor, task) {
|
|
3187
3827
|
const grant = this.store.readDelegation(projectId, grantId);
|
|
3188
3828
|
if (!task.milestoneId || grant.scope.kind !== 'milestone' || grant.scope.id !== task.milestoneId) {
|
|
@@ -3252,6 +3892,14 @@ export class WorkflowService {
|
|
|
3252
3892
|
? milestone
|
|
3253
3893
|
: readMilestoneWithIntegrity(this.store, projectId, milestone.id, (taskId) => this.store.readTask(projectId, taskId), () => this.store.listTasks(projectId))));
|
|
3254
3894
|
}
|
|
3895
|
+
listMilestonesObserved(projectId, milestones, posture) {
|
|
3896
|
+
if (!posture)
|
|
3897
|
+
return milestones;
|
|
3898
|
+
const grandfatheredMilestones = new Set(posture.terminalMilestones.map((milestone) => `${milestone.milestoneId}:${milestone.revision}:${milestone.status}`));
|
|
3899
|
+
return milestones.map((milestone) => (grandfatheredMilestones.has(`${milestone.id}:${milestone.revision}:${milestone.status}`)
|
|
3900
|
+
? milestone
|
|
3901
|
+
: inspectMilestoneWithIntegrity(this.store, projectId, milestone.id, (taskId) => this.store.readTask(projectId, taskId), () => this.store.listTasks(projectId))));
|
|
3902
|
+
}
|
|
3255
3903
|
taskGitNavigationOverride(task) {
|
|
3256
3904
|
if (task.status !== 'merging' || task.workspaceOwner !== 'local' || !task.baseCommit || !task.taskBranch) {
|
|
3257
3905
|
return null;
|
|
@@ -3277,14 +3925,44 @@ export class WorkflowService {
|
|
|
3277
3925
|
if (!action)
|
|
3278
3926
|
return next;
|
|
3279
3927
|
const policy = gitMutationPolicyForAction(task, action);
|
|
3280
|
-
const
|
|
3928
|
+
const c1Posture = readTaskC1Posture(this.store, task);
|
|
3929
|
+
const claimedC1Posture = c1Posture.state === 'claimed';
|
|
3281
3930
|
const writerTokenContract = writerTokenContractForAction(action, claimedC1Posture);
|
|
3931
|
+
const activeLease = writerTokenContract
|
|
3932
|
+
? inspectRawWriterLease(this.store.lockRoot(task.projectId), task.id, this.store.root)
|
|
3933
|
+
: null;
|
|
3934
|
+
const writerCredentialRecoveryActor = this.writerCredentialRecoveryActor(task, action, claimedC1Posture ? c1Posture.claimant : null, activeLease?.owner ?? null);
|
|
3935
|
+
const writerCredentialRecovery = writerTokenContract && writerCredentialRecoveryActor
|
|
3936
|
+
? {
|
|
3937
|
+
action: 'task writer-credential-replace',
|
|
3938
|
+
when: 'the active writer credential is unavailable',
|
|
3939
|
+
requiredActor: writerCredentialRecoveryActor,
|
|
3940
|
+
expectedRevision: task.revision,
|
|
3941
|
+
invalidatesPriorCredential: true,
|
|
3942
|
+
acquiresLeaseWhenAbsent: true,
|
|
3943
|
+
}
|
|
3944
|
+
: null;
|
|
3282
3945
|
return {
|
|
3283
3946
|
...next,
|
|
3284
3947
|
...(policy ? { gitMutationPolicy: policy } : {}),
|
|
3285
3948
|
...(writerTokenContract ? { writerTokenContract } : {}),
|
|
3949
|
+
...(writerCredentialRecovery ? { writerCredentialRecovery } : {}),
|
|
3286
3950
|
};
|
|
3287
3951
|
}
|
|
3952
|
+
writerCredentialRecoveryActor(task, action, claimedActor, activeLeaseOwner) {
|
|
3953
|
+
if (claimedActor)
|
|
3954
|
+
return claimedActor;
|
|
3955
|
+
if (activeLeaseOwner)
|
|
3956
|
+
return activeLeaseOwner;
|
|
3957
|
+
const authorizationKind = ['task merge', 'task merge-confirm'].includes(action)
|
|
3958
|
+
? 'final_acceptance'
|
|
3959
|
+
: ['task step-complete', 'task submit'].includes(action)
|
|
3960
|
+
? 'execution'
|
|
3961
|
+
: null;
|
|
3962
|
+
if (!authorizationKind)
|
|
3963
|
+
return null;
|
|
3964
|
+
return [...task.authorizations].reverse().find((authorization) => authorization.kind === authorizationKind && authorization.decision === 'approved')?.actor ?? null;
|
|
3965
|
+
}
|
|
3288
3966
|
describeMilestoneInitialAssembly(projectId, milestone, tasks) {
|
|
3289
3967
|
const assessment = assessMilestoneInitialAssembly({
|
|
3290
3968
|
store: this.store,
|
|
@@ -3325,6 +4003,12 @@ export class WorkflowService {
|
|
|
3325
4003
|
actor,
|
|
3326
4004
|
});
|
|
3327
4005
|
}
|
|
4006
|
+
publicWriterLeaseDiagnostic(lease) {
|
|
4007
|
+
return {
|
|
4008
|
+
...toPublicWriterLease(lease, this.now()),
|
|
4009
|
+
tokenFingerprint: createHash('sha256').update(lease.token).digest('hex').slice(0, 16),
|
|
4010
|
+
};
|
|
4011
|
+
}
|
|
3328
4012
|
proveWriterLeaseTokenForHandoff(context, taskId, writerToken) {
|
|
3329
4013
|
const currentLease = context.locks.inspect(taskId);
|
|
3330
4014
|
if (!currentLease) {
|
|
@@ -3414,9 +4098,12 @@ export class WorkflowService {
|
|
|
3414
4098
|
storedKnowledgeMapHash: storedHash,
|
|
3415
4099
|
});
|
|
3416
4100
|
}
|
|
3417
|
-
inspectAdoptionPreparation(identity, tasks, milestones) {
|
|
3418
|
-
const
|
|
3419
|
-
|
|
4101
|
+
inspectAdoptionPreparation(identity, tasks, milestones, observedLeases, observedPosture) {
|
|
4102
|
+
const leases = observedLeases
|
|
4103
|
+
?? new WriterLockManager(this.store.lockRoot(identity.projectId), this.now, this.store.root).list();
|
|
4104
|
+
const currentPosture = observedPosture === undefined
|
|
4105
|
+
? readCurrentAdoptionPosture(this.store, identity.projectId)
|
|
4106
|
+
: observedPosture;
|
|
3420
4107
|
if (currentPosture) {
|
|
3421
4108
|
assertAdoptionBaselinePreserved(currentPosture, tasks, milestones);
|
|
3422
4109
|
}
|
|
@@ -3429,7 +4116,7 @@ export class WorkflowService {
|
|
|
3429
4116
|
tasks,
|
|
3430
4117
|
milestones,
|
|
3431
4118
|
sidecarInventory: this.collectAdoptionSidecarInventory(identity.projectId, tasks, milestones),
|
|
3432
|
-
blockers: this.computeAdoptionBoundaryBlockers(identity, tasks,
|
|
4119
|
+
blockers: this.computeAdoptionBoundaryBlockers(identity, tasks, leases),
|
|
3433
4120
|
now: this.now(),
|
|
3434
4121
|
}, currentPosture);
|
|
3435
4122
|
}
|
|
@@ -4046,11 +4733,14 @@ function writerTokenContractForAction(action, claimedC1Posture = false) {
|
|
|
4046
4733
|
const claimedC1LeaseBound = [
|
|
4047
4734
|
'task run',
|
|
4048
4735
|
'task step-review',
|
|
4736
|
+
'task step-review-record',
|
|
4049
4737
|
'task review-record',
|
|
4050
4738
|
'task review-launch',
|
|
4739
|
+
'task review-sealed-record',
|
|
4051
4740
|
'task corrective-decision-recover',
|
|
4052
4741
|
'task remediation-mode-recover',
|
|
4053
4742
|
'task corrective-decision',
|
|
4743
|
+
'task corrective-yield',
|
|
4054
4744
|
'task handoff',
|
|
4055
4745
|
'task handoff-prepare',
|
|
4056
4746
|
'task handback',
|
|
@@ -4064,7 +4754,26 @@ function writerTokenContractForAction(action, claimedC1Posture = false) {
|
|
|
4064
4754
|
option: '--writer-token',
|
|
4065
4755
|
source: 'active-writer-lease',
|
|
4066
4756
|
sensitive: true,
|
|
4757
|
+
renewalDisposition: 'reuse-presented-token',
|
|
4758
|
+
tokenReissuedOnRenewal: false,
|
|
4759
|
+
};
|
|
4760
|
+
}
|
|
4761
|
+
function observationPreflightBlockers(observation) {
|
|
4762
|
+
const messages = {
|
|
4763
|
+
PROJECT_REGISTRATION_MISSING: 'Workflow project registration is missing.',
|
|
4764
|
+
PROJECT_REPOSITORY_ROOT_UNBOUND: 'The current repository root is not bound to the Workflow project.',
|
|
4765
|
+
PROJECT_RECOVERY_REQUIRED: 'A project transaction requires explicit recovery.',
|
|
4766
|
+
MILESTONE_RECOVERY_REQUIRED: 'A Milestone transaction requires explicit recovery.',
|
|
4767
|
+
TASK_RECOVERY_REQUIRED: 'A Task corrective-replan transaction requires explicit recovery.',
|
|
4768
|
+
TASK_TRANSACTION_INTEGRITY_FAILED: 'A Task transaction journal failed integrity validation.',
|
|
4769
|
+
TASK_OPERATION_IN_PROGRESS: 'A Core Task operation is in progress.',
|
|
4770
|
+
STALE_TASK_OPERATION: 'A stale Core Task operation requires bounded repair.',
|
|
4771
|
+
ADOPTION_MISSING: 'Workflow lifecycle adoption is missing.',
|
|
4067
4772
|
};
|
|
4773
|
+
return observation.reasonCodes.flatMap((reasonCode) => {
|
|
4774
|
+
const message = messages[reasonCode];
|
|
4775
|
+
return message ? [message] : [];
|
|
4776
|
+
});
|
|
4068
4777
|
}
|
|
4069
4778
|
function knowledgeSummary(map) {
|
|
4070
4779
|
return {
|