codex-workflow-v2 2.0.0-beta.1 → 2.0.0-beta.10
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 +52 -4
- 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 +12 -0
- package/dist/src/alpha6/milestone.js +113 -1
- 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/remediation.js +18 -6
- package/dist/src/alpha6/remediation.js.map +1 -1
- package/dist/src/alpha6/review.d.ts +3 -0
- package/dist/src/alpha6/review.js +24 -0
- package/dist/src/alpha6/review.js.map +1 -1
- package/dist/src/cli.js +473 -45
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +169 -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/dependency-provenance.d.ts +19 -0
- package/dist/src/dependency-provenance.js +185 -0
- package/dist/src/dependency-provenance.js.map +1 -0
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/git.js +5 -4
- package/dist/src/git.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 +43 -1
- package/dist/src/reviewer.js +203 -50
- 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/version.js.map +1 -1
- package/dist/src/workflow.d.ts +83 -24
- package/dist/src/workflow.js +1255 -187
- package/dist/src/workflow.js.map +1 -1
- package/docs/autonomy-guardrails.md +40 -1
- package/docs/beta2-initial-assembly-navigation-brief.md +616 -0
- package/docs/delegated-approval.md +26 -1
- package/docs/development-flow.md +32 -2
- package/docs/lifecycle/state-machine-stabilization.md +641 -0
- package/docs/release.md +17 -2
- package/docs/split-required-recovery.md +5 -4
- package/docs/stable-release-defect-register.md +465 -3
- package/docs/updating-existing-project.md +59 -4
- package/docs/validation-report.md +85 -56
- package/package.json +5 -1
- package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +10 -3
- package/plugins/codex-workflow-gateway/references/protocol.md +74 -3
- package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +376 -0
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +246 -1
- package/references/state-machine.md +4 -1
- package/roles/delivery-coordinator.md +39 -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/task.schema.json +24 -1
- package/schemas/transition-definition.schema.json +2609 -0
- package/schemas/transition-payloads.schema.json +140 -0
package/dist/src/workflow.js
CHANGED
|
@@ -2,6 +2,7 @@ import { appendFileSync, existsSync, lstatSync, readFileSync, readdirSync } from
|
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { PROTOCOL_VERSION, STATE_SCHEMA_VERSION, } from './contracts.js';
|
|
5
|
+
import { inspectDependencyProvenanceRecovery } from './dependency-provenance.js';
|
|
5
6
|
import { renderBrief, renderPlan, renderResult } from './artifacts.js';
|
|
6
7
|
import { assertDelegationCanAuthorize, hashDelegationPolicy, prepareDelegationGrantGate, } from './delegation.js';
|
|
7
8
|
import { assessDiscovery, mergeUnique } from './domain/discovery.js';
|
|
@@ -9,24 +10,32 @@ import { pathAllowed, validatePlan } from './domain/validation.js';
|
|
|
9
10
|
import { WorkflowError } from './errors.js';
|
|
10
11
|
import { bindGraphEvidence, createGraphRefreshRequest, fallbackGraphBinding, inspectGraphBinding, validateGraphRequestCurrent, } from './graph.js';
|
|
11
12
|
import { inspectLocalDependency, inspectWorkflowVersions } from './diagnostics.js';
|
|
12
|
-
import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, readTaskC1Posture, } from './alpha6/handoff.js';
|
|
13
|
+
import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, appendTaskWriterCredentialReplacement, assertTaskClaimAllowed, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, readTaskC1Posture, } from './alpha6/handoff.js';
|
|
13
14
|
import { applyAdoptionPosture, assertAdoptionBaselinePreserved, buildAdoptionPreparation, initializeProjectRegistrationAdoption, readCurrentAdoptionPosture, } from './alpha6/adoption.js';
|
|
14
15
|
import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, readCurrentPlanRiskAudit, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
|
|
15
16
|
import { appendCorrectiveDecisionEvent, appendRemediationEvent, appendRemediationModeRecovery, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, defaultCorrectiveAuditorActor, findCurrentGuardedRemediationPosture, findRemediationModeRecoveryCandidate, findFailedGuardedStepRequiringCorrectiveDecision, readGuardedRemediationPostureForStep, readCorrectiveDecisionEvents, readRemediationEvents, } from './alpha6/remediation.js';
|
|
16
|
-
import { applyMilestoneScopeChangeTransaction, assertMilestoneMembershipIntegrity, assertMilestoneScopeChangeStatusAllowed, normalizeMilestonePlan, prepareMilestoneScopeChangeCandidate, prepareMilestoneScopeChangeBundle, readMilestoneForScopeChange, readMilestoneWithIntegrity, } from './alpha6/milestone.js';
|
|
17
|
+
import { assessMilestoneInitialAssembly, applyMilestoneScopeChangeTransaction, assertMilestoneMembershipIntegrity, assertMilestoneScopeChangeStatusAllowed, inspectMilestoneWithIntegrity, normalizeMilestonePlan, prepareMilestoneScopeChangeCandidate, prepareMilestoneScopeChangeBundle, readMilestoneForScopeChange, readMilestoneWithIntegrity, } from './alpha6/milestone.js';
|
|
17
18
|
import { applyProjectTransaction, recoverProjectTransactions, runRollbackProjectTransaction, } from './beta1/project-transaction.js';
|
|
18
|
-
import { buildCurrentStrictStepReviewCycle, ensurePendingStrictStepReview, isStepReviewRequired, recordStrictStepReview, } from './alpha6/review.js';
|
|
19
|
+
import { buildCurrentStrictStepReviewCycle, countUnverifiedStrictStepReviews, ensurePendingStrictStepReview, isStepReviewRequired, latestPassedVerifiedStepReviewEvent, recordStrictStepReview, verifiedHistoricalStepReviewCommits, } from './alpha6/review.js';
|
|
19
20
|
import { applyMilestoneAutonomyContract, assertAutonomousPlanEvolution, prepareMilestoneAutonomyContract, readMilestoneAutonomyEvents, requireActiveMilestoneAutonomy, } from './alpha7/autonomy.js';
|
|
20
21
|
import { appendCorrectiveDecisionRecovery, readValidatedCorrectiveDecisionRecoveryForNavigation, requireCorrectiveDecisionRecoveryForRun, } from './alpha7/corrective-recovery.js';
|
|
21
22
|
import { applyKnowledgeSelections, hashKnowledgeMap, inspectKnowledgeMap, normalizeRelativePath, reconcileKnowledgeMap, scanProjectKnowledge, selectKnowledgeSources, } from './memory.js';
|
|
22
23
|
import { canonicalJsonStringify, sha256Hex } from './alpha6/store-sidecars.js';
|
|
24
|
+
import { ensureDirectory } from './fs-utils.js';
|
|
23
25
|
import { commitStep, mergeTaskBranch, runChecks, startLocalTaskBranch, syncBaseIntoTask, validateTaskHistory, } from './git.js';
|
|
24
26
|
import { changedFiles, currentBranch, gitIsAncestor, headCommit, isClean, resolveRepositoryIdentity, runGit, } from './repository.js';
|
|
25
|
-
import { launchStrictStepReviewer } from './reviewer.js';
|
|
26
|
-
import { WriterLockManager } from './state/lock.js';
|
|
27
|
+
import { launchStrictStepReviewer, prepareExternalStrictStepReviewPacket, prepareExternalStrictTaskReviewPacket, strictReviewInputHash, validateExternalStrictStepReviewInput, validateExternalStrictTaskReviewInput, } from './reviewer.js';
|
|
28
|
+
import { listCoreTaskOperationMutexes, inspectWriterLease as inspectRawWriterLease, toPublicWriterLease, WriterLockManager, } from './state/lock.js';
|
|
27
29
|
import { FileStateStore } from './state/store.js';
|
|
30
|
+
import { inspectCorrectiveReplanTaskTransactions } from './state/corrective-replan-transaction.js';
|
|
31
|
+
import { inspectCorrectiveYieldTaskTransactions } from './state/corrective-yield-transaction.js';
|
|
32
|
+
import { createCorrectiveYieldExecutor } from './state/corrective-yield-executor.js';
|
|
33
|
+
import { createCorrectiveReplanPublicController, } from './state/corrective-replan-public.js';
|
|
34
|
+
import { inspectWorkflowObservationBoundary, } from './observation.js';
|
|
28
35
|
import { createEntityId } from './ulid.js';
|
|
29
|
-
import { PACKAGE_VERSION } from './version.js';
|
|
36
|
+
import { PACKAGE_NAME, PACKAGE_VERSION } from './version.js';
|
|
37
|
+
import { correctiveReplanBlockedNavigation, correctiveReplanNavigation, evaluateTaskCorrectiveReplanAvailability, evaluateTaskCorrectiveReplanCandidate, identifyCorrectiveReplanPosture, prepareCorrectiveReplanCandidate, TASK_CORRECTIVE_REPLAN_TRANSITION_ID, } from './lifecycle/corrective-replan.js';
|
|
38
|
+
const MAX_STRICT_REVIEW_INFRASTRUCTURE_FAILURES = 2;
|
|
30
39
|
export class WorkflowService {
|
|
31
40
|
store;
|
|
32
41
|
now;
|
|
@@ -34,13 +43,42 @@ export class WorkflowService {
|
|
|
34
43
|
this.store = store;
|
|
35
44
|
this.now = now;
|
|
36
45
|
}
|
|
37
|
-
|
|
46
|
+
#mutationContext(repository) {
|
|
38
47
|
const identity = resolveRepositoryIdentity(repository);
|
|
39
48
|
const projectRoot = this.store.projectRoot(identity.projectId);
|
|
49
|
+
const replanTransactions = inspectCorrectiveReplanTaskTransactions(projectRoot, this.store.root);
|
|
50
|
+
const yieldTransactions = inspectCorrectiveYieldTaskTransactions(projectRoot, this.store.root);
|
|
51
|
+
const taskTransactions = {
|
|
52
|
+
corrupt: [...replanTransactions.corrupt, ...yieldTransactions.corrupt],
|
|
53
|
+
pending: [...replanTransactions.pending, ...yieldTransactions.pending],
|
|
54
|
+
};
|
|
55
|
+
if (taskTransactions.corrupt.length > 0) {
|
|
56
|
+
throw new WorkflowError('STATE_CORRUPT', 'A Task transaction journal failed integrity validation.', {
|
|
57
|
+
transactions: taskTransactions.corrupt,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (taskTransactions.pending.length > 0) {
|
|
61
|
+
throw new WorkflowError('TASK_RECOVERY_REQUIRED', 'A Task transaction requires explicit recovery.', {
|
|
62
|
+
taskIds: [...new Set(taskTransactions.pending.map((transaction) => transaction.taskId))].sort(),
|
|
63
|
+
transactionIds: taskTransactions.pending.map((transaction) => transaction.transactionId).sort(),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const coreTaskOperations = listCoreTaskOperationMutexes(this.store.lockRoot(identity.projectId), this.store.root);
|
|
67
|
+
if (coreTaskOperations.length > 0) {
|
|
68
|
+
throw new WorkflowError('LOCKED', 'A Core Task operation owns the project mutation boundary.', {
|
|
69
|
+
taskIds: coreTaskOperations.map((operation) => operation.entityId).sort(),
|
|
70
|
+
operationIds: coreTaskOperations.map((operation) => operation.operationId).sort(),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
40
73
|
recoverProjectTransactions(projectRoot, identity.projectId);
|
|
41
74
|
const projectFile = path.join(projectRoot, 'project.json');
|
|
42
75
|
const projectAlreadyRegistered = existsSync(projectFile);
|
|
43
76
|
if (!projectAlreadyRegistered) {
|
|
77
|
+
// The registration posture is a sidecar under the project root. A completely
|
|
78
|
+
// fresh CODEX_WORKFLOW_STATE_HOME has no project directory yet, so establish
|
|
79
|
+
// only that directory before writing the posture; project.json remains the
|
|
80
|
+
// final registration record written below.
|
|
81
|
+
ensureDirectory(projectRoot);
|
|
44
82
|
initializeProjectRegistrationAdoption(this.store, {
|
|
45
83
|
projectId: identity.projectId,
|
|
46
84
|
repositoryIdentity: identity,
|
|
@@ -54,17 +92,103 @@ export class WorkflowService {
|
|
|
54
92
|
return {
|
|
55
93
|
identity,
|
|
56
94
|
store: this.store,
|
|
57
|
-
locks: new WriterLockManager(this.store.lockRoot(identity.projectId), this.now),
|
|
95
|
+
locks: new WriterLockManager(this.store.lockRoot(identity.projectId), this.now, this.store.root),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
inspectWriterLease(repository, entityId) {
|
|
99
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
100
|
+
const lease = inspectRawWriterLease(this.store.lockRoot(identity.projectId), entityId, this.store.root);
|
|
101
|
+
return lease ? this.publicWriterLeaseDiagnostic(lease) : null;
|
|
102
|
+
}
|
|
103
|
+
repairStaleWriterLease(repository, entityId) {
|
|
104
|
+
const context = this.#mutationContext(repository);
|
|
105
|
+
const lease = context.locks.repairStale(entityId);
|
|
106
|
+
return {
|
|
107
|
+
...this.publicWriterLeaseDiagnostic(lease),
|
|
108
|
+
repaired: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
prepareCorrectiveReplan(repository, taskId, candidate) {
|
|
112
|
+
return this.correctiveReplanController(repository).prepare({ taskId, candidate });
|
|
113
|
+
}
|
|
114
|
+
confirmCorrectiveReplan(repository, taskId, candidate, gate, confirmationCode) {
|
|
115
|
+
return this.correctiveReplanController(repository).confirm({ taskId, candidate, gate, confirmationCode });
|
|
116
|
+
}
|
|
117
|
+
executeCorrectiveReplan(repository, taskId, candidate, credential) {
|
|
118
|
+
return this.correctiveReplanController(repository).execute({ taskId, candidate, credential });
|
|
119
|
+
}
|
|
120
|
+
recoverCorrectiveReplan(repository, taskId, transactionId) {
|
|
121
|
+
return this.correctiveReplanController(repository).recover({ taskId, transactionId });
|
|
122
|
+
}
|
|
123
|
+
executeCorrectiveYield(repository, taskId, expectedTaskRevision, actor, writerToken, reason) {
|
|
124
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
125
|
+
return createCorrectiveYieldExecutor({
|
|
126
|
+
store: this.store,
|
|
127
|
+
projectId: identity.projectId,
|
|
128
|
+
now: this.now,
|
|
129
|
+
}).execute({
|
|
130
|
+
projectId: identity.projectId,
|
|
131
|
+
taskId,
|
|
132
|
+
expectedTaskRevision,
|
|
133
|
+
actor,
|
|
134
|
+
writerToken,
|
|
135
|
+
reason,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
recoverCorrectiveYield(repository, taskId, transactionId) {
|
|
139
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
140
|
+
return createCorrectiveYieldExecutor({
|
|
141
|
+
store: this.store,
|
|
142
|
+
projectId: identity.projectId,
|
|
143
|
+
now: this.now,
|
|
144
|
+
}).recover({ taskId, transactionId });
|
|
145
|
+
}
|
|
146
|
+
correctiveReplanController(repository) {
|
|
147
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
148
|
+
return createCorrectiveReplanPublicController({
|
|
149
|
+
store: this.store,
|
|
150
|
+
projectId: identity.projectId,
|
|
151
|
+
repositoryRoot: identity.repositoryRoot,
|
|
152
|
+
now: this.now,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
observationSnapshot(repository) {
|
|
156
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
157
|
+
const boundary = inspectWorkflowObservationBoundary({
|
|
158
|
+
store: this.store,
|
|
159
|
+
identity,
|
|
160
|
+
now: this.now(),
|
|
161
|
+
});
|
|
162
|
+
if (boundary.observation.kind !== 'assessment') {
|
|
163
|
+
return {
|
|
164
|
+
...boundary,
|
|
165
|
+
discoveries: [],
|
|
166
|
+
tasks: [],
|
|
167
|
+
milestones: [],
|
|
168
|
+
delegations: [],
|
|
169
|
+
adoption: null,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const tasks = this.store.listTasks(identity.projectId);
|
|
173
|
+
const rawMilestones = this.store.listMilestones(identity.projectId);
|
|
174
|
+
const adoption = this.inspectAdoptionPreparation(identity, tasks, rawMilestones, boundary.leases, boundary.currentAdoptionPosture);
|
|
175
|
+
return {
|
|
176
|
+
...boundary,
|
|
177
|
+
discoveries: this.store.listDiscoveries(identity.projectId),
|
|
178
|
+
tasks,
|
|
179
|
+
milestones: this.listMilestonesObserved(identity.projectId, rawMilestones, adoption.currentPosture),
|
|
180
|
+
delegations: this.store.listDelegations(identity.projectId),
|
|
181
|
+
adoption,
|
|
58
182
|
};
|
|
59
183
|
}
|
|
60
184
|
prepareDelegation(repository, input) {
|
|
61
|
-
const { identity } = this
|
|
185
|
+
const { identity } = this.#mutationContext(repository);
|
|
62
186
|
const gate = prepareDelegationGrantGate(identity.projectId, input, this.now());
|
|
63
187
|
this.assertDelegationScopeExists(identity.projectId, gate.policy);
|
|
64
188
|
return gate;
|
|
65
189
|
}
|
|
66
190
|
grantDelegation(repository, input, confirmationCode) {
|
|
67
|
-
const { identity } = this
|
|
191
|
+
const { identity } = this.#mutationContext(repository);
|
|
68
192
|
const gate = prepareDelegationGrantGate(identity.projectId, input, this.now());
|
|
69
193
|
this.assertDelegationScopeExists(identity.projectId, gate.policy);
|
|
70
194
|
if (confirmationCode.trim() !== gate.confirmationCode) {
|
|
@@ -100,7 +224,7 @@ export class WorkflowService {
|
|
|
100
224
|
}, null);
|
|
101
225
|
}
|
|
102
226
|
revokeDelegation(repository, grantId, expectedRevision, actor, reason) {
|
|
103
|
-
const { identity } = this
|
|
227
|
+
const { identity } = this.#mutationContext(repository);
|
|
104
228
|
const grant = this.store.readDelegation(identity.projectId, grantId);
|
|
105
229
|
assertExpectedRevision(grant, expectedRevision);
|
|
106
230
|
if (grant.status !== 'active') {
|
|
@@ -124,7 +248,7 @@ export class WorkflowService {
|
|
|
124
248
|
startDiscovery(repository, input) {
|
|
125
249
|
if (!input.goal.trim())
|
|
126
250
|
throw new WorkflowError('INVALID_ARGUMENT', 'Discovery requires a substantive goal.');
|
|
127
|
-
const { identity } = this
|
|
251
|
+
const { identity } = this.#mutationContext(repository);
|
|
128
252
|
const timestamp = this.now().toISOString();
|
|
129
253
|
const initial = {
|
|
130
254
|
schemaVersion: STATE_SCHEMA_VERSION,
|
|
@@ -152,7 +276,7 @@ export class WorkflowService {
|
|
|
152
276
|
return this.store.writeDiscovery(initial, null);
|
|
153
277
|
}
|
|
154
278
|
updateDiscovery(repository, discoveryId, expectedRevision, patch) {
|
|
155
|
-
const { identity } = this
|
|
279
|
+
const { identity } = this.#mutationContext(repository);
|
|
156
280
|
const current = this.store.readDiscovery(identity.projectId, discoveryId);
|
|
157
281
|
if (current.status === 'materialized' || current.status === 'abandoned') {
|
|
158
282
|
throw new WorkflowError('TRANSITION_BLOCKED', `Discovery ${discoveryId} is ${current.status}.`);
|
|
@@ -176,7 +300,7 @@ export class WorkflowService {
|
|
|
176
300
|
return this.store.writeDiscovery(updated, expectedRevision);
|
|
177
301
|
}
|
|
178
302
|
materializeTask(repository, discoveryId, expectedRevision, title, baseBranch = 'main', milestoneId = null) {
|
|
179
|
-
const { identity } = this
|
|
303
|
+
const { identity } = this.#mutationContext(repository);
|
|
180
304
|
const discovery = this.store.readDiscovery(identity.projectId, discoveryId);
|
|
181
305
|
assertExpectedRevision(discovery, expectedRevision);
|
|
182
306
|
const readiness = assessDiscovery(discovery);
|
|
@@ -253,7 +377,7 @@ export class WorkflowService {
|
|
|
253
377
|
return { discovery: savedDiscovery, task: savedTask, taskRoot };
|
|
254
378
|
}
|
|
255
379
|
replaceSplitRequiredTask(repository, oldTaskId, expectedOldTaskRevision, discoveryId, expectedDiscoveryRevision, expectedMilestoneRevision, title, actor) {
|
|
256
|
-
const { identity, locks } = this
|
|
380
|
+
const { identity, locks } = this.#mutationContext(repository);
|
|
257
381
|
const oldTask = this.store.readTask(identity.projectId, oldTaskId);
|
|
258
382
|
assertExpectedRevision(oldTask, expectedOldTaskRevision);
|
|
259
383
|
if (!oldTask.milestoneId) {
|
|
@@ -504,7 +628,7 @@ export class WorkflowService {
|
|
|
504
628
|
};
|
|
505
629
|
}
|
|
506
630
|
materializeMilestone(repository, discoveryId, expectedRevision, title, baseBranch = 'main') {
|
|
507
|
-
const { identity } = this
|
|
631
|
+
const { identity } = this.#mutationContext(repository);
|
|
508
632
|
const discovery = this.store.readDiscovery(identity.projectId, discoveryId);
|
|
509
633
|
assertExpectedRevision(discovery, expectedRevision);
|
|
510
634
|
const readiness = assessDiscovery(discovery);
|
|
@@ -552,7 +676,7 @@ export class WorkflowService {
|
|
|
552
676
|
};
|
|
553
677
|
}
|
|
554
678
|
setMilestonePlan(repository, milestoneId, expectedRevision, plan) {
|
|
555
|
-
const { identity } = this
|
|
679
|
+
const { identity } = this.#mutationContext(repository);
|
|
556
680
|
const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
|
|
557
681
|
assertExpectedRevision(milestone, expectedRevision);
|
|
558
682
|
if (!['planning', 'awaiting_execution_authorization', 'active', 'blocked'].includes(milestone.status)) {
|
|
@@ -624,7 +748,7 @@ export class WorkflowService {
|
|
|
624
748
|
return saved;
|
|
625
749
|
}
|
|
626
750
|
prepareMilestoneScopeChange(repository, milestoneId, expectedRevision, plan, actor) {
|
|
627
|
-
const { identity } = this
|
|
751
|
+
const { identity } = this.#mutationContext(repository);
|
|
628
752
|
const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
|
|
629
753
|
assertExpectedRevision(milestone, expectedRevision);
|
|
630
754
|
assertMilestoneScopeChangeStatusAllowed(milestone);
|
|
@@ -641,7 +765,7 @@ export class WorkflowService {
|
|
|
641
765
|
};
|
|
642
766
|
}
|
|
643
767
|
applyMilestoneScopeChange(repository, milestoneId, expectedRevision, plan, actor, confirmationCode) {
|
|
644
|
-
const { identity } = this
|
|
768
|
+
const { identity } = this.#mutationContext(repository);
|
|
645
769
|
const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
|
|
646
770
|
assertExpectedRevision(milestone, expectedRevision);
|
|
647
771
|
assertMilestoneScopeChangeStatusAllowed(milestone);
|
|
@@ -664,9 +788,10 @@ export class WorkflowService {
|
|
|
664
788
|
});
|
|
665
789
|
}
|
|
666
790
|
prepareMilestoneAutonomy(repository, milestoneId, expectedRevision, principal, delegate, expiresAt) {
|
|
667
|
-
const { identity } = this
|
|
791
|
+
const { identity } = this.#mutationContext(repository);
|
|
668
792
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
669
793
|
assertExpectedRevision(milestone, expectedRevision);
|
|
794
|
+
this.requireActiveKnowledgeMap(identity.repositoryRoot, identity.projectId);
|
|
670
795
|
return prepareMilestoneAutonomyContract({
|
|
671
796
|
projectId: identity.projectId,
|
|
672
797
|
milestone,
|
|
@@ -677,9 +802,10 @@ export class WorkflowService {
|
|
|
677
802
|
});
|
|
678
803
|
}
|
|
679
804
|
grantMilestoneAutonomy(repository, milestoneId, expectedRevision, principal, delegate, expiresAt, confirmationCode) {
|
|
680
|
-
const { identity } = this
|
|
805
|
+
const { identity } = this.#mutationContext(repository);
|
|
681
806
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
682
807
|
assertExpectedRevision(milestone, expectedRevision);
|
|
808
|
+
this.requireActiveKnowledgeMap(identity.repositoryRoot, identity.projectId);
|
|
683
809
|
return applyMilestoneAutonomyContract({
|
|
684
810
|
store: this.store,
|
|
685
811
|
projectId: identity.projectId,
|
|
@@ -692,7 +818,7 @@ export class WorkflowService {
|
|
|
692
818
|
});
|
|
693
819
|
}
|
|
694
820
|
evolveMilestonePlanAutonomously(repository, milestoneId, expectedRevision, plan, actor) {
|
|
695
|
-
const { identity } = this
|
|
821
|
+
const { identity } = this.#mutationContext(repository);
|
|
696
822
|
const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
|
|
697
823
|
assertExpectedRevision(milestone, expectedRevision);
|
|
698
824
|
assertMilestoneScopeChangeStatusAllowed(milestone);
|
|
@@ -722,12 +848,13 @@ export class WorkflowService {
|
|
|
722
848
|
});
|
|
723
849
|
}
|
|
724
850
|
authorizeMilestone(repository, milestoneId, expectedRevision, actor, reason = '', delegationGrantId = null) {
|
|
725
|
-
const { identity } = this
|
|
851
|
+
const { identity } = this.#mutationContext(repository);
|
|
726
852
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
727
853
|
assertExpectedRevision(milestone, expectedRevision);
|
|
728
854
|
if (milestone.status !== 'awaiting_execution_authorization' || !milestone.planHash) {
|
|
729
855
|
throw new WorkflowError('TRANSITION_BLOCKED', `Milestone ${milestoneId} is not awaiting authorization.`);
|
|
730
856
|
}
|
|
857
|
+
this.requireActiveKnowledgeMap(identity.repositoryRoot, identity.projectId);
|
|
731
858
|
const root = this.store.milestoneRoot(identity.projectId, milestoneId);
|
|
732
859
|
const planHash = this.store.hashArtifact(root, 'plan.json');
|
|
733
860
|
if (planHash !== milestone.planHash) {
|
|
@@ -751,7 +878,7 @@ export class WorkflowService {
|
|
|
751
878
|
return this.store.writeMilestone({ ...milestone, status: 'active', planHash, authorizations: [...milestone.authorizations, event] }, expectedRevision);
|
|
752
879
|
}
|
|
753
880
|
validateMilestone(repository, milestoneId, expectedRevision) {
|
|
754
|
-
const { identity } = this
|
|
881
|
+
const { identity } = this.#mutationContext(repository);
|
|
755
882
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
756
883
|
assertExpectedRevision(milestone, expectedRevision);
|
|
757
884
|
if (!['active', 'awaiting_final_acceptance'].includes(milestone.status)) {
|
|
@@ -814,7 +941,7 @@ export class WorkflowService {
|
|
|
814
941
|
return saved;
|
|
815
942
|
}
|
|
816
943
|
acceptMilestone(repository, milestoneId, expectedRevision, actor, confirmationCode, delegationGrantId = null) {
|
|
817
|
-
const { identity } = this
|
|
944
|
+
const { identity } = this.#mutationContext(repository);
|
|
818
945
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
819
946
|
assertExpectedRevision(milestone, expectedRevision);
|
|
820
947
|
if (milestone.status !== 'awaiting_final_acceptance' || !milestone.resultHash || !milestone.evidenceHash) {
|
|
@@ -853,7 +980,7 @@ export class WorkflowService {
|
|
|
853
980
|
resultHash,
|
|
854
981
|
evidenceHash,
|
|
855
982
|
headCommit: currentHead,
|
|
856
|
-
reason:
|
|
983
|
+
reason: 'Milestone final acceptance confirmed against the current validated state.',
|
|
857
984
|
authorizationMode: delegation ? 'delegated' : 'human',
|
|
858
985
|
...(delegation ? { delegation } : {}),
|
|
859
986
|
};
|
|
@@ -869,7 +996,7 @@ export class WorkflowService {
|
|
|
869
996
|
cancelMilestone(repository, milestoneId, expectedRevision, reason) {
|
|
870
997
|
if (!reason.trim())
|
|
871
998
|
throw new WorkflowError('INVALID_ARGUMENT', 'Milestone cancellation requires a reason.');
|
|
872
|
-
const { identity } = this
|
|
999
|
+
const { identity } = this.#mutationContext(repository);
|
|
873
1000
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
874
1001
|
assertExpectedRevision(milestone, expectedRevision);
|
|
875
1002
|
if (['accepted', 'cancelled'].includes(milestone.status)) {
|
|
@@ -879,21 +1006,59 @@ export class WorkflowService {
|
|
|
879
1006
|
}
|
|
880
1007
|
setTaskPlan(repository, taskId, expectedRevision, plan, correctiveAudit = null, planRiskAudit = null) {
|
|
881
1008
|
validatePlan(plan);
|
|
882
|
-
const { identity } = this
|
|
1009
|
+
const { identity } = this.#mutationContext(repository);
|
|
883
1010
|
const knowledgeMap = this.assertKnowledgeMapBinding(identity.repositoryRoot, identity.projectId, plan.knowledgeMapRevision, plan.knowledgeMapHash);
|
|
884
1011
|
this.validateKnowledgeTargets(knowledgeMap, plan);
|
|
885
1012
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
886
1013
|
assertExpectedRevision(task, expectedRevision);
|
|
887
|
-
|
|
888
|
-
throw new WorkflowError('TRANSITION_BLOCKED', `Cannot update Plan while Task is ${task.status}.`);
|
|
889
|
-
}
|
|
1014
|
+
const correctiveDecisionEvents = task.planHash ? readCorrectiveDecisionEvents(this.store, task) : [];
|
|
890
1015
|
const currentPlanCorrectiveDecisions = task.planHash
|
|
891
|
-
?
|
|
1016
|
+
? correctiveDecisionEvents.filter((event) => event.planHash === task.planHash)
|
|
892
1017
|
: [];
|
|
893
1018
|
const currentReplanDecisions = currentPlanCorrectiveDecisions.filter((event) => event.decision === 'replan-required');
|
|
894
|
-
const currentContinueFixDecisions = currentPlanCorrectiveDecisions.filter((event) =>
|
|
1019
|
+
const currentContinueFixDecisions = currentPlanCorrectiveDecisions.filter((event) => {
|
|
1020
|
+
if (event.decision !== 'continue-fix')
|
|
1021
|
+
return false;
|
|
1022
|
+
const passedBoundary = latestPassedVerifiedStepReviewEvent(this.store, task, event.stepId);
|
|
1023
|
+
return !passedBoundary || passedBoundary.recordedAt <= event.recordedAt;
|
|
1024
|
+
});
|
|
895
1025
|
const currentHardBlockingDecisions = currentPlanCorrectiveDecisions.filter((event) => event.decision === 'split-required' || event.decision === 'stop-escalate');
|
|
896
|
-
const
|
|
1026
|
+
const currentPlanRiskAudit = task.status === 'needs_fix' || currentReplanDecisions.length > 0
|
|
1027
|
+
? readCurrentPlanRiskAudit(this.store, task)
|
|
1028
|
+
: null;
|
|
1029
|
+
const classifiedCorrectivePosture = currentReplanDecisions.length > 0 && currentPlanRiskAudit
|
|
1030
|
+
? findCurrentGuardedRemediationPosture(this.store, task, currentPlanRiskAudit)
|
|
1031
|
+
: null;
|
|
1032
|
+
const correctiveReplanPosture = classifiedCorrectivePosture?.posture === 'replan-required'
|
|
1033
|
+
? classifiedCorrectivePosture
|
|
1034
|
+
: currentPlanRiskAudit
|
|
1035
|
+
? identifyCorrectiveReplanPosture(task, currentPlanRiskAudit.reviewRequiredStepIds, correctiveDecisionEvents, readRemediationEvents(this.store, task))
|
|
1036
|
+
: null;
|
|
1037
|
+
const correctiveReplanAvailability = correctiveReplanPosture?.posture === 'replan-required'
|
|
1038
|
+
? evaluateTaskCorrectiveReplanAvailability(task, correctiveReplanPosture)
|
|
1039
|
+
: null;
|
|
1040
|
+
if (currentReplanDecisions.length > 0) {
|
|
1041
|
+
if (!correctiveReplanAvailability?.allowed) {
|
|
1042
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Transition ${TASK_CORRECTIVE_REPLAN_TRANSITION_ID} is blocked for Task ${task.id}.`, {
|
|
1043
|
+
transitionId: TASK_CORRECTIVE_REPLAN_TRANSITION_ID,
|
|
1044
|
+
posture: correctiveReplanPosture?.posture ?? null,
|
|
1045
|
+
evaluationPhase: correctiveReplanAvailability?.phase ?? 'availability',
|
|
1046
|
+
blockingGuardIds: correctiveReplanAvailability?.blockingGuardIds ?? [],
|
|
1047
|
+
pendingGuardIds: correctiveReplanAvailability?.pendingGuardIds ?? [],
|
|
1048
|
+
guardResults: correctiveReplanAvailability?.guardResults ?? [],
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Legacy task plan-set cannot execute ${TASK_CORRECTIVE_REPLAN_TRANSITION_ID}.`, {
|
|
1052
|
+
taskId: task.id,
|
|
1053
|
+
transitionId: TASK_CORRECTIVE_REPLAN_TRANSITION_ID,
|
|
1054
|
+
requiredAction: readTaskC1Posture(this.store, task).state === 'claimed'
|
|
1055
|
+
? 'Run task corrective-yield first, then use task corrective-replan-prepare.'
|
|
1056
|
+
: 'Use task corrective-replan-prepare, confirm, and execute.',
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
else if (!['planning', 'awaiting_execution_authorization', 'needs_fix', 'blocked'].includes(task.status)) {
|
|
1060
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Cannot update Plan while Task is ${task.status}.`);
|
|
1061
|
+
}
|
|
897
1062
|
if (currentHardBlockingDecisions.length > 0) {
|
|
898
1063
|
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${task.id} has current corrective decisions that prohibit normal plan updates.`, {
|
|
899
1064
|
taskId: task.id,
|
|
@@ -923,9 +1088,6 @@ export class WorkflowService {
|
|
|
923
1088
|
})),
|
|
924
1089
|
});
|
|
925
1090
|
}
|
|
926
|
-
const currentPlanRiskAudit = task.status === 'needs_fix'
|
|
927
|
-
? readCurrentPlanRiskAudit(this.store, task)
|
|
928
|
-
: null;
|
|
929
1091
|
const failedReviewAttempts = countFailedReviewAttempts(this.store.taskRoot(identity.projectId, taskId));
|
|
930
1092
|
if (task.status === 'needs_fix' && task.review.status === 'failed' && failedReviewAttempts >= 2) {
|
|
931
1093
|
validateCorrectivePlanAudit(task, correctiveAudit, failedReviewAttempts);
|
|
@@ -968,7 +1130,7 @@ export class WorkflowService {
|
|
|
968
1130
|
}
|
|
969
1131
|
}
|
|
970
1132
|
const root = this.store.taskRoot(identity.projectId, taskId);
|
|
971
|
-
const
|
|
1133
|
+
const preparedPlan = prepareCorrectiveReplanCandidate(task, plan);
|
|
972
1134
|
const authorizations = task.authorizations.map((authorization) => authorization.kind === 'execution' && authorization.decision === 'approved'
|
|
973
1135
|
? { ...authorization, decision: 'superseded' }
|
|
974
1136
|
: authorization);
|
|
@@ -985,12 +1147,17 @@ export class WorkflowService {
|
|
|
985
1147
|
plannedSteps.every((step) => step.status === 'completed')) {
|
|
986
1148
|
throw new WorkflowError('TRANSITION_BLOCKED', 'A failed review requires at least one new or changed remediation Step.');
|
|
987
1149
|
}
|
|
988
|
-
if (
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1150
|
+
if (correctiveReplanPosture?.posture === 'replan-required') {
|
|
1151
|
+
const executionEvaluation = evaluateTaskCorrectiveReplanCandidate(task, correctiveReplanPosture, preparedPlan);
|
|
1152
|
+
if (!executionEvaluation.allowed) {
|
|
1153
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Transition ${TASK_CORRECTIVE_REPLAN_TRANSITION_ID} rejected the candidate Plan for Task ${task.id}.`, {
|
|
1154
|
+
transitionId: TASK_CORRECTIVE_REPLAN_TRANSITION_ID,
|
|
1155
|
+
evaluationPhase: executionEvaluation.phase,
|
|
1156
|
+
blockingGuardIds: executionEvaluation.blockingGuardIds,
|
|
1157
|
+
pendingGuardIds: executionEvaluation.pendingGuardIds,
|
|
1158
|
+
guardResults: executionEvaluation.guardResults,
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
994
1161
|
}
|
|
995
1162
|
if (guardedRemediationGate && currentReplanDecisions.length === 0 && !plannedSteps.some((step) => step.id === guardedRemediationGate.stepId)) {
|
|
996
1163
|
throw new WorkflowError('TRANSITION_BLOCKED', `Continue-fix must retain guarded Step ${guardedRemediationGate.stepId}; removing or renaming it would reset the remediation breaker.`, {
|
|
@@ -998,14 +1165,10 @@ export class WorkflowService {
|
|
|
998
1165
|
stepId: guardedRemediationGate.stepId,
|
|
999
1166
|
});
|
|
1000
1167
|
}
|
|
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
1168
|
const candidate = {
|
|
1006
1169
|
...task,
|
|
1007
1170
|
status: 'awaiting_execution_authorization',
|
|
1008
|
-
planHash:
|
|
1171
|
+
planHash: preparedPlan.planHash,
|
|
1009
1172
|
planObjective: plan.objective,
|
|
1010
1173
|
requirements: plan.requirements,
|
|
1011
1174
|
acceptance: plan.acceptance,
|
|
@@ -1025,6 +1188,18 @@ export class WorkflowService {
|
|
|
1025
1188
|
};
|
|
1026
1189
|
if (planRiskAudit)
|
|
1027
1190
|
validatePlanRiskAuditCandidate(this.store, candidate, planRiskAudit);
|
|
1191
|
+
const stagedPlan = this.store.stageArtifact(root, 'plan.md', preparedPlan.planBytes);
|
|
1192
|
+
if (stagedPlan.hash !== preparedPlan.planHash) {
|
|
1193
|
+
throw new WorkflowError('STATE_CORRUPT', 'Prepared candidate Plan hash changed during staging.', {
|
|
1194
|
+
taskId: task.id,
|
|
1195
|
+
preparedPlanHash: preparedPlan.planHash,
|
|
1196
|
+
stagedPlanHash: stagedPlan.hash,
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
if (correctiveAudit && guardedRemediationGate && currentReplanDecisions.length === 0 && currentPlanRiskAudit) {
|
|
1200
|
+
assertCorrectiveAuditorIndependence(this.store, task, guardedRemediationGate.stepId, correctiveAudit.auditor);
|
|
1201
|
+
appendCorrectiveDecisionEvent(this.store, task, guardedRemediationGate.stepId, correctiveAudit, currentPlanRiskAudit, preparedPlan.planHash, this.now());
|
|
1202
|
+
}
|
|
1028
1203
|
const saved = this.store.writeTask(candidate, expectedRevision);
|
|
1029
1204
|
this.store.publishArtifact(stagedPlan);
|
|
1030
1205
|
if (planRiskAudit)
|
|
@@ -1035,7 +1210,7 @@ export class WorkflowService {
|
|
|
1035
1210
|
return saved;
|
|
1036
1211
|
}
|
|
1037
1212
|
rebindTaskKnowledge(repository, taskId, expectedRevision) {
|
|
1038
|
-
const { identity } = this
|
|
1213
|
+
const { identity } = this.#mutationContext(repository);
|
|
1039
1214
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1040
1215
|
assertExpectedRevision(task, expectedRevision);
|
|
1041
1216
|
this.assertTaskContextChangeAllowed(task);
|
|
@@ -1120,7 +1295,7 @@ export class WorkflowService {
|
|
|
1120
1295
|
if (!delegationGrantId) {
|
|
1121
1296
|
throw new WorkflowError('TRANSITION_BLOCKED', 'Task context refresh combines approval transitions and therefore requires an explicit delegated approval grant.');
|
|
1122
1297
|
}
|
|
1123
|
-
const { identity } = this
|
|
1298
|
+
const { identity } = this.#mutationContext(repository);
|
|
1124
1299
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1125
1300
|
const currentMap = this.store.readKnowledgeMap(identity.projectId);
|
|
1126
1301
|
assertExpectedRevision(task, expectedTaskRevision);
|
|
@@ -1211,7 +1386,7 @@ export class WorkflowService {
|
|
|
1211
1386
|
});
|
|
1212
1387
|
}
|
|
1213
1388
|
authorizeTask(repository, taskId, expectedRevision, actor, reason = '', delegationGrantId = null) {
|
|
1214
|
-
const { identity } = this
|
|
1389
|
+
const { identity } = this.#mutationContext(repository);
|
|
1215
1390
|
this.readCurrentAdoptionPostureStrict(identity.projectId);
|
|
1216
1391
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1217
1392
|
assertExpectedRevision(task, expectedRevision);
|
|
@@ -1241,7 +1416,7 @@ export class WorkflowService {
|
|
|
1241
1416
|
return this.store.writeTask({ ...task, status: 'ready', briefHash, planHash, authorizations: [...task.authorizations, event] }, expectedRevision);
|
|
1242
1417
|
}
|
|
1243
1418
|
recordPlanRiskAudit(repository, taskId, expectedRevision, input) {
|
|
1244
|
-
const { identity } = this
|
|
1419
|
+
const { identity } = this.#mutationContext(repository);
|
|
1245
1420
|
this.readCurrentAdoptionPostureStrict(identity.projectId);
|
|
1246
1421
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1247
1422
|
assertExpectedRevision(task, expectedRevision);
|
|
@@ -1249,7 +1424,7 @@ export class WorkflowService {
|
|
|
1249
1424
|
return appendCurrentPlanRiskAudit(this.store, task, input, this.now());
|
|
1250
1425
|
}
|
|
1251
1426
|
showTaskHandoff(repository, taskId) {
|
|
1252
|
-
const { identity } = this
|
|
1427
|
+
const { identity } = this.#mutationContext(repository);
|
|
1253
1428
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1254
1429
|
return {
|
|
1255
1430
|
task,
|
|
@@ -1257,8 +1432,8 @@ export class WorkflowService {
|
|
|
1257
1432
|
claimTokenAvailable: false,
|
|
1258
1433
|
};
|
|
1259
1434
|
}
|
|
1260
|
-
handoffTask(repository, taskId, expectedRevision, actor, targetActor, reason, writerToken = null, grantId = null, expiresAt = null) {
|
|
1261
|
-
const context = this
|
|
1435
|
+
handoffTask(repository, taskId, expectedRevision, actor, targetActor, reason, writerToken = null, grantId = null, expiresAt = null, replacePending = false) {
|
|
1436
|
+
const context = this.#mutationContext(repository);
|
|
1262
1437
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1263
1438
|
assertExpectedRevision(task, expectedRevision);
|
|
1264
1439
|
const normalizedActor = actor.trim();
|
|
@@ -1316,6 +1491,7 @@ export class WorkflowService {
|
|
|
1316
1491
|
expectedNext: String(nextForTask(task).action ?? 'task show'),
|
|
1317
1492
|
grantId,
|
|
1318
1493
|
expiresAt: expiresAt ?? new Date(this.now().getTime() + 24 * 60 * 60 * 1000).toISOString(),
|
|
1494
|
+
replacePending,
|
|
1319
1495
|
}, this.now());
|
|
1320
1496
|
releaseLease();
|
|
1321
1497
|
return { task, event: prepared.event, bundle: prepared.bundle };
|
|
@@ -1327,15 +1503,15 @@ export class WorkflowService {
|
|
|
1327
1503
|
}
|
|
1328
1504
|
}
|
|
1329
1505
|
claimTask(repository, taskId, expectedRevision, actor, claimToken, writerToken = null) {
|
|
1330
|
-
const context = this
|
|
1506
|
+
const context = this.#mutationContext(repository);
|
|
1331
1507
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1332
1508
|
assertExpectedRevision(task, expectedRevision);
|
|
1333
1509
|
const normalizedActor = actor.trim();
|
|
1334
1510
|
if (!normalizedActor) {
|
|
1335
1511
|
throw new WorkflowError('INVALID_ARGUMENT', 'Claim actor is required.');
|
|
1336
1512
|
}
|
|
1337
|
-
const pendingHandoff =
|
|
1338
|
-
if (pendingHandoff.
|
|
1513
|
+
const pendingHandoff = assertTaskClaimAllowed(this.store, task, normalizedActor, claimToken, this.now());
|
|
1514
|
+
if (pendingHandoff.latestEvent.grantId) {
|
|
1339
1515
|
this.assertHandoffGrant(context.identity.projectId, task, normalizedActor, pendingHandoff.latestEvent.grantId);
|
|
1340
1516
|
}
|
|
1341
1517
|
const existingLease = context.locks.inspect(taskId);
|
|
@@ -1368,7 +1544,14 @@ export class WorkflowService {
|
|
|
1368
1544
|
}
|
|
1369
1545
|
try {
|
|
1370
1546
|
const event = appendTaskClaim(this.store, task, actor, claimToken, lease.token, this.now());
|
|
1371
|
-
return {
|
|
1547
|
+
return {
|
|
1548
|
+
task,
|
|
1549
|
+
event,
|
|
1550
|
+
lease: toPublicWriterLease(lease, this.now()),
|
|
1551
|
+
writerCredential: acquiredHere
|
|
1552
|
+
? { disposition: 'issued', token: lease.token }
|
|
1553
|
+
: { disposition: 'renewed' },
|
|
1554
|
+
};
|
|
1372
1555
|
}
|
|
1373
1556
|
catch (error) {
|
|
1374
1557
|
if (acquiredHere)
|
|
@@ -1376,8 +1559,45 @@ export class WorkflowService {
|
|
|
1376
1559
|
throw error;
|
|
1377
1560
|
}
|
|
1378
1561
|
}
|
|
1562
|
+
replaceClaimedWriterCredential(repository, taskId, expectedRevision, actor) {
|
|
1563
|
+
const context = this.#mutationContext(repository);
|
|
1564
|
+
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1565
|
+
assertExpectedRevision(task, expectedRevision);
|
|
1566
|
+
const normalizedActor = actor.trim();
|
|
1567
|
+
if (!normalizedActor)
|
|
1568
|
+
throw new WorkflowError('INVALID_ARGUMENT', 'Writer credential replacement actor is required.');
|
|
1569
|
+
const posture = readTaskC1Posture(this.store, task);
|
|
1570
|
+
const navigation = this.taskGitNavigationOverride(task) ?? nextForTask(task);
|
|
1571
|
+
const action = String(navigation.action);
|
|
1572
|
+
const currentLease = context.locks.inspect(taskId);
|
|
1573
|
+
const writerTokenContract = writerTokenContractForAction(action, posture.state === 'claimed');
|
|
1574
|
+
const requiredActor = writerTokenContract
|
|
1575
|
+
? this.writerCredentialRecoveryActor(task, action, posture.state === 'claimed' ? posture.claimant : null, currentLease?.owner ?? null)
|
|
1576
|
+
: null;
|
|
1577
|
+
if (!requiredActor || requiredActor !== normalizedActor) {
|
|
1578
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} writer credential replacement requires the exact advertised recovery actor.`, {
|
|
1579
|
+
taskId,
|
|
1580
|
+
posture: posture.state,
|
|
1581
|
+
claimant: posture.state === 'claimed' ? posture.claimant : null,
|
|
1582
|
+
actor: normalizedActor,
|
|
1583
|
+
requiredActor,
|
|
1584
|
+
nextAction: action,
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
const lease = currentLease
|
|
1588
|
+
? context.locks.replaceCredential(taskId, normalizedActor)
|
|
1589
|
+
: context.locks.acquire(taskId, normalizedActor);
|
|
1590
|
+
if (posture.state === 'claimed') {
|
|
1591
|
+
appendTaskWriterCredentialReplacement(this.store, task, normalizedActor, lease.token, this.now());
|
|
1592
|
+
}
|
|
1593
|
+
return {
|
|
1594
|
+
task,
|
|
1595
|
+
lease: toPublicWriterLease(lease, this.now()),
|
|
1596
|
+
writerCredential: { disposition: 'issued', token: lease.token },
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1379
1599
|
handbackTask(repository, taskId, expectedRevision, actor, reason, writerToken = null, limitations = [], recommendedNext = 'Coordinator reviews the terminal Task result and recorded evidence.') {
|
|
1380
|
-
const context = this
|
|
1600
|
+
const context = this.#mutationContext(repository);
|
|
1381
1601
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1382
1602
|
assertExpectedRevision(task, expectedRevision);
|
|
1383
1603
|
const lease = task.status === 'merged' || task.status === 'cancelled'
|
|
@@ -1389,7 +1609,7 @@ export class WorkflowService {
|
|
|
1389
1609
|
return { task, event };
|
|
1390
1610
|
}
|
|
1391
1611
|
startTask(repository, taskId, expectedRevision, workspaceOwner) {
|
|
1392
|
-
const { identity } = this
|
|
1612
|
+
const { identity } = this.#mutationContext(repository);
|
|
1393
1613
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1394
1614
|
assertExpectedRevision(task, expectedRevision);
|
|
1395
1615
|
this.requireExecutionAdoptionPosture(identity, taskId);
|
|
@@ -1441,7 +1661,7 @@ export class WorkflowService {
|
|
|
1441
1661
|
return this.store.writeTask({ ...task, baseCommit, taskBranch, workspaceOwner, blockReason: null }, expectedRevision);
|
|
1442
1662
|
}
|
|
1443
1663
|
runStep(repository, taskId, stepId, expectedRevision, actor, writerToken) {
|
|
1444
|
-
const context = this
|
|
1664
|
+
const context = this.#mutationContext(repository);
|
|
1445
1665
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1446
1666
|
assertExpectedRevision(task, expectedRevision);
|
|
1447
1667
|
this.requireExecutionAdoptionPosture(context.identity, taskId);
|
|
@@ -1470,6 +1690,7 @@ export class WorkflowService {
|
|
|
1470
1690
|
throw new WorkflowError('TRANSITION_BLOCKED', `${stepId} cannot start from ${step.status}.`);
|
|
1471
1691
|
}
|
|
1472
1692
|
const planRiskAudit = readCurrentPlanRiskAudit(this.store, task);
|
|
1693
|
+
let resumesCorrectiveReplan = false;
|
|
1473
1694
|
if (remediationHistoryForStep.length > 0 && (!planRiskAudit || !isStepReviewRequired(task, stepId, planRiskAudit.reviewRequiredStepIds))) {
|
|
1474
1695
|
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
1696
|
taskId,
|
|
@@ -1484,20 +1705,21 @@ export class WorkflowService {
|
|
|
1484
1705
|
&& guardedPosture.correctiveDecision.planHash !== task.planHash
|
|
1485
1706
|
? guardedPosture.correctiveDecision
|
|
1486
1707
|
: null;
|
|
1487
|
-
const recovery = staleDecision
|
|
1708
|
+
const recovery = staleDecision?.decision === 'continue-fix'
|
|
1488
1709
|
? requireCorrectiveDecisionRecoveryForRun(this.store, task, stepId, actor, writerToken)
|
|
1489
1710
|
: null;
|
|
1490
|
-
assertGuardedRemediationAttemptAllowed(this.store, task, stepId, planRiskAudit, recovery?.correctiveDecisionEventId ?? null);
|
|
1711
|
+
const gate = assertGuardedRemediationAttemptAllowed(this.store, task, stepId, planRiskAudit, recovery?.correctiveDecisionEventId ?? null);
|
|
1712
|
+
resumesCorrectiveReplan = gate.posture === 'replan-ready';
|
|
1491
1713
|
}
|
|
1492
1714
|
const knowledgeMap = this.assertTaskKnowledgeBinding(context.identity.repositoryRoot, task);
|
|
1493
1715
|
const dirty = changedFiles(context.identity.repositoryRoot);
|
|
1494
|
-
if (step.status === 'planned' && dirty.length > 0) {
|
|
1716
|
+
if (step.status === 'planned' && dirty.length > 0 && !resumesCorrectiveReplan) {
|
|
1495
1717
|
throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} must start from a clean checkout.`, {
|
|
1496
1718
|
changedFiles: dirty,
|
|
1497
1719
|
});
|
|
1498
1720
|
}
|
|
1499
1721
|
const dirtyOutsideStep = dirty.filter((file) => !pathAllowed(file, step.allowedWrites));
|
|
1500
|
-
if (step.status === 'failed' && dirtyOutsideStep.length > 0) {
|
|
1722
|
+
if ((step.status === 'failed' || resumesCorrectiveReplan) && dirtyOutsideStep.length > 0) {
|
|
1501
1723
|
throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} retry has changes outside allowedWrites.`, {
|
|
1502
1724
|
changedFiles: dirtyOutsideStep,
|
|
1503
1725
|
});
|
|
@@ -1555,7 +1777,8 @@ export class WorkflowService {
|
|
|
1555
1777
|
}
|
|
1556
1778
|
return {
|
|
1557
1779
|
task: saved,
|
|
1558
|
-
lease,
|
|
1780
|
+
lease: toPublicWriterLease(lease, this.now()),
|
|
1781
|
+
writerCredential: { disposition: acquiredHere ? 'issued' : 'renewed' },
|
|
1559
1782
|
envelope: {
|
|
1560
1783
|
protocolVersion: PROTOCOL_VERSION,
|
|
1561
1784
|
packageVersion: PACKAGE_VERSION,
|
|
@@ -1591,14 +1814,14 @@ export class WorkflowService {
|
|
|
1591
1814
|
taskId,
|
|
1592
1815
|
stepId,
|
|
1593
1816
|
expectedRevision: saved.revision,
|
|
1594
|
-
writerToken: lease.token,
|
|
1817
|
+
writerToken: acquiredHere ? lease.token : null,
|
|
1595
1818
|
knowledgeMap: knowledgeSummary(knowledgeMap),
|
|
1596
1819
|
knowledgeSources: selectKnowledgeSources(knowledgeMap, 'worker', task.knowledgeTargets.map((target) => target.path)),
|
|
1597
1820
|
},
|
|
1598
1821
|
};
|
|
1599
1822
|
}
|
|
1600
1823
|
taskContext(repository, taskId, role) {
|
|
1601
|
-
const { identity } = this
|
|
1824
|
+
const { identity } = this.#mutationContext(repository);
|
|
1602
1825
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1603
1826
|
const historicalStrictReview = role === 'independent-reviewer'
|
|
1604
1827
|
&& task.steps.some((step) => step.status === 'in_progress' && step.evidence !== null);
|
|
@@ -1677,7 +1900,7 @@ export class WorkflowService {
|
|
|
1677
1900
|
};
|
|
1678
1901
|
}
|
|
1679
1902
|
completeStep(repository, taskId, stepId, expectedRevision, writerToken, notes, actor) {
|
|
1680
|
-
const context = this
|
|
1903
|
+
const context = this.#mutationContext(repository);
|
|
1681
1904
|
const lease = context.locks.heartbeat(taskId, writerToken);
|
|
1682
1905
|
this.readCurrentAdoptionPostureStrict(context.identity.projectId);
|
|
1683
1906
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
@@ -1763,7 +1986,7 @@ export class WorkflowService {
|
|
|
1763
1986
|
return this.store.writeTask({ ...task, status: 'in_progress', steps, blockReason: null }, expectedRevision);
|
|
1764
1987
|
}
|
|
1765
1988
|
reviewStep(repository, taskId, stepId, expectedRevision, runner, actor, writerToken = null) {
|
|
1766
|
-
const context = this
|
|
1989
|
+
const context = this.#mutationContext(repository);
|
|
1767
1990
|
this.readCurrentAdoptionPostureStrict(context.identity.projectId);
|
|
1768
1991
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1769
1992
|
assertExpectedRevision(task, expectedRevision);
|
|
@@ -1819,10 +2042,18 @@ export class WorkflowService {
|
|
|
1819
2042
|
resolution: currentCycle.resolution,
|
|
1820
2043
|
});
|
|
1821
2044
|
}
|
|
1822
|
-
|
|
2045
|
+
const infrastructureFailures = countUnverifiedStrictStepReviews(this.store, task, stepId);
|
|
2046
|
+
if (infrastructureFailures >= MAX_STRICT_REVIEW_INFRASTRUCTURE_FAILURES) {
|
|
2047
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Strict Step Review exhausted its bounded infrastructure retries.', {
|
|
2048
|
+
taskId: task.id,
|
|
2049
|
+
stepId,
|
|
2050
|
+
infrastructureFailures,
|
|
2051
|
+
requiredAction: 'Use an external sealed reviewer or run doctor; do not start another local reviewer process.',
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
1823
2054
|
}
|
|
1824
2055
|
else if (currentCycle.resolution !== 'pending') {
|
|
1825
|
-
if (currentCycle.resolution === 'failed'
|
|
2056
|
+
if (currentCycle.resolution === 'failed') {
|
|
1826
2057
|
if (!currentCycle.reviewRecordedEvent || !currentCycle.reviewerAttestationEvent) {
|
|
1827
2058
|
throw new WorkflowError('STATE_CORRUPT', 'Strict Step Review recovery requires exact review and attestation sidecars.', {
|
|
1828
2059
|
taskId: task.id,
|
|
@@ -1830,7 +2061,7 @@ export class WorkflowService {
|
|
|
1830
2061
|
resolution: currentCycle.resolution,
|
|
1831
2062
|
});
|
|
1832
2063
|
}
|
|
1833
|
-
appendRemediationEvent(this.store, task, stepId,
|
|
2064
|
+
appendRemediationEvent(this.store, task, stepId, 'review-failed', {
|
|
1834
2065
|
completionCommit: currentCycle.reviewRecordedEvent.completionCommit,
|
|
1835
2066
|
checksHash: currentCycle.reviewRecordedEvent.checksHash,
|
|
1836
2067
|
reviewEventHash: currentCycle.reviewRecordedEvent.eventHash,
|
|
@@ -1848,7 +2079,7 @@ export class WorkflowService {
|
|
|
1848
2079
|
reviewedCommit: launch.reviewedCommit,
|
|
1849
2080
|
isolationResult: launch.verifiedIsolation ? 'verified' : 'unverified',
|
|
1850
2081
|
}, this.now());
|
|
1851
|
-
if (recorded.resolution === 'failed'
|
|
2082
|
+
if (recorded.resolution === 'failed') {
|
|
1852
2083
|
const reviewRecordedEvent = recorded.reviewRecordedEvent;
|
|
1853
2084
|
const reviewerAttestationEvent = recorded.reviewerAttestationEvent;
|
|
1854
2085
|
if (!reviewRecordedEvent || !reviewerAttestationEvent) {
|
|
@@ -1858,7 +2089,7 @@ export class WorkflowService {
|
|
|
1858
2089
|
resolution: recorded.resolution,
|
|
1859
2090
|
});
|
|
1860
2091
|
}
|
|
1861
|
-
appendRemediationEvent(this.store, task, stepId,
|
|
2092
|
+
appendRemediationEvent(this.store, task, stepId, 'review-failed', {
|
|
1862
2093
|
completionCommit: reviewRecordedEvent.completionCommit,
|
|
1863
2094
|
checksHash: reviewRecordedEvent.checksHash,
|
|
1864
2095
|
reviewEventHash: reviewRecordedEvent.eventHash,
|
|
@@ -1867,8 +2098,132 @@ export class WorkflowService {
|
|
|
1867
2098
|
}
|
|
1868
2099
|
return this.store.writeTask(applyStrictStepReviewResolution(task, stepId, recorded.resolution, launch.review.summary), expectedRevision);
|
|
1869
2100
|
}
|
|
2101
|
+
prepareExternalStepReview(repository, taskId, stepId, expectedRevision) {
|
|
2102
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
2103
|
+
const task = this.store.readTask(identity.projectId, taskId);
|
|
2104
|
+
assertExpectedRevision(task, expectedRevision);
|
|
2105
|
+
if (task.status !== 'in_progress' && task.status !== 'blocked') {
|
|
2106
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `External Strict Step Review cannot run while task ${taskId} is ${task.status}.`);
|
|
2107
|
+
}
|
|
2108
|
+
const planRiskAudit = readCurrentPlanRiskAudit(this.store, task);
|
|
2109
|
+
if (!planRiskAudit || !isStepReviewRequired(task, stepId, planRiskAudit.reviewRequiredStepIds)) {
|
|
2110
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Step ${stepId} is not marked for strict review by the current Plan Risk Audit.`);
|
|
2111
|
+
}
|
|
2112
|
+
const step = requireStep(task, stepId);
|
|
2113
|
+
if (step.status !== 'in_progress' || !step.evidence) {
|
|
2114
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `External Strict Step Review requires ${stepId} in progress with canonical evidence.`);
|
|
2115
|
+
}
|
|
2116
|
+
validateTaskHistory(identity.repositoryRoot, task);
|
|
2117
|
+
if (!isClean(identity.repositoryRoot) || headCommit(identity.repositoryRoot) !== step.evidence.commitSha) {
|
|
2118
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'External Strict Step Review requires a clean checkout at the completion commit.');
|
|
2119
|
+
}
|
|
2120
|
+
const envelope = this.taskContext(repository, taskId, 'independent-reviewer');
|
|
2121
|
+
return prepareExternalStrictStepReviewPacket(identity.repositoryRoot, task, this.store.taskRoot(task.projectId, task.id), envelope, stepId, step.evidence);
|
|
2122
|
+
}
|
|
2123
|
+
recordExternalStepReview(repository, taskId, stepId, expectedRevision, input, actor, writerToken = null) {
|
|
2124
|
+
const context = this.#mutationContext(repository);
|
|
2125
|
+
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
2126
|
+
assertExpectedRevision(task, expectedRevision);
|
|
2127
|
+
this.proveOptionalWriterLeaseToken(context, taskId, writerToken);
|
|
2128
|
+
this.assertTaskMutationAllowedByHandoff(task, actor, writerToken);
|
|
2129
|
+
const packet = this.prepareExternalStepReview(repository, taskId, stepId, expectedRevision);
|
|
2130
|
+
let review;
|
|
2131
|
+
try {
|
|
2132
|
+
review = validateExternalStrictStepReviewInput(input, packet);
|
|
2133
|
+
}
|
|
2134
|
+
catch (error) {
|
|
2135
|
+
throw new WorkflowError('INVALID_ARGUMENT', error instanceof Error ? error.message : 'Invalid external strict review.');
|
|
2136
|
+
}
|
|
2137
|
+
if (review.reviewer.trim() === actor.trim()) {
|
|
2138
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'The independent Step reviewer must differ from the lifecycle mutation actor.');
|
|
2139
|
+
}
|
|
2140
|
+
const currentCycle = buildCurrentStrictStepReviewCycle(this.store, task, stepId);
|
|
2141
|
+
if (task.status === 'blocked' && currentCycle.resolution !== 'unverified') {
|
|
2142
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'External sealed review rescue requires an unverified prior review cycle.', {
|
|
2143
|
+
taskId,
|
|
2144
|
+
stepId,
|
|
2145
|
+
resolution: currentCycle.resolution,
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
const infrastructureFailures = countUnverifiedStrictStepReviews(this.store, task, stepId);
|
|
2149
|
+
if (infrastructureFailures > MAX_STRICT_REVIEW_INFRASTRUCTURE_FAILURES) {
|
|
2150
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'External sealed review rescue exceeded its bounded infrastructure history.', {
|
|
2151
|
+
taskId,
|
|
2152
|
+
stepId,
|
|
2153
|
+
infrastructureFailures,
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
ensurePendingStrictStepReview(this.store, task, stepId, this.now());
|
|
2157
|
+
const recorded = recordStrictStepReview(this.store, task, stepId, {
|
|
2158
|
+
review,
|
|
2159
|
+
reviewerOutputHash: strictReviewInputHash(review),
|
|
2160
|
+
reviewedCommit: packet.reviewedCommit,
|
|
2161
|
+
isolationResult: 'verified',
|
|
2162
|
+
}, this.now());
|
|
2163
|
+
if (recorded.resolution === 'failed') {
|
|
2164
|
+
appendRemediationEvent(this.store, task, stepId, 'review-failed', {
|
|
2165
|
+
completionCommit: recorded.reviewRecordedEvent.completionCommit,
|
|
2166
|
+
checksHash: recorded.reviewRecordedEvent.checksHash,
|
|
2167
|
+
reviewEventHash: recorded.reviewRecordedEvent.eventHash,
|
|
2168
|
+
reviewerAttestationHash: recorded.reviewerAttestationEvent.eventHash,
|
|
2169
|
+
}, readCurrentPlanRiskAudit(this.store, task), this.now());
|
|
2170
|
+
}
|
|
2171
|
+
const saved = this.store.writeTask(applyStrictStepReviewResolution(task, stepId, recorded.resolution, review.summary), expectedRevision);
|
|
2172
|
+
return {
|
|
2173
|
+
mode: 'external-sealed',
|
|
2174
|
+
packetHash: packet.packetHash,
|
|
2175
|
+
repositorySealHash: packet.repositorySealHash,
|
|
2176
|
+
reviewerThreadId: input.reviewerThreadId,
|
|
2177
|
+
task: saved,
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
prepareExternalTaskReview(repository, taskId, expectedRevision) {
|
|
2181
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
2182
|
+
const task = this.store.readTask(identity.projectId, taskId);
|
|
2183
|
+
assertExpectedRevision(task, expectedRevision);
|
|
2184
|
+
const retryingUnverified = task.status === 'blocked' && task.review.status === 'unverified';
|
|
2185
|
+
if (task.status !== 'validating' && !retryingUnverified) {
|
|
2186
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `External Strict Task Review cannot run while task ${taskId} is ${task.status}.`);
|
|
2187
|
+
}
|
|
2188
|
+
validateTaskHistory(identity.repositoryRoot, task);
|
|
2189
|
+
assertTaskBranch(identity.repositoryRoot, task);
|
|
2190
|
+
if (!isClean(identity.repositoryRoot)) {
|
|
2191
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'External Strict Task Review requires a clean Task checkout.');
|
|
2192
|
+
}
|
|
2193
|
+
const taskRoot = this.store.taskRoot(task.projectId, task.id);
|
|
2194
|
+
if (!task.evidenceHash || this.store.hashArtifact(taskRoot, 'evidence.json') !== task.evidenceHash) {
|
|
2195
|
+
throw new WorkflowError('STATE_CORRUPT', 'External Strict Task Review requires the exact submitted evidence artifact.');
|
|
2196
|
+
}
|
|
2197
|
+
return prepareExternalStrictTaskReviewPacket(identity.repositoryRoot, task, taskRoot, this.taskContext(repository, taskId, 'independent-reviewer'));
|
|
2198
|
+
}
|
|
2199
|
+
recordExternalTaskReview(repository, taskId, expectedRevision, input, actor, writerToken = null) {
|
|
2200
|
+
const context = this.#mutationContext(repository);
|
|
2201
|
+
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
2202
|
+
assertExpectedRevision(task, expectedRevision);
|
|
2203
|
+
this.proveOptionalWriterLeaseToken(context, taskId, writerToken);
|
|
2204
|
+
this.assertTaskMutationAllowedByHandoff(task, actor, writerToken);
|
|
2205
|
+
const packet = this.prepareExternalTaskReview(repository, taskId, expectedRevision);
|
|
2206
|
+
let review;
|
|
2207
|
+
try {
|
|
2208
|
+
review = validateExternalStrictTaskReviewInput(input, packet);
|
|
2209
|
+
}
|
|
2210
|
+
catch (error) {
|
|
2211
|
+
throw new WorkflowError('INVALID_ARGUMENT', error instanceof Error ? error.message : 'Invalid external strict Task review.');
|
|
2212
|
+
}
|
|
2213
|
+
if (review.reviewer.trim() === actor.trim()) {
|
|
2214
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'The independent Task reviewer must differ from the lifecycle mutation actor.');
|
|
2215
|
+
}
|
|
2216
|
+
const saved = this.recordReview(repository, taskId, expectedRevision, review, actor, writerToken);
|
|
2217
|
+
return {
|
|
2218
|
+
mode: 'external-sealed',
|
|
2219
|
+
packetHash: packet.packetHash,
|
|
2220
|
+
repositorySealHash: packet.repositorySealHash,
|
|
2221
|
+
reviewerThreadId: input.reviewerThreadId,
|
|
2222
|
+
task: saved,
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
1870
2225
|
submitTask(repository, taskId, expectedRevision, writerToken, actor) {
|
|
1871
|
-
const context = this
|
|
2226
|
+
const context = this.#mutationContext(repository);
|
|
1872
2227
|
const lease = context.locks.heartbeat(taskId, writerToken);
|
|
1873
2228
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1874
2229
|
assertExpectedRevision(task, expectedRevision);
|
|
@@ -1900,11 +2255,11 @@ export class WorkflowService {
|
|
|
1900
2255
|
return saved;
|
|
1901
2256
|
}
|
|
1902
2257
|
recordReview(repository, taskId, expectedRevision, review, actor, writerToken = null) {
|
|
1903
|
-
const { identity } = this
|
|
2258
|
+
const { identity } = this.#mutationContext(repository);
|
|
1904
2259
|
this.readCurrentAdoptionPostureStrict(identity.projectId);
|
|
1905
2260
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1906
2261
|
assertExpectedRevision(task, expectedRevision);
|
|
1907
|
-
this.proveOptionalWriterLeaseToken(this
|
|
2262
|
+
this.proveOptionalWriterLeaseToken(this.#mutationContext(repository), taskId, writerToken);
|
|
1908
2263
|
this.assertTaskMutationAllowedByHandoff(task, actor ?? null, writerToken);
|
|
1909
2264
|
const retryingUnverified = task.status === 'blocked' && task.review.status === 'unverified';
|
|
1910
2265
|
if (task.status !== 'validating' && !retryingUnverified) {
|
|
@@ -1942,7 +2297,7 @@ export class WorkflowService {
|
|
|
1942
2297
|
return saved;
|
|
1943
2298
|
}
|
|
1944
2299
|
recoverStepCorrectiveDecision(repository, taskId, stepId, expectedRevision, actor, writerToken) {
|
|
1945
|
-
const context = this
|
|
2300
|
+
const context = this.#mutationContext(repository);
|
|
1946
2301
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1947
2302
|
assertExpectedRevision(task, expectedRevision);
|
|
1948
2303
|
this.requireExecutionAdoptionPosture(context.identity, taskId);
|
|
@@ -1954,18 +2309,18 @@ export class WorkflowService {
|
|
|
1954
2309
|
return appendCorrectiveDecisionRecovery(this.store, task, stepId, expectedRevision, actor, writerToken, this.now());
|
|
1955
2310
|
}
|
|
1956
2311
|
recoverRemediationMode(repository, taskId, stepId, expectedRevision, actor, writerToken) {
|
|
1957
|
-
const context = this
|
|
2312
|
+
const context = this.#mutationContext(repository);
|
|
1958
2313
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
1959
2314
|
assertExpectedRevision(task, expectedRevision);
|
|
1960
2315
|
this.requireExecutionAdoptionPosture(context.identity, taskId);
|
|
1961
2316
|
return appendRemediationModeRecovery(this.store, task, stepId, expectedRevision, actor, writerToken, this.now());
|
|
1962
2317
|
}
|
|
1963
2318
|
recordStepCorrectiveDecision(repository, taskId, stepId, expectedRevision, correctiveAudit, actor, writerToken = null) {
|
|
1964
|
-
const { identity } = this
|
|
2319
|
+
const { identity } = this.#mutationContext(repository);
|
|
1965
2320
|
this.readCurrentAdoptionPostureStrict(identity.projectId);
|
|
1966
2321
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1967
2322
|
assertExpectedRevision(task, expectedRevision);
|
|
1968
|
-
this.proveOptionalWriterLeaseToken(this
|
|
2323
|
+
this.proveOptionalWriterLeaseToken(this.#mutationContext(repository), taskId, writerToken);
|
|
1969
2324
|
this.assertTaskMutationAllowedByHandoff(task, actor ?? null, writerToken);
|
|
1970
2325
|
if (!['ready', 'needs_fix', 'blocked'].includes(task.status)) {
|
|
1971
2326
|
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} cannot record a corrective decision while ${task.status}.`);
|
|
@@ -2007,7 +2362,7 @@ export class WorkflowService {
|
|
|
2007
2362
|
return appendCorrectiveDecisionEvent(this.store, task, stepId, correctiveAudit, planRiskAudit, task.planHash, this.now());
|
|
2008
2363
|
}
|
|
2009
2364
|
setTaskResult(repository, taskId, expectedRevision, summary, limitations) {
|
|
2010
|
-
const { identity } = this
|
|
2365
|
+
const { identity } = this.#mutationContext(repository);
|
|
2011
2366
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
2012
2367
|
assertExpectedRevision(task, expectedRevision);
|
|
2013
2368
|
if (task.status !== 'validating' || task.review.status !== 'passed') {
|
|
@@ -2021,7 +2376,7 @@ export class WorkflowService {
|
|
|
2021
2376
|
return saved;
|
|
2022
2377
|
}
|
|
2023
2378
|
acceptTask(repository, taskId, expectedRevision, actor, delegationGrantId = null) {
|
|
2024
|
-
const { identity } = this
|
|
2379
|
+
const { identity } = this.#mutationContext(repository);
|
|
2025
2380
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
2026
2381
|
assertExpectedRevision(task, expectedRevision);
|
|
2027
2382
|
if (task.status !== 'awaiting_final_acceptance' || !task.resultHash || !task.evidenceHash) {
|
|
@@ -2059,7 +2414,7 @@ export class WorkflowService {
|
|
|
2059
2414
|
}, expectedRevision);
|
|
2060
2415
|
}
|
|
2061
2416
|
syncTaskBase(repository, taskId, expectedRevision) {
|
|
2062
|
-
const { identity } = this
|
|
2417
|
+
const { identity } = this.#mutationContext(repository);
|
|
2063
2418
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
2064
2419
|
assertExpectedRevision(task, expectedRevision);
|
|
2065
2420
|
if (task.status !== 'merging' || task.workspaceOwner !== 'local' || !task.taskBranch || !task.baseCommit) {
|
|
@@ -2111,7 +2466,7 @@ export class WorkflowService {
|
|
|
2111
2466
|
return saved;
|
|
2112
2467
|
}
|
|
2113
2468
|
mergeTask(repository, taskId, expectedRevision, writerToken, actor) {
|
|
2114
|
-
const context = this
|
|
2469
|
+
const context = this.#mutationContext(repository);
|
|
2115
2470
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
2116
2471
|
assertExpectedRevision(task, expectedRevision);
|
|
2117
2472
|
const lease = context.locks.heartbeat(taskId, writerToken);
|
|
@@ -2133,7 +2488,7 @@ export class WorkflowService {
|
|
|
2133
2488
|
return saved;
|
|
2134
2489
|
}
|
|
2135
2490
|
confirmExternalMerge(repository, taskId, expectedRevision, writerToken, actor) {
|
|
2136
|
-
const context = this
|
|
2491
|
+
const context = this.#mutationContext(repository);
|
|
2137
2492
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
2138
2493
|
assertExpectedRevision(task, expectedRevision);
|
|
2139
2494
|
const lease = context.locks.heartbeat(taskId, writerToken);
|
|
@@ -2150,21 +2505,22 @@ export class WorkflowService {
|
|
|
2150
2505
|
return saved;
|
|
2151
2506
|
}
|
|
2152
2507
|
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);
|
|
2508
|
+
const snapshot = this.observationSnapshot(repository);
|
|
2157
2509
|
return {
|
|
2158
|
-
projectId: identity.projectId,
|
|
2159
|
-
discoveries:
|
|
2160
|
-
tasks,
|
|
2161
|
-
milestones:
|
|
2162
|
-
|
|
2163
|
-
|
|
2510
|
+
projectId: snapshot.identity.projectId,
|
|
2511
|
+
discoveries: snapshot.discoveries,
|
|
2512
|
+
tasks: snapshot.tasks,
|
|
2513
|
+
milestones: snapshot.milestones,
|
|
2514
|
+
milestoneInitialAssembly: snapshot.milestones
|
|
2515
|
+
.map((milestone) => this.describeMilestoneInitialAssembly(snapshot.identity.projectId, milestone, snapshot.tasks))
|
|
2516
|
+
.filter((value) => value !== null),
|
|
2517
|
+
delegations: snapshot.delegations,
|
|
2518
|
+
adoption: snapshot.adoption,
|
|
2519
|
+
observation: snapshot.observation,
|
|
2164
2520
|
};
|
|
2165
2521
|
}
|
|
2166
2522
|
recoverMilestoneTransactions(repository) {
|
|
2167
|
-
const { identity } = this
|
|
2523
|
+
const { identity } = this.#mutationContext(repository);
|
|
2168
2524
|
const milestones = this.listMilestonesCurrent(identity.projectId, readCurrentAdoptionPosture(this.store, identity.projectId));
|
|
2169
2525
|
return {
|
|
2170
2526
|
projectId: identity.projectId,
|
|
@@ -2172,11 +2528,11 @@ export class WorkflowService {
|
|
|
2172
2528
|
};
|
|
2173
2529
|
}
|
|
2174
2530
|
prepareAdoption(repository) {
|
|
2175
|
-
const { identity } = this
|
|
2531
|
+
const { identity } = this.#mutationContext(repository);
|
|
2176
2532
|
return this.inspectAdoptionPreparation(identity, this.store.listTasks(identity.projectId), this.store.listMilestones(identity.projectId));
|
|
2177
2533
|
}
|
|
2178
2534
|
applyAdoption(repository, actor, confirmationCode) {
|
|
2179
|
-
const context = this
|
|
2535
|
+
const context = this.#mutationContext(repository);
|
|
2180
2536
|
const tasks = context.store.listTasks(context.identity.projectId);
|
|
2181
2537
|
const milestones = context.store.listMilestones(context.identity.projectId);
|
|
2182
2538
|
return applyAdoptionPosture(this.store, {
|
|
@@ -2193,19 +2549,19 @@ export class WorkflowService {
|
|
|
2193
2549
|
}, actor, confirmationCode);
|
|
2194
2550
|
}
|
|
2195
2551
|
updatePreflight(repository) {
|
|
2196
|
-
const
|
|
2197
|
-
const tasks =
|
|
2198
|
-
const milestones =
|
|
2199
|
-
const dirty = changedFiles(
|
|
2552
|
+
const snapshot = this.observationSnapshot(repository);
|
|
2553
|
+
const tasks = snapshot.tasks;
|
|
2554
|
+
const milestones = snapshot.milestones;
|
|
2555
|
+
const dirty = changedFiles(snapshot.identity.repositoryRoot);
|
|
2200
2556
|
const runningSteps = tasks.flatMap((task) => task.steps
|
|
2201
2557
|
.filter((step) => step.status === 'in_progress')
|
|
2202
2558
|
.map((step) => ({ taskId: task.id, stepId: step.id })));
|
|
2203
|
-
const leases =
|
|
2559
|
+
const leases = snapshot.leases;
|
|
2204
2560
|
const now = this.now().getTime();
|
|
2205
2561
|
const activeLeases = leases.filter((lease) => Date.parse(lease.expiresAt) > now);
|
|
2206
2562
|
const staleLeases = leases.filter((lease) => Date.parse(lease.expiresAt) <= now);
|
|
2207
2563
|
const activeMilestones = milestones.filter((milestone) => !['accepted', 'cancelled'].includes(milestone.status));
|
|
2208
|
-
const inspectedVersions = inspectWorkflowVersions(
|
|
2564
|
+
const inspectedVersions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, activeMilestones.map((milestone) => milestone.baseBranch));
|
|
2209
2565
|
const milestoneBases = inspectedVersions.milestoneBases.map((base) => ({
|
|
2210
2566
|
...base,
|
|
2211
2567
|
milestoneIds: activeMilestones.filter((milestone) => milestone.baseBranch === base.branch).map((milestone) => milestone.id),
|
|
@@ -2232,31 +2588,20 @@ export class WorkflowService {
|
|
|
2232
2588
|
...(activeLeases.length > 0 ? ['At least one writer lease is active.'] : []),
|
|
2233
2589
|
...(staleLeases.length > 0 ? ['At least one stale writer lease requires explicit repair.'] : []),
|
|
2234
2590
|
...versionBlockers,
|
|
2591
|
+
...observationPreflightBlockers(snapshot.observation),
|
|
2235
2592
|
];
|
|
2236
|
-
const adoption = buildAdoptionPreparation({
|
|
2237
|
-
projectId: context.identity.projectId,
|
|
2238
|
-
repositoryIdentity: context.identity,
|
|
2239
|
-
packageVersion: PACKAGE_VERSION,
|
|
2240
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
2241
|
-
stateSchemaVersion: STATE_SCHEMA_VERSION,
|
|
2242
|
-
tasks,
|
|
2243
|
-
milestones,
|
|
2244
|
-
sidecarInventory: this.collectAdoptionSidecarInventory(context.identity.projectId, tasks, milestones),
|
|
2245
|
-
blockers,
|
|
2246
|
-
now: this.now(),
|
|
2247
|
-
}, readCurrentAdoptionPosture(this.store, context.identity.projectId));
|
|
2248
2593
|
return {
|
|
2249
2594
|
safe: blockers.length === 0,
|
|
2250
2595
|
packageVersion: PACKAGE_VERSION,
|
|
2251
2596
|
protocolVersion: PROTOCOL_VERSION,
|
|
2252
2597
|
stateSchemaVersion: STATE_SCHEMA_VERSION,
|
|
2253
|
-
projectId:
|
|
2254
|
-
repositoryRoot:
|
|
2598
|
+
projectId: snapshot.identity.projectId,
|
|
2599
|
+
repositoryRoot: snapshot.identity.repositoryRoot,
|
|
2255
2600
|
clean: dirty.length === 0,
|
|
2256
2601
|
changedFiles: dirty,
|
|
2257
2602
|
runningSteps,
|
|
2258
|
-
activeLeases,
|
|
2259
|
-
staleLeases,
|
|
2603
|
+
activeLeases: activeLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
|
|
2604
|
+
staleLeases: staleLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
|
|
2260
2605
|
blockers,
|
|
2261
2606
|
workflowVersions: {
|
|
2262
2607
|
runtime: inspectedVersions.runtime,
|
|
@@ -2272,11 +2617,118 @@ export class WorkflowService {
|
|
|
2272
2617
|
? null
|
|
2273
2618
|
: 'Land only package.json/package-lock.json dependency changes on every active Milestone base; do not transport unrelated product commits.',
|
|
2274
2619
|
},
|
|
2275
|
-
adoption,
|
|
2620
|
+
adoption: snapshot.adoption,
|
|
2621
|
+
observation: snapshot.observation,
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
dependencyProvenanceRecoveryPreflight(repository, taskId) {
|
|
2625
|
+
const snapshot = this.observationSnapshot(repository);
|
|
2626
|
+
if (snapshot.observation.kind !== 'assessment') {
|
|
2627
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Dependency provenance recovery requires a coherent observation boundary.', {
|
|
2628
|
+
observation: snapshot.observation,
|
|
2629
|
+
});
|
|
2630
|
+
}
|
|
2631
|
+
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
2632
|
+
if (!task)
|
|
2633
|
+
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
2634
|
+
return this.inspectDependencyProvenanceCandidate(snapshot, task);
|
|
2635
|
+
}
|
|
2636
|
+
recoverDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
|
|
2637
|
+
const normalizedActor = actor.trim();
|
|
2638
|
+
const normalizedReason = reason.trim();
|
|
2639
|
+
if (!normalizedActor)
|
|
2640
|
+
throw new WorkflowError('INVALID_ARGUMENT', 'Dependency provenance recovery requires an actor.');
|
|
2641
|
+
if (!normalizedReason)
|
|
2642
|
+
throw new WorkflowError('INVALID_ARGUMENT', 'Dependency provenance recovery requires a reason.');
|
|
2643
|
+
const context = this.#mutationContext(repository);
|
|
2644
|
+
const current = context.store.readTask(context.identity.projectId, taskId);
|
|
2645
|
+
const currentHead = headCommit(context.identity.repositoryRoot);
|
|
2646
|
+
const prior = current.dependencyProvenanceRecoveries?.find((record) => record.commitSha === currentHead && record.packageVersion === PACKAGE_VERSION);
|
|
2647
|
+
if (prior && current.systemCommits.includes(currentHead))
|
|
2648
|
+
return current;
|
|
2649
|
+
assertExpectedRevision(current, expectedRevision);
|
|
2650
|
+
const snapshot = this.observationSnapshot(repository);
|
|
2651
|
+
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
2652
|
+
if (!task)
|
|
2653
|
+
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
2654
|
+
const preflight = this.inspectDependencyProvenanceCandidate(snapshot, task);
|
|
2655
|
+
if (!preflight.eligible || !preflight.commitSha || !preflight.parentCommitSha) {
|
|
2656
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'The bounded dependency provenance recovery preflight failed.', {
|
|
2657
|
+
taskId,
|
|
2658
|
+
blockers: preflight.blockers,
|
|
2659
|
+
});
|
|
2660
|
+
}
|
|
2661
|
+
return context.store.writeTask({
|
|
2662
|
+
...task,
|
|
2663
|
+
systemCommits: [...task.systemCommits, preflight.commitSha],
|
|
2664
|
+
dependencyProvenanceRecoveries: [
|
|
2665
|
+
...(task.dependencyProvenanceRecoveries ?? []),
|
|
2666
|
+
{
|
|
2667
|
+
commitSha: preflight.commitSha,
|
|
2668
|
+
parentCommitSha: preflight.parentCommitSha,
|
|
2669
|
+
packageName: PACKAGE_NAME,
|
|
2670
|
+
packageVersion: PACKAGE_VERSION,
|
|
2671
|
+
files: ['package-lock.json', 'package.json'],
|
|
2672
|
+
actor: normalizedActor,
|
|
2673
|
+
reason: normalizedReason,
|
|
2674
|
+
recordedAt: this.now().toISOString(),
|
|
2675
|
+
retainedHistoricalCommits: preflight.retainedHistoricalCommits,
|
|
2676
|
+
},
|
|
2677
|
+
],
|
|
2678
|
+
}, expectedRevision);
|
|
2679
|
+
}
|
|
2680
|
+
milestoneProgress(repository, milestoneId) {
|
|
2681
|
+
const snapshot = this.observationSnapshot(repository);
|
|
2682
|
+
if (snapshot.observation.kind !== 'assessment') {
|
|
2683
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone progress requires a coherent read-only observation boundary.', {
|
|
2684
|
+
observation: snapshot.observation,
|
|
2685
|
+
});
|
|
2686
|
+
}
|
|
2687
|
+
const milestone = snapshot.milestones.find((candidate) => candidate.id === milestoneId);
|
|
2688
|
+
if (!milestone)
|
|
2689
|
+
throw new WorkflowError('NOT_FOUND', `Milestone ${milestoneId} was not found in this repository.`);
|
|
2690
|
+
const tasksById = new Map(snapshot.tasks.map((task) => [task.id, task]));
|
|
2691
|
+
const tasks = milestone.memberships.map((membership, index) => {
|
|
2692
|
+
const task = tasksById.get(membership.taskId);
|
|
2693
|
+
if (!task)
|
|
2694
|
+
throw new WorkflowError('STATE_CORRUPT', `Milestone member ${membership.taskId} does not exist.`);
|
|
2695
|
+
const stepsCompleted = task.steps.filter((step) => ['completed', 'skipped'].includes(step.status)).length;
|
|
2696
|
+
return {
|
|
2697
|
+
ordinal: `T${String(index + 1).padStart(2, '0')}`,
|
|
2698
|
+
taskId: task.id,
|
|
2699
|
+
title: task.title,
|
|
2700
|
+
status: task.status,
|
|
2701
|
+
revision: task.revision,
|
|
2702
|
+
disposition: membership.disposition,
|
|
2703
|
+
membershipReason: membership.reason,
|
|
2704
|
+
replacementForTaskId: task.replacementForTaskId ?? null,
|
|
2705
|
+
replacedByTaskId: task.replacedByTaskId ?? null,
|
|
2706
|
+
stepsCompleted,
|
|
2707
|
+
stepsTotal: task.steps.length,
|
|
2708
|
+
steps: task.steps.map((step, stepIndex) => ({
|
|
2709
|
+
ordinal: `S${String(stepIndex + 1).padStart(2, '0')}`,
|
|
2710
|
+
stepId: step.id,
|
|
2711
|
+
title: step.title,
|
|
2712
|
+
status: step.status,
|
|
2713
|
+
})),
|
|
2714
|
+
};
|
|
2715
|
+
});
|
|
2716
|
+
const required = tasks.filter((task) => task.disposition === 'required');
|
|
2717
|
+
return {
|
|
2718
|
+
readOnly: true,
|
|
2719
|
+
projectId: snapshot.identity.projectId,
|
|
2720
|
+
milestoneId: milestone.id,
|
|
2721
|
+
milestoneTitle: milestone.title,
|
|
2722
|
+
milestoneStatus: milestone.status,
|
|
2723
|
+
milestoneRevision: milestone.revision,
|
|
2724
|
+
membershipRevision: milestone.membershipRevision,
|
|
2725
|
+
tasksCompleted: required.filter((task) => task.status === 'merged').length,
|
|
2726
|
+
tasksRequired: required.length,
|
|
2727
|
+
tasks,
|
|
2276
2728
|
};
|
|
2277
2729
|
}
|
|
2278
2730
|
alpha6StrictReviewRescuePreflight(repository) {
|
|
2279
|
-
const context = this
|
|
2731
|
+
const context = this.#mutationContext(repository);
|
|
2280
2732
|
const dependency = inspectLocalDependency(context.identity.repositoryRoot);
|
|
2281
2733
|
const tasks = context.store.listTasks(context.identity.projectId);
|
|
2282
2734
|
const leases = context.locks.list();
|
|
@@ -2371,15 +2823,15 @@ export class WorkflowService {
|
|
|
2371
2823
|
projectId: context.identity.projectId,
|
|
2372
2824
|
repositoryRoot: context.identity.repositoryRoot,
|
|
2373
2825
|
clean: dirty.length === 0,
|
|
2374
|
-
activeLeases,
|
|
2375
|
-
staleLeases,
|
|
2826
|
+
activeLeases: activeLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
|
|
2827
|
+
staleLeases: staleLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
|
|
2376
2828
|
candidate,
|
|
2377
2829
|
blockers,
|
|
2378
2830
|
requiredActions,
|
|
2379
2831
|
};
|
|
2380
2832
|
}
|
|
2381
2833
|
scanKnowledgeMap(repository) {
|
|
2382
|
-
const { identity } = this
|
|
2834
|
+
const { identity } = this.#mutationContext(repository);
|
|
2383
2835
|
const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
|
|
2384
2836
|
try {
|
|
2385
2837
|
const current = this.store.readKnowledgeMap(identity.projectId);
|
|
@@ -2392,11 +2844,11 @@ export class WorkflowService {
|
|
|
2392
2844
|
}
|
|
2393
2845
|
}
|
|
2394
2846
|
showKnowledgeMap(repository) {
|
|
2395
|
-
const { identity } = this
|
|
2847
|
+
const { identity } = this.#mutationContext(repository);
|
|
2396
2848
|
return this.store.readKnowledgeMap(identity.projectId);
|
|
2397
2849
|
}
|
|
2398
2850
|
knowledgeMapStatus(repository) {
|
|
2399
|
-
const { identity } = this
|
|
2851
|
+
const { identity } = this.#mutationContext(repository);
|
|
2400
2852
|
const current = this.store.readKnowledgeMap(identity.projectId);
|
|
2401
2853
|
const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
|
|
2402
2854
|
return inspectKnowledgeMap(current, scanned);
|
|
@@ -2405,7 +2857,7 @@ export class WorkflowService {
|
|
|
2405
2857
|
return this.approveKnowledgeMapForTaskRefresh(repository, expectedRevision, selections, actor, delegationGrantId, null);
|
|
2406
2858
|
}
|
|
2407
2859
|
approveKnowledgeMapForTaskRefresh(repository, expectedRevision, selections, actor, delegationGrantId, autonomyTaskId) {
|
|
2408
|
-
const { identity } = this
|
|
2860
|
+
const { identity } = this.#mutationContext(repository);
|
|
2409
2861
|
const current = this.store.readKnowledgeMap(identity.projectId);
|
|
2410
2862
|
assertExpectedRevision(current, expectedRevision);
|
|
2411
2863
|
const inspected = inspectKnowledgeMap(current, scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now()));
|
|
@@ -2422,43 +2874,35 @@ export class WorkflowService {
|
|
|
2422
2874
|
return this.store.writeKnowledgeMap(approved, expectedRevision);
|
|
2423
2875
|
}
|
|
2424
2876
|
reconcileKnowledgeMap(repository, expectedRevision, allowAtomicTaskRefresh = false) {
|
|
2425
|
-
const { identity } = this
|
|
2877
|
+
const { identity } = this.#mutationContext(repository);
|
|
2426
2878
|
const current = this.store.readKnowledgeMap(identity.projectId);
|
|
2427
2879
|
assertExpectedRevision(current, expectedRevision);
|
|
2428
2880
|
const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
|
|
2429
2881
|
if (!allowAtomicTaskRefresh && current.status === 'active' && current.approval) {
|
|
2430
2882
|
const milestonesById = new Map(this.listMilestonesCurrent(identity.projectId).map((milestone) => [milestone.id, milestone]));
|
|
2431
|
-
const
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
const assessment = assessDelegatedKnowledgeRefresh(current, inspected, activeTask, this.store.hashArtifact(this.store.taskRoot(identity.projectId, activeTask.id), 'plan.md'));
|
|
2439
|
-
if (assessment.eligible
|
|
2440
|
-
&& this.delegatedContextRefreshOptions(identity.projectId, activeTask, inspected, assessment.mode).length > 0) {
|
|
2441
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Standalone Project Knowledge reconcile would strand an authorized Task context refresh.', {
|
|
2442
|
-
taskId: activeTask.id,
|
|
2443
|
-
requiredAction: 'Run the exact task context-refresh action returned by next.',
|
|
2444
|
-
});
|
|
2445
|
-
}
|
|
2883
|
+
const inspected = inspectKnowledgeMap(current, scanned);
|
|
2884
|
+
const refreshOwner = this.findDelegatedContextRefreshOwner(identity.projectId, this.store.listTasks(identity.projectId), milestonesById, current, inspected);
|
|
2885
|
+
if (refreshOwner) {
|
|
2886
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Standalone Project Knowledge reconcile would strand an authorized Task context refresh.', {
|
|
2887
|
+
taskId: refreshOwner.task.id,
|
|
2888
|
+
requiredAction: 'Run the exact task context-refresh action returned by next.',
|
|
2889
|
+
});
|
|
2446
2890
|
}
|
|
2447
2891
|
}
|
|
2448
2892
|
return this.store.writeKnowledgeMap(reconcileKnowledgeMap(current, scanned), expectedRevision);
|
|
2449
2893
|
}
|
|
2450
2894
|
graphRefreshRequest(repository, graphKind, mode = 'moderate') {
|
|
2451
|
-
const { identity } = this
|
|
2895
|
+
const { identity } = this.#mutationContext(repository);
|
|
2452
2896
|
return createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
|
|
2453
2897
|
}
|
|
2454
2898
|
graphStatus(repository, graphKind, mode = 'moderate') {
|
|
2455
|
-
const { identity } = this
|
|
2899
|
+
const { identity } = this.#mutationContext(repository);
|
|
2456
2900
|
const request = createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
|
|
2457
2901
|
const binding = this.readGraphBindingOrNull(identity.projectId, graphKind);
|
|
2458
2902
|
return { request, binding: binding ? inspectGraphBinding(binding, request) : null };
|
|
2459
2903
|
}
|
|
2460
2904
|
bindGraph(repository, expectedRevision, request, evidence) {
|
|
2461
|
-
const { identity } = this
|
|
2905
|
+
const { identity } = this.#mutationContext(repository);
|
|
2462
2906
|
const currentRequest = createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, request.graphKind, request.mode, this.now());
|
|
2463
2907
|
validateGraphRequestCurrent(request, currentRequest);
|
|
2464
2908
|
const existing = this.readGraphBindingOrNull(identity.projectId, request.graphKind);
|
|
@@ -2466,13 +2910,78 @@ export class WorkflowService {
|
|
|
2466
2910
|
return this.store.writeGraphBinding(binding, expectedRevision);
|
|
2467
2911
|
}
|
|
2468
2912
|
recordGraphFallback(repository, graphKind, mode, expectedRevision, reason) {
|
|
2469
|
-
const { identity } = this
|
|
2913
|
+
const { identity } = this.#mutationContext(repository);
|
|
2470
2914
|
const request = createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
|
|
2471
2915
|
const existing = this.readGraphBindingOrNull(identity.projectId, graphKind);
|
|
2472
2916
|
return this.store.writeGraphBinding(fallbackGraphBinding(identity.projectId, request, reason, existing, this.now()), expectedRevision);
|
|
2473
2917
|
}
|
|
2474
|
-
next(repository) {
|
|
2918
|
+
next(repository, taskId = null) {
|
|
2475
2919
|
const status = this.status(repository);
|
|
2920
|
+
if (status.observation.kind === 'blocked') {
|
|
2921
|
+
return {
|
|
2922
|
+
scope: 'project',
|
|
2923
|
+
projectId: status.projectId,
|
|
2924
|
+
status: 'observation-blocked',
|
|
2925
|
+
action: 'blocked',
|
|
2926
|
+
observation: status.observation,
|
|
2927
|
+
navigation: {
|
|
2928
|
+
kind: 'blocked',
|
|
2929
|
+
reasonCodes: status.observation.reasonCodes,
|
|
2930
|
+
availableRepairTransitionId: null,
|
|
2931
|
+
},
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
if (status.observation.kind === 'repair') {
|
|
2935
|
+
const repair = status.observation.repair;
|
|
2936
|
+
return {
|
|
2937
|
+
scope: 'project',
|
|
2938
|
+
projectId: status.projectId,
|
|
2939
|
+
status: 'repair-required',
|
|
2940
|
+
action: repair?.command ?? 'blocked',
|
|
2941
|
+
observation: status.observation,
|
|
2942
|
+
navigation: {
|
|
2943
|
+
kind: repair ? 'repair' : 'blocked',
|
|
2944
|
+
transitionId: null,
|
|
2945
|
+
availableRepairTransitionId: null,
|
|
2946
|
+
...(repair ? {
|
|
2947
|
+
command: repair.command,
|
|
2948
|
+
cataloged: repair.cataloged,
|
|
2949
|
+
...('available' in repair ? { available: repair.available } : {}),
|
|
2950
|
+
bindings: repair.command === 'milestone recover'
|
|
2951
|
+
? {
|
|
2952
|
+
projectId: repair.projectId,
|
|
2953
|
+
milestoneIds: repair.milestoneIds,
|
|
2954
|
+
transactionIds: repair.transactionIds,
|
|
2955
|
+
}
|
|
2956
|
+
: 'taskIds' in repair
|
|
2957
|
+
? {
|
|
2958
|
+
projectId: repair.projectId,
|
|
2959
|
+
taskIds: repair.taskIds,
|
|
2960
|
+
transactionIds: repair.transactionIds,
|
|
2961
|
+
}
|
|
2962
|
+
: {
|
|
2963
|
+
projectId: repair.projectId,
|
|
2964
|
+
entityIds: repair.entityIds,
|
|
2965
|
+
},
|
|
2966
|
+
} : {}),
|
|
2967
|
+
reasonCodes: status.observation.reasonCodes,
|
|
2968
|
+
},
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2971
|
+
if (!status.adoption) {
|
|
2972
|
+
return {
|
|
2973
|
+
scope: 'project',
|
|
2974
|
+
projectId: status.projectId,
|
|
2975
|
+
status: 'observation-blocked',
|
|
2976
|
+
action: 'blocked',
|
|
2977
|
+
observation: status.observation,
|
|
2978
|
+
navigation: {
|
|
2979
|
+
kind: 'blocked',
|
|
2980
|
+
reasonCodes: status.observation.reasonCodes,
|
|
2981
|
+
availableRepairTransitionId: null,
|
|
2982
|
+
},
|
|
2983
|
+
};
|
|
2984
|
+
}
|
|
2476
2985
|
const adoptionInfo = {
|
|
2477
2986
|
status: status.adoption.status,
|
|
2478
2987
|
safe: status.adoption.safe,
|
|
@@ -2490,19 +2999,117 @@ export class WorkflowService {
|
|
|
2490
2999
|
status: status.adoption.status,
|
|
2491
3000
|
action: status.adoption.safe ? 'state adoption-apply' : 'restore safe boundary, then state adoption-prepare',
|
|
2492
3001
|
adoption: adoptionInfo,
|
|
3002
|
+
observation: status.observation,
|
|
3003
|
+
navigation: status.adoption.safe
|
|
3004
|
+
? {
|
|
3005
|
+
kind: 'human-gate',
|
|
3006
|
+
transitionId: null,
|
|
3007
|
+
cataloged: false,
|
|
3008
|
+
command: 'state adoption-apply',
|
|
3009
|
+
bindings: {
|
|
3010
|
+
projectId: status.projectId,
|
|
3011
|
+
confirmationCode: status.adoption.confirmationCode,
|
|
3012
|
+
confirmationCodeBindingHash: status.adoption.confirmationCodeBindingHash,
|
|
3013
|
+
actorRequired: true,
|
|
3014
|
+
},
|
|
3015
|
+
}
|
|
3016
|
+
: {
|
|
3017
|
+
kind: 'blocked',
|
|
3018
|
+
transitionId: null,
|
|
3019
|
+
availableRepairTransitionId: null,
|
|
3020
|
+
reasonCodes: ['ADOPTION_MISSING'],
|
|
3021
|
+
blockers: status.adoption.blockers,
|
|
3022
|
+
},
|
|
2493
3023
|
};
|
|
2494
3024
|
}
|
|
2495
|
-
const withAdoption = (value) => ({
|
|
3025
|
+
const withAdoption = (value) => ({
|
|
3026
|
+
...value,
|
|
3027
|
+
adoption: adoptionInfo,
|
|
3028
|
+
observation: status.observation,
|
|
3029
|
+
});
|
|
2496
3030
|
const repositoryRoot = resolveRepositoryIdentity(repository).repositoryRoot;
|
|
3031
|
+
const inspectCurrentKnowledgeMap = () => inspectKnowledgeMap(this.store.readKnowledgeMap(status.projectId), scanProjectKnowledge(repositoryRoot, status.projectId, this.now()));
|
|
2497
3032
|
const milestonesById = new Map(status.milestones.map((milestone) => [milestone.id, milestone]));
|
|
2498
|
-
const
|
|
3033
|
+
const requestedTask = taskId === null
|
|
3034
|
+
? null
|
|
3035
|
+
: status.tasks.find((task) => task.id === taskId) ?? null;
|
|
3036
|
+
if (taskId !== null && !requestedTask) {
|
|
3037
|
+
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
3038
|
+
}
|
|
3039
|
+
if (requestedTask && !taskIsActionableFromRepositoryNext(requestedTask, milestonesById)) {
|
|
3040
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${requestedTask.id} is not actionable from repository next.`, {
|
|
3041
|
+
taskId: requestedTask.id,
|
|
3042
|
+
taskStatus: requestedTask.status,
|
|
3043
|
+
milestoneId: requestedTask.milestoneId,
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
3046
|
+
const activeTask = requestedTask
|
|
3047
|
+
?? status.tasks.find((task) => taskIsActionableFromRepositoryNext(task, milestonesById));
|
|
2499
3048
|
if (activeTask) {
|
|
2500
|
-
const
|
|
2501
|
-
|
|
3049
|
+
const dependencyRecovery = this.dependencyProvenanceRecoveryPreflight(repositoryRoot, activeTask.id);
|
|
3050
|
+
if (dependencyRecovery.eligible) {
|
|
3051
|
+
return withAdoption({
|
|
3052
|
+
scope: 'task',
|
|
3053
|
+
id: activeTask.id,
|
|
3054
|
+
status: activeTask.status,
|
|
3055
|
+
action: 'update dependency-provenance-recover',
|
|
3056
|
+
revision: activeTask.revision,
|
|
3057
|
+
dependencyProvenanceRecovery: dependencyRecovery,
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
const activeTaskMilestone = activeTask.milestoneId
|
|
3061
|
+
? milestonesById.get(activeTask.milestoneId) ?? null
|
|
2502
3062
|
: null;
|
|
3063
|
+
const milestoneCancellationSafe = Boolean(activeTaskMilestone
|
|
3064
|
+
&& !activeTask.steps.some((step) => step.status === 'in_progress')
|
|
3065
|
+
&& status.observation.leases.length === 0
|
|
3066
|
+
&& status.observation.coreTaskOperations.length === 0
|
|
3067
|
+
&& status.observation.pendingProjectTransactions.length === 0
|
|
3068
|
+
&& status.observation.pendingMilestoneTransactions.length === 0
|
|
3069
|
+
&& status.observation.pendingTaskTransactions.length === 0
|
|
3070
|
+
&& status.observation.corruptTaskTransactions.length === 0);
|
|
3071
|
+
const withMilestoneCancellation = (value) => (milestoneCancellationSafe
|
|
3072
|
+
? {
|
|
3073
|
+
...value,
|
|
3074
|
+
milestoneCancellationOption: {
|
|
3075
|
+
command: 'milestone cancel',
|
|
3076
|
+
milestoneId: activeTaskMilestone.id,
|
|
3077
|
+
expectedRevision: activeTaskMilestone.revision,
|
|
3078
|
+
reasonRequired: true,
|
|
3079
|
+
taskDisposition: 'retained-historical',
|
|
3080
|
+
},
|
|
3081
|
+
}
|
|
3082
|
+
: value);
|
|
3083
|
+
let activePlanRiskAudit = null;
|
|
3084
|
+
let planRiskAuditRecovery = null;
|
|
3085
|
+
if (activeTask.planHash) {
|
|
3086
|
+
try {
|
|
3087
|
+
activePlanRiskAudit = readCurrentPlanRiskAudit(this.store, activeTask);
|
|
3088
|
+
}
|
|
3089
|
+
catch (error) {
|
|
3090
|
+
if (!(error instanceof WorkflowError) || error.code !== 'TRANSITION_BLOCKED')
|
|
3091
|
+
throw error;
|
|
3092
|
+
planRiskAuditRecovery = {
|
|
3093
|
+
scope: 'task',
|
|
3094
|
+
id: activeTask.id,
|
|
3095
|
+
status: activeTask.status,
|
|
3096
|
+
action: 'task plan-risk-audit',
|
|
3097
|
+
revision: activeTask.revision,
|
|
3098
|
+
planRiskAuditRecovery: {
|
|
3099
|
+
state: 'required',
|
|
3100
|
+
reason: error.message,
|
|
3101
|
+
currentBriefHash: activeTask.briefHash,
|
|
3102
|
+
currentPlanHash: activeTask.planHash,
|
|
3103
|
+
knowledgeMapRevision: activeTask.knowledgeMapRevision,
|
|
3104
|
+
knowledgeMapHash: activeTask.knowledgeMapHash,
|
|
3105
|
+
},
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
2503
3109
|
const taskNext = activeTask.status === 'planning'
|
|
2504
3110
|
? null
|
|
2505
|
-
:
|
|
3111
|
+
: planRiskAuditRecovery
|
|
3112
|
+
?? this.nextForTaskWithDelegation(status.projectId, activeTask, activePlanRiskAudit);
|
|
2506
3113
|
// Active Step work must outrank Project Knowledge refresh/rebind. Both completion and
|
|
2507
3114
|
// strict review are executable while context refresh correctly remains forbidden.
|
|
2508
3115
|
const hardStop = taskNext?.action === 'task corrective-decision'
|
|
@@ -2514,12 +3121,27 @@ export class WorkflowService {
|
|
|
2514
3121
|
|| hardStop
|
|
2515
3122
|
|| terminalCorrectiveDecision === 'split-required'
|
|
2516
3123
|
|| terminalCorrectiveDecision === 'stop-escalate') {
|
|
2517
|
-
return withAdoption(taskNext);
|
|
3124
|
+
return withAdoption(withMilestoneCancellation(taskNext));
|
|
2518
3125
|
}
|
|
2519
3126
|
if (!taskCanRebindKnowledge(activeTask) && activeTask.status !== 'planning') {
|
|
2520
|
-
return withAdoption(taskNext);
|
|
3127
|
+
return withAdoption(withMilestoneCancellation(taskNext));
|
|
3128
|
+
}
|
|
3129
|
+
let knowledgeMap;
|
|
3130
|
+
try {
|
|
3131
|
+
knowledgeMap = inspectCurrentKnowledgeMap();
|
|
3132
|
+
}
|
|
3133
|
+
catch (error) {
|
|
3134
|
+
if (!(error instanceof WorkflowError) || error.code !== 'NOT_FOUND')
|
|
3135
|
+
throw error;
|
|
3136
|
+
return withAdoption(withMilestoneCancellation({
|
|
3137
|
+
scope: 'project-memory',
|
|
3138
|
+
status: 'missing',
|
|
3139
|
+
action: 'project-memory scan',
|
|
3140
|
+
stalenessOriginTaskId: activeTask.id,
|
|
3141
|
+
authorizesTask: false,
|
|
3142
|
+
provenanceNotice: 'This Task identifier is diagnostic provenance only; the Project Knowledge action does not authorize or resume it.',
|
|
3143
|
+
}));
|
|
2521
3144
|
}
|
|
2522
|
-
const knowledgeMap = this.knowledgeMapStatus(repository);
|
|
2523
3145
|
if (knowledgeMap.status !== 'active' || !knowledgeMap.approval) {
|
|
2524
3146
|
const action = knowledgeMap.status === 'stale' ? 'project-memory reconcile' : 'project-memory approve';
|
|
2525
3147
|
const delegatedApprovalOptions = action === 'project-memory approve'
|
|
@@ -2535,7 +3157,17 @@ export class WorkflowService {
|
|
|
2535
3157
|
const contextRefreshOptions = action === 'project-memory reconcile' && refreshAssessment?.eligible
|
|
2536
3158
|
? this.delegatedContextRefreshOptions(status.projectId, activeTask, knowledgeMap, refreshAssessment.mode)
|
|
2537
3159
|
: [];
|
|
2538
|
-
|
|
3160
|
+
if (requestedTask && action === 'project-memory reconcile' && contextRefreshOptions.length === 0) {
|
|
3161
|
+
const refreshOwner = this.findDelegatedContextRefreshOwner(status.projectId, status.tasks, milestonesById, storedKnowledgeMap, knowledgeMap);
|
|
3162
|
+
if (refreshOwner && refreshOwner.task.id !== requestedTask.id) {
|
|
3163
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${requestedTask.id} cannot be selected while Task ${refreshOwner.task.id} requires an atomic context refresh.`, {
|
|
3164
|
+
taskId: requestedTask.id,
|
|
3165
|
+
blockingTaskId: refreshOwner.task.id,
|
|
3166
|
+
requiredAction: 'Run repository next and its exact task context-refresh action first.',
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
return withAdoption(withMilestoneCancellation({
|
|
2539
3171
|
scope: contextRefreshOptions.length > 0 ? 'task' : 'project-memory',
|
|
2540
3172
|
status: knowledgeMap.status,
|
|
2541
3173
|
action: contextRefreshOptions.length > 0 ? 'task context-refresh' : action,
|
|
@@ -2567,14 +3199,14 @@ export class WorkflowService {
|
|
|
2567
3199
|
},
|
|
2568
3200
|
}
|
|
2569
3201
|
: {}),
|
|
2570
|
-
});
|
|
3202
|
+
}));
|
|
2571
3203
|
}
|
|
2572
3204
|
const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
|
|
2573
3205
|
if (taskCanRebindKnowledge(activeTask) &&
|
|
2574
3206
|
activeTask.planHash &&
|
|
2575
3207
|
(activeTask.knowledgeMapRevision !== knowledgeMap.revision ||
|
|
2576
3208
|
activeTask.knowledgeMapHash !== knowledgeMapHash)) {
|
|
2577
|
-
return withAdoption({
|
|
3209
|
+
return withAdoption(withMilestoneCancellation({
|
|
2578
3210
|
scope: 'task',
|
|
2579
3211
|
id: activeTask.id,
|
|
2580
3212
|
status: activeTask.status,
|
|
@@ -2582,12 +3214,69 @@ export class WorkflowService {
|
|
|
2582
3214
|
revision: activeTask.revision,
|
|
2583
3215
|
knowledgeMapRevision: knowledgeMap.revision,
|
|
2584
3216
|
knowledgeMapHash,
|
|
2585
|
-
});
|
|
3217
|
+
}));
|
|
2586
3218
|
}
|
|
2587
|
-
return withAdoption(taskNext ?? this.nextForTaskWithDelegation(status.projectId, activeTask, null));
|
|
3219
|
+
return withAdoption(withMilestoneCancellation(taskNext ?? this.nextForTaskWithDelegation(status.projectId, activeTask, null)));
|
|
3220
|
+
}
|
|
3221
|
+
let repositoryKnowledgeMap;
|
|
3222
|
+
try {
|
|
3223
|
+
repositoryKnowledgeMap = inspectCurrentKnowledgeMap();
|
|
3224
|
+
}
|
|
3225
|
+
catch (error) {
|
|
3226
|
+
if (!(error instanceof WorkflowError) || error.code !== 'NOT_FOUND')
|
|
3227
|
+
throw error;
|
|
3228
|
+
return withAdoption({
|
|
3229
|
+
scope: 'project-memory',
|
|
3230
|
+
status: 'missing',
|
|
3231
|
+
action: 'project-memory scan',
|
|
3232
|
+
authorizesMilestone: false,
|
|
3233
|
+
provenanceNotice: 'Project Knowledge must be current before Discovery or Milestone authorization.',
|
|
3234
|
+
});
|
|
3235
|
+
}
|
|
3236
|
+
if (repositoryKnowledgeMap.status !== 'active' || !repositoryKnowledgeMap.approval) {
|
|
3237
|
+
const action = repositoryKnowledgeMap.status === 'stale'
|
|
3238
|
+
? 'project-memory reconcile'
|
|
3239
|
+
: 'project-memory approve';
|
|
3240
|
+
const delegatedApprovalOptions = action === 'project-memory approve'
|
|
3241
|
+
? this.delegatedApprovalOptions(status.projectId, 'project_memory.approve', repositoryKnowledgeMap)
|
|
3242
|
+
: [];
|
|
3243
|
+
return withAdoption({
|
|
3244
|
+
scope: 'project-memory',
|
|
3245
|
+
status: repositoryKnowledgeMap.status,
|
|
3246
|
+
action,
|
|
3247
|
+
revision: repositoryKnowledgeMap.revision,
|
|
3248
|
+
authorizesMilestone: false,
|
|
3249
|
+
provenanceNotice: 'Project Knowledge must be reconciled and approved before Discovery or Milestone authorization.',
|
|
3250
|
+
...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
|
|
3251
|
+
});
|
|
2588
3252
|
}
|
|
2589
3253
|
const activeMilestone = status.milestones.find((milestone) => !['accepted', 'cancelled'].includes(milestone.status));
|
|
2590
3254
|
if (activeMilestone) {
|
|
3255
|
+
const initialAssembly = status.milestoneInitialAssembly
|
|
3256
|
+
.find((candidate) => candidate.milestoneId === activeMilestone.id) ?? null;
|
|
3257
|
+
if (initialAssembly) {
|
|
3258
|
+
const activeDiscovery = status.discoveries.find((discovery) => !['materialized', 'abandoned'].includes(discovery.status));
|
|
3259
|
+
if (activeDiscovery) {
|
|
3260
|
+
return withAdoption({
|
|
3261
|
+
scope: 'discovery',
|
|
3262
|
+
id: activeDiscovery.id,
|
|
3263
|
+
status: activeDiscovery.status,
|
|
3264
|
+
action: activeDiscovery.status === 'ready_to_materialize' ? 'discovery materialize' : 'discovery update',
|
|
3265
|
+
readiness: assessDiscovery(activeDiscovery),
|
|
3266
|
+
milestoneInitialAssembly: initialAssembly,
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3269
|
+
return withAdoption({
|
|
3270
|
+
scope: 'milestone',
|
|
3271
|
+
id: activeMilestone.id,
|
|
3272
|
+
status: activeMilestone.status,
|
|
3273
|
+
action: 'milestone initial-assembly',
|
|
3274
|
+
revision: activeMilestone.revision,
|
|
3275
|
+
linkedTaskIds: initialAssembly.linkedTaskIds,
|
|
3276
|
+
executionAvailable: initialAssembly.executionAvailable,
|
|
3277
|
+
milestoneInitialAssembly: initialAssembly,
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
2591
3280
|
if (activeMilestone.status === 'awaiting_final_acceptance') {
|
|
2592
3281
|
const root = this.store.milestoneRoot(activeMilestone.projectId, activeMilestone.id);
|
|
2593
3282
|
const evidence = JSON.parse(readFileSync(path.join(root, 'evidence.json'), 'utf8'));
|
|
@@ -2638,6 +3327,19 @@ export class WorkflowService {
|
|
|
2638
3327
|
status: activeMilestone.status,
|
|
2639
3328
|
action: nextForMilestone(activeMilestone),
|
|
2640
3329
|
revision: activeMilestone.revision,
|
|
3330
|
+
...(activeMilestone.status === 'awaiting_execution_authorization' && activeMilestone.planHash
|
|
3331
|
+
? {
|
|
3332
|
+
milestoneAuthorizationOptions: [
|
|
3333
|
+
{ action: 'milestone authorize', mode: 'direct-human' },
|
|
3334
|
+
{
|
|
3335
|
+
action: 'milestone autonomy-prepare',
|
|
3336
|
+
mode: 'delegated',
|
|
3337
|
+
requiresExactHumanConfirmation: true,
|
|
3338
|
+
maximumDurationHours: 72,
|
|
3339
|
+
},
|
|
3340
|
+
],
|
|
3341
|
+
}
|
|
3342
|
+
: {}),
|
|
2641
3343
|
...(activeMilestone.status === 'active'
|
|
2642
3344
|
? {
|
|
2643
3345
|
completionPolicy: {
|
|
@@ -2794,7 +3496,20 @@ export class WorkflowService {
|
|
|
2794
3496
|
throw error;
|
|
2795
3497
|
}
|
|
2796
3498
|
}
|
|
2797
|
-
|
|
3499
|
+
let catalogCorrectiveReplanPosture = guardedCorrectivePosture?.posture === 'replan-required'
|
|
3500
|
+
? guardedCorrectivePosture
|
|
3501
|
+
: null;
|
|
3502
|
+
if (!remediationModeRecovery && planRiskAudit && guardedCorrectivePosture === null) {
|
|
3503
|
+
try {
|
|
3504
|
+
catalogCorrectiveReplanPosture = identifyCorrectiveReplanPosture(task, planRiskAudit.reviewRequiredStepIds, readCorrectiveDecisionEvents(this.store, task), readRemediationEvents(this.store, task));
|
|
3505
|
+
}
|
|
3506
|
+
catch (error) {
|
|
3507
|
+
remediationModeRecovery = findRemediationModeRecoveryCandidate(this.store, task);
|
|
3508
|
+
if (!remediationModeRecovery)
|
|
3509
|
+
throw error;
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
const next = this.taskGitNavigationOverride(task) ?? nextForTask(task);
|
|
2798
3513
|
const handoffPosture = readTaskC1Posture(this.store, task);
|
|
2799
3514
|
const strictStepReviewStep = planRiskAudit
|
|
2800
3515
|
? task.steps.find((step) => {
|
|
@@ -2806,6 +3521,11 @@ export class WorkflowService {
|
|
|
2806
3521
|
return buildCurrentStrictStepReviewCycle(this.store, task, step.id).resolution === 'unverified';
|
|
2807
3522
|
}) ?? null
|
|
2808
3523
|
: null;
|
|
3524
|
+
const strictReviewInfrastructureFailures = strictStepReviewStep
|
|
3525
|
+
? countUnverifiedStrictStepReviews(this.store, task, strictStepReviewStep.id)
|
|
3526
|
+
: 0;
|
|
3527
|
+
const strictTaskReviewRequired = (task.status === 'validating' && task.review.status === 'pending')
|
|
3528
|
+
|| (task.status === 'blocked' && task.review.status === 'unverified');
|
|
2809
3529
|
const handoffOverlay = handoffPosture.state === 'pending'
|
|
2810
3530
|
? {
|
|
2811
3531
|
action: 'task claim',
|
|
@@ -2814,6 +3534,15 @@ export class WorkflowService {
|
|
|
2814
3534
|
state: 'pending',
|
|
2815
3535
|
targetActor: handoffPosture.targetActor,
|
|
2816
3536
|
eventId: handoffPosture.latestEvent.eventId,
|
|
3537
|
+
credentialRecovery: {
|
|
3538
|
+
action: 'task handoff-replace',
|
|
3539
|
+
when: 'claim credential is unavailable',
|
|
3540
|
+
requiredActor: handoffPosture.latestEvent.actor,
|
|
3541
|
+
targetActor: handoffPosture.targetActor,
|
|
3542
|
+
grantId: handoffPosture.latestEvent.grantId,
|
|
3543
|
+
expectedRevision: task.revision,
|
|
3544
|
+
invalidatesPriorCredential: true,
|
|
3545
|
+
},
|
|
2817
3546
|
},
|
|
2818
3547
|
}
|
|
2819
3548
|
: handoffPosture.state === 'claimed'
|
|
@@ -2836,6 +3565,63 @@ export class WorkflowService {
|
|
|
2836
3565
|
},
|
|
2837
3566
|
}
|
|
2838
3567
|
: {};
|
|
3568
|
+
const correctiveReplanOverlay = (posture) => {
|
|
3569
|
+
const evaluation = evaluateTaskCorrectiveReplanAvailability(task, posture);
|
|
3570
|
+
const writerLease = inspectRawWriterLease(this.store.lockRoot(task.projectId), task.id, this.store.root);
|
|
3571
|
+
const command = handoffPosture.state === 'claimed'
|
|
3572
|
+
? 'task corrective-yield'
|
|
3573
|
+
: writerLease
|
|
3574
|
+
? 'doctor'
|
|
3575
|
+
: 'task corrective-replan-prepare';
|
|
3576
|
+
if (evaluation.allowed && handoffPosture.state === 'none' && writerLease) {
|
|
3577
|
+
const navigation = correctiveReplanNavigation(evaluation);
|
|
3578
|
+
return {
|
|
3579
|
+
action: command,
|
|
3580
|
+
blockedAction: 'task corrective-replan-prepare',
|
|
3581
|
+
stepId: posture.stepId,
|
|
3582
|
+
navigation: {
|
|
3583
|
+
kind: 'blocked',
|
|
3584
|
+
transitionId: navigation.transitionId,
|
|
3585
|
+
semanticFingerprint: navigation.semanticFingerprint,
|
|
3586
|
+
command: navigation.command,
|
|
3587
|
+
blockingGuardIds: ['task.corrective-replan.no-writer-lease'],
|
|
3588
|
+
pendingInputGuardIds: navigation.pendingInputGuardIds,
|
|
3589
|
+
reasonCode: 'ACTIVE_WRITER_LEASE',
|
|
3590
|
+
},
|
|
3591
|
+
correctiveDecisionGate: {
|
|
3592
|
+
attemptCount: posture.attemptCount,
|
|
3593
|
+
decision: posture.correctiveDecision?.decision ?? null,
|
|
3594
|
+
decisionEventId: posture.correctiveDecision?.eventId ?? null,
|
|
3595
|
+
requiredAction: 'The active non-C1 writer lease must expire and be explicitly repaired before corrective replan preparation.',
|
|
3596
|
+
writerLease: {
|
|
3597
|
+
owner: writerLease.owner,
|
|
3598
|
+
expiresAt: writerLease.expiresAt,
|
|
3599
|
+
},
|
|
3600
|
+
},
|
|
3601
|
+
};
|
|
3602
|
+
}
|
|
3603
|
+
return evaluation.allowed
|
|
3604
|
+
? {
|
|
3605
|
+
action: command,
|
|
3606
|
+
blockedAction: 'task plan-set',
|
|
3607
|
+
stepId: posture.stepId,
|
|
3608
|
+
navigation: { ...correctiveReplanNavigation(evaluation), command },
|
|
3609
|
+
correctiveDecisionGate: {
|
|
3610
|
+
attemptCount: posture.attemptCount,
|
|
3611
|
+
decision: posture.correctiveDecision?.decision ?? null,
|
|
3612
|
+
decisionEventId: posture.correctiveDecision?.eventId ?? null,
|
|
3613
|
+
requiredAction: handoffPosture.state === 'claimed'
|
|
3614
|
+
? 'Yield the exact C1 claim and writer lease before preparing replacement authority.'
|
|
3615
|
+
: 'Prepare a state-bound candidate, confirm the Human gate, then execute its exact receipt.',
|
|
3616
|
+
},
|
|
3617
|
+
}
|
|
3618
|
+
: {
|
|
3619
|
+
action: 'doctor',
|
|
3620
|
+
blockedAction: 'task plan-set',
|
|
3621
|
+
stepId: posture.stepId,
|
|
3622
|
+
navigation: correctiveReplanBlockedNavigation(evaluation),
|
|
3623
|
+
};
|
|
3624
|
+
};
|
|
2839
3625
|
let guardedCorrectiveOverlay = {};
|
|
2840
3626
|
if (remediationModeRecovery) {
|
|
2841
3627
|
guardedCorrectiveOverlay = {
|
|
@@ -2854,7 +3640,12 @@ export class WorkflowService {
|
|
|
2854
3640
|
},
|
|
2855
3641
|
};
|
|
2856
3642
|
}
|
|
2857
|
-
if (!remediationModeRecovery &&
|
|
3643
|
+
if (!remediationModeRecovery && guardedCorrectivePosture === null && catalogCorrectiveReplanPosture) {
|
|
3644
|
+
guardedCorrectiveOverlay = correctiveReplanOverlay(catalogCorrectiveReplanPosture);
|
|
3645
|
+
}
|
|
3646
|
+
if (!remediationModeRecovery
|
|
3647
|
+
&& guardedCorrectivePosture
|
|
3648
|
+
&& (!strictStepReviewStep || guardedCorrectivePosture.posture === 'replan-required')) {
|
|
2858
3649
|
switch (guardedCorrectivePosture.posture) {
|
|
2859
3650
|
case 'decision-required':
|
|
2860
3651
|
if (guardedCorrectivePosture.correctiveDecision
|
|
@@ -2929,16 +3720,7 @@ export class WorkflowService {
|
|
|
2929
3720
|
};
|
|
2930
3721
|
break;
|
|
2931
3722
|
case 'replan-required':
|
|
2932
|
-
guardedCorrectiveOverlay =
|
|
2933
|
-
action: 'task plan-set',
|
|
2934
|
-
stepId: guardedCorrectivePosture.stepId,
|
|
2935
|
-
correctiveDecisionGate: {
|
|
2936
|
-
attemptCount: guardedCorrectivePosture.attemptCount,
|
|
2937
|
-
decision: guardedCorrectivePosture.correctiveDecision?.decision ?? null,
|
|
2938
|
-
decisionEventId: guardedCorrectivePosture.correctiveDecision?.eventId ?? null,
|
|
2939
|
-
requiredAction: 'Record an actual new Plan posture before another remediation run.',
|
|
2940
|
-
},
|
|
2941
|
-
};
|
|
3723
|
+
guardedCorrectiveOverlay = correctiveReplanOverlay(guardedCorrectivePosture);
|
|
2942
3724
|
break;
|
|
2943
3725
|
case 'split-required':
|
|
2944
3726
|
{
|
|
@@ -3022,6 +3804,30 @@ export class WorkflowService {
|
|
|
3022
3804
|
strictStepReview: {
|
|
3023
3805
|
reviewRequired: true,
|
|
3024
3806
|
completionCommit: strictStepReviewStep.evidence.commitSha,
|
|
3807
|
+
infrastructureFailures: strictReviewInfrastructureFailures,
|
|
3808
|
+
maxLocalInfrastructureFailures: MAX_STRICT_REVIEW_INFRASTRUCTURE_FAILURES,
|
|
3809
|
+
recommendedMode: 'external-sealed',
|
|
3810
|
+
externalSealedReview: {
|
|
3811
|
+
packetCommand: 'task step-review-packet',
|
|
3812
|
+
recordCommand: 'task step-review-record',
|
|
3813
|
+
reviewerMustUseSeparateChat: true,
|
|
3814
|
+
repositorySealRequired: true,
|
|
3815
|
+
},
|
|
3816
|
+
},
|
|
3817
|
+
}
|
|
3818
|
+
: {}),
|
|
3819
|
+
...(strictTaskReviewRequired
|
|
3820
|
+
? {
|
|
3821
|
+
strictTaskReview: {
|
|
3822
|
+
reviewRequired: true,
|
|
3823
|
+
localInfrastructureFailureObserved: task.review.status === 'unverified',
|
|
3824
|
+
recommendedMode: 'external-sealed',
|
|
3825
|
+
externalSealedReview: {
|
|
3826
|
+
packetCommand: 'task review-packet',
|
|
3827
|
+
recordCommand: 'task review-sealed-record',
|
|
3828
|
+
reviewerMustUseSeparateChat: true,
|
|
3829
|
+
repositorySealRequired: true,
|
|
3830
|
+
},
|
|
3025
3831
|
},
|
|
3026
3832
|
}
|
|
3027
3833
|
: {}),
|
|
@@ -3051,13 +3857,37 @@ export class WorkflowService {
|
|
|
3051
3857
|
...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
|
|
3052
3858
|
};
|
|
3053
3859
|
if (handoffPosture.state === 'pending') {
|
|
3054
|
-
return {
|
|
3860
|
+
return this.withTaskNavigationContracts(task, {
|
|
3055
3861
|
...result,
|
|
3056
3862
|
action: 'task claim',
|
|
3057
3863
|
blockedAction: next.action,
|
|
3058
|
-
};
|
|
3864
|
+
});
|
|
3059
3865
|
}
|
|
3060
|
-
|
|
3866
|
+
const navigableResult = ['task plan-set', 'task plan-set with independent corrective audit']
|
|
3867
|
+
.includes(String(result.action))
|
|
3868
|
+
? {
|
|
3869
|
+
...result,
|
|
3870
|
+
taskPlanContract: this.taskPlanContract(projectId, task),
|
|
3871
|
+
}
|
|
3872
|
+
: result;
|
|
3873
|
+
return this.withTaskNavigationContracts(task, navigableResult);
|
|
3874
|
+
}
|
|
3875
|
+
taskPlanContract(projectId, task) {
|
|
3876
|
+
const map = this.store.readKnowledgeMap(projectId);
|
|
3877
|
+
return {
|
|
3878
|
+
requiredRequirementIds: [...task.requirements],
|
|
3879
|
+
requiredAcceptanceIds: [...task.acceptance],
|
|
3880
|
+
currentRisks: [...task.risks],
|
|
3881
|
+
exactInclusionRequired: true,
|
|
3882
|
+
briefHash: task.briefHash,
|
|
3883
|
+
knowledgeMap: knowledgeSummary(map),
|
|
3884
|
+
planRiskAuditContract: {
|
|
3885
|
+
required: true,
|
|
3886
|
+
option: '--risk-audit-file',
|
|
3887
|
+
atomicWithPlanSet: true,
|
|
3888
|
+
plannerAuditorSeparationRequired: true,
|
|
3889
|
+
},
|
|
3890
|
+
};
|
|
3061
3891
|
}
|
|
3062
3892
|
assertTaskContextChangeAllowed(task) {
|
|
3063
3893
|
const posture = this.taskTerminalCorrectivePosture(task);
|
|
@@ -3136,6 +3966,22 @@ export class WorkflowService {
|
|
|
3136
3966
|
policyHash: grant.policyHash,
|
|
3137
3967
|
}));
|
|
3138
3968
|
}
|
|
3969
|
+
findDelegatedContextRefreshOwner(projectId, tasks, milestonesById, approved, inspected) {
|
|
3970
|
+
for (const task of tasks) {
|
|
3971
|
+
if (!taskIsActionableFromRepositoryNext(task, milestonesById)
|
|
3972
|
+
|| !task.planHash
|
|
3973
|
+
|| !taskCanRebindKnowledge(task)
|
|
3974
|
+
|| this.taskHasTerminalCorrectiveDecision(task))
|
|
3975
|
+
continue;
|
|
3976
|
+
const assessment = assessDelegatedKnowledgeRefresh(approved, inspected, task, this.store.hashArtifact(this.store.taskRoot(projectId, task.id), 'plan.md'));
|
|
3977
|
+
if (!assessment.eligible)
|
|
3978
|
+
continue;
|
|
3979
|
+
const options = this.delegatedContextRefreshOptions(projectId, task, inspected, assessment.mode);
|
|
3980
|
+
if (options.length > 0)
|
|
3981
|
+
return { task, assessment, options };
|
|
3982
|
+
}
|
|
3983
|
+
return null;
|
|
3984
|
+
}
|
|
3139
3985
|
assertPlanBoundedSupportingSourceGrant(projectId, grantId, actor, task) {
|
|
3140
3986
|
const grant = this.store.readDelegation(projectId, grantId);
|
|
3141
3987
|
if (!task.milestoneId || grant.scope.kind !== 'milestone' || grant.scope.id !== task.milestoneId) {
|
|
@@ -3205,6 +4051,130 @@ export class WorkflowService {
|
|
|
3205
4051
|
? milestone
|
|
3206
4052
|
: readMilestoneWithIntegrity(this.store, projectId, milestone.id, (taskId) => this.store.readTask(projectId, taskId), () => this.store.listTasks(projectId))));
|
|
3207
4053
|
}
|
|
4054
|
+
listMilestonesObserved(projectId, milestones, posture) {
|
|
4055
|
+
if (!posture)
|
|
4056
|
+
return milestones;
|
|
4057
|
+
const grandfatheredMilestones = new Set(posture.terminalMilestones.map((milestone) => `${milestone.milestoneId}:${milestone.revision}:${milestone.status}`));
|
|
4058
|
+
return milestones.map((milestone) => (grandfatheredMilestones.has(`${milestone.id}:${milestone.revision}:${milestone.status}`)
|
|
4059
|
+
? milestone
|
|
4060
|
+
: inspectMilestoneWithIntegrity(this.store, projectId, milestone.id, (taskId) => this.store.readTask(projectId, taskId), () => this.store.listTasks(projectId))));
|
|
4061
|
+
}
|
|
4062
|
+
taskGitNavigationOverride(task) {
|
|
4063
|
+
if (task.status !== 'merging' || task.workspaceOwner !== 'local' || !task.baseCommit || !task.taskBranch) {
|
|
4064
|
+
return null;
|
|
4065
|
+
}
|
|
4066
|
+
const currentBase = runGit(task.repositoryRoot, ['rev-parse', task.baseBranch]);
|
|
4067
|
+
if (currentBase === task.baseCommit)
|
|
4068
|
+
return null;
|
|
4069
|
+
return {
|
|
4070
|
+
scope: 'task',
|
|
4071
|
+
id: task.id,
|
|
4072
|
+
status: task.status,
|
|
4073
|
+
action: 'task sync-base',
|
|
4074
|
+
revision: task.revision,
|
|
4075
|
+
baseSyncRequired: {
|
|
4076
|
+
baseBranch: task.baseBranch,
|
|
4077
|
+
recordedBaseCommit: task.baseCommit,
|
|
4078
|
+
currentBaseCommit: currentBase,
|
|
4079
|
+
},
|
|
4080
|
+
};
|
|
4081
|
+
}
|
|
4082
|
+
inspectDependencyProvenanceCandidate(snapshot, task) {
|
|
4083
|
+
const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
|
|
4084
|
+
const now = this.now().getTime();
|
|
4085
|
+
return inspectDependencyProvenanceRecovery(snapshot.identity.repositoryRoot, task, {
|
|
4086
|
+
activeLeaseCount: snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) > now).length,
|
|
4087
|
+
staleLeaseCount: snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) <= now).length,
|
|
4088
|
+
pendingTransactionCount: snapshot.observation.pendingProjectTransactions.length
|
|
4089
|
+
+ snapshot.observation.pendingMilestoneTransactions.length
|
|
4090
|
+
+ snapshot.observation.pendingTaskTransactions.length,
|
|
4091
|
+
corruptTransactionCount: snapshot.observation.corruptTaskTransactions.length,
|
|
4092
|
+
coreOperationCount: snapshot.observation.coreTaskOperations.length,
|
|
4093
|
+
verifiedHistoricalStepCommits: verifiedHistoricalStepReviewCommits(this.store, task),
|
|
4094
|
+
versions: {
|
|
4095
|
+
runtime: versions.runtime,
|
|
4096
|
+
declared: versions.declared,
|
|
4097
|
+
locked: versions.locked,
|
|
4098
|
+
installed: versions.installed,
|
|
4099
|
+
currentBranch: versions.currentBranch,
|
|
4100
|
+
baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
|
|
4101
|
+
},
|
|
4102
|
+
});
|
|
4103
|
+
}
|
|
4104
|
+
withTaskNavigationContracts(task, next) {
|
|
4105
|
+
const action = typeof next.action === 'string' ? next.action : null;
|
|
4106
|
+
if (!action)
|
|
4107
|
+
return next;
|
|
4108
|
+
const policy = gitMutationPolicyForAction(task, action);
|
|
4109
|
+
const c1Posture = readTaskC1Posture(this.store, task);
|
|
4110
|
+
const claimedC1Posture = c1Posture.state === 'claimed';
|
|
4111
|
+
const writerTokenContract = writerTokenContractForAction(action, claimedC1Posture);
|
|
4112
|
+
const activeLease = writerTokenContract
|
|
4113
|
+
? inspectRawWriterLease(this.store.lockRoot(task.projectId), task.id, this.store.root)
|
|
4114
|
+
: null;
|
|
4115
|
+
const writerCredentialRecoveryActor = this.writerCredentialRecoveryActor(task, action, claimedC1Posture ? c1Posture.claimant : null, activeLease?.owner ?? null);
|
|
4116
|
+
const writerCredentialRecovery = writerTokenContract && writerCredentialRecoveryActor
|
|
4117
|
+
? {
|
|
4118
|
+
action: 'task writer-credential-replace',
|
|
4119
|
+
when: 'the active writer credential is unavailable',
|
|
4120
|
+
requiredActor: writerCredentialRecoveryActor,
|
|
4121
|
+
expectedRevision: task.revision,
|
|
4122
|
+
invalidatesPriorCredential: true,
|
|
4123
|
+
acquiresLeaseWhenAbsent: true,
|
|
4124
|
+
}
|
|
4125
|
+
: null;
|
|
4126
|
+
return {
|
|
4127
|
+
...next,
|
|
4128
|
+
...(policy ? { gitMutationPolicy: policy } : {}),
|
|
4129
|
+
...(writerTokenContract ? { writerTokenContract } : {}),
|
|
4130
|
+
...(writerCredentialRecovery ? { writerCredentialRecovery } : {}),
|
|
4131
|
+
};
|
|
4132
|
+
}
|
|
4133
|
+
writerCredentialRecoveryActor(task, action, claimedActor, activeLeaseOwner) {
|
|
4134
|
+
if (claimedActor)
|
|
4135
|
+
return claimedActor;
|
|
4136
|
+
if (activeLeaseOwner)
|
|
4137
|
+
return activeLeaseOwner;
|
|
4138
|
+
const authorizationKind = ['task merge', 'task merge-confirm'].includes(action)
|
|
4139
|
+
? 'final_acceptance'
|
|
4140
|
+
: ['task step-complete', 'task submit'].includes(action)
|
|
4141
|
+
? 'execution'
|
|
4142
|
+
: null;
|
|
4143
|
+
if (!authorizationKind)
|
|
4144
|
+
return null;
|
|
4145
|
+
return [...task.authorizations].reverse().find((authorization) => authorization.kind === authorizationKind && authorization.decision === 'approved')?.actor ?? null;
|
|
4146
|
+
}
|
|
4147
|
+
describeMilestoneInitialAssembly(projectId, milestone, tasks) {
|
|
4148
|
+
const assessment = assessMilestoneInitialAssembly({
|
|
4149
|
+
store: this.store,
|
|
4150
|
+
projectId,
|
|
4151
|
+
milestone,
|
|
4152
|
+
listTasks: () => tasks,
|
|
4153
|
+
});
|
|
4154
|
+
if (!assessment.eligible)
|
|
4155
|
+
return null;
|
|
4156
|
+
const options = [
|
|
4157
|
+
{
|
|
4158
|
+
action: 'linked-task-assembly',
|
|
4159
|
+
commands: ['discovery start', 'discovery materialize'],
|
|
4160
|
+
description: 'Start or materialize the next linked Task Discovery for this Milestone before closing the initial membership Plan.',
|
|
4161
|
+
},
|
|
4162
|
+
];
|
|
4163
|
+
if (assessment.orphanTaskIds.length > 0) {
|
|
4164
|
+
options.push({
|
|
4165
|
+
action: 'milestone plan-set',
|
|
4166
|
+
commands: ['milestone plan-set'],
|
|
4167
|
+
description: 'Record one complete initial Milestone Plan that classifies every currently linked Task.',
|
|
4168
|
+
});
|
|
4169
|
+
}
|
|
4170
|
+
return {
|
|
4171
|
+
milestoneId: milestone.id,
|
|
4172
|
+
revision: milestone.revision,
|
|
4173
|
+
linkedTaskIds: assessment.orphanTaskIds,
|
|
4174
|
+
executionAvailable: false,
|
|
4175
|
+
options,
|
|
4176
|
+
};
|
|
4177
|
+
}
|
|
3208
4178
|
prepareMilestoneScopeChangeCandidate(projectId, milestone, plan, actor) {
|
|
3209
4179
|
validateMilestonePlan(plan);
|
|
3210
4180
|
return prepareMilestoneScopeChangeCandidate({
|
|
@@ -3214,6 +4184,12 @@ export class WorkflowService {
|
|
|
3214
4184
|
actor,
|
|
3215
4185
|
});
|
|
3216
4186
|
}
|
|
4187
|
+
publicWriterLeaseDiagnostic(lease) {
|
|
4188
|
+
return {
|
|
4189
|
+
...toPublicWriterLease(lease, this.now()),
|
|
4190
|
+
tokenFingerprint: createHash('sha256').update(lease.token).digest('hex').slice(0, 16),
|
|
4191
|
+
};
|
|
4192
|
+
}
|
|
3217
4193
|
proveWriterLeaseTokenForHandoff(context, taskId, writerToken) {
|
|
3218
4194
|
const currentLease = context.locks.inspect(taskId);
|
|
3219
4195
|
if (!currentLease) {
|
|
@@ -3303,9 +4279,12 @@ export class WorkflowService {
|
|
|
3303
4279
|
storedKnowledgeMapHash: storedHash,
|
|
3304
4280
|
});
|
|
3305
4281
|
}
|
|
3306
|
-
inspectAdoptionPreparation(identity, tasks, milestones) {
|
|
3307
|
-
const
|
|
3308
|
-
|
|
4282
|
+
inspectAdoptionPreparation(identity, tasks, milestones, observedLeases, observedPosture) {
|
|
4283
|
+
const leases = observedLeases
|
|
4284
|
+
?? new WriterLockManager(this.store.lockRoot(identity.projectId), this.now, this.store.root).list();
|
|
4285
|
+
const currentPosture = observedPosture === undefined
|
|
4286
|
+
? readCurrentAdoptionPosture(this.store, identity.projectId)
|
|
4287
|
+
: observedPosture;
|
|
3309
4288
|
if (currentPosture) {
|
|
3310
4289
|
assertAdoptionBaselinePreserved(currentPosture, tasks, milestones);
|
|
3311
4290
|
}
|
|
@@ -3318,7 +4297,7 @@ export class WorkflowService {
|
|
|
3318
4297
|
tasks,
|
|
3319
4298
|
milestones,
|
|
3320
4299
|
sidecarInventory: this.collectAdoptionSidecarInventory(identity.projectId, tasks, milestones),
|
|
3321
|
-
blockers: this.computeAdoptionBoundaryBlockers(identity, tasks,
|
|
4300
|
+
blockers: this.computeAdoptionBoundaryBlockers(identity, tasks, leases),
|
|
3322
4301
|
now: this.now(),
|
|
3323
4302
|
}, currentPosture);
|
|
3324
4303
|
}
|
|
@@ -3860,13 +4839,13 @@ function nextForTask(task) {
|
|
|
3860
4839
|
awaiting_execution_authorization: 'task authorize',
|
|
3861
4840
|
ready: task.workspaceOwner ? (stepsComplete ? 'task submit' : 'task run') : 'task start',
|
|
3862
4841
|
in_progress: activeStep ? 'task step-complete' : stepsComplete ? 'task submit' : 'task run',
|
|
3863
|
-
validating: task.review.status === 'passed' ? 'task result-set' : 'task review-
|
|
4842
|
+
validating: task.review.status === 'passed' ? 'task result-set' : 'task review-launch',
|
|
3864
4843
|
needs_fix: task.steps.some((step) => step.status === 'failed') ? 'task run' : 'task plan-set',
|
|
3865
4844
|
awaiting_final_acceptance: 'task accept',
|
|
3866
4845
|
merging: 'task merge',
|
|
3867
4846
|
ready_to_merge: 'task merge-confirm',
|
|
3868
4847
|
merged: 'none',
|
|
3869
|
-
blocked: task.review.status === 'unverified' ? 'task review-
|
|
4848
|
+
blocked: task.review.status === 'unverified' ? 'task review-launch' : 'doctor',
|
|
3870
4849
|
cancelled: 'none',
|
|
3871
4850
|
};
|
|
3872
4851
|
const selectedStep = activeStep ?? failedStep ?? runnableStep;
|
|
@@ -3888,6 +4867,95 @@ function nextForTask(task) {
|
|
|
3888
4867
|
: {}),
|
|
3889
4868
|
};
|
|
3890
4869
|
}
|
|
4870
|
+
function gitMutationPolicyForAction(task, action) {
|
|
4871
|
+
const metadataWrites = ['.git/index', '.git/index.lock', '.git/HEAD', '.git/refs', '.git/logs'];
|
|
4872
|
+
switch (action) {
|
|
4873
|
+
case 'task start':
|
|
4874
|
+
return {
|
|
4875
|
+
permission: 'git-metadata-write',
|
|
4876
|
+
requiredBeforeFirstCall: true,
|
|
4877
|
+
boundedToTransition: true,
|
|
4878
|
+
transition: 'task start',
|
|
4879
|
+
appliesWhen: { workspaceOwner: 'local' },
|
|
4880
|
+
metadataWrites,
|
|
4881
|
+
};
|
|
4882
|
+
case 'task step-complete':
|
|
4883
|
+
return {
|
|
4884
|
+
permission: 'git-metadata-write',
|
|
4885
|
+
requiredBeforeFirstCall: true,
|
|
4886
|
+
boundedToTransition: true,
|
|
4887
|
+
transition: 'task step-complete',
|
|
4888
|
+
metadataWrites,
|
|
4889
|
+
};
|
|
4890
|
+
case 'task merge':
|
|
4891
|
+
if (task.workspaceOwner !== 'local')
|
|
4892
|
+
return null;
|
|
4893
|
+
return {
|
|
4894
|
+
permission: 'git-metadata-write',
|
|
4895
|
+
requiredBeforeFirstCall: true,
|
|
4896
|
+
boundedToTransition: true,
|
|
4897
|
+
transition: 'task merge',
|
|
4898
|
+
metadataWrites,
|
|
4899
|
+
};
|
|
4900
|
+
case 'task sync-base':
|
|
4901
|
+
return {
|
|
4902
|
+
permission: 'git-metadata-write',
|
|
4903
|
+
requiredBeforeFirstCall: true,
|
|
4904
|
+
boundedToTransition: true,
|
|
4905
|
+
transition: 'task sync-base',
|
|
4906
|
+
metadataWrites,
|
|
4907
|
+
};
|
|
4908
|
+
default:
|
|
4909
|
+
return null;
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
function writerTokenContractForAction(action, claimedC1Posture = false) {
|
|
4913
|
+
const alwaysLeaseBound = ['task step-complete', 'task submit', 'task merge', 'task merge-confirm'];
|
|
4914
|
+
const claimedC1LeaseBound = [
|
|
4915
|
+
'task run',
|
|
4916
|
+
'task step-review',
|
|
4917
|
+
'task step-review-record',
|
|
4918
|
+
'task review-record',
|
|
4919
|
+
'task review-launch',
|
|
4920
|
+
'task review-sealed-record',
|
|
4921
|
+
'task corrective-decision-recover',
|
|
4922
|
+
'task remediation-mode-recover',
|
|
4923
|
+
'task corrective-decision',
|
|
4924
|
+
'task corrective-yield',
|
|
4925
|
+
'task handoff',
|
|
4926
|
+
'task handoff-prepare',
|
|
4927
|
+
'task handback',
|
|
4928
|
+
'task handback-create',
|
|
4929
|
+
];
|
|
4930
|
+
if (!alwaysLeaseBound.includes(action) && !(claimedC1Posture && claimedC1LeaseBound.includes(action))) {
|
|
4931
|
+
return null;
|
|
4932
|
+
}
|
|
4933
|
+
return {
|
|
4934
|
+
required: true,
|
|
4935
|
+
option: '--writer-token',
|
|
4936
|
+
source: 'active-writer-lease',
|
|
4937
|
+
sensitive: true,
|
|
4938
|
+
renewalDisposition: 'reuse-presented-token',
|
|
4939
|
+
tokenReissuedOnRenewal: false,
|
|
4940
|
+
};
|
|
4941
|
+
}
|
|
4942
|
+
function observationPreflightBlockers(observation) {
|
|
4943
|
+
const messages = {
|
|
4944
|
+
PROJECT_REGISTRATION_MISSING: 'Workflow project registration is missing.',
|
|
4945
|
+
PROJECT_REPOSITORY_ROOT_UNBOUND: 'The current repository root is not bound to the Workflow project.',
|
|
4946
|
+
PROJECT_RECOVERY_REQUIRED: 'A project transaction requires explicit recovery.',
|
|
4947
|
+
MILESTONE_RECOVERY_REQUIRED: 'A Milestone transaction requires explicit recovery.',
|
|
4948
|
+
TASK_RECOVERY_REQUIRED: 'A Task corrective-replan transaction requires explicit recovery.',
|
|
4949
|
+
TASK_TRANSACTION_INTEGRITY_FAILED: 'A Task transaction journal failed integrity validation.',
|
|
4950
|
+
TASK_OPERATION_IN_PROGRESS: 'A Core Task operation is in progress.',
|
|
4951
|
+
STALE_TASK_OPERATION: 'A stale Core Task operation requires bounded repair.',
|
|
4952
|
+
ADOPTION_MISSING: 'Workflow lifecycle adoption is missing.',
|
|
4953
|
+
};
|
|
4954
|
+
return observation.reasonCodes.flatMap((reasonCode) => {
|
|
4955
|
+
const message = messages[reasonCode];
|
|
4956
|
+
return message ? [message] : [];
|
|
4957
|
+
});
|
|
4958
|
+
}
|
|
3891
4959
|
function knowledgeSummary(map) {
|
|
3892
4960
|
return {
|
|
3893
4961
|
revision: map.revision,
|