Luigit
repositories / termux-janitor

termux-janitor

Interactive cleanup assistant for Termux: transparent, safe, confirmed disk reclamation.

owned by admin

tools/spec-engine/src/validate.zig

Raw
const std = @import("std");
const model_types = @import("model.zig");
const diag = @import("diag.zig");
const fail = diag.fail;
const on = diag.subject;
const text = @import("text.zig");
const fixtures = @import("fixture.zig");
const scaffold = @import("scaffold.zig");
const profile = @import("profile");

/// Validate every generic relationship in the graph.
///
/// Product-domain registries are validated by their owning profile consumer.
pub fn graph(
    allocator: std.mem.Allocator,
    io: std.Io,
    model: *const model_types.Graph,
    stderr: *std.Io.Writer,
) !void {
    if (model.documents.len == 0) {
        return fail(stderr, on.document, "document catalog is empty", .{});
    }
    if (model.documents.len > model_types.document_count_max) {
        return fail(stderr, on.document, "document count exceeds {d}", .{
            model_types.document_count_max,
        });
    }
    if (model.concepts.len > model_types.concept_count_max) {
        return fail(stderr, on.concept, "concept count exceeds {d}", .{
            model_types.concept_count_max,
        });
    }
    if (model.groups.len > model_types.group_count_max) {
        return fail(
            stderr,
            on.requirement,
            "group count exceeds {d}",
            .{model_types.group_count_max},
        );
    }
    try validateDocuments(io, model.documents, stderr);
    try validateRequirements(allocator, io, model, stderr);
    try validateConcepts(allocator, io, model, stderr);
    try validateVerification(model, stderr);
    try validateTestSpecs(allocator, io, model, stderr);
    try validateTestBindings(allocator, io, model, stderr);
    try validateImplementations(allocator, io, model, stderr);
    try validateSourceInventories(allocator, io, model, stderr);
}

pub fn validateDocuments(
    io: std.Io,
    documents: []const model_types.Document,
    stderr: *std.Io.Writer,
) !void {
    for (documents, 0..) |document, index| {
        if (!text.nameValid(document.name)) {
            return fail(stderr, on.document, "invalid document name: {s}", .{document.name});
        }
        if (std.mem.trim(u8, document.description, " \t\r\n").len == 0) {
            return fail(stderr, on.document, "empty description: {s}", .{document.name});
        }
        try diag.validateRepositoryPath(io, document.location, stderr);
        for (documents[0..index]) |previous| {
            if (std.mem.eql(u8, previous.name, document.name)) {
                return fail(stderr, on.document, "duplicate document name: {s}", .{document.name});
            }
            if (std.mem.eql(u8, previous.location, document.location)) {
                return fail(
                    stderr,
                    on.document,
                    "duplicate document location: {s}",
                    .{document.location},
                );
            }
        }
    }
}

pub fn validateConcepts(
    allocator: std.mem.Allocator,
    io: std.Io,
    model: *const model_types.Graph,
    stderr: *std.Io.Writer,
) !void {
    for (model.concepts, 0..) |concept, index| {
        if (!text.conceptIdValid(concept.id)) {
            return fail(stderr, on.concept, "invalid concept ID: {s}", .{concept.id});
        }
        if (std.mem.trim(u8, concept.term, " \t\r\n").len == 0) {
            return fail(stderr, on.concept, "empty concept term: {s}", .{concept.id});
        }
        if (std.mem.trim(u8, concept.definition, " \t\r\n").len == 0) {
            return fail(stderr, on.concept, "empty concept definition: {s}", .{concept.id});
        }
        if (concept.invariants.len == 0) {
            return fail(stderr, on.concept, "concept has no invariant: {s}", .{concept.id});
        }
        try validateOwner(allocator, io, concept.owner, stderr);
        for (model.concepts[0..index]) |previous| {
            if (std.mem.eql(u8, previous.id, concept.id)) {
                return fail(stderr, on.concept, "duplicate concept ID: {s}", .{concept.id});
            }
            if (std.ascii.eqlIgnoreCase(previous.term, concept.term)) {
                return fail(stderr, on.concept, "duplicate concept term: {s}", .{concept.term});
            }
        }
        try validateConceptAliases(model.concepts, &concept, stderr);
        try validateConceptReferences(model, &concept, stderr);
    }
}

pub fn validateConceptAliases(
    concepts: []const model_types.Concept,
    concept: *const model_types.Concept,
    stderr: *std.Io.Writer,
) !void {
    for (concept.aliases, 0..) |alias, index| {
        if (alias.len == 0) return fail(stderr, on.concept, "empty alias on {s}", .{concept.id});
        if (std.ascii.eqlIgnoreCase(alias, concept.term)) {
            return fail(stderr, on.concept, "canonical term repeated as alias: {s}", .{concept.id});
        }
        for (concept.aliases[0..index]) |previous| {
            if (std.ascii.eqlIgnoreCase(previous, alias)) {
                return fail(
                    stderr,
                    on.concept,
                    "duplicate alias on {s}: {s}",
                    .{ concept.id, alias },
                );
            }
        }
        for (concepts) |candidate| {
            if (std.ascii.eqlIgnoreCase(candidate.term, alias)) {
                return fail(
                    stderr,
                    on.concept,
                    "alias conflicts with canonical term: {s}",
                    .{alias},
                );
            }
        }
    }
}

