runApplyPipeline function

Future<ApplyPipelineResult> runApplyPipeline({
  1. required ChangePlan approvedPlan,
  2. required PlanLocationRecord location,
  3. required PlanStore planStore,
  4. required AppliedHistoryStore historyStore,
  5. required ProcessRunner processRunner,
  6. required Future<ValidationOutcome> validate(
    1. String projectRoot,
    2. List<CommandRecord> commands
    ),
  7. required void releaseStaging(
    1. String stagingRoot
    ),
  8. ApplyFileSystem fileSystem = const ApplyFileSystem(),
  9. ApplyObservations? observations,
})

Implementation

Future<ApplyPipelineResult> runApplyPipeline({
  required ChangePlan approvedPlan,
  required PlanLocationRecord location,
  required PlanStore planStore,
  required AppliedHistoryStore historyStore,
  required ProcessRunner processRunner,
  required Future<ValidationOutcome> Function(
    String projectRoot,
    List<CommandRecord> commands,
  )
  validate,
  required void Function(String stagingRoot) releaseStaging,
  ApplyFileSystem fileSystem = const ApplyFileSystem(),
  ApplyObservations? observations,
}) async {
  try {
    applyPreflight(
      plan: approvedPlan,
      stagingRoot: location.stagingRoot,
      canonicalDestination: location.canonicalDestination,
      fileSystem: fileSystem,
      observations: observations,
    );
  } on ApplyException catch (error) {
    return _preflightFailure(error.exitCode, error.message);
  } on FileSystemException catch (error) {
    return _preflightFailure(
      ExitReporter.planStaleOrStagingIntegrityFailure,
      error.toString(),
    );
  }

  final applying = _with(approvedPlan, state: PlanState.applying);
  planStore.savePlan(applying);

  late ApplyPhase1Result phase1;
  try {
    phase1 = applyPhase1(
      plan: approvedPlan,
      location: location,
      fileSystem: fileSystem,
      observations: observations,
      runPreflight: false,
    );
  } on Object catch (error) {
    if (error is! ApplyException && error is! FileSystemException) rethrow;
    final failureMessage = error is ApplyException
        ? error.message
        : error.toString();
    final failed = _with(applying, state: PlanState.failed);
    planStore.savePlan(failed);
    final recovery = rollbackMaterialization(
      plan: approvedPlan,
      canonicalDestination: location.canonicalDestination,
      fileSystem: fileSystem,
    );
    final terminalState = PlanState.values.byName(recovery.state.name);
    planStore.savePlan(_with(failed, state: terminalState));
    final exitCode = recovery.state == RecoveryState.rolledBack
        ? ExitReporter.applyFailedRolledBack
        : ExitReporter.applyFailedPartialRecoveryOrManualRequired;
    final summary = switch (recovery.state) {
      RecoveryState.rolledBack =>
        'rolled back ${recovery.removedCreatedPaths.length} created files',
      RecoveryState.partiallyRecovered =>
        'partial recovery: ${recovery.removedCreatedPaths.length} removed, '
            '${recovery.unresolvedPaths.length} unresolved',
      RecoveryState.manualRecoveryRequired =>
        'manual recovery required: ${recovery.unresolvedPaths.length} '
            'unresolved',
    };
    final message = '$failureMessage Recovery $summary.';
    return ApplyPipelineResult(
      exitCode: exitCode,
      ok: false,
      result: {
        'planId': approvedPlan.planId,
        'state': recovery.state.name,
        'phase1': const {'filesWritten': 0, 'invariantHolds': false},
        'phase2': null,
        'validation': null,
        'recovery': recovery.toJson(),
      },
      error: _error('APPLY_FAILED', exitCode, message),
      textLine: 'APPLY_FAILED: $message',
    );
  }

  final phase2 = await applyPhase2(
    plan: approvedPlan,
    location: location,
    processRunner: processRunner,
  );
  final phase2Json = {
    'commands': phase2.commands,
    'pathsTouched': phase2.pathsTouched,
    'outsideDeclaredSet': phase2.outsideDeclaredSet,
    if (phase2.failure != null) 'failure': phase2.failure!.toJson(),
  };
  if (!phase2.ok) {
    final failed = _with(applying, state: PlanState.failed);
    planStore.savePlan(failed);
    planStore.savePlan(_with(failed, state: PlanState.manualRecoveryRequired));
    final message = phase2.failure == null
        ? 'Project materialized but not build-ready: Phase 2 wrote outside '
              'the declared set: ${phase2.outsideDeclaredSet.join(', ')}.'
        : materializedButNotBuildReadyMessage(phase2.failure!);
    return ApplyPipelineResult(
      exitCode: ExitReporter.applyFailedPartialRecoveryOrManualRequired,
      ok: false,
      result: {
        'planId': approvedPlan.planId,
        'state': PlanState.manualRecoveryRequired.name,
        'phase1': {
          'filesWritten': phase1.filesWritten,
          'invariantHolds': phase1.invariantHolds,
        },
        'phase2': phase2Json,
        'validation': null,
        'recovery': null,
      },
      error: {
        'code': 'PHASE2_FAILED',
        'exitCode': ExitReporter.applyFailedPartialRecoveryOrManualRequired,
        'message': message,
        'cause': phase2Json,
        'missingDecisions': const [],
      },
      textLine: 'PHASE2_FAILED: $message',
    );
  }

  final validating = _with(applying, state: PlanState.validating);
  planStore.savePlan(validating);
  final validation = await validate(
    location.canonicalDestination,
    approvedPlan.validationCommands,
  );
  final validationJson = {
    ...validation.report.toJson(),
    'violations': validation.violations,
  };
  if (validation.report.result == ValidationResult.failed) {
    final failed = _with(validating, state: PlanState.failed);
    planStore.savePlan(failed);
    planStore.savePlan(_with(failed, state: PlanState.manualRecoveryRequired));
    final failingGate = _failingGate(validation);
    final recovery = validationFailureRecovery(failingGate: failingGate);
    final message =
        'Project applied but NOT validated — $failingGate. DEFECT: the '
        'staging-time generation-validity check should have caught this. '
        'Staging is retained for recovery.';
    return ApplyPipelineResult(
      exitCode: ExitReporter.validationFailed,
      ok: false,
      result: {
        'planId': approvedPlan.planId,
        'state': PlanState.manualRecoveryRequired.name,
        'phase1': {
          'filesWritten': phase1.filesWritten,
          'invariantHolds': phase1.invariantHolds,
        },
        'phase2': phase2Json,
        'validation': validationJson,
        'recovery': {...recovery.toJson(), 'stagingRetained': true},
      },
      error: {
        'code': 'VALIDATION_FAILED',
        'exitCode': ExitReporter.validationFailed,
        'message': message,
        'cause': validationJson,
        'missingDecisions': const [],
      },
      textLine: 'VALIDATION_FAILED: $message',
    );
  }

  finalizeSuccessfulApply(
    appliedPlan: approvedPlan,
    location: location,
    validationResult: validation.report.result,
    historyStore: historyStore,
    releaseStaging: releaseStaging,
  );
  planStore.savePlan(_with(validating, state: PlanState.applied));
  return ApplyPipelineResult(
    exitCode: ExitReporter.success,
    ok: true,
    result: {
      'planId': approvedPlan.planId,
      'state': PlanState.applied.name,
      'phase1': {
        'filesWritten': phase1.filesWritten,
        'invariantHolds': phase1.invariantHolds,
      },
      'phase2': phase2Json,
      'validation': validationJson,
      'recovery': null,
    },
    error: null,
    textLine:
        'Applied ${approvedPlan.planId}: ${phase1.filesWritten} files written; '
        'reviewed-content invariant holds; Phase 2 regenerated '
        '${phase2.pathsTouched.length} paths; validation passed.',
  );
}