validatePatchbayExecutionEvidence function
PatchbayExecutionValidationResult
validatePatchbayExecutionEvidence(
- PatchbayExecutionContract contract,
- Object? payload, {
- String path = r'$.payload',
- String? terminalPhase,
- 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,
);
}