pub fn validateConceptReferences(
    model: *const model_types.Graph,
    concept: *const model_types.Concept,
    stderr: *std.Io.Writer,
) !void {
    for (concept.non_equivalent) |reference| {
        var found = false;
        for (model.concepts) |candidate| {
            if (std.mem.eql(u8, candidate.id, reference)) found = true;
        }
        if (!found) {
            return fail(
                stderr,
                on.concept,
                "unknown concept reference {s} from {s}",
                .{ reference, concept.id },
            );
        }
        if (std.mem.eql(u8, concept.id, reference)) {
            return fail(
                stderr,
                on.concept,
                "concept is non-equivalent to itself: {s}",
                .{concept.id},
            );
        }
        const target = findConcept(model.concepts, reference).?;
        if (!stringListContains(target.non_equivalent, concept.id)) {
            return fail(stderr, on.concept, "asymmetric non-equivalence: {s} and {s}", .{
                concept.id,
                reference,
            });
        }
    }
    for (concept.requirements, 0..) |requirement_id, index| {
        if (!requirementExists(model, requirement_id)) {
            return fail(
                stderr,
                on.concept,
                "unknown requirement {s} on {s}",
                .{ requirement_id, concept.id },
            );
        }
        if (stringListContains(concept.requirements[0..index], requirement_id)) {
            return fail(stderr, on.concept, "duplicate requirement on concept: {s}", .{concept.id});
        }
    }
}

pub fn findConcept(concepts: []const model_types.Concept, id: []const u8) ?model_types.Concept {
    for (concepts) |concept| if (std.mem.eql(u8, concept.id, id)) return concept;
    return null;
}

pub fn stringListContains(values: []const []const u8, expected: []const u8) bool {
    for (values) |value| if (std.mem.eql(u8, value, expected)) return true;
    return false;
}

pub fn requirementExists(model: *const model_types.Graph, id: []const u8) bool {
    for (model.groups) |group| {
        for (group.requirements) |requirement| {
            if (std.mem.eql(u8, requirement.id, id)) return true;
        }
    }
    return false;
}

pub const ClaimedRequirements = struct {
    ids: std.StringHashMapUnmanaged(void) = .empty,

    pub fn deinit(self: *ClaimedRequirements, allocator: std.mem.Allocator) void {
        self.ids.deinit(allocator);
    }

    pub fn has(self: *const ClaimedRequirements, id: []const u8) bool {
        return self.ids.contains(id);
    }
};

/// Resolves every claim's obligation references once so per-requirement
/// traceability checks are set lookups instead of repeated graph scans.
pub fn buildClaimedRequirements(
    allocator: std.mem.Allocator,
    model: *const model_types.Graph,
) !ClaimedRequirements {
    var claimed: ClaimedRequirements = .{};
    errdefer claimed.deinit(allocator);
    for (model.test_specs) |test_spec| {
        for (test_spec.claims) |claim| {
            for (claim.obligations) |reference| {
                const obligation = model_types.findObligation(model.verification, reference.id) orelse continue;
                try claimed.ids.put(allocator, obligation.requirement.id, {});
            }
        }
    }
    return claimed;
}

pub fn validateRequirements(
    allocator: std.mem.Allocator,
    io: std.Io,
    model: *const model_types.Graph,
    stderr: *std.Io.Writer,
) !void {
    const requirement_count = model.requirementCount();
    if (requirement_count == 0) {
        return fail(stderr, on.requirement, "requirement registry is empty", .{});
    }
    if (requirement_count > model_types.requirement_count_max) {
        return fail(
            stderr,
            on.requirement,
            "requirement count exceeds {d}",
            .{model_types.requirement_count_max},
        );
    }
    var claimed = try buildClaimedRequirements(allocator, model);
    defer claimed.deinit(allocator);
    for (model.groups, 0..) |group, group_index| {
        if (group.requirements.len == 0) {
            return fail(stderr, on.requirement, "empty requirement group: {s}", .{group.title});
        }
        for (model.groups[0..group_index]) |previous| {
            if (std.mem.eql(u8, previous.title, group.title)) {
                return fail(
                    stderr,
                    on.requirement,
                    "duplicate requirement group: {s}",
                    .{group.title},
                );
            }
        }
        for (group.requirements) |requirement| {
            try validateRequirement(allocator, io, model, &claimed, &requirement, stderr);
        }
    }
}

pub fn validateRequirement(
    allocator: std.mem.Allocator,
    io: std.Io,
    model: *const model_types.Graph,
    claimed: *const ClaimedRequirements,
    requirement: *const model_types.Requirement,
    stderr: *std.Io.Writer,
) !void {
    if (!profile.requirementIdValid(requirement.id)) {
        return fail(stderr, on.requirement, "invalid requirement ID: {s}", .{requirement.id});
    }
    if (requirement.revision == 0) {
        return fail(stderr, on.requirement, "zero requirement revision: {s}", .{requirement.id});
    }
    if (requirement.label.len == 0) {
        return fail(stderr, on.requirement, "empty requirement label: {s}", .{requirement.id});
    }
    if (requirement.suites.len == 0) {
        return fail(stderr, on.requirement, "requirement has no suites: {s}", .{requirement.id});
    }
    try validateOwner(allocator, io, requirement.owner, stderr);
    try validateUniqueSuites(requirement, stderr);
    try validateRequirementTraceability(allocator, io, claimed, requirement, stderr);
    try validateUniqueRequirement(model, requirement, stderr);
}

