DecisionReportTemplateProvenance.java

package com.taxonomy.architecture.decision;

import com.taxonomy.templates.DocumentTemplateService.TemplateFile;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;

/** Exact render-time identity of the Word template used for one decision report. */
public record DecisionReportTemplateProvenance(
        String templateId,
        String commitId,
        String packageSha256,
        int schemaVersion) {

    public static final String METADATA_TEMPLATE_ID = "taxonomy.template.id";
    public static final String METADATA_TEMPLATE_COMMIT = "taxonomy.template.commit";
    public static final String METADATA_TEMPLATE_SHA256 = "taxonomy.template.sha256";
    public static final String METADATA_TEMPLATE_SCHEMA_VERSION =
            "taxonomy.template.schema-version";

    private static final Pattern TEMPLATE_ID =
            Pattern.compile("[a-z0-9][a-z0-9._-]{0,79}");
    private static final Pattern COMMIT_ID = Pattern.compile("[0-9a-f]{40}");
    private static final Pattern SHA256 = Pattern.compile("[0-9a-f]{64}");
    private static final List<String> METADATA_KEYS = List.of(
            METADATA_TEMPLATE_ID,
            METADATA_TEMPLATE_COMMIT,
            METADATA_TEMPLATE_SHA256,
            METADATA_TEMPLATE_SCHEMA_VERSION);

    public DecisionReportTemplateProvenance {
        templateId = requireMatch(templateId, TEMPLATE_ID, "template ID");
        commitId = requireMatch(commitId, COMMIT_ID, "template commit");
        packageSha256 = requireMatch(
                packageSha256, SHA256, "template package SHA-256");
        if (schemaVersion < 1) {
            throw new IllegalArgumentException(
                    "Template schema version must be positive");
        }
    }

    public static DecisionReportTemplateProvenance from(TemplateFile template) {
        Objects.requireNonNull(template, "template");
        return new DecisionReportTemplateProvenance(
                template.manifest().templateId(),
                template.commitId(),
                template.manifest().packageSha256(),
                template.manifest().schemaVersion());
    }

    public Map<String, String> artifactMetadata() {
        Map<String, String> metadata = new LinkedHashMap<>();
        metadata.put(METADATA_TEMPLATE_ID, templateId);
        metadata.put(METADATA_TEMPLATE_COMMIT, commitId);
        metadata.put(METADATA_TEMPLATE_SHA256, packageSha256);
        metadata.put(
                METADATA_TEMPLATE_SCHEMA_VERSION,
                Integer.toString(schemaVersion));
        return Map.copyOf(metadata);
    }

    /**
     * Parse the complete allow-listed template identity from renderer metadata.
     * No matching key means that the rendered format did not use a Word template;
     * a partial or malformed identity fails closed.
     */
    public static Optional<DecisionReportTemplateProvenance> fromArtifactMetadata(
            Map<String, String> metadata) {
        Objects.requireNonNull(metadata, "metadata");
        long present = METADATA_KEYS.stream().filter(metadata::containsKey).count();
        if (present == 0) {
            return Optional.empty();
        }
        if (present != METADATA_KEYS.size()) {
            throw new IllegalStateException(
                    "Incomplete decision-report template provenance metadata");
        }
        try {
            return Optional.of(new DecisionReportTemplateProvenance(
                    metadata.get(METADATA_TEMPLATE_ID),
                    metadata.get(METADATA_TEMPLATE_COMMIT),
                    metadata.get(METADATA_TEMPLATE_SHA256),
                    Integer.parseInt(
                            metadata.get(METADATA_TEMPLATE_SCHEMA_VERSION))));
        } catch (RuntimeException exception) {
            throw new IllegalStateException(
                    "Invalid decision-report template provenance metadata",
                    exception);
        }
    }

    private static String requireMatch(
            String value,
            Pattern pattern,
            String label) {
        if (value == null || !pattern.matcher(value).matches()) {
            throw new IllegalArgumentException("Invalid " + label);
        }
        return value;
    }
}