codex-workflow-v2 2.0.0-alpha.7 → 2.0.0-alpha.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/alpha6/remediation.d.ts +3 -0
- package/dist/src/alpha6/remediation.js +70 -18
- package/dist/src/alpha6/remediation.js.map +1 -1
- package/dist/src/contracts.d.ts +2 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/src/workflow.d.ts +1 -0
- package/dist/src/workflow.js +238 -55
- package/dist/src/workflow.js.map +1 -1
- package/docs/alpha7.1-implementation-brief.md +268 -0
- package/docs/autonomy-guardrails.md +29 -14
- package/package.json +1 -1
- package/plugins/codex-workflow-gateway/references/protocol.md +13 -4
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +16 -4
- package/references/state-machine.md +17 -6
package/dist/src/workflow.js
CHANGED
|
@@ -12,7 +12,7 @@ import { inspectLocalDependency } from './diagnostics.js';
|
|
|
12
12
|
import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, readTaskC1Posture, } from './alpha6/handoff.js';
|
|
13
13
|
import { applyAdoptionPosture, assertAdoptionBaselinePreserved, buildAdoptionPreparation, initializeProjectRegistrationAdoption, readCurrentAdoptionPosture, } from './alpha6/adoption.js';
|
|
14
14
|
import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, readCurrentPlanRiskAudit, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
|
|
15
|
-
import { appendCorrectiveDecisionEvent, appendRemediationEvent, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, defaultCorrectiveAuditorActor, findFailedGuardedStepRequiringCorrectiveDecision, readCorrectiveDecisionEvents, readRemediationEvents, } from './alpha6/remediation.js';
|
|
15
|
+
import { appendCorrectiveDecisionEvent, appendRemediationEvent, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, defaultCorrectiveAuditorActor, findCurrentGuardedRemediationPosture, findFailedGuardedStepRequiringCorrectiveDecision, readGuardedRemediationPostureForStep, readCorrectiveDecisionEvents, readRemediationEvents, } from './alpha6/remediation.js';
|
|
16
16
|
import { applyMilestoneScopeChangeTransaction, assertMilestoneMembershipIntegrity, assertMilestoneScopeChangeStatusAllowed, normalizeMilestonePlan, prepareMilestoneScopeChangeCandidate, readMilestoneForScopeChange, readMilestoneWithIntegrity, } from './alpha6/milestone.js';
|
|
17
17
|
import { buildCurrentStrictStepReviewCycle, ensurePendingStrictStepReview, isStepReviewRequired, recordStrictStepReview, } from './alpha6/review.js';
|
|
18
18
|
import { applyMilestoneAutonomyContract, assertAutonomousPlanEvolution, prepareMilestoneAutonomyContract, readMilestoneAutonomyEvents, requireActiveMilestoneAutonomy, } from './alpha7/autonomy.js';
|
|
@@ -879,15 +879,25 @@ export class WorkflowService {
|
|
|
879
879
|
const inspectedMap = inspectKnowledgeMap(currentMap, scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now()));
|
|
880
880
|
const transitions = [];
|
|
881
881
|
const currentPlanRiskAudit = readCurrentPlanRiskAudit(this.store, task);
|
|
882
|
+
const currentPlanArtifactHash = task.planHash
|
|
883
|
+
? this.store.hashArtifact(this.store.taskRoot(identity.projectId, task.id), 'plan.md')
|
|
884
|
+
: null;
|
|
885
|
+
const refreshAssessment = assessDelegatedKnowledgeRefresh(currentMap, inspectedMap, task, currentPlanArtifactHash);
|
|
882
886
|
// Validate every delegated transition before the first write so a narrow grant cannot
|
|
883
887
|
// leave the composite operation half-applied.
|
|
884
888
|
this.delegatedAuthorization(identity.projectId, delegationGrantId, 'task.execution_authorize', actor, task);
|
|
885
889
|
let knowledgeMap = currentMap;
|
|
886
890
|
if (inspectedMap.status !== 'active' || !inspectedMap.approval) {
|
|
887
|
-
if (currentMap.status !== 'active' || !currentMap.approval
|
|
888
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Automatic Task context refresh requires a previously approved map with
|
|
891
|
+
if (currentMap.status !== 'active' || !currentMap.approval) {
|
|
892
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Automatic Task context refresh requires a previously approved map with safe delegated drift.', { currentStatus: currentMap.status, inspectedStatus: inspectedMap.status });
|
|
893
|
+
}
|
|
894
|
+
assertDelegatedKnowledgeRefresh(refreshAssessment);
|
|
895
|
+
if (inspectedMap.status !== 'stale') {
|
|
896
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Safe delegated Knowledge drift must reconcile to a stale candidate before approval.', { currentStatus: currentMap.status, inspectedStatus: inspectedMap.status });
|
|
897
|
+
}
|
|
898
|
+
if (refreshAssessment.mode === 'delegated-plan-bounded-supporting-source') {
|
|
899
|
+
this.assertPlanBoundedSupportingSourceGrant(identity.projectId, delegationGrantId, actor, task);
|
|
889
900
|
}
|
|
890
|
-
assertContentOnlyKnowledgeRefresh(currentMap, inspectedMap);
|
|
891
901
|
this.delegatedAuthorization(identity.projectId, delegationGrantId, 'project_memory.approve', actor, inspectedMap, task.id);
|
|
892
902
|
knowledgeMap = this.reconcileKnowledgeMap(repository, currentMap.revision);
|
|
893
903
|
transitions.push('project-memory reconcile');
|
|
@@ -904,11 +914,14 @@ export class WorkflowService {
|
|
|
904
914
|
appendReboundPlanRiskAudit(this.store, task, refreshedTask, currentPlanRiskAudit, this.now());
|
|
905
915
|
}
|
|
906
916
|
transitions.push('task knowledge-rebind');
|
|
907
|
-
refreshedTask = this.authorizeTask(repository, taskId, refreshedTask.revision, actor,
|
|
917
|
+
refreshedTask = this.authorizeTask(repository, taskId, refreshedTask.revision, actor, refreshAssessment.mode === 'delegated-plan-bounded-supporting-source'
|
|
918
|
+
? 'Delegated Plan-bounded supporting-source context refresh.'
|
|
919
|
+
: 'Delegated content-only context refresh.', delegationGrantId);
|
|
908
920
|
transitions.push('task execution authorize');
|
|
909
921
|
return {
|
|
910
|
-
mode:
|
|
922
|
+
mode: refreshAssessment.mode,
|
|
911
923
|
transitions,
|
|
924
|
+
addedSources: refreshAssessment.addedSources,
|
|
912
925
|
knowledgeMap,
|
|
913
926
|
task: refreshedTask,
|
|
914
927
|
};
|
|
@@ -1643,7 +1656,7 @@ export class WorkflowService {
|
|
|
1643
1656
|
assertExpectedRevision(task, expectedRevision);
|
|
1644
1657
|
this.proveOptionalWriterLeaseToken(this.context(repository), taskId, writerToken);
|
|
1645
1658
|
this.assertTaskMutationAllowedByHandoff(task, actor ?? null, writerToken);
|
|
1646
|
-
if (
|
|
1659
|
+
if (!['ready', 'needs_fix', 'blocked'].includes(task.status)) {
|
|
1647
1660
|
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} cannot record a corrective decision while ${task.status}.`);
|
|
1648
1661
|
}
|
|
1649
1662
|
const planRiskAudit = readCurrentPlanRiskAudit(this.store, task);
|
|
@@ -1659,32 +1672,25 @@ export class WorkflowService {
|
|
|
1659
1672
|
stepId,
|
|
1660
1673
|
});
|
|
1661
1674
|
}
|
|
1662
|
-
const step = requireStep(task, stepId);
|
|
1663
1675
|
if (!isStepReviewRequired(task, stepId, planRiskAudit.reviewRequiredStepIds)) {
|
|
1664
1676
|
throw new WorkflowError('TRANSITION_BLOCKED', `Step ${stepId} is not guarded by the current Plan Risk Audit and cannot receive a corrective decision.`, {
|
|
1665
1677
|
taskId,
|
|
1666
1678
|
stepId,
|
|
1667
1679
|
});
|
|
1668
1680
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
taskId,
|
|
1683
|
-
stepId,
|
|
1684
|
-
stepStatus: step.status,
|
|
1685
|
-
resolution: currentCycle.resolution,
|
|
1686
|
-
});
|
|
1687
|
-
}
|
|
1681
|
+
const posture = readGuardedRemediationPostureForStep(this.store, task, stepId, planRiskAudit);
|
|
1682
|
+
if (!posture) {
|
|
1683
|
+
const step = requireStep(task, stepId);
|
|
1684
|
+
const currentCycle = step.status === 'in_progress' && step.evidence
|
|
1685
|
+
? buildCurrentStrictStepReviewCycle(this.store, task, stepId).resolution
|
|
1686
|
+
: null;
|
|
1687
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Corrective decision for ${stepId} requires a failed guarded Step or a blocked unverified strict-review retry posture.`, {
|
|
1688
|
+
taskId,
|
|
1689
|
+
stepId,
|
|
1690
|
+
taskStatus: task.status,
|
|
1691
|
+
stepStatus: step.status,
|
|
1692
|
+
resolution: currentCycle,
|
|
1693
|
+
});
|
|
1688
1694
|
}
|
|
1689
1695
|
assertCorrectiveAuditorIndependence(this.store, task, stepId, correctiveAudit.auditor);
|
|
1690
1696
|
return appendCorrectiveDecisionEvent(this.store, task, stepId, correctiveAudit, planRiskAudit, task.planHash, this.now());
|
|
@@ -2146,11 +2152,14 @@ export class WorkflowService {
|
|
|
2146
2152
|
? this.delegatedApprovalOptions(status.projectId, 'project_memory.approve', knowledgeMap)
|
|
2147
2153
|
: [];
|
|
2148
2154
|
const storedKnowledgeMap = this.store.readKnowledgeMap(status.projectId);
|
|
2149
|
-
const
|
|
2150
|
-
&& storedKnowledgeMap.status === 'active'
|
|
2155
|
+
const refreshAssessment = storedKnowledgeMap.status === 'active'
|
|
2151
2156
|
&& storedKnowledgeMap.approval
|
|
2152
|
-
|
|
2153
|
-
|
|
2157
|
+
? assessDelegatedKnowledgeRefresh(storedKnowledgeMap, knowledgeMap, activeTask, activeTask.planHash
|
|
2158
|
+
? this.store.hashArtifact(this.store.taskRoot(status.projectId, activeTask.id), 'plan.md')
|
|
2159
|
+
: null)
|
|
2160
|
+
: null;
|
|
2161
|
+
const contextRefreshOptions = action === 'project-memory reconcile' && refreshAssessment?.eligible
|
|
2162
|
+
? this.delegatedContextRefreshOptions(status.projectId, activeTask, knowledgeMap, refreshAssessment.mode)
|
|
2154
2163
|
: [];
|
|
2155
2164
|
return withAdoption({
|
|
2156
2165
|
scope: 'project-memory',
|
|
@@ -2163,11 +2172,21 @@ export class WorkflowService {
|
|
|
2163
2172
|
? {
|
|
2164
2173
|
contextRefresh: {
|
|
2165
2174
|
action: 'task context-refresh',
|
|
2166
|
-
mode:
|
|
2175
|
+
mode: refreshAssessment.mode,
|
|
2176
|
+
addedSources: refreshAssessment.addedSources,
|
|
2167
2177
|
options: contextRefreshOptions,
|
|
2168
2178
|
},
|
|
2169
2179
|
}
|
|
2170
2180
|
: {}),
|
|
2181
|
+
...(refreshAssessment && !refreshAssessment.eligible
|
|
2182
|
+
? {
|
|
2183
|
+
contextRefreshBlocked: {
|
|
2184
|
+
reason: 'Knowledge drift is outside the atomic delegated refresh predicate.',
|
|
2185
|
+
unsafeDifferences: refreshAssessment.unsafeDifferences,
|
|
2186
|
+
requiredAction: 'Inspect and approve the Knowledge Map through the ordinary human flow.',
|
|
2187
|
+
},
|
|
2188
|
+
}
|
|
2189
|
+
: {}),
|
|
2171
2190
|
});
|
|
2172
2191
|
}
|
|
2173
2192
|
const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
|
|
@@ -2383,8 +2402,8 @@ export class WorkflowService {
|
|
|
2383
2402
|
? this.delegatedApprovalOptions(projectId, transition, task)
|
|
2384
2403
|
: [];
|
|
2385
2404
|
const failedReviewAttempts = countFailedReviewAttempts(this.store.taskRoot(projectId, task.id));
|
|
2386
|
-
const
|
|
2387
|
-
?
|
|
2405
|
+
const guardedCorrectivePosture = planRiskAudit
|
|
2406
|
+
? findCurrentGuardedRemediationPosture(this.store, task, planRiskAudit)
|
|
2388
2407
|
: null;
|
|
2389
2408
|
const next = nextForTask(task);
|
|
2390
2409
|
const handoffPosture = readTaskC1Posture(this.store, task);
|
|
@@ -2428,6 +2447,68 @@ export class WorkflowService {
|
|
|
2428
2447
|
},
|
|
2429
2448
|
}
|
|
2430
2449
|
: {};
|
|
2450
|
+
let guardedCorrectiveOverlay = {};
|
|
2451
|
+
if (!strictStepReviewStep && guardedCorrectivePosture) {
|
|
2452
|
+
switch (guardedCorrectivePosture.posture) {
|
|
2453
|
+
case 'decision-required':
|
|
2454
|
+
guardedCorrectiveOverlay = {
|
|
2455
|
+
action: 'task corrective-decision',
|
|
2456
|
+
stepId: guardedCorrectivePosture.stepId,
|
|
2457
|
+
correctiveDecisionGate: {
|
|
2458
|
+
attemptCount: guardedCorrectivePosture.attemptCount,
|
|
2459
|
+
recommendedAuditor: defaultCorrectiveAuditorActor(task.id),
|
|
2460
|
+
auditorDerived: true,
|
|
2461
|
+
auditorMustDifferFromLatestStrictReviewer: true,
|
|
2462
|
+
allowedDecision: 'continue-fix',
|
|
2463
|
+
escalationDecisions: ['replan-required', 'split-required', 'stop-escalate'],
|
|
2464
|
+
humanApprovalRequired: false,
|
|
2465
|
+
},
|
|
2466
|
+
};
|
|
2467
|
+
break;
|
|
2468
|
+
case 'hard-stop':
|
|
2469
|
+
guardedCorrectiveOverlay = {
|
|
2470
|
+
action: 'task corrective-decision',
|
|
2471
|
+
stepId: guardedCorrectivePosture.stepId,
|
|
2472
|
+
correctiveDecisionGate: {
|
|
2473
|
+
attemptCount: guardedCorrectivePosture.attemptCount,
|
|
2474
|
+
recommendedAuditor: defaultCorrectiveAuditorActor(task.id),
|
|
2475
|
+
auditorDerived: true,
|
|
2476
|
+
auditorMustDifferFromLatestStrictReviewer: true,
|
|
2477
|
+
allowedDecisions: ['split-required', 'stop-escalate'],
|
|
2478
|
+
hardStop: true,
|
|
2479
|
+
humanApprovalRequired: false,
|
|
2480
|
+
},
|
|
2481
|
+
};
|
|
2482
|
+
break;
|
|
2483
|
+
case 'replan-required':
|
|
2484
|
+
guardedCorrectiveOverlay = {
|
|
2485
|
+
action: 'task plan-set',
|
|
2486
|
+
stepId: guardedCorrectivePosture.stepId,
|
|
2487
|
+
correctiveDecisionGate: {
|
|
2488
|
+
attemptCount: guardedCorrectivePosture.attemptCount,
|
|
2489
|
+
decision: guardedCorrectivePosture.correctiveDecision?.decision ?? null,
|
|
2490
|
+
decisionEventId: guardedCorrectivePosture.correctiveDecision?.eventId ?? null,
|
|
2491
|
+
requiredAction: 'Record an actual new Plan posture before another remediation run.',
|
|
2492
|
+
},
|
|
2493
|
+
};
|
|
2494
|
+
break;
|
|
2495
|
+
case 'split-required':
|
|
2496
|
+
case 'stop-escalate':
|
|
2497
|
+
guardedCorrectiveOverlay = {
|
|
2498
|
+
action: 'doctor',
|
|
2499
|
+
stepId: guardedCorrectivePosture.stepId,
|
|
2500
|
+
correctiveDecisionGate: {
|
|
2501
|
+
attemptCount: guardedCorrectivePosture.attemptCount,
|
|
2502
|
+
decision: guardedCorrectivePosture.correctiveDecision?.decision ?? null,
|
|
2503
|
+
decisionEventId: guardedCorrectivePosture.correctiveDecision?.eventId ?? null,
|
|
2504
|
+
humanApprovalRequired: false,
|
|
2505
|
+
},
|
|
2506
|
+
};
|
|
2507
|
+
break;
|
|
2508
|
+
default:
|
|
2509
|
+
break;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2431
2512
|
const result = {
|
|
2432
2513
|
...next,
|
|
2433
2514
|
...handoffOverlay,
|
|
@@ -2468,21 +2549,7 @@ export class WorkflowService {
|
|
|
2468
2549
|
},
|
|
2469
2550
|
}
|
|
2470
2551
|
: {}),
|
|
2471
|
-
...
|
|
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
|
-
: {}),
|
|
2552
|
+
...guardedCorrectiveOverlay,
|
|
2486
2553
|
...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
|
|
2487
2554
|
};
|
|
2488
2555
|
if (handoffPosture.state === 'pending') {
|
|
@@ -2516,10 +2583,13 @@ export class WorkflowService {
|
|
|
2516
2583
|
policyHash: grant.policyHash,
|
|
2517
2584
|
}));
|
|
2518
2585
|
}
|
|
2519
|
-
delegatedContextRefreshOptions(projectId, task, knowledgeMap) {
|
|
2586
|
+
delegatedContextRefreshOptions(projectId, task, knowledgeMap, mode) {
|
|
2520
2587
|
return this.store.listDelegations(projectId)
|
|
2521
2588
|
.filter((grant) => {
|
|
2522
2589
|
try {
|
|
2590
|
+
if (mode === 'delegated-plan-bounded-supporting-source') {
|
|
2591
|
+
this.assertPlanBoundedSupportingSourceGrant(projectId, grant.id, grant.delegate, task);
|
|
2592
|
+
}
|
|
2523
2593
|
return Boolean(this.delegatedAuthorization(projectId, grant.id, 'project_memory.approve', grant.delegate, knowledgeMap, task.id)
|
|
2524
2594
|
&& this.delegatedAuthorization(projectId, grant.id, 'task.execution_authorize', grant.delegate, task));
|
|
2525
2595
|
}
|
|
@@ -2540,6 +2610,23 @@ export class WorkflowService {
|
|
|
2540
2610
|
policyHash: grant.policyHash,
|
|
2541
2611
|
}));
|
|
2542
2612
|
}
|
|
2613
|
+
assertPlanBoundedSupportingSourceGrant(projectId, grantId, actor, task) {
|
|
2614
|
+
const grant = this.store.readDelegation(projectId, grantId);
|
|
2615
|
+
if (!task.milestoneId || grant.scope.kind !== 'milestone' || grant.scope.id !== task.milestoneId) {
|
|
2616
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Plan-bounded supporting-source refresh requires the active Task Milestone autonomy grant.', { taskId: task.id, taskMilestoneId: task.milestoneId, grantScope: grant.scope });
|
|
2617
|
+
}
|
|
2618
|
+
const milestone = this.store.readMilestone(projectId, task.milestoneId);
|
|
2619
|
+
const autonomy = requireActiveMilestoneAutonomy({
|
|
2620
|
+
store: this.store,
|
|
2621
|
+
projectId,
|
|
2622
|
+
milestone,
|
|
2623
|
+
actor,
|
|
2624
|
+
now: this.now(),
|
|
2625
|
+
});
|
|
2626
|
+
if (autonomy.grant.id !== grant.id) {
|
|
2627
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone autonomy grant has been superseded.');
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2543
2630
|
assertDelegationScopeExists(projectId, policy) {
|
|
2544
2631
|
if (policy.scope.kind === 'task') {
|
|
2545
2632
|
this.store.readTask(projectId, policy.scope.id);
|
|
@@ -3028,11 +3115,6 @@ function appendCorrectivePlanAudit(taskRoot, previous, saved, failedReviewAttemp
|
|
|
3028
3115
|
...audit,
|
|
3029
3116
|
})}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
3030
3117
|
}
|
|
3031
|
-
function assertContentOnlyKnowledgeRefresh(approved, inspected) {
|
|
3032
|
-
if (!isContentOnlyKnowledgeRefresh(approved, inspected)) {
|
|
3033
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Automatic Task context refresh is limited to content-hash drift. Knowledge sources, categories, scopes, authorities, gaps, or conflicts changed.', { requiredAction: 'Run project-memory reconcile, inspect the classification diff, and approve it explicitly.' });
|
|
3034
|
-
}
|
|
3035
|
-
}
|
|
3036
3118
|
function isContentOnlyKnowledgeRefresh(approved, inspected) {
|
|
3037
3119
|
const classification = (map) => JSON.stringify({
|
|
3038
3120
|
entries: map.entries.map((entry) => ({
|
|
@@ -3048,6 +3130,107 @@ function isContentOnlyKnowledgeRefresh(approved, inspected) {
|
|
|
3048
3130
|
});
|
|
3049
3131
|
return classification(approved) === classification(inspected);
|
|
3050
3132
|
}
|
|
3133
|
+
function assertDelegatedKnowledgeRefresh(assessment) {
|
|
3134
|
+
if (assessment.eligible)
|
|
3135
|
+
return;
|
|
3136
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Knowledge drift is outside the atomic delegated context-refresh predicate: it is neither content-hash drift nor an exact Plan-bounded supporting-source addition.', {
|
|
3137
|
+
unsafeDifferences: assessment.unsafeDifferences,
|
|
3138
|
+
requiredAction: 'Run project-memory reconcile, inspect the classification diff, and approve it explicitly.',
|
|
3139
|
+
});
|
|
3140
|
+
}
|
|
3141
|
+
function assessDelegatedKnowledgeRefresh(approved, inspected, task, currentPlanArtifactHash) {
|
|
3142
|
+
if (isContentOnlyKnowledgeRefresh(approved, inspected)) {
|
|
3143
|
+
return {
|
|
3144
|
+
eligible: true,
|
|
3145
|
+
mode: 'delegated-content-only',
|
|
3146
|
+
addedSources: [],
|
|
3147
|
+
unsafeDifferences: [],
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
const unsafeDifferences = [];
|
|
3151
|
+
const sourceKey = (source) => `${source.category}\0${source.path}\0${source.scope}`;
|
|
3152
|
+
const approvedByKey = new Map(approved.entries.map((source) => [sourceKey(source), source]));
|
|
3153
|
+
const inspectedByKey = new Map(inspected.entries.map((source) => [sourceKey(source), source]));
|
|
3154
|
+
const inspectedByPath = new Map(inspected.entries.map((source) => [source.path, source]));
|
|
3155
|
+
for (const source of approved.entries) {
|
|
3156
|
+
const candidate = inspectedByKey.get(sourceKey(source));
|
|
3157
|
+
if (!candidate) {
|
|
3158
|
+
const samePath = inspectedByPath.get(source.path);
|
|
3159
|
+
unsafeDifferences.push(samePath
|
|
3160
|
+
? `Existing source ${source.path} changed category or scope from ${source.category}/${source.scope} to ${samePath.category}/${samePath.scope}.`
|
|
3161
|
+
: `Previously approved source was removed: ${source.path}.`);
|
|
3162
|
+
continue;
|
|
3163
|
+
}
|
|
3164
|
+
if (candidate.authority !== source.authority) {
|
|
3165
|
+
unsafeDifferences.push(`Existing source ${source.path} changed authority from ${source.authority} to ${candidate.authority}.`);
|
|
3166
|
+
}
|
|
3167
|
+
if (candidate.id !== source.id || candidate.discoveredBy !== source.discoveredBy) {
|
|
3168
|
+
unsafeDifferences.push(`Existing source ${source.path} changed classification identity.`);
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
const addedSources = inspected.entries.filter((source) => !approvedByKey.has(sourceKey(source)));
|
|
3172
|
+
const currentExecutionAuthorization = [...task.authorizations].reverse().find((authorization) => authorization.kind === 'execution'
|
|
3173
|
+
&& authorization.decision === 'approved'
|
|
3174
|
+
&& authorization.planHash === task.planHash
|
|
3175
|
+
&& authorization.briefHash === task.briefHash);
|
|
3176
|
+
if (!task.planHash || !currentExecutionAuthorization) {
|
|
3177
|
+
unsafeDifferences.push('The current Task Plan was not execution-authorized before Knowledge drift.');
|
|
3178
|
+
}
|
|
3179
|
+
if (!task.planHash || currentPlanArtifactHash !== task.planHash) {
|
|
3180
|
+
unsafeDifferences.push('The current Task Plan artifact does not match its authorized state binding.');
|
|
3181
|
+
}
|
|
3182
|
+
if (task.knowledgeImpact !== 'create') {
|
|
3183
|
+
unsafeDifferences.push(`Task Plan knowledgeImpact is ${task.knowledgeImpact ?? 'unset'}, not create.`);
|
|
3184
|
+
}
|
|
3185
|
+
const declaredTargets = new Set();
|
|
3186
|
+
for (const target of task.knowledgeTargets) {
|
|
3187
|
+
let normalized;
|
|
3188
|
+
try {
|
|
3189
|
+
normalized = normalizeRelativePath(target.path);
|
|
3190
|
+
}
|
|
3191
|
+
catch {
|
|
3192
|
+
unsafeDifferences.push(`Task Plan Knowledge target is not an exact repository-relative path: ${target.path}.`);
|
|
3193
|
+
continue;
|
|
3194
|
+
}
|
|
3195
|
+
if (normalized !== target.path || /[*?\[\]{}]/.test(target.path) || target.path.endsWith('/')) {
|
|
3196
|
+
unsafeDifferences.push(`Task Plan Knowledge target is wildcard, directory-like, or not normalized: ${target.path}.`);
|
|
3197
|
+
continue;
|
|
3198
|
+
}
|
|
3199
|
+
declaredTargets.add(`${target.category}\0${target.path}`);
|
|
3200
|
+
}
|
|
3201
|
+
if (addedSources.length === 0) {
|
|
3202
|
+
unsafeDifferences.push('No new Knowledge source matches the Plan-bounded addition path.');
|
|
3203
|
+
}
|
|
3204
|
+
for (const source of addedSources) {
|
|
3205
|
+
if (source.authority !== 'supporting') {
|
|
3206
|
+
unsafeDifferences.push(`New source ${source.path} has forbidden ${source.authority} authority.`);
|
|
3207
|
+
}
|
|
3208
|
+
if (!declaredTargets.has(`${source.category}\0${source.path}`)) {
|
|
3209
|
+
unsafeDifferences.push(`New source ${source.path} (${source.category}) was not declared by the authorized Task Plan.`);
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
if (JSON.stringify(approved.gaps) !== JSON.stringify(inspected.gaps)) {
|
|
3213
|
+
unsafeDifferences.push('Project Knowledge gaps changed.');
|
|
3214
|
+
}
|
|
3215
|
+
if (JSON.stringify(approved.conflicts) !== JSON.stringify(inspected.conflicts)) {
|
|
3216
|
+
unsafeDifferences.push('Project Knowledge conflicts changed.');
|
|
3217
|
+
}
|
|
3218
|
+
if (inspected.conflicts.length > 0) {
|
|
3219
|
+
unsafeDifferences.push('The inspected Project Knowledge Map contains conflicts.');
|
|
3220
|
+
}
|
|
3221
|
+
return {
|
|
3222
|
+
eligible: unsafeDifferences.length === 0,
|
|
3223
|
+
mode: 'delegated-plan-bounded-supporting-source',
|
|
3224
|
+
addedSources: addedSources.map(({ id, path: sourcePath, category, scope, authority }) => ({
|
|
3225
|
+
id,
|
|
3226
|
+
path: sourcePath,
|
|
3227
|
+
category,
|
|
3228
|
+
scope,
|
|
3229
|
+
authority,
|
|
3230
|
+
})),
|
|
3231
|
+
unsafeDifferences: [...new Set(unsafeDifferences)],
|
|
3232
|
+
};
|
|
3233
|
+
}
|
|
3051
3234
|
function validateMilestonePlan(plan) {
|
|
3052
3235
|
if (!plan.outcome.trim())
|
|
3053
3236
|
throw new WorkflowError('INVALID_ARGUMENT', 'Milestone outcome is required.');
|