pub fn validateRequirementTraceability(
    allocator: std.mem.Allocator,
    io: std.Io,
    claimed: *const ClaimedRequirements,
    requirement: *const model_types.Requirement,
    stderr: *std.Io.Writer,
) !void {
    if (requirement.invariant_status == .mapped and !claimed.has(requirement.id)) {
        return fail(stderr, on.requirement, "mapped requirement has no claimed verification path: {s}", .{
            requirement.id,
        });
    }
    try validateTraceabilityReferences(
        allocator,
        io,
        requirement.platform_evidence_status,
        requirement.platform_evidence_references,
        "platform evidence",
        requirement.id,
        stderr,
    );
    try validateTraceabilityReferences(
        allocator,
        io,
        requirement.limitation_status,
        requirement.limitation_references,
        "limitation",
        requirement.id,
        stderr,
    );
}

fn validateTraceabilityReferences(
    allocator: std.mem.Allocator,
    io: std.Io,
    status: anytype,
    references: []const []const u8,
    kind: []const u8,
    requirement_id: []const u8,
    stderr: *std.Io.Writer,
) !void {
    const requires_reference = switch (@TypeOf(status)) {
        model_types.PlatformEvidenceStatus => status == .recorded,
        model_types.LimitationStatus => status == .recorded,
        else => @compileError("unsupported traceability status"),
    };
    if (requires_reference and references.len == 0) {
        return fail(stderr, on.requirement, "recorded {s} has no reference: {s}", .{
            kind,
            requirement_id,
        });
    }
    if (!requires_reference and references.len != 0) {
        return fail(stderr, on.requirement, "unrecorded {s} has references: {s}", .{
            kind,
            requirement_id,
        });
    }
    for (references, 0..) |reference, index| {
        if (reference.len == 0) {
            return fail(stderr, on.requirement, "empty {s} reference: {s}", .{ kind, requirement_id });
        }
        try validateOwner(allocator, io, reference, stderr);
        for (references[0..index]) |previous| {
            if (std.mem.eql(u8, previous, reference)) {
                return fail(stderr, on.requirement, "duplicate {s} reference: {s}", .{ kind, requirement_id });
            }
        }
    }
}

pub fn validateUniqueSuites(
    requirement: *const model_types.Requirement,
    stderr: *std.Io.Writer,
) !void {
    for (requirement.suites, 0..) |suite, index| {
        for (requirement.suites[0..index]) |previous| {
            if (suite == previous) {
                return fail(stderr, on.requirement, "duplicate suite on {s}", .{requirement.id});
            }
        }
        if (!profile.suiteIsConcrete(suite) and requirement.suites.len != 1) {
            return fail(
                stderr,
                on.requirement,
                "suite '{s}' must stand alone on {s}",
                .{ @tagName(suite), requirement.id },
            );
        }
    }
}

pub fn validateUniqueRequirement(
    model: *const model_types.Graph,
    requirement: *const model_types.Requirement,
    stderr: *std.Io.Writer,
) !void {
    var seen = false;
    for (model.groups) |group| {
        for (group.requirements) |candidate| {
            if (!std.mem.eql(u8, candidate.id, requirement.id)) continue;
            if (seen) {
                return fail(
                    stderr,
                    on.requirement,
                    "duplicate requirement ID: {s}",
                    .{requirement.id},
                );
            }
            seen = true;
        }
    }
    std.debug.assert(seen);
}

pub fn validateVerification(
    model: *const model_types.Graph,
    stderr: *std.Io.Writer,
) !void {
    if (model.verification.len == 0) {
        return fail(stderr, on.obligation, "verification-obligation registry is empty", .{});
    }
    if (model.verification.len > model_types.verification_count_max) {
        return fail(stderr, on.obligation, "verification-obligation count exceeds {d}", .{
            model_types.verification_count_max,
        });
    }
    for (model.verification, 0..) |obligation, index| {
        if (!text.prefixedNameValid(obligation.id, "obligation-")) {
            return fail(stderr, on.obligation, "invalid obligation ID: {s}", .{obligation.id});
        }
        if (obligation.revision == 0) {
            return fail(stderr, on.obligation, "zero obligation revision: {s}", .{obligation.id});
        }
        if (obligation.label.len == 0) {
            return fail(stderr, on.obligation, "empty obligation label: {s}", .{obligation.id});
        }
        try validateObligationRequirement(model, &obligation, stderr);
        try validateObligationSuites(&obligation, stderr);
        for (model.verification[0..index]) |previous| {
            if (std.mem.eql(u8, previous.id, obligation.id)) {
                return fail(
                    stderr,
                    on.obligation,
                    "duplicate obligation ID: {s}",
                    .{obligation.id},
                );
            }
        }
    }
}

pub fn validateObligationRequirement(
    model: *const model_types.Graph,
    obligation: *const model_types.VerificationObligation,
    stderr: *std.Io.Writer,
) !void {
    const reference = obligation.requirement;
    const requirement = model_types.findRequirement(model.groups, reference.id) orelse {
        return fail(
            stderr,
            on.obligation,
            "obligation references unknown requirement: {s}",
            .{reference.id},
        );
    };
    if (reference.revision != requirement.revision) {
        return diag.staleRequirement(stderr, reference, requirement.revision, obligation.id);
    }
    for (obligation.suites) |suite| {
        if (!model_types.requirementHasSuite(&requirement, suite)) {
            return fail(
                stderr,
                on.obligation,
                "obligation suite is absent from requirement: {s}",
                .{obligation.id},
            );
        }
    }
}

