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.
@@ -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 || inspectedMap.status !== 'stale') {
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 });
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, 'Delegated content-only context refresh.', delegationGrantId);
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: 'delegated-content-only',
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 (task.status !== 'needs_fix' && task.status !== 'blocked') {
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
- if (task.status === 'needs_fix') {
1670
- if (step.status !== 'failed') {
1671
- throw new WorkflowError('TRANSITION_BLOCKED', `Corrective decision for ${stepId} requires a currently failed guarded Step while task ${taskId} is needs_fix.`, {
1672
- taskId,
1673
- stepId,
1674
- stepStatus: step.status,
1675
- });
1676
- }
1677
- }
1678
- else {
1679
- const currentCycle = buildCurrentStrictStepReviewCycle(this.store, task, stepId);
1680
- if (step.status !== 'in_progress' || !step.evidence || currentCycle.resolution !== 'unverified') {
1681
- throw new WorkflowError('TRANSITION_BLOCKED', `Corrective decision for ${stepId} requires a blocked unverified guarded Step with current evidence.`, {
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 contextRefreshOptions = action === 'project-memory reconcile'
2150
- && storedKnowledgeMap.status === 'active'
2155
+ const refreshAssessment = storedKnowledgeMap.status === 'active'
2151
2156
  && storedKnowledgeMap.approval
2152
- && isContentOnlyKnowledgeRefresh(storedKnowledgeMap, knowledgeMap)
2153
- ? this.delegatedContextRefreshOptions(status.projectId, activeTask, knowledgeMap)
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: 'delegated-content-only',
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 guardedCorrectiveGate = task.status === 'needs_fix' && planRiskAudit
2387
- ? findFailedGuardedStepRequiringCorrectiveDecision(this.store, task, planRiskAudit)
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
- ...(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
- : {}),
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.');