codex-workflow-v2 2.0.0-alpha.6 → 2.0.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -15
- package/dist/src/alpha6/handoff.d.ts +1 -0
- package/dist/src/alpha6/handoff.js +6 -0
- package/dist/src/alpha6/handoff.js.map +1 -1
- package/dist/src/alpha6/milestone.d.ts +3 -0
- package/dist/src/alpha6/milestone.js +57 -3
- package/dist/src/alpha6/milestone.js.map +1 -1
- package/dist/src/alpha6/plan-risk.js +61 -0
- package/dist/src/alpha6/plan-risk.js.map +1 -1
- package/dist/src/alpha6/remediation.d.ts +1 -0
- package/dist/src/alpha6/remediation.js +6 -0
- package/dist/src/alpha6/remediation.js.map +1 -1
- package/dist/src/alpha7/autonomy.d.ts +61 -0
- package/dist/src/alpha7/autonomy.js +256 -0
- package/dist/src/alpha7/autonomy.js.map +1 -0
- package/dist/src/cli.js +24 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +9 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/workflow.d.ts +35 -0
- package/dist/src/workflow.js +342 -23
- package/dist/src/workflow.js.map +1 -1
- package/docs/autonomy-guardrails.md +32 -11
- package/docs/decisions.md +16 -0
- package/docs/delegated-approval.md +12 -5
- package/docs/development-flow.md +12 -7
- package/docs/release.md +4 -2
- package/docs/updating-existing-project.md +10 -0
- package/docs/validation-report.md +51 -46
- package/package.json +1 -1
- package/plugins/codex-workflow-gateway/references/protocol.md +42 -10
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +19 -0
- package/references/state-machine.md +17 -10
- package/roles/delivery-coordinator.md +10 -1
- package/roles/technical-planner.md +3 -0
- package/schemas/milestone-autonomy-event.schema.json +45 -0
- package/schemas/milestone-scope-change-event.schema.json +3 -1
- package/schemas/plan-risk-audit-event.schema.json +21 -4
package/dist/src/workflow.js
CHANGED
|
@@ -3,17 +3,19 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { PROTOCOL_VERSION, STATE_SCHEMA_VERSION, } from './contracts.js';
|
|
5
5
|
import { renderBrief, renderPlan, renderResult } from './artifacts.js';
|
|
6
|
-
import { assertDelegationCanAuthorize,
|
|
6
|
+
import { assertDelegationCanAuthorize, hashDelegationPolicy, prepareDelegationGrantGate, } from './delegation.js';
|
|
7
7
|
import { assessDiscovery, mergeUnique } from './domain/discovery.js';
|
|
8
8
|
import { pathAllowed, validatePlan } from './domain/validation.js';
|
|
9
9
|
import { WorkflowError } from './errors.js';
|
|
10
10
|
import { bindGraphEvidence, createGraphRefreshRequest, fallbackGraphBinding, inspectGraphBinding, validateGraphRequestCurrent, } from './graph.js';
|
|
11
|
-
import {
|
|
11
|
+
import { inspectLocalDependency } from './diagnostics.js';
|
|
12
|
+
import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, readTaskC1Posture, } from './alpha6/handoff.js';
|
|
12
13
|
import { applyAdoptionPosture, assertAdoptionBaselinePreserved, buildAdoptionPreparation, initializeProjectRegistrationAdoption, readCurrentAdoptionPosture, } from './alpha6/adoption.js';
|
|
13
14
|
import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, readCurrentPlanRiskAudit, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
|
|
14
|
-
import { appendCorrectiveDecisionEvent, appendRemediationEvent, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, findFailedGuardedStepRequiringCorrectiveDecision, readCorrectiveDecisionEvents, readRemediationEvents, } from './alpha6/remediation.js';
|
|
15
|
+
import { appendCorrectiveDecisionEvent, appendRemediationEvent, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, defaultCorrectiveAuditorActor, findFailedGuardedStepRequiringCorrectiveDecision, readCorrectiveDecisionEvents, readRemediationEvents, } from './alpha6/remediation.js';
|
|
15
16
|
import { applyMilestoneScopeChangeTransaction, assertMilestoneMembershipIntegrity, assertMilestoneScopeChangeStatusAllowed, normalizeMilestonePlan, prepareMilestoneScopeChangeCandidate, readMilestoneForScopeChange, readMilestoneWithIntegrity, } from './alpha6/milestone.js';
|
|
16
17
|
import { buildCurrentStrictStepReviewCycle, ensurePendingStrictStepReview, isStepReviewRequired, recordStrictStepReview, } from './alpha6/review.js';
|
|
18
|
+
import { applyMilestoneAutonomyContract, assertAutonomousPlanEvolution, prepareMilestoneAutonomyContract, readMilestoneAutonomyEvents, requireActiveMilestoneAutonomy, } from './alpha7/autonomy.js';
|
|
17
19
|
import { applyKnowledgeSelections, hashKnowledgeMap, inspectKnowledgeMap, normalizeRelativePath, reconcileKnowledgeMap, scanProjectKnowledge, selectKnowledgeSources, } from './memory.js';
|
|
18
20
|
import { canonicalJsonStringify, sha256Hex } from './alpha6/store-sidecars.js';
|
|
19
21
|
import { commitStep, mergeTaskBranch, runChecks, startLocalTaskBranch, syncBaseIntoTask, validateTaskHistory, } from './git.js';
|
|
@@ -182,7 +184,12 @@ export class WorkflowService {
|
|
|
182
184
|
if (!title.trim())
|
|
183
185
|
throw new WorkflowError('INVALID_ARGUMENT', 'Task title is required.');
|
|
184
186
|
if (milestoneId) {
|
|
185
|
-
|
|
187
|
+
// A Milestone is assembled from independently materialized Task discoveries. During
|
|
188
|
+
// that bounded assembly window the reverse-membership set is intentionally incomplete;
|
|
189
|
+
// execution remains unavailable until one complete initial Milestone Plan classifies
|
|
190
|
+
// every linked Task. The scope-change reader preserves every other integrity check while
|
|
191
|
+
// allowing exactly that repairable reverse gap.
|
|
192
|
+
const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
|
|
186
193
|
if (milestone.status === 'accepted' || milestone.status === 'cancelled') {
|
|
187
194
|
throw new WorkflowError('TRANSITION_BLOCKED', `Milestone ${milestoneId} is ${milestone.status}.`);
|
|
188
195
|
}
|
|
@@ -401,6 +408,64 @@ export class WorkflowService {
|
|
|
401
408
|
readTask: (taskId) => this.store.readTask(identity.projectId, taskId),
|
|
402
409
|
});
|
|
403
410
|
}
|
|
411
|
+
prepareMilestoneAutonomy(repository, milestoneId, expectedRevision, principal, delegate, expiresAt) {
|
|
412
|
+
const { identity } = this.context(repository);
|
|
413
|
+
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
414
|
+
assertExpectedRevision(milestone, expectedRevision);
|
|
415
|
+
return prepareMilestoneAutonomyContract({
|
|
416
|
+
projectId: identity.projectId,
|
|
417
|
+
milestone,
|
|
418
|
+
principal,
|
|
419
|
+
delegate,
|
|
420
|
+
expiresAt,
|
|
421
|
+
now: this.now(),
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
grantMilestoneAutonomy(repository, milestoneId, expectedRevision, principal, delegate, expiresAt, confirmationCode) {
|
|
425
|
+
const { identity } = this.context(repository);
|
|
426
|
+
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
427
|
+
assertExpectedRevision(milestone, expectedRevision);
|
|
428
|
+
return applyMilestoneAutonomyContract({
|
|
429
|
+
store: this.store,
|
|
430
|
+
projectId: identity.projectId,
|
|
431
|
+
milestone,
|
|
432
|
+
principal,
|
|
433
|
+
delegate,
|
|
434
|
+
expiresAt,
|
|
435
|
+
confirmationCode,
|
|
436
|
+
now: this.now(),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
evolveMilestonePlanAutonomously(repository, milestoneId, expectedRevision, plan, actor) {
|
|
440
|
+
const { identity } = this.context(repository);
|
|
441
|
+
const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
|
|
442
|
+
assertExpectedRevision(milestone, expectedRevision);
|
|
443
|
+
assertMilestoneScopeChangeStatusAllowed(milestone);
|
|
444
|
+
const autonomy = requireActiveMilestoneAutonomy({
|
|
445
|
+
store: this.store,
|
|
446
|
+
projectId: identity.projectId,
|
|
447
|
+
milestone,
|
|
448
|
+
actor,
|
|
449
|
+
now: this.now(),
|
|
450
|
+
});
|
|
451
|
+
validateMilestonePlan(plan);
|
|
452
|
+
assertAutonomousPlanEvolution(milestone, plan, autonomy.contract);
|
|
453
|
+
const prepared = prepareMilestoneScopeChangeCandidate({
|
|
454
|
+
milestone,
|
|
455
|
+
plan,
|
|
456
|
+
readTask: (taskId) => this.store.readTask(identity.projectId, taskId),
|
|
457
|
+
actor,
|
|
458
|
+
autonomyContractEventHash: autonomy.contract.eventHash,
|
|
459
|
+
});
|
|
460
|
+
return applyMilestoneScopeChangeTransaction({
|
|
461
|
+
store: this.store,
|
|
462
|
+
milestone,
|
|
463
|
+
expectedRevision,
|
|
464
|
+
prepared,
|
|
465
|
+
now: this.now(),
|
|
466
|
+
readTask: (taskId) => this.store.readTask(identity.projectId, taskId),
|
|
467
|
+
});
|
|
468
|
+
}
|
|
404
469
|
authorizeMilestone(repository, milestoneId, expectedRevision, actor, reason = '', delegationGrantId = null) {
|
|
405
470
|
const { identity } = this.context(repository);
|
|
406
471
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
@@ -823,10 +888,10 @@ export class WorkflowService {
|
|
|
823
888
|
throw new WorkflowError('TRANSITION_BLOCKED', 'Automatic Task context refresh requires a previously approved map with content-only drift.', { currentStatus: currentMap.status, inspectedStatus: inspectedMap.status });
|
|
824
889
|
}
|
|
825
890
|
assertContentOnlyKnowledgeRefresh(currentMap, inspectedMap);
|
|
826
|
-
this.delegatedAuthorization(identity.projectId, delegationGrantId, 'project_memory.approve', actor, inspectedMap);
|
|
891
|
+
this.delegatedAuthorization(identity.projectId, delegationGrantId, 'project_memory.approve', actor, inspectedMap, task.id);
|
|
827
892
|
knowledgeMap = this.reconcileKnowledgeMap(repository, currentMap.revision);
|
|
828
893
|
transitions.push('project-memory reconcile');
|
|
829
|
-
knowledgeMap = this.
|
|
894
|
+
knowledgeMap = this.approveKnowledgeMapForTaskRefresh(repository, knowledgeMap.revision, [], actor, delegationGrantId, task.id);
|
|
830
895
|
transitions.push('project-memory approve');
|
|
831
896
|
}
|
|
832
897
|
const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
|
|
@@ -1230,9 +1295,13 @@ export class WorkflowService {
|
|
|
1230
1295
|
taskContext(repository, taskId, role) {
|
|
1231
1296
|
const { identity } = this.context(repository);
|
|
1232
1297
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
1298
|
+
const historicalStrictReview = role === 'independent-reviewer'
|
|
1299
|
+
&& task.steps.some((step) => step.status === 'in_progress' && step.evidence !== null);
|
|
1233
1300
|
const knowledgeMap = task.knowledgeMapRevision === null
|
|
1234
1301
|
? this.requireActiveKnowledgeMap(identity.repositoryRoot, identity.projectId)
|
|
1235
|
-
:
|
|
1302
|
+
: historicalStrictReview
|
|
1303
|
+
? this.strictReviewKnowledgeMap(identity.repositoryRoot, task)
|
|
1304
|
+
: this.assertTaskKnowledgeBinding(identity.repositoryRoot, task);
|
|
1236
1305
|
const root = this.store.taskRoot(task.projectId, task.id);
|
|
1237
1306
|
const planRiskAudit = task.planHash && !['planning', 'merged', 'cancelled'].includes(task.status)
|
|
1238
1307
|
? readCurrentPlanRiskAudit(this.store, task)
|
|
@@ -1263,7 +1332,7 @@ export class WorkflowService {
|
|
|
1263
1332
|
if (role === 'technical-planner') {
|
|
1264
1333
|
return {
|
|
1265
1334
|
...common,
|
|
1266
|
-
objective: `Produce an execution-ready Plan for ${task.title}. Perform an explicit risk review before plan-set: migrations, concurrency, restart/replay, crash windows, and external-provider behavior require failure-specific checks in the original Plan.${planRiskReviewScope}`,
|
|
1335
|
+
objective: `Produce an execution-ready Plan for ${task.title}. Perform an explicit risk review before plan-set: migrations, concurrency, restart/replay, crash windows, and external-provider behavior require failure-specific checks and executable proof obligations in the original Plan.${planRiskReviewScope}`,
|
|
1267
1336
|
resultSchema: {
|
|
1268
1337
|
type: 'object',
|
|
1269
1338
|
required: ['objective', 'requirements', 'acceptance', 'risks', 'steps'],
|
|
@@ -1274,6 +1343,7 @@ export class WorkflowService {
|
|
|
1274
1343
|
'Concurrency or cursor/state ownership is planned without deterministic race checks.',
|
|
1275
1344
|
'Restart, replay, or crash recovery is claimed without restart/crash-window evidence.',
|
|
1276
1345
|
'An external provider is used without failure-injection and hidden-default verification.',
|
|
1346
|
+
'A guarded failure mode lacks a hostile counterexample, expected safe result, or exact executable evidence command.',
|
|
1277
1347
|
'The safe risk slice crosses persistence, recovery orchestration, and executable composition and should be split.',
|
|
1278
1348
|
],
|
|
1279
1349
|
};
|
|
@@ -1398,7 +1468,6 @@ export class WorkflowService {
|
|
|
1398
1468
|
throw new WorkflowError('TRANSITION_BLOCKED', `Strict Step Review cannot run while task ${taskId} is ${task.status}.`);
|
|
1399
1469
|
}
|
|
1400
1470
|
assertTaskBranch(context.identity.repositoryRoot, task);
|
|
1401
|
-
this.assertTaskKnowledgeBinding(context.identity.repositoryRoot, task);
|
|
1402
1471
|
const planRiskAudit = readCurrentPlanRiskAudit(this.store, task);
|
|
1403
1472
|
if (!planRiskAudit || !isStepReviewRequired(task, stepId, planRiskAudit.reviewRequiredStepIds)) {
|
|
1404
1473
|
throw new WorkflowError('TRANSITION_BLOCKED', `Step ${stepId} is not marked for strict review by the current Plan Risk Audit.`, {
|
|
@@ -1414,6 +1483,13 @@ export class WorkflowService {
|
|
|
1414
1483
|
stepStatus: step.status,
|
|
1415
1484
|
});
|
|
1416
1485
|
}
|
|
1486
|
+
// A strict review is bound to the immutable completion commit, Task Plan, and the
|
|
1487
|
+
// Knowledge Map revision that authorized the Step. Project knowledge commonly becomes
|
|
1488
|
+
// stale because the completion commit itself changes a classified source. Requiring the
|
|
1489
|
+
// *current* map here creates a deadlock: review cannot run before rebind, while rebind is
|
|
1490
|
+
// intentionally forbidden until the in-progress Step becomes terminal. The pending
|
|
1491
|
+
// strict-review binding below validates the historical Task context; a current-map rebind
|
|
1492
|
+
// remains mandatory after the review finalizes the Step and before another Step can run.
|
|
1417
1493
|
validateTaskHistory(context.identity.repositoryRoot, task);
|
|
1418
1494
|
if (!isClean(context.identity.repositoryRoot)) {
|
|
1419
1495
|
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Strict Step Review requires a clean Task checkout.', {
|
|
@@ -1845,6 +1921,109 @@ export class WorkflowService {
|
|
|
1845
1921
|
adoption,
|
|
1846
1922
|
};
|
|
1847
1923
|
}
|
|
1924
|
+
alpha6StrictReviewRescuePreflight(repository) {
|
|
1925
|
+
const context = this.context(repository);
|
|
1926
|
+
const dependency = inspectLocalDependency(context.identity.repositoryRoot);
|
|
1927
|
+
const tasks = context.store.listTasks(context.identity.projectId);
|
|
1928
|
+
const leases = context.locks.list();
|
|
1929
|
+
const now = this.now().getTime();
|
|
1930
|
+
const activeLeases = leases.filter((lease) => Date.parse(lease.expiresAt) > now);
|
|
1931
|
+
const staleLeases = leases.filter((lease) => Date.parse(lease.expiresAt) <= now);
|
|
1932
|
+
const dirty = changedFiles(context.identity.repositoryRoot);
|
|
1933
|
+
const currentHead = headCommit(context.identity.repositoryRoot);
|
|
1934
|
+
const pendingCandidates = [];
|
|
1935
|
+
const malformedCandidates = [];
|
|
1936
|
+
for (const task of tasks) {
|
|
1937
|
+
if (task.status !== 'in_progress')
|
|
1938
|
+
continue;
|
|
1939
|
+
const planRiskAudit = task.planHash ? readCurrentPlanRiskAudit(this.store, task) : null;
|
|
1940
|
+
if (!planRiskAudit)
|
|
1941
|
+
continue;
|
|
1942
|
+
for (const step of task.steps) {
|
|
1943
|
+
if (step.status !== 'in_progress' || !step.evidence)
|
|
1944
|
+
continue;
|
|
1945
|
+
if (!isStepReviewRequired(task, step.id, planRiskAudit.reviewRequiredStepIds))
|
|
1946
|
+
continue;
|
|
1947
|
+
try {
|
|
1948
|
+
const cycle = buildCurrentStrictStepReviewCycle(this.store, task, step.id);
|
|
1949
|
+
if (cycle.resolution !== 'pending')
|
|
1950
|
+
continue;
|
|
1951
|
+
validateTaskHistory(context.identity.repositoryRoot, task);
|
|
1952
|
+
let knowledgeBindingStale = false;
|
|
1953
|
+
try {
|
|
1954
|
+
const storedKnowledgeMap = this.store.readKnowledgeMap(context.identity.projectId);
|
|
1955
|
+
const knowledgeMap = inspectKnowledgeMap(storedKnowledgeMap, scanProjectKnowledge(context.identity.repositoryRoot, context.identity.projectId, this.now()));
|
|
1956
|
+
knowledgeBindingStale = knowledgeMap.status !== 'active'
|
|
1957
|
+
|| task.knowledgeMapRevision !== knowledgeMap.revision
|
|
1958
|
+
|| task.knowledgeMapHash !== hashKnowledgeMap(knowledgeMap);
|
|
1959
|
+
}
|
|
1960
|
+
catch {
|
|
1961
|
+
knowledgeBindingStale = true;
|
|
1962
|
+
}
|
|
1963
|
+
pendingCandidates.push({
|
|
1964
|
+
taskId: task.id,
|
|
1965
|
+
taskRevision: task.revision,
|
|
1966
|
+
stepId: step.id,
|
|
1967
|
+
completionCommit: step.evidence.commitSha,
|
|
1968
|
+
currentHead,
|
|
1969
|
+
knowledgeBindingStale,
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
catch (error) {
|
|
1973
|
+
malformedCandidates.push(error instanceof Error ? error.message : String(error));
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
const candidate = pendingCandidates.length === 1 ? pendingCandidates[0] : null;
|
|
1978
|
+
const candidateLeaseIds = new Set(candidate ? [candidate.taskId] : []);
|
|
1979
|
+
const blockers = [
|
|
1980
|
+
...(dependency.declared !== '2.0.0-alpha.6'
|
|
1981
|
+
? ['Project package.json must declare codex-workflow-v2 exactly at 2.0.0-alpha.6.']
|
|
1982
|
+
: []),
|
|
1983
|
+
...(dirty.length > 0 ? ['Repository checkout is not clean.'] : []),
|
|
1984
|
+
...(activeLeases.length > 0 ? ['At least one writer lease is active.'] : []),
|
|
1985
|
+
...(pendingCandidates.length !== 1
|
|
1986
|
+
? [`Expected exactly one pending alpha.6 strict review; found ${pendingCandidates.length}.`]
|
|
1987
|
+
: []),
|
|
1988
|
+
...(candidate && candidate.currentHead !== candidate.completionCommit
|
|
1989
|
+
? ['HEAD does not match the pending strict review completion commit.']
|
|
1990
|
+
: []),
|
|
1991
|
+
...(staleLeases.some((lease) => !candidateLeaseIds.has(lease.entityId))
|
|
1992
|
+
? ['A stale writer lease exists outside the rescue Task.']
|
|
1993
|
+
: []),
|
|
1994
|
+
...malformedCandidates.map((message) => `Pending strict review state is invalid: ${message}`),
|
|
1995
|
+
];
|
|
1996
|
+
const eligible = blockers.length === 0 && candidate !== null;
|
|
1997
|
+
const requiredActions = eligible
|
|
1998
|
+
? [
|
|
1999
|
+
...staleLeases.map((lease) => ({
|
|
2000
|
+
action: 'locks repair',
|
|
2001
|
+
entityId: lease.entityId,
|
|
2002
|
+
})),
|
|
2003
|
+
{
|
|
2004
|
+
action: 'task step-review',
|
|
2005
|
+
taskId: candidate.taskId,
|
|
2006
|
+
stepId: candidate.stepId,
|
|
2007
|
+
expectedRevision: candidate.taskRevision,
|
|
2008
|
+
},
|
|
2009
|
+
]
|
|
2010
|
+
: [];
|
|
2011
|
+
return {
|
|
2012
|
+
eligible,
|
|
2013
|
+
mutationFree: true,
|
|
2014
|
+
runnerPackageVersion: PACKAGE_VERSION,
|
|
2015
|
+
requiredProjectPackageVersion: '2.0.0-alpha.6',
|
|
2016
|
+
declaredProjectPackageVersion: dependency.declared,
|
|
2017
|
+
projectId: context.identity.projectId,
|
|
2018
|
+
repositoryRoot: context.identity.repositoryRoot,
|
|
2019
|
+
clean: dirty.length === 0,
|
|
2020
|
+
activeLeases,
|
|
2021
|
+
staleLeases,
|
|
2022
|
+
candidate,
|
|
2023
|
+
blockers,
|
|
2024
|
+
requiredActions,
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
1848
2027
|
scanKnowledgeMap(repository) {
|
|
1849
2028
|
const { identity } = this.context(repository);
|
|
1850
2029
|
const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
|
|
@@ -1869,11 +2048,14 @@ export class WorkflowService {
|
|
|
1869
2048
|
return inspectKnowledgeMap(current, scanned);
|
|
1870
2049
|
}
|
|
1871
2050
|
approveKnowledgeMap(repository, expectedRevision, selections, actor, delegationGrantId = null) {
|
|
2051
|
+
return this.approveKnowledgeMapForTaskRefresh(repository, expectedRevision, selections, actor, delegationGrantId, null);
|
|
2052
|
+
}
|
|
2053
|
+
approveKnowledgeMapForTaskRefresh(repository, expectedRevision, selections, actor, delegationGrantId, autonomyTaskId) {
|
|
1872
2054
|
const { identity } = this.context(repository);
|
|
1873
2055
|
const current = this.store.readKnowledgeMap(identity.projectId);
|
|
1874
2056
|
assertExpectedRevision(current, expectedRevision);
|
|
1875
2057
|
const inspected = inspectKnowledgeMap(current, scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now()));
|
|
1876
|
-
const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'project_memory.approve', actor, inspected);
|
|
2058
|
+
const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'project_memory.approve', actor, inspected, autonomyTaskId);
|
|
1877
2059
|
const approved = applyKnowledgeSelections(inspected, selections, actor, this.now());
|
|
1878
2060
|
if (!approved.approval) {
|
|
1879
2061
|
throw new WorkflowError('STATE_CORRUPT', 'Knowledge Map approval was not recorded.');
|
|
@@ -1942,11 +2124,20 @@ export class WorkflowService {
|
|
|
1942
2124
|
const milestonesById = new Map(status.milestones.map((milestone) => [milestone.id, milestone]));
|
|
1943
2125
|
const activeTask = status.tasks.find((task) => taskIsActionableFromRepositoryNext(task, milestonesById));
|
|
1944
2126
|
if (activeTask) {
|
|
2127
|
+
const activePlanRiskAudit = activeTask.planHash
|
|
2128
|
+
? readCurrentPlanRiskAudit(this.store, activeTask)
|
|
2129
|
+
: null;
|
|
2130
|
+
const taskNext = activeTask.status === 'planning'
|
|
2131
|
+
? null
|
|
2132
|
+
: this.nextForTaskWithDelegation(status.projectId, activeTask, activePlanRiskAudit);
|
|
2133
|
+
// Pending strict review is completion work for the already-recorded commit. It must
|
|
2134
|
+
// outrank Project Knowledge refresh/rebind; otherwise the active Step makes the rebind
|
|
2135
|
+
// returned by `next` impossible to execute.
|
|
2136
|
+
if (taskNext?.action === 'task step-review') {
|
|
2137
|
+
return withAdoption(taskNext);
|
|
2138
|
+
}
|
|
1945
2139
|
if (!taskCanRebindKnowledge(activeTask) && activeTask.status !== 'planning') {
|
|
1946
|
-
|
|
1947
|
-
? readCurrentPlanRiskAudit(this.store, activeTask)
|
|
1948
|
-
: null;
|
|
1949
|
-
return withAdoption(this.nextForTaskWithDelegation(status.projectId, activeTask, planRiskAudit));
|
|
2140
|
+
return withAdoption(taskNext);
|
|
1950
2141
|
}
|
|
1951
2142
|
const knowledgeMap = this.knowledgeMapStatus(repository);
|
|
1952
2143
|
if (knowledgeMap.status !== 'active' || !knowledgeMap.approval) {
|
|
@@ -1994,10 +2185,7 @@ export class WorkflowService {
|
|
|
1994
2185
|
knowledgeMapHash,
|
|
1995
2186
|
});
|
|
1996
2187
|
}
|
|
1997
|
-
|
|
1998
|
-
? readCurrentPlanRiskAudit(this.store, activeTask)
|
|
1999
|
-
: null;
|
|
2000
|
-
return withAdoption(this.nextForTaskWithDelegation(status.projectId, activeTask, planRiskAudit));
|
|
2188
|
+
return withAdoption(taskNext ?? this.nextForTaskWithDelegation(status.projectId, activeTask, null));
|
|
2001
2189
|
}
|
|
2002
2190
|
const activeMilestone = status.milestones.find((milestone) => !['accepted', 'cancelled'].includes(milestone.status));
|
|
2003
2191
|
if (activeMilestone) {
|
|
@@ -2082,7 +2270,7 @@ export class WorkflowService {
|
|
|
2082
2270
|
}
|
|
2083
2271
|
return withAdoption({ scope: 'repository', status: 'idle', action: 'discovery start' });
|
|
2084
2272
|
}
|
|
2085
|
-
delegatedAuthorization(projectId, grantId, transition, actor, target) {
|
|
2273
|
+
delegatedAuthorization(projectId, grantId, transition, actor, target, autonomyTaskId = null) {
|
|
2086
2274
|
if (!grantId) {
|
|
2087
2275
|
if (actor.trim().startsWith('agent:')) {
|
|
2088
2276
|
throw new WorkflowError('TRANSITION_BLOCKED', 'An agent actor requires an explicit delegated approval grant.');
|
|
@@ -2090,6 +2278,52 @@ export class WorkflowService {
|
|
|
2090
2278
|
return null;
|
|
2091
2279
|
}
|
|
2092
2280
|
const grant = this.store.readDelegation(projectId, grantId);
|
|
2281
|
+
if (grant.scope.kind === 'milestone') {
|
|
2282
|
+
const autonomyContract = readMilestoneAutonomyEvents(this.store, projectId, grant.scope.id)
|
|
2283
|
+
.find((event) => event.delegationGrantId === grant.id) ?? null;
|
|
2284
|
+
if (autonomyContract) {
|
|
2285
|
+
const contextTask = target.kind === 'knowledge-map' && autonomyTaskId
|
|
2286
|
+
? this.store.readTask(projectId, autonomyTaskId)
|
|
2287
|
+
: null;
|
|
2288
|
+
const milestone = target.kind === 'milestone'
|
|
2289
|
+
? target
|
|
2290
|
+
: target.kind === 'task' && target.milestoneId === grant.scope.id
|
|
2291
|
+
? this.store.readMilestone(projectId, grant.scope.id)
|
|
2292
|
+
: contextTask?.milestoneId === grant.scope.id
|
|
2293
|
+
? this.store.readMilestone(projectId, grant.scope.id)
|
|
2294
|
+
: null;
|
|
2295
|
+
if (!milestone) {
|
|
2296
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone autonomy grant cannot authorize this target.');
|
|
2297
|
+
}
|
|
2298
|
+
const activeAutonomy = requireActiveMilestoneAutonomy({
|
|
2299
|
+
store: this.store,
|
|
2300
|
+
projectId,
|
|
2301
|
+
milestone,
|
|
2302
|
+
actor,
|
|
2303
|
+
now: this.now(),
|
|
2304
|
+
});
|
|
2305
|
+
if (activeAutonomy.grant.id !== grant.id) {
|
|
2306
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone autonomy grant has been superseded.');
|
|
2307
|
+
}
|
|
2308
|
+
if (target.kind === 'knowledge-map') {
|
|
2309
|
+
if (transition !== 'project_memory.approve'
|
|
2310
|
+
|| !contextTask
|
|
2311
|
+
|| !taskCanRebindKnowledge(contextTask)) {
|
|
2312
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone autonomy may approve Project Memory only inside a content-only Task context refresh.');
|
|
2313
|
+
}
|
|
2314
|
+
if (!grant.transitions.includes(transition)) {
|
|
2315
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Delegation ${grant.id} does not allow ${transition}.`);
|
|
2316
|
+
}
|
|
2317
|
+
return {
|
|
2318
|
+
grantId: grant.id,
|
|
2319
|
+
policyHash: grant.policyHash,
|
|
2320
|
+
principal: grant.principal,
|
|
2321
|
+
delegate: grant.delegate,
|
|
2322
|
+
transition,
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2093
2327
|
return assertDelegationCanAuthorize(grant, transition, actor, target, this.now());
|
|
2094
2328
|
}
|
|
2095
2329
|
assertHandoffGrant(projectId, task, targetActor, grantId) {
|
|
@@ -2121,6 +2355,23 @@ export class WorkflowService {
|
|
|
2121
2355
|
grantExpiresAt: grant.expiresAt,
|
|
2122
2356
|
});
|
|
2123
2357
|
}
|
|
2358
|
+
if (grant.scope.kind === 'milestone') {
|
|
2359
|
+
const autonomyContract = readMilestoneAutonomyEvents(this.store, projectId, grant.scope.id)
|
|
2360
|
+
.find((event) => event.delegationGrantId === grant.id) ?? null;
|
|
2361
|
+
if (autonomyContract) {
|
|
2362
|
+
const milestone = this.store.readMilestone(projectId, grant.scope.id);
|
|
2363
|
+
const activeAutonomy = requireActiveMilestoneAutonomy({
|
|
2364
|
+
store: this.store,
|
|
2365
|
+
projectId,
|
|
2366
|
+
milestone,
|
|
2367
|
+
actor: targetActor,
|
|
2368
|
+
now: this.now(),
|
|
2369
|
+
});
|
|
2370
|
+
if (activeAutonomy.grant.id !== grant.id) {
|
|
2371
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone autonomy grant has been superseded.');
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2124
2375
|
}
|
|
2125
2376
|
nextForTaskWithDelegation(projectId, task, planRiskAudit = null) {
|
|
2126
2377
|
const transition = task.status === 'awaiting_execution_authorization'
|
|
@@ -2132,6 +2383,9 @@ export class WorkflowService {
|
|
|
2132
2383
|
? this.delegatedApprovalOptions(projectId, transition, task)
|
|
2133
2384
|
: [];
|
|
2134
2385
|
const failedReviewAttempts = countFailedReviewAttempts(this.store.taskRoot(projectId, task.id));
|
|
2386
|
+
const guardedCorrectiveGate = task.status === 'needs_fix' && planRiskAudit
|
|
2387
|
+
? findFailedGuardedStepRequiringCorrectiveDecision(this.store, task, planRiskAudit)
|
|
2388
|
+
: null;
|
|
2135
2389
|
const next = nextForTask(task);
|
|
2136
2390
|
const handoffPosture = readTaskC1Posture(this.store, task);
|
|
2137
2391
|
const strictStepReviewStep = planRiskAudit
|
|
@@ -2163,7 +2417,17 @@ export class WorkflowService {
|
|
|
2163
2417
|
requiredActor: handoffPosture.claimant,
|
|
2164
2418
|
},
|
|
2165
2419
|
}
|
|
2166
|
-
:
|
|
2420
|
+
: task.milestoneId && next.action === 'task start'
|
|
2421
|
+
? {
|
|
2422
|
+
action: 'task handoff-prepare',
|
|
2423
|
+
blockedAction: next.action,
|
|
2424
|
+
c1Handoff: {
|
|
2425
|
+
state: 'required',
|
|
2426
|
+
recommendedTargetActor: defaultTaskWorkerActor(task.id),
|
|
2427
|
+
targetActorDerived: true,
|
|
2428
|
+
},
|
|
2429
|
+
}
|
|
2430
|
+
: {};
|
|
2167
2431
|
const result = {
|
|
2168
2432
|
...next,
|
|
2169
2433
|
...handoffOverlay,
|
|
@@ -2204,6 +2468,21 @@ export class WorkflowService {
|
|
|
2204
2468
|
},
|
|
2205
2469
|
}
|
|
2206
2470
|
: {}),
|
|
2471
|
+
...(guardedCorrectiveGate
|
|
2472
|
+
? {
|
|
2473
|
+
action: 'task corrective-decision',
|
|
2474
|
+
stepId: guardedCorrectiveGate.stepId,
|
|
2475
|
+
correctiveDecisionGate: {
|
|
2476
|
+
attemptCount: guardedCorrectiveGate.attemptCount,
|
|
2477
|
+
recommendedAuditor: defaultCorrectiveAuditorActor(task.id),
|
|
2478
|
+
auditorDerived: true,
|
|
2479
|
+
auditorMustDifferFromLatestStrictReviewer: true,
|
|
2480
|
+
allowedDecision: 'continue-fix',
|
|
2481
|
+
escalationDecisions: ['replan-required', 'split-required', 'stop-escalate'],
|
|
2482
|
+
humanApprovalRequired: false,
|
|
2483
|
+
},
|
|
2484
|
+
}
|
|
2485
|
+
: {}),
|
|
2207
2486
|
...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
|
|
2208
2487
|
};
|
|
2209
2488
|
if (handoffPosture.state === 'pending') {
|
|
@@ -2217,7 +2496,16 @@ export class WorkflowService {
|
|
|
2217
2496
|
}
|
|
2218
2497
|
delegatedApprovalOptions(projectId, transition, target) {
|
|
2219
2498
|
return this.store.listDelegations(projectId)
|
|
2220
|
-
.filter((grant) =>
|
|
2499
|
+
.filter((grant) => {
|
|
2500
|
+
try {
|
|
2501
|
+
return Boolean(this.delegatedAuthorization(projectId, grant.id, transition, grant.delegate, target));
|
|
2502
|
+
}
|
|
2503
|
+
catch (error) {
|
|
2504
|
+
if (error instanceof WorkflowError && error.code === 'TRANSITION_BLOCKED')
|
|
2505
|
+
return false;
|
|
2506
|
+
throw error;
|
|
2507
|
+
}
|
|
2508
|
+
})
|
|
2221
2509
|
.map((grant) => ({
|
|
2222
2510
|
grantId: grant.id,
|
|
2223
2511
|
principal: grant.principal,
|
|
@@ -2230,8 +2518,17 @@ export class WorkflowService {
|
|
|
2230
2518
|
}
|
|
2231
2519
|
delegatedContextRefreshOptions(projectId, task, knowledgeMap) {
|
|
2232
2520
|
return this.store.listDelegations(projectId)
|
|
2233
|
-
.filter((grant) =>
|
|
2234
|
-
|
|
2521
|
+
.filter((grant) => {
|
|
2522
|
+
try {
|
|
2523
|
+
return Boolean(this.delegatedAuthorization(projectId, grant.id, 'project_memory.approve', grant.delegate, knowledgeMap, task.id)
|
|
2524
|
+
&& this.delegatedAuthorization(projectId, grant.id, 'task.execution_authorize', grant.delegate, task));
|
|
2525
|
+
}
|
|
2526
|
+
catch (error) {
|
|
2527
|
+
if (error instanceof WorkflowError && error.code === 'TRANSITION_BLOCKED')
|
|
2528
|
+
return false;
|
|
2529
|
+
throw error;
|
|
2530
|
+
}
|
|
2531
|
+
})
|
|
2235
2532
|
.map((grant) => ({
|
|
2236
2533
|
grantId: grant.id,
|
|
2237
2534
|
principal: grant.principal,
|
|
@@ -2371,6 +2668,28 @@ export class WorkflowService {
|
|
|
2371
2668
|
}
|
|
2372
2669
|
return inspected;
|
|
2373
2670
|
}
|
|
2671
|
+
strictReviewKnowledgeMap(repositoryRoot, task) {
|
|
2672
|
+
const stored = this.store.readKnowledgeMap(task.projectId);
|
|
2673
|
+
const inspected = inspectKnowledgeMap(stored, scanProjectKnowledge(repositoryRoot, task.projectId, this.now()));
|
|
2674
|
+
if (inspected.status === 'active' && inspected.approval)
|
|
2675
|
+
return inspected;
|
|
2676
|
+
const storedHash = hashKnowledgeMap(stored);
|
|
2677
|
+
if (stored.status === 'active'
|
|
2678
|
+
&& stored.approval
|
|
2679
|
+
&& task.knowledgeMapRevision === stored.revision
|
|
2680
|
+
&& task.knowledgeMapHash === storedHash
|
|
2681
|
+
&& stored.approval.mapHash === storedHash) {
|
|
2682
|
+
return stored;
|
|
2683
|
+
}
|
|
2684
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Strict Step Review has neither a current active Knowledge Map nor its exact historical Task binding.', {
|
|
2685
|
+
taskId: task.id,
|
|
2686
|
+
inspectedStatus: inspected.status,
|
|
2687
|
+
taskKnowledgeMapRevision: task.knowledgeMapRevision,
|
|
2688
|
+
storedKnowledgeMapRevision: stored.revision,
|
|
2689
|
+
taskKnowledgeMapHash: task.knowledgeMapHash,
|
|
2690
|
+
storedKnowledgeMapHash: storedHash,
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2374
2693
|
inspectAdoptionPreparation(identity, tasks, milestones) {
|
|
2375
2694
|
const locks = new WriterLockManager(this.store.lockRoot(identity.projectId), this.now);
|
|
2376
2695
|
const currentPosture = readCurrentAdoptionPosture(this.store, identity.projectId);
|