pub fn validateObligationSuites(
    obligation: *const model_types.VerificationObligation,
    stderr: *std.Io.Writer,
) !void {
    if (obligation.suites.len == 0) {
        return fail(stderr, on.obligation, "obligation has no suite: {s}", .{obligation.id});
    }
    for (obligation.suites, 0..) |suite, index| {
        if (!profile.suiteIsConcrete(suite)) {
            return fail(
                stderr,
                on.obligation,
                "obligation requires a concrete suite: {s}",
                .{obligation.id},
            );
        }
        for (obligation.suites[0..index]) |previous| {
            if (suite == previous) {
                return fail(
                    stderr,
                    on.obligation,
                    "duplicate suite on obligation: {s}",
                    .{obligation.id},
                );
            }
        }
    }
}

pub fn validateTestSpecs(
    allocator: std.mem.Allocator,
    io: std.Io,
    model: *const model_types.Graph,
    stderr: *std.Io.Writer,
) !void {
    if (model.test_specs.len == 0) {
        return fail(stderr, on.test_spec, "test-spec registry is empty", .{});
    }
    if (model.test_specs.len > model_types.test_spec_count_max) {
        return fail(
            stderr,
            on.test_spec,
            "test-spec count exceeds {d}",
            .{model_types.test_spec_count_max},
        );
    }
    for (model.test_specs, 0..) |test_spec, index| {
        try validateTestSpecIdentity(model.test_specs[0..index], &test_spec, stderr);
        try validateTestSpecClaims(model, &test_spec, stderr);
        try validateTestSpecFixture(allocator, io, &test_spec, stderr);
    }
}

pub fn validateTestSpecIdentity(
    previous_specs: []const model_types.TestSpec,
    test_spec: *const model_types.TestSpec,
    stderr: *std.Io.Writer,
) !void {
    if (!text.prefixedNameValid(test_spec.id, "test-")) {
        return fail(stderr, on.test_spec, "invalid test-spec ID: {s}", .{test_spec.id});
    }
    if (test_spec.revision == 0) {
        return fail(stderr, on.test_spec, "zero test-spec revision: {s}", .{test_spec.id});
    }
    if (!profile.suiteIsConcrete(test_spec.suite)) {
        return fail(
            stderr,
            on.test_spec,
            "test spec requires a concrete suite: {s}",
            .{test_spec.id},
        );
    }
    if (test_spec.claims.len == 0) {
        return fail(
            stderr,
            on.test_spec,
            "test spec has no verification claim: {s}",
            .{test_spec.id},
        );
    }
    for (previous_specs) |previous| {
        if (std.mem.eql(u8, previous.id, test_spec.id)) {
            return fail(stderr, on.test_spec, "duplicate test-spec ID: {s}", .{test_spec.id});
        }
    }
}

pub fn validateTestSpecClaims(
    model: *const model_types.Graph,
    test_spec: *const model_types.TestSpec,
    stderr: *std.Io.Writer,
) !void {
    var required_count: u32 = 0;
    var forbidden_count: u32 = 0;
    for (test_spec.claims, 0..) |claim, claim_index| {
        if (!text.oracleIdValid(claim.oracle)) {
            return fail(stderr, on.test_spec, "invalid claim oracle: {s}", .{claim.oracle});
        }
        if (claim.kind == .required) required_count += 1 else forbidden_count += 1;
        try validateClaimOracleKind(&claim, stderr);
        if (claim.obligations.len == 0) {
            return fail(stderr, on.test_spec, "claim has no obligation: {s}", .{claim.oracle});
        }
        for (test_spec.claims[0..claim_index]) |previous| {
            if (std.mem.eql(u8, previous.oracle, claim.oracle)) {
                return fail(stderr, on.test_spec, "duplicate claim oracle: {s}", .{claim.oracle});
            }
        }
        try validateClaimObligations(model, test_spec, &claim, stderr);
    }
    if (required_count == 0) {
        return fail(stderr, on.test_spec, "test spec has no required claim: {s}", .{test_spec.id});
    }
    if (forbidden_count == 0) {
        return fail(stderr, on.test_spec, "test spec has no forbidden claim: {s}", .{test_spec.id});
    }
}

pub fn validateClaimOracleKind(
    claim: *const model_types.VerificationClaim,
    stderr: *std.Io.Writer,
) !void {
    const prefix = if (claim.kind == .required) "INV-" else "NO-";
    if (!std.mem.startsWith(u8, claim.oracle, prefix)) {
        return fail(
            stderr,
            on.test_spec,
            "claim polarity conflicts with oracle: {s}",
            .{claim.oracle},
        );
    }
}

pub fn validateClaimObligations(
    model: *const model_types.Graph,
    test_spec: *const model_types.TestSpec,
    claim: *const model_types.VerificationClaim,
    stderr: *std.Io.Writer,
) !void {
    for (claim.obligations, 0..) |reference, index| {
        const obligation = model_types.findObligation(model.verification, reference.id) orelse {
            return fail(
                stderr,
                on.test_spec,
                "unknown obligation {s} on {s}",
                .{ reference.id, claim.oracle },
            );
        };
        if (reference.revision != obligation.revision) {
            return diag.staleObligation(stderr, reference, obligation.revision, claim.oracle);
        }
        if (claim.kind != obligation.kind) {
            return fail(
                stderr,
                on.test_spec,
                "claim polarity conflicts with obligation: {s}",
                .{reference.id},
            );
        }
        if (!model_types.suiteListContains(obligation.suites, test_spec.suite)) {
            return fail(
                stderr,
                on.test_spec,
                "test suite is not required by obligation: {s}",
                .{reference.id},
            );
        }
        for (claim.obligations[0..index]) |previous| {
            if (std.mem.eql(u8, previous.id, reference.id)) {
                return fail(
                    stderr,
                    on.test_spec,
                    "duplicate obligation on claim: {s}",
                    .{claim.oracle},
                );
            }
        }
    }
}

