validatePatchbayExecutionEvidence function

PatchbayExecutionValidationResult validatePatchbayExecutionEvidence(
  1. PatchbayExecutionContract contract,
  2. Object? payload, {
  3. String path = r'$.payload',
  4. String? terminalPhase,
  5. required int nowMs,
})

Implementation

PatchbayExecutionValidationResult validatePatchbayExecutionEvidence(
  PatchbayExecutionContract contract,
  Object? payload, {
  String path = r'$.payload',
  String? terminalPhase,
  required int nowMs,
}) {
  if (payload is! Map<Object?, Object?> || !payload.containsKey('execution')) {
    return const PatchbayExecutionValidationResult();
  }
  final List<PatchbayResponseValidationIssue> issues =
      <PatchbayResponseValidationIssue>[];
  final Object? rawExecution = payload['execution'];
  if (rawExecution is! Map<Object?, Object?>) {
    return PatchbayExecutionValidationResult(
      issues: <PatchbayResponseValidationIssue>[
        PatchbayResponseValidationIssue(
          field: '$path.execution',
          reason: 'wrongType',
          expected: 'object',
        ),
      ],
    );
  }
  const Set<String> baseFields = <String>{
    'classification',
    'factSource',
    'observedAtMs',
    'reasonCode',
  };
  const Set<String> priorFields = <String>{
    'priorValueSource',
    'priorObservedAtMs',
  };
  for (final Object? key in rawExecution.keys) {
    if (key is! String ||
        (!baseFields.contains(key) && !priorFields.contains(key))) {
      issues.add(
        PatchbayResponseValidationIssue(
          field: key is String ? '$path.execution.$key' : '$path.execution',
          reason: 'unknownField',
        ),
      );
    }
  }
  for (final String field in baseFields) {
    if (!rawExecution.containsKey(field)) {
      issues.add(
        PatchbayResponseValidationIssue(
          field: '$path.execution.$field',
          reason: 'missingField',
        ),
      );
    }
  }
  final Object? rawClassification = rawExecution['classification'];
  final PatchbayExecutionClassification? classification =
      rawClassification is String
      ? PatchbayExecutionClassification.values
            .cast<PatchbayExecutionClassification?>()
            .firstWhere(
              (PatchbayExecutionClassification? value) =>
                  value?.name == rawClassification,
              orElse: () => null,
            )
      : null;
  if (rawClassification != null && rawClassification is! String) {
    issues.add(
      PatchbayResponseValidationIssue(
        field: '$path.execution.classification',
        reason: 'wrongType',
        expected: 'string',
      ),
    );
  } else if (rawClassification is String && classification == null) {
    issues.add(
      PatchbayResponseValidationIssue(
        field: '$path.execution.classification',
        reason: 'unknownVariant',
        expected: PatchbayExecutionClassification.values
            .map((PatchbayExecutionClassification value) => value.name)
            .join('|'),
      ),
    );
  }
  final Object? rawSource = rawExecution['factSource'];
  final PatchbayFactSource? source = rawSource is String
      ? _factSource(rawSource)
      : null;
  if (rawSource != null && rawSource is! String) {
    issues.add(
      PatchbayResponseValidationIssue(
        field: '$path.execution.factSource',
        reason: 'wrongType',
        expected: 'string',
      ),
    );
  } else if (rawSource is String &&
      (source == null || !contract.factSources.contains(source))) {
    issues.add(
      PatchbayResponseValidationIssue(
        field: '$path.execution.factSource',
        reason: 'unknownVariant',
        expected:
            (contract.factSources
                    .map((PatchbayFactSource value) => value.name)
                    .toList(growable: false)
                  ..sort())
                .join('|'),
      ),
    );
  }
  _nullableScalar(
    rawExecution,
    'observedAtMs',
    int,
    '$path.execution.observedAtMs',
    issues,
  );
  final Object? observedAtMs = rawExecution['observedAtMs'];
  if (observedAtMs is int && observedAtMs < 0) {
    issues.add(
      PatchbayResponseValidationIssue(
        field: '$path.execution.observedAtMs',
        reason: 'unknownVariant',
        expected: 'nonNegativeInteger|null',
      ),
    );
  }
  if (source == PatchbayFactSource.unknown && observedAtMs != null) {
    issues.add(
      PatchbayResponseValidationIssue(
        field: '$path.execution.observedAtMs',
        reason: 'unknownVariant',
        expected: 'nullWhenFactSourceUnknown',
      ),
    );
  }
  _nullableScalar(
    rawExecution,
    'reasonCode',
    String,
    '$path.execution.reasonCode',
    issues,
  );

  if (classification == PatchbayExecutionClassification.unchanged) {
    _validateUnchanged(contract, rawExecution, path, nowMs, issues);
  } else {
    for (final String field in priorFields) {
      if (rawExecution.containsKey(field)) {
        issues.add(
          PatchbayResponseValidationIssue(
            field: '$path.execution.$field',
            reason: 'unknownField',
          ),
        );
      }
    }
  }
  if (classification == PatchbayExecutionClassification.sentUnconfirmed ||
      classification == PatchbayExecutionClassification.deviceConfirmed) {
    if (contract.confirmationBudgetMs == null) {
      issues.add(
        PatchbayResponseValidationIssue(
          field: r'$.descriptor.confirmationBudgetMs',
          reason: 'missingField',
        ),
      );
    }
  }
  if (classification == PatchbayExecutionClassification.deviceConfirmed &&
      source != PatchbayFactSource.deviceReported) {
    issues.add(
      PatchbayResponseValidationIssue(
        field: '$path.execution.factSource',
        reason: 'unknownVariant',
        expected: PatchbayFactSource.deviceReported.name,
      ),
    );
  }
  if (classification != null && terminalPhase != null) {
    final bool phaseAllowed = switch (classification) {
      PatchbayExecutionClassification.notSent => terminalPhase == 'failed',
      PatchbayExecutionClassification.sentUnconfirmed =>
        terminalPhase == 'failed' ||
            (contract.weakConfirmationCompletes &&
                terminalPhase == 'completed'),
      PatchbayExecutionClassification.unchanged ||
      PatchbayExecutionClassification.deviceConfirmed =>
        terminalPhase == 'completed',
    };
    if (!phaseAllowed) {
      issues.add(
        PatchbayResponseValidationIssue(
          field: '$path.execution.classification',
          reason: 'unknownVariant',
          expected: 'phaseCompatible:$terminalPhase',
        ),
      );
    }
  }

  final Object? dispatched = payload['dispatched'];
  final bool conflict =
      dispatched is bool &&
      classification != null &&
      dispatched != (classification != PatchbayExecutionClassification.notSent);
  return PatchbayExecutionValidationResult(
    issues: List<PatchbayResponseValidationIssue>.unmodifiable(
      issues.take(patchbayResponseValidationMaxIssues),
    ),
    legacyDispatchedConflict: conflict,
  );
}