ComplianceEnforcement.java
package com.cloudforgeci.api.core.rules;
import com.cloudforge.core.enums.ComplianceMode;
import com.cloudforge.core.enums.SecurityProfile;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Logger;
/**
* Shared STAGING/ADVISORY/ENFORCE enforcement policy for a {@code FrameworkRules} plugin's
* {@code Node.addValidation} callback -- every framework class ({@code HipaaRules},
* {@code PciDssRules}, {@code GdprRules}, {@code FedRampRules}, {@code Iso27001Rules},
* {@code Soc2Rules}) was independently reimplementing this identical policy, risking exactly the
* kind of drift that happened before this class existed (FedRAMP's STAGING/ADVISORY branches not
* recording to {@link ComplianceFindingsCollector} the way every other framework's did).
*
* <p>Policy, in order:
* <ul>
* <li>No failures -- pass, nothing blocks.</li>
* <li>{@link ComplianceMode#ADVISORY} -- every failure is a visible, non-blocking finding.</li>
* <li>{@link SecurityProfile#STAGING} -- runs the same checks as PRODUCTION, but only rule IDs
* in {@code stagingBlockingRules} actually block synthesis; everything else is a visible,
* non-blocking finding. Pass {@link Set#of()} for a framework where nothing blocks in
* STAGING.</li>
* <li>Otherwise (PRODUCTION under {@link ComplianceMode#ENFORCE}) -- every failure blocks.</li>
* </ul>
*
* <p>Every branch that produces a non-empty error list also records the underlying {@link
* ComplianceRule} list with {@link ComplianceFindingsCollector}, so a caller draining findings
* (e.g. {@code CloudForgeSynthesizer#synthesizeAdvisoryDryRun}) sees them regardless of which
* branch fired.
*/
public final class ComplianceEnforcement {
/** Static utility only -- no instances. */
private ComplianceEnforcement() {
}
/**
* @param frameworkDisplayName name used in log lines (e.g. "HIPAA", "PCI-DSS", "SOC 2")
* @param rules every {@link ComplianceRule} the framework's validators produced, pass and fail
* @param complianceMode the resolved {@link ComplianceMode} for this deployment
* @param profile the resolved {@link SecurityProfile} for this deployment
* @param stagingBlockingRules rule IDs that still block synthesis in STAGING; ignored outside
* STAGING
* @return the error strings to return from the {@code Node.addValidation} callback -- an
* empty list means synthesis proceeds, a non-empty list blocks it
*/
public static List<String> resolve(
String frameworkDisplayName,
List<ComplianceRule> rules,
ComplianceMode complianceMode,
SecurityProfile profile,
Set<String> stagingBlockingRules,
Logger log) {
List<ComplianceRule> failedRules = rules.stream().filter(rule -> !rule.passed()).toList();
List<String> errors = failedRules.stream()
.map(ComplianceRule::toErrorString)
.flatMap(Optional::stream)
.toList();
if (errors.isEmpty()) {
log.info(frameworkDisplayName + " validation passed (" + rules.size() + " checks)");
return List.of();
}
if (complianceMode == ComplianceMode.ADVISORY) {
log.warning(frameworkDisplayName + " validation found " + errors.size()
+ " recommendations (ADVISORY mode - not blocking)");
errors.forEach(err -> log.warning(" - " + err));
ComplianceFindingsCollector.record(failedRules);
return List.of();
}
if (profile == SecurityProfile.STAGING) {
List<String> blocking = failedRules.stream()
.filter(rule -> stagingBlockingRules.contains(rule.ruleId()))
.map(ComplianceRule::toErrorString)
.flatMap(Optional::stream)
.toList();
log.warning(frameworkDisplayName + " validation found " + errors.size() + " violations (STAGING - "
+ blocking.size() + " blocking)");
errors.forEach(err -> log.warning(" - " + err));
ComplianceFindingsCollector.record(failedRules);
return blocking;
}
log.severe(frameworkDisplayName + " validation failed with " + errors.size()
+ " violations (ENFORCE mode - blocking deployment)");
errors.forEach(err -> log.severe(" - " + err));
return errors;
}
}