pub fn validateTestSpecFixture(
    allocator: std.mem.Allocator,
    io: std.Io,
    test_spec: *const model_types.TestSpec,
    stderr: *std.Io.Writer,
) !void {
    const fixture = &test_spec.fixture;
    if (!text.prefixedNameValid(fixture.id, "fixture-")) {
        return fail(stderr, on.fixture, "invalid fixture ID: {s}", .{fixture.id});
    }
    if (fixture.revision == 0) {
        return fail(stderr, on.fixture, "zero fixture revision: {s}", .{fixture.id});
    }
    if (!std.mem.startsWith(u8, fixture.path, profile.paths.fixture_root)) {
        return fail(stderr, on.fixture, "fixture outside {s}: {s}", .{
            profile.paths.fixture_root,
            fixture.path,
        });
    }
    if (!std.mem.endsWith(u8, fixture.path, ".md")) {
        return fail(stderr, on.fixture, "fixture is not Markdown: {s}", .{fixture.path});
    }
    try diag.validateRepositoryPath(io, fixture.path, stderr);
    const source = try std.Io.Dir.cwd().readFileAlloc(
        io,
        fixture.path,
        allocator,
        .limited(model_types.file_size_max),
    );
    defer allocator.free(source);
    try diag.checkFingerprint(
        source,
        fixture.sha256,
        .fixture,
        fixture.id,
        fixture.path,
        stderr,
    );
    try fixtures.validateFixtureSource(source, test_spec, stderr);
}

pub fn validateTestBindings(
    allocator: std.mem.Allocator,
    io: std.Io,
    model: *const model_types.Graph,
    stderr: *std.Io.Writer,
) !void {
    if (model.test_bindings.len > model_types.test_binding_count_max) {
        return fail(
            stderr,
            on.binding,
            "test-binding count exceeds {d}",
            .{model_types.test_binding_count_max},
        );
    }
    for (model.test_bindings, 0..) |binding, index| {
        try validateTestBindingIdentity(model.test_specs, &binding, stderr);
        for (model.test_bindings[0..index]) |previous| {
            if (std.mem.eql(u8, previous.test_spec.id, binding.test_spec.id)) {
                return fail(
                    stderr,
                    on.binding,
                    "duplicate test binding: {s}",
                    .{binding.test_spec.id},
                );
            }
        }
        const test_spec = model_types.findTestSpec(model.test_specs, binding.test_spec.id).?;
        try validateTestBindingSource(allocator, io, &test_spec, &binding, stderr);
    }
    for (model.test_specs) |test_spec| {
        try validateInstrumentationBinding(allocator, io, model.test_bindings, &test_spec, stderr);
    }
}

pub fn validateInstrumentationBinding(
    allocator: std.mem.Allocator,
    io: std.Io,
    bindings: []const model_types.TestBinding,
    test_spec: *const model_types.TestSpec,
    stderr: *std.Io.Writer,
) !void {
    const source = try std.Io.Dir.cwd().readFileAlloc(
        io,
        test_spec.fixture.path,
        allocator,
        .limited(model_types.file_size_max),
    );
    defer allocator.free(source);
    const instrumented = std.mem.indexOf(u8, source, "```zig tj-test") != null;
    const bound = model_types.findTestBinding(bindings, test_spec.id) != null;
    if (instrumented and !bound) {
        try stderr.print(
            "TJSP-E240: instrumented fixture has no executable binding\n\n" ++
                "test: {s}@{d}\nfixture: {s}@{d}\npath: {s}\n\n" ++
                "Create a fixture implementation with:\n" ++
                "  " ++ profile.tool_invocation ++ " scaffold-test {s}\n" ++
                "Then register it in {s}.\n",
            .{
                test_spec.id,
                test_spec.revision,
                test_spec.fixture.id,
                test_spec.fixture.revision,
                test_spec.fixture.path,
                test_spec.id,
                profile.paths.test_bindings,
            },
        );
        return error.InvalidSpecification;
    }
    if (!instrumented and bound) {
        return fail(
            stderr,
            on.binding,
            "binding targets an uninstrumented fixture: {s}",
            .{test_spec.id},
        );
    }
}

pub fn validateTestBindingIdentity(
    test_specs: []const model_types.TestSpec,
    binding: *const model_types.TestBinding,
    stderr: *std.Io.Writer,
) !void {
    const test_spec = model_types.findTestSpec(test_specs, binding.test_spec.id) orelse {
        return fail(
            stderr,
            on.binding,
            "binding references unknown test spec: {s}",
            .{binding.test_spec.id},
        );
    };
    if (binding.test_spec.revision != test_spec.revision) {
        return fail(stderr, on.binding, "stale test-spec binding: {s}@{d}", .{
            binding.test_spec.id,
            binding.test_spec.revision,
        });
    }
    if (binding.fixture_revision != test_spec.fixture.revision) {
        return fail(stderr, on.binding, "stale fixture binding: {s}@{d}", .{
            test_spec.fixture.id,
            binding.fixture_revision,
        });
    }
}

pub fn validateTestBindingSource(
    allocator: std.mem.Allocator,
    io: std.Io,
    test_spec: *const model_types.TestSpec,
    binding: *const model_types.TestBinding,
    stderr: *std.Io.Writer,
) !void {
    if (!std.mem.startsWith(u8, binding.source, profile.paths.binding_root)) {
        return fail(stderr, on.binding, "test binding outside {s}: {s}", .{
            profile.paths.binding_root,
            binding.source,
        });
    }
    if (!std.mem.endsWith(u8, binding.source, ".zig")) {
        return fail(stderr, on.binding, "test binding is not Zig: {s}", .{binding.source});
    }
    if (!text.symbolValid(binding.module)) {
        return fail(stderr, on.binding, "invalid test-binding module: {s}", .{binding.module});
    }
    try diag.validateRepositoryPath(io, binding.source, stderr);
    const source = try std.Io.Dir.cwd().readFileAlloc(
        io,
        binding.source,
        allocator,
        .limited(model_types.file_size_max),
    );
    defer allocator.free(source);
    try diag.checkFingerprint(
        source,
        binding.source_sha256,
        .binding,
        test_spec.id,
        binding.source,
        stderr,
    );
    if (std.mem.indexOf(u8, source, "error.SkipZigTest") != null) {
        return fail(stderr, on.binding, "bound test is skipped: {s}", .{binding.source});
    }
    if (std.mem.indexOf(u8, source, "@compileError") != null) {
        return fail(stderr, on.binding, "bound test is unfinished: {s}", .{binding.source});
    }
    try rejectProductTestDeclarations(allocator, source, binding.source, stderr);
    var parsed = fixtures.parse(allocator, io, test_spec.fixture.path) catch |err| {
        return fail(stderr, on.binding, "cannot instrument {s}: {s}", .{
            test_spec.fixture.id,
            @errorName(err),
        });
    };
    defer parsed.deinit(allocator);
    try fixtures.validateFixtureInstrumentation(test_spec, source, parsed.instrumentation, stderr);
}

pub fn rejectProductTestDeclarations(
    allocator: std.mem.Allocator,
    source: []const u8,
    path: []const u8,
    stderr: *std.Io.Writer,
) !void {
    const sentinel_source = try allocator.dupeZ(u8, source);
    defer allocator.free(sentinel_source);
    var tree = try std.zig.Ast.parse(allocator, sentinel_source, .zig);
    defer tree.deinit(allocator);
    if (tree.errors.len != 0) {
        return fail(stderr, on.binding, "cannot inspect malformed Zig source: {s}", .{path});
    }
    for (tree.rootDecls()) |declaration| {
        if (tree.nodeTag(declaration) == .test_decl) {
            return fail(
                stderr,
                on.product_test,
                "handwritten test declaration outside the fixture graph: {s}",
                .{path},
            );
        }
    }
}

pub fn validateImplementations(
    allocator: std.mem.Allocator,
    io: std.Io,
    model: *const model_types.Graph,
    stderr: *std.Io.Writer,
) !void {
    if (model.implementations.len == 0) {
        return fail(stderr, on.implementation, "implementation-unit registry is empty", .{});
    }
    if (model.implementations.len > model_types.implementation_count_max) {
        return fail(stderr, on.implementation, "implementation-unit count exceeds {d}", .{
            model_types.implementation_count_max,
        });
    }
    for (model.implementations, 0..) |unit, index| {
        try validateImplementationIdentity(model.implementations[0..index], &unit, stderr);
        try validateImplementationSources(allocator, io, model.implementations, &unit, stderr);
        try validateImplementationObligations(model.verification, &unit, stderr);
    }
}

pub fn validateImplementationIdentity(
    previous_units: []const model_types.ImplementationUnit,
    unit: *const model_types.ImplementationUnit,
    stderr: *std.Io.Writer,
) !void {
    if (!text.prefixedNameValid(unit.id, "implementation-")) {
        return fail(stderr, on.implementation, "invalid implementation-unit ID: {s}", .{unit.id});
    }
    if (unit.revision == 0) {
        return fail(
            stderr,
            on.implementation,
            "zero implementation-unit revision: {s}",
            .{unit.id},
        );
    }
    if (unit.sources.len == 0) {
        return fail(stderr, on.implementation, "implementation has no source: {s}", .{unit.id});
    }
    if (unit.realizes.len == 0) {
        return fail(stderr, on.implementation, "implementation has no obligation: {s}", .{unit.id});
    }
    for (previous_units) |previous| {
        if (std.mem.eql(u8, previous.id, unit.id)) {
            return fail(
                stderr,
                on.implementation,
                "duplicate implementation-unit ID: {s}",
                .{unit.id},
            );
        }
    }
}

pub fn validateImplementationSources(
    allocator: std.mem.Allocator,
    io: std.Io,
    units: []const model_types.ImplementationUnit,
    unit: *const model_types.ImplementationUnit,
    stderr: *std.Io.Writer,
) !void {
    for (unit.sources, 0..) |source_reference, index| {
        if (!std.mem.startsWith(u8, source_reference.path, profile.paths.implementation_root)) {
            return fail(stderr, on.implementation, "implementation source outside {s}: {s}", .{
                profile.paths.implementation_root,
                source_reference.path,
            });
        }
        if (!std.mem.endsWith(u8, source_reference.path, ".zig")) {
            return fail(
                stderr,
                on.implementation,
                "implementation source is not Zig: {s}",
                .{source_reference.path},
            );
        }
        try diag.validateRepositoryPath(io, source_reference.path, stderr);
        for (unit.sources[0..index]) |previous| {
            if (std.mem.eql(u8, previous.path, source_reference.path)) {
                return fail(
                    stderr,
                    on.implementation,
                    "duplicate source on implementation: {s}",
                    .{unit.id},
                );
            }
        }
        try validateUniqueImplementationSource(units, unit.id, source_reference.path, stderr);
        const source = try std.Io.Dir.cwd().readFileAlloc(
            io,
            source_reference.path,
            allocator,
            .limited(model_types.file_size_max),
        );
        defer allocator.free(source);
        try rejectProductTestDeclarations(allocator, source, source_reference.path, stderr);
        try diag.checkFingerprint(
            source,
            source_reference.sha256,
            .implementation,
            unit.id,
            source_reference.path,
            stderr,
        );
    }
}

pub fn validateUniqueImplementationSource(
    units: []const model_types.ImplementationUnit,
    owner_id: []const u8,
    path: []const u8,
    stderr: *std.Io.Writer,
) !void {
    var count: u32 = 0;
    for (units) |unit| {
        for (unit.sources) |source| {
            if (std.mem.eql(u8, source.path, path)) count += 1;
        }
    }
    if (count != 1) {
        return fail(
            stderr,
            on.implementation,
            "source belongs to multiple implementation units: {s} ({s})",
            .{ path, owner_id },
        );
    }
}

pub fn validateImplementationObligations(
    obligations: []const model_types.VerificationObligation,
    unit: *const model_types.ImplementationUnit,
    stderr: *std.Io.Writer,
) !void {
    for (unit.realizes, 0..) |reference, index| {
        const obligation = model_types.findObligation(obligations, reference.id) orelse {
            return fail(
                stderr,
                on.implementation,
                "implementation references unknown obligation: {s}",
                .{reference.id},
            );
        };
        if (reference.revision != obligation.revision) {
            return diag.staleObligation(stderr, reference, obligation.revision, unit.id);
        }
        for (unit.realizes[0..index]) |previous| {
            if (std.mem.eql(u8, previous.id, reference.id)) {
                return fail(
                    stderr,
                    on.implementation,
                    "duplicate obligation on implementation: {s}",
                    .{unit.id},
                );
            }
        }
    }
}

pub fn validateSourceInventories(
    allocator: std.mem.Allocator,
    io: std.Io,
    model: *const model_types.Graph,
    stderr: *std.Io.Writer,
) !void {
    try validateSourceInventory(
        allocator,
        io,
        model,
        profile.paths.implementation_inventory_root,
        .implementation,
        stderr,
    );
    try validateSourceInventory(
        allocator,
        io,
        model,
        profile.paths.binding_inventory_root,
        .binding,
        stderr,
    );
}

pub fn validateSourceInventory(
    allocator: std.mem.Allocator,
    io: std.Io,
    model: *const model_types.Graph,
    root: []const u8,
    domain: diag.FingerprintDomain,
    stderr: *std.Io.Writer,
) !void {
    const directory = try std.Io.Dir.cwd().openDir(io, root, .{ .iterate = true });
    defer directory.close(io);
    var walker = try directory.walk(allocator);
    defer walker.deinit();
    while (try walker.next(io)) |entry| {
        if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".zig")) continue;
        const path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ root, entry.path });
        defer allocator.free(path);
        const owned = switch (domain) {
            .implementation => implementationSourceOwned(model.implementations, path),
            .binding => bindingSourceOwned(model.test_bindings, path),
            .fixture => unreachable,
        };
        if (!owned) {
            const context = switch (domain) {
                .implementation => on.unregistered_implementation,
                .binding => on.unregistered_binding,
                .fixture => unreachable,
            };
            return fail(stderr, context, "source is in no registry: {s}", .{path});
        }
    }
}

pub fn implementationSourceOwned(
    units: []const model_types.ImplementationUnit,
    path: []const u8,
) bool {
    for (units) |unit| {
        for (unit.sources) |source| {
            if (std.mem.eql(u8, source.path, path)) return true;
        }
    }
    return false;
}

pub fn bindingSourceOwned(bindings: []const model_types.TestBinding, path: []const u8) bool {
    for (bindings) |binding| {
        if (std.mem.eql(u8, binding.source, path)) return true;
    }
    return false;
}

pub fn validateOwner(
    allocator: std.mem.Allocator,
    io: std.Io,
    owner: []const u8,
    stderr: *std.Io.Writer,
) !void {
    const separator = std.mem.indexOfScalar(u8, owner, '#');
    const path = if (separator) |index| owner[0..index] else owner;
    const anchor = if (separator) |index| owner[index + 1 ..] else "";
    if (path.len == 0) return fail(stderr, on.document, "owner path is empty: {s}", .{owner});
    try diag.validateRepositoryPath(io, path, stderr);
    if (anchor.len == 0) return;

    const source = std.Io.Dir.cwd().readFileAlloc(
        io,
        path,
        allocator,
        .limited(model_types.file_size_max),
    ) catch return fail(stderr, on.document, "cannot read owner: {s}", .{path});
    defer allocator.free(source);
    if (!text.markdownHasAnchor(source, anchor)) {
        return fail(stderr, on.document, "missing owner anchor: {s}", .{owner});
    }
}

test "requirement traceability rejects an unmapped invariant claim" {
    const requirement: model_types.Requirement = .{
        .id = "REQ-EXAMPLE-01",
        .revision = 1,
        .status = .accepted,
        .label = "Example.",
        .owner = "spec/PRODUCT.md",
        .suites = &.{model_types.testSuite(1)},
        .invariant_status = .mapped,
        .limitation_status = .unassessed,
    };
    var output = std.Io.Writer.Allocating.init(std.testing.allocator);
    defer output.deinit();
    try std.testing.expectError(
        error.InvalidSpecification,
        validateRequirementTraceability(
            std.testing.allocator,
            undefined,
            &.{},
            &requirement,
            &output.writer,
        ),
    );
}

test "requirement traceability requires references for recorded status" {
    const requirement: model_types.Requirement = .{
        .id = "REQ-EXAMPLE-01",
        .revision = 1,
        .status = .accepted,
        .label = "Example.",
        .owner = "spec/PRODUCT.md",
        .suites = &.{model_types.testSuite(1)},
        .platform_evidence_status = .recorded,
        .limitation_status = .unassessed,
    };
    var output = std.Io.Writer.Allocating.init(std.testing.allocator);
    defer output.deinit();
    try std.testing.expectError(
        error.InvalidSpecification,
        validateRequirementTraceability(
            std.testing.allocator,
            undefined,
            &.{},
            &requirement,
            &output.writer,
        ),
    );
}

test "handwritten product test declarations are rejected" {
    var output = std.Io.Writer.Allocating.init(std.testing.allocator);
    defer output.deinit();
    try rejectProductTestDeclarations(
        std.testing.allocator,
        "const text = \"test \\\"not a declaration\\\"\";\n",
        "sources/example.zig",
        &output.writer,
    );
    try std.testing.expectError(
        error.InvalidSpecification,
        rejectProductTestDeclarations(
            std.testing.allocator,
            "test \"unregistered\" {}\n",
            "sources/example.zig",
            &output.writer,
        ),
    );
    try std.testing.expect(std.mem.indexOf(u8, output.written(), "TJSP-E250") != null);
}

test "verification obligations reject stale requirement revisions" {
    const requirements = [_]model_types.Requirement{.{
        .id = "REQ-EXAMPLE-01",
        .revision = 2,
        .status = .accepted,
        .label = "Example.",
        .owner = "spec/PRODUCT.md",
        .suites = &.{model_types.testSuite(1)},
        .limitation_status = .unassessed,
    }};
    const groups = [_]model_types.RequirementGroup{.{
        .title = "Example",
        .requirements = &requirements,
    }};
    const obligation: model_types.VerificationObligation = .{
        .id = "obligation-example",
        .revision = 1,
        .requirement = .{ .id = "REQ-EXAMPLE-01", .revision = 1 },
        .kind = .required,
        .label = "Example.",
        .suites = &.{model_types.testSuite(1)},
    };
    const model: model_types.Graph = .{
        .documents = &.{},
        .concepts = &.{},
        .groups = &groups,
        .test_specs = &.{},
        .test_bindings = &.{},
        .verification = &.{obligation},
        .implementations = &.{},
    };
    var output = std.Io.Writer.Allocating.init(std.testing.allocator);
    defer output.deinit();
    try std.testing.expectError(
        error.InvalidSpecification,
        validateObligationRequirement(&model, &obligation, &output.writer),
    );
}

test "test scaffold is deterministic and cannot pass unfinished" {
    const claims = [_]model_types.VerificationClaim{
        .{
            .oracle = "INV-EXAMPLE",
            .kind = .required,
            .obligations = &.{.{ .id = "obligation-required", .revision = 1 }},
        },
        .{
            .oracle = "NO-EXAMPLE",
            .kind = .forbidden,
            .obligations = &.{.{ .id = "obligation-forbidden", .revision = 1 }},
        },
    };
    const test_specs = [_]model_types.TestSpec{.{
        .id = "test-example",
        .revision = 1,
        .suite = model_types.testSuite(1),
        .fixture = .{
            .id = "fixture-example",
            .revision = 1,
            .path = "fixture.md",
            .sha256 = "0000000000000000000000000000000000000000000000000000000000000000",
        },
        .claims = &claims,
        .production_boundary = .state_transition,
    }};
    var first = std.Io.Writer.Allocating.init(std.testing.allocator);
    defer first.deinit();
    var second = std.Io.Writer.Allocating.init(std.testing.allocator);
    defer second.deinit();
    try scaffold.renderFixtureScaffold(&test_specs[0], &first.writer);
    try scaffold.renderFixtureScaffold(&test_specs[0], &second.writer);
    try std.testing.expectEqualStrings(first.written(), second.written());
    try std.testing.expect(std.mem.indexOf(u8, first.written(), "@compileError") != null);
    try std.testing.expect(std.mem.indexOf(u8, first.written(), "SkipZigTest") == null);
    try std.testing.expect(std.mem.indexOf(u8, first.written(), "TODO") == null);

    const stale_binding: model_types.TestBinding = .{
        .test_spec = .{ .id = "test-example", .revision = 1 },
        .fixture_revision = 2,
        .source = "bindings/example.zig",
        .source_sha256 = "0000000000000000000000000000000000000000000000000000000000000000",
        .module = "fixture_example",
    };
    try std.testing.expectError(
        error.InvalidSpecification,
        validateTestBindingIdentity(&test_specs, &stale_binding, &first.writer),
    );
}