CtrlK
BlogDocsLog inGet started
Tessl Logo

sdas/leg-spec-from-code-oracle

Extract a complete, source-traceable legacy evidence specification for one Oracle Forms module from Forms XML/FMT/FMB/FMX, PLL/PLD, DDL, message catalogues, menus/object libraries, and screenshots. Use for fresh or incremental reverse engineering when the output must preserve every evidenced screen region, tab, field, operation, rule, message, database dependency, provenance locator, uncertainty, and source-supported detail without inventing target requirements, target architecture, target tests, POC assumptions, or implementation decisions.

66

Quality

83%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

oracle_spec_guard.pyscripts/

#!/usr/bin/env python3
"""Validate an Oracle Forms evidence model against its generated specification.

The guard is deliberately dependency-free.  It accepts either the aggregate
``evidence-model.json`` produced by the Oracle evidence compiler or a run
directory containing the contract artifacts documented by the skill.

Open, precisely registered source gaps are evidence, not validator failures.
Structural/schema errors, unaccounted coverage, stale specifications, lost gap
history, and claims that exceed the available coverage are failures.
"""

from __future__ import annotations

import argparse
import hashlib
import json
import re
import sys
from collections import defaultdict
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Iterable, Mapping, Sequence
from urllib.parse import unquote


TOOL_VERSION = "1.6.0"
SUPPORTED_SCHEMA_MAJORS = {1, 2}
SHA256_RE = re.compile(r"^[0-9a-fA-F]{64}$")
GAP_ID_RE = re.compile(r"^GAP-[A-Z0-9][A-Z0-9_-]*-[A-Z0-9][A-Z0-9_-]*$")

ARTIFACT_ALIASES = {
    "source_inventory": ("source_inventory", "source-inventory", "sources"),
    "normalized_evidence": (
        "normalized_evidence",
        "normalized-evidence",
        "facts",
    ),
    "behavior_ledger": ("behavior_ledger", "behavior-ledger", "paths"),
    "coverage": ("coverage",),
    "gaps": ("gaps", "gap_register", "gap-register"),
    "source_delta": ("source_delta", "source-delta", "delta"),
}

ARTIFACT_FILENAMES = {
    "source_inventory": "source-inventory.json",
    "normalized_evidence": "normalized-evidence.json",
    "behavior_ledger": "behavior-ledger.json",
    "coverage": "coverage.json",
    "gaps": "gaps.json",
    "source_delta": "source-delta.json",
}

CANONICAL_SECTIONS = [str(i) for i in range(1, 23)] + [
    f"Appendix {letter}" for letter in "ABCDEFGHIJ"
]

SECTION_IMPACT = {
    "source": {"2", "3", "Appendix A", "Appendix I", "Appendix J"},
    "item": {"3", "6", "8", "Appendix B"},
    "block": {"3", "6", "7", "8", "Appendix B"},
    "mapping": {"8", "15", "Appendix B", "Appendix F"},
    "behavior": {
        "6",
        "11",
        "12",
        "14",
        "16",
        "17",
        "19",
        "20",
        "Appendix C",
        "Appendix D",
        "Appendix G",
    },
    "ddl": {"8", "12", "15", "16", "Appendix F", "Appendix G"},
    "message": {"6", "12", "17", "19", "20", "Appendix C"},
    "gap": {"2", "21", "Appendix I", "Appendix J"},
    "audit": {"2", "20", "21", "Appendix I", "Appendix J"},
}

SECTION_MARKERS = {
    "2": {"executive-capabilities"},
    "3": {"legacy-screen", "legacy-regions"},
    "6": {"functional-requirements"},
    "8": {"field-inventory"},
    "11": {"actions"},
    "12": {"business-rules", "delete-save-dependencies"},
    "13": {"lookups"},
    "14": {"legacy-state-model"},
    "15": {"database-objects", "database-constraints", "database-defaults"},
    "16": {"retrieval-processing"},
    "17": {"messages"},
    "19": {"evidence-derived-tests"},
    "20": {"traceability"},
    "Appendix A": {"source-evidence"},
    "Appendix B": {"item-coverage"},
    "Appendix C": {"code-units"},
    "Appendix D": {"event-mapping"},
    "Appendix E": {"framework-exclusions"},
    "Appendix F": {"ddl-inventory"},
    "Appendix G": {"technical-notes"},
    "Appendix I": {"extraction-coverage", "missing-sources"},
    "Appendix J": {"incremental-history"},
}

MANDATORY_INCREMENTAL_MARKERS = {"extraction-coverage", "missing-sources", "incremental-history"}
MARKER_START_RE = re.compile(r'^<!--\s*oracle-evidence:start\s+key="([A-Za-z0-9_.:-]+)"\s*-->\s*$', re.M)
MARKER_END_RE = re.compile(r'^<!--\s*oracle-evidence:end\s+key="([A-Za-z0-9_.:-]+)"\s*-->\s*$', re.M)

GATE_DIMENSION_ALIASES = {
    "GATE-SOURCE-CLASSIFICATION": ("source_classification", "sources", "source_files"),
    "GATE-SOURCE-INTEGRITY": ("source_integrity", "source_files"),
    "GATE-FORMS-STRUCTURE": ("forms_structure", "blocks", "items", "regions"),
    "GATE-EFFECTIVE-CRUD": ("effective_crud", "block_crud", "item_crud"),
    "GATE-PHYSICAL-MAPPING": ("physical_mapping", "item_column_mapping", "mappings"),
    "GATE-ENTRY-POINTS": ("entry_points", "persistence_entry_points"),
    "GATE-CALL-REACHABILITY": ("call_reachability", "reachable_calls", "unresolved_calls"),
    "GATE-OPERATION-PATHS": ("operation_paths", "behavior_paths", "behavior"),
    "GATE-DATABASE-REFERENCES": (
        "database_references",
        "ddl",
        "ddl_definitions",
        "database_objects",
    ),
    "GATE-RULES-MESSAGES": ("rules_messages", "messages", "validations"),
    "GATE-TRANSACTION-BEHAVIOR": ("transaction_behavior", "transactions"),
    "GATE-SPECIFICATION-COVERAGE": ("specification_coverage", "spec_coverage"),
    "GATE-INDEPENDENT-AUDIT": ("independent_audit", "audit"),
}

LIMITING_CLAIMS = (
    (
        "EDIT_SCOPE",
        re.compile(
            r"\b(?:header\s+only|only\s+(?:the\s+)?header\b|"
            r"only\s+.{0,80}\b(?:editable|edited|updated)|"
            r"(?:updates?|edits?)\s+only\s+)",
            re.I,
        ),
        ("sources", "blocks", "items", "behavior", "audit"),
    ),
    (
        "SINGLE_WRITE_TARGET",
        re.compile(r"\b(?:only\s+(?:one|a\s+single)\s+table|updates?\s+only\s+(?:the\s+)?[A-Z0-9_$#]+)", re.I),
        ("sources", "behavior", "ddl", "audit"),
    ),
    (
        "NO_VALIDATION_OR_EFFECT",
        re.compile(r"\bno\s+(?:validation|message|side[ -]?effect|dependency|additional\s+update|custom\s+logic)s?\b", re.I),
        ("sources", "behavior", "audit"),
    ),
    (
        "ABSOLUTE_DELETE",
        re.compile(r"\bdelete\s+(?:is\s+)?(?:always\s+)?(?:blocked|cascades?)\b|\balways\s+cascades?\b", re.I),
        ("sources", "behavior", "ddl", "audit"),
    ),
    (
        "ATOMICITY",
        re.compile(r"\b(?:is|are|remains?)\s+atomic\b|\balways\s+rolls?\s+back\b", re.I),
        ("sources", "behavior", "audit"),
    ),
    (
        "NEVER_EDITABLE",
        re.compile(
            r"\bnever\s+(?:editable|updated|inserted|deleted)\b|"
            r"\b(?:field|item|block|screen|module|record)s?\b.{0,60}\bread[ -]?only\b|"
            r"\bread[ -]?only\b.{0,60}\b(?:field|item|block|screen|module|record)s?\b",
            re.I,
        ),
        ("sources", "blocks", "items", "behavior", "audit"),
    ),
    (
        "FRAMEWORK_NO_EFFECT",
        re.compile(r"\bframework\b.{0,100}\bno\s+module-specific\s+(?:business\s+)?effect\b", re.I),
        ("sources", "behavior", "audit"),
    ),
)


@dataclass
class EvidenceBundle:
    root: Path
    aggregate: dict[str, Any] = field(default_factory=dict)
    artifacts: dict[str, dict[str, Any]] = field(default_factory=dict)
    documents: list[tuple[str, dict[str, Any], Path]] = field(default_factory=list)
    audit: dict[str, Any] = field(default_factory=dict)
    load_errors: list[dict[str, Any]] = field(default_factory=list)

    def component(self, name: str) -> Any:
        if name in self.artifacts:
            return self.artifacts[name]
        for alias in ARTIFACT_ALIASES[name]:
            value = _mapping_get(self.aggregate, alias)
            if value is not None:
                return value
        artifacts = self.aggregate.get("artifacts")
        if isinstance(artifacts, Mapping):
            for alias in ARTIFACT_ALIASES[name]:
                value = _mapping_get(artifacts, alias)
                if value is not None:
                    return value
        return {}

    def records(self, name: str) -> list[dict[str, Any]]:
        return _records(self.component(name))


def _mapping_get(mapping: Any, key: str) -> Any:
    if not isinstance(mapping, Mapping):
        return None
    normalized = key.lower().replace("-", "_")
    for candidate, value in mapping.items():
        if str(candidate).lower().replace("-", "_") == normalized:
            return value
    return None


def _records(component: Any) -> list[dict[str, Any]]:
    if isinstance(component, list):
        return [row for row in component if isinstance(row, dict)]
    if not isinstance(component, Mapping):
        return []
    records = component.get("records")
    if isinstance(records, list):
        return [row for row in records if isinstance(row, dict)]
    for key in ("items", "facts", "paths", "gaps", "sources", "entries"):
        rows = component.get(key)
        if isinstance(rows, list):
            return [row for row in rows if isinstance(row, dict)]
    return []


def _read_json(path: Path, errors: list[dict[str, Any]]) -> dict[str, Any]:
    try:
        value = json.loads(path.read_text(encoding="utf-8-sig"))
    except (OSError, UnicodeError, json.JSONDecodeError) as exc:
        errors.append(_issue("EVIDENCE_JSON_PARSE", f"Cannot parse {path}: {exc}"))
        return {}
    if not isinstance(value, dict):
        errors.append(_issue("EVIDENCE_JSON_SHAPE", f"{path} must contain a JSON object."))
        return {}
    return value


def _select_run_directory(root: Path) -> Path:
    if any((root / filename).is_file() for filename in ARTIFACT_FILENAMES.values()):
        return root
    candidates = list(root.rglob("evidence-model.json"))
    if not candidates:
        candidates = list(root.rglob("normalized-evidence.json"))
    if not candidates:
        return root

    def rank(path: Path) -> tuple[str, float]:
        try:
            data = json.loads(path.read_text(encoding="utf-8-sig"))
            generated = str(data.get("generated_at") or data.get("run", {}).get("generated_at") or "")
        except Exception:
            generated = ""
        return generated, path.stat().st_mtime

    return max(candidates, key=rank).parent


def load_evidence(path_value: str | Path) -> EvidenceBundle:
    path = Path(path_value).resolve()
    bundle = EvidenceBundle(root=path if path.is_dir() else path.parent)
    if not path.exists():
        bundle.load_errors.append(_issue("EVIDENCE_NOT_FOUND", f"Evidence path does not exist: {path}"))
        return bundle

    if path.is_file():
        data = _read_json(path, bundle.load_errors)
        bundle.aggregate = data
        bundle.documents.append((path.name, data, path))
        _load_embedded_artifacts(bundle, data, path.parent)
        return bundle

    run_dir = _select_run_directory(path)
    aggregate_path = run_dir / "evidence-model.json"
    if aggregate_path.is_file():
        bundle.aggregate = _read_json(aggregate_path, bundle.load_errors)
        bundle.documents.append((aggregate_path.name, bundle.aggregate, aggregate_path))
        _load_embedded_artifacts(bundle, bundle.aggregate, run_dir)

    for name, filename in ARTIFACT_FILENAMES.items():
        artifact_path = run_dir / filename
        if artifact_path.is_file():
            data = _read_json(artifact_path, bundle.load_errors)
            bundle.artifacts[name] = data
            bundle.documents.append((name, data, artifact_path))

    run_id = str(_metadata(bundle.aggregate).get("run_id") or "")
    audit_candidates = [run_dir / "audit-status.json"]
    if run_id:
        audit_candidates.append(path / "runs" / run_id / "audit-status.json")
    for audit_path in audit_candidates:
        if audit_path.is_file():
            bundle.audit = _read_json(audit_path, bundle.load_errors)
            break

    if not bundle.aggregate and not bundle.artifacts:
        bundle.load_errors.append(
            _issue(
                "EVIDENCE_ARTIFACTS_MISSING",
                f"No evidence-model.json or contract artifacts found under {path}.",
            )
        )
    return bundle


def _load_embedded_artifacts(bundle: EvidenceBundle, aggregate: dict[str, Any], base: Path) -> None:
    artifacts = aggregate.get("artifacts")
    if not isinstance(artifacts, Mapping):
        return
    for name, aliases in ARTIFACT_ALIASES.items():
        value = None
        for alias in aliases:
            value = _mapping_get(artifacts, alias)
            if value is not None:
                break
        if isinstance(value, Mapping):
            bundle.artifacts[name] = dict(value)
            if any(
                key in value
                for key in (
                    "schema_version",
                    "extractor_version",
                    "module_id",
                    "run_id",
                    "generated_at",
                    "input_manifest_sha256",
                )
            ):
                bundle.documents.append((name, dict(value), base / f"<{name}>"))
        elif isinstance(value, list):
            bundle.artifacts[name] = {"records": value}
        elif isinstance(value, str):
            candidate = (base / value).resolve()
            if candidate.is_file():
                data = _read_json(candidate, bundle.load_errors)
                bundle.artifacts[name] = data
                bundle.documents.append((name, data, candidate))


def _issue(
    code: str,
    message: str,
    *,
    record_ids: Iterable[str] = (),
    sections: Iterable[str] = (),
    line: int | None = None,
) -> dict[str, Any]:
    value: dict[str, Any] = {
        "code": code,
        "message": message,
        "record_ids": sorted({str(item) for item in record_ids if item}),
        "sections": sorted({str(item) for item in sections if item}, key=_section_sort_key),
    }
    if line is not None:
        value["line"] = line
    return value


def _section_sort_key(value: str) -> tuple[int, str]:
    if value.isdigit():
        return int(value), ""
    match = re.match(r"Appendix\s+([A-Z])", value, re.I)
    return (100 + ord(match.group(1).upper()) - ord("A"), value) if match else (999, value)


def _metadata(document: Mapping[str, Any]) -> dict[str, Any]:
    metadata: dict[str, Any] = {}
    for holder_key in ("metadata", "run"):
        holder = document.get(holder_key)
        if isinstance(holder, Mapping):
            metadata.update(holder)
    extractor = document.get("extractor")
    if isinstance(extractor, Mapping):
        metadata.setdefault("extractor_version", extractor.get("version"))
        metadata.setdefault("schema_version", extractor.get("schema"))
    modules = document.get("modules")
    if isinstance(modules, list) and len(modules) == 1 and isinstance(modules[0], Mapping):
        metadata.setdefault("module_id", modules[0].get("module_id"))
    for key in (
        "schema_version",
        "extractor_version",
        "module_id",
        "run_id",
        "generated_at",
        "input_manifest_sha256",
        "evidence_fingerprint",
        "evidence_sha256",
    ):
        if document.get(key) is not None:
            metadata[key] = document[key]
    return metadata


def _fingerprints(bundle: EvidenceBundle) -> tuple[set[str], set[str]]:
    evidence: set[str] = set()
    manifests: set[str] = set()
    documents = bundle.documents or [("aggregate", bundle.aggregate, bundle.root)]
    for _, document, _ in documents:
        metadata = _metadata(document)
        for key in ("evidence_fingerprint", "evidence_sha256"):
            value = metadata.get(key)
            if isinstance(value, str) and value:
                evidence.add(value.lower())
        value = metadata.get("input_manifest_sha256")
        if isinstance(value, str) and value:
            manifests.add(value.lower())
    return evidence, manifests


def _module_ids(bundle: EvidenceBundle) -> set[str]:
    result: set[str] = set()
    documents = bundle.documents or [("aggregate", bundle.aggregate, bundle.root)]
    for _, document, _ in documents:
        value = _metadata(document).get("module_id")
        if isinstance(value, str) and value.strip():
            result.add(value.strip().upper())
    return result


def _validate_metadata(bundle: EvidenceBundle, errors: list[dict[str, Any]], warnings: list[dict[str, Any]]) -> None:
    errors.extend(bundle.load_errors)
    for name, document, path in bundle.documents:
        metadata = _metadata(document)
        for key in ("schema_version", "extractor_version", "module_id", "run_id", "generated_at"):
            if metadata.get(key) in (None, ""):
                errors.append(_issue("EVIDENCE_METADATA_MISSING", f"{path} is missing {key}."))
        schema = str(metadata.get("schema_version") or "")
        try:
            version_part = schema.rsplit("/", 1)[-1]
            major = int(version_part.split(".", 1)[0])
        except ValueError:
            major = -1
        if schema and major not in SUPPORTED_SCHEMA_MAJORS:
            errors.append(_issue("EVIDENCE_SCHEMA_UNSUPPORTED", f"{path} uses unsupported schema {schema}."))
        fingerprint = metadata.get("input_manifest_sha256")
        if not isinstance(fingerprint, str) or not SHA256_RE.fullmatch(fingerprint):
            errors.append(
                _issue(
                    "EVIDENCE_FINGERPRINT_INVALID",
                    f"{path} is missing a valid input_manifest_sha256.",
                )
            )
        if name != "evidence-model.json" and "records" not in document:
            warnings.append(
                _issue(
                    "ARTIFACT_RECORDS_MISSING",
                    f"Contract artifact {path} has no records array.",
                )
            )

    module_ids = _module_ids(bundle)
    if len(module_ids) > 1:
        errors.append(
            _issue(
                "EVIDENCE_MODULE_CONFLICT",
                f"Evidence artifacts disagree on module_id: {', '.join(sorted(module_ids))}.",
            )
        )
    evidence_fps, manifest_fps = _fingerprints(bundle)
    if len(manifest_fps) > 1:
        errors.append(
            _issue(
                "EVIDENCE_MANIFEST_CONFLICT",
                "Evidence artifacts carry different input manifest fingerprints.",
            )
        )
    if len(evidence_fps) > 1:
        errors.append(
            _issue(
                "EVIDENCE_FINGERPRINT_CONFLICT",
                "Evidence artifacts carry different evidence fingerprints.",
            )
        )


def _parse_frontmatter(text: str) -> dict[str, str]:
    lines = text.lstrip("\ufeff").splitlines()
    if not lines or lines[0].strip() != "---":
        return {}
    result: dict[str, str] = {}
    for line in lines[1:]:
        if line.strip() == "---":
            break
        match = re.match(r"^([A-Za-z0-9_-]+):\s*(.*?)\s*$", line)
        if match:
            result[match.group(1)] = match.group(2).strip().strip("\"'")
    return result


def _section_text(text: str, appendix: str) -> str:
    pattern = re.compile(rf"^###\s+Appendix\s+{re.escape(appendix)}\b.*$", re.I | re.M)
    match = pattern.search(text)
    if not match:
        return ""
    next_heading = re.compile(r"^###\s+Appendix\s+[A-Z]\b.*$", re.I | re.M).search(text, match.end())
    end = next_heading.start() if next_heading else len(text)
    return text[match.start() : end]


def _spec_sections(text: str) -> set[str]:
    result: set[str] = set()
    for match in re.finditer(r"^##\s+(\d+)\.", text, re.M):
        result.add(match.group(1))
    for match in re.finditer(r"^###\s+Appendix\s+([A-Z])\b", text, re.I | re.M):
        result.add(f"Appendix {match.group(1).upper()}")
    return result


def _load_spec_package(
    spec_path: Path,
    master_text: str,
    errors: list[dict[str, Any]],
) -> dict[str, tuple[Path, str]]:
    """Load generated child references declared by the master front matter."""
    result: dict[str, tuple[Path, str]] = {"master": (spec_path, master_text)}
    frontmatter = _parse_frontmatter(master_text)
    for key in ("operation_details", "decoded_source", "database_reference"):
        value = str(frontmatter.get(key) or "").strip()
        if not value:
            if str(frontmatter.get("artifact_kind") or "").lower() == "legacy_evidence_specification":
                errors.append(_issue("SPEC_PACKAGE_CHILD_UNDECLARED", f"Master specification does not declare {key}."))
            continue
        child_path = (spec_path.parent / unquote(value)).resolve()
        try:
            child_text = child_path.read_text(encoding="utf-8-sig")
        except (OSError, UnicodeError) as exc:
            errors.append(_issue("SPEC_PACKAGE_CHILD_READ_FAILED", f"Cannot read {key} child {child_path}: {exc}"))
            continue
        result[key] = (child_path, child_text)
    return result


def _numbered_section_text(text: str, section: str) -> str:
    match = re.search(rf"^##\s+{re.escape(section)}\..*$", text, re.M)
    if not match:
        return ""
    following = re.search(r"^##\s+\d+\..*$", text[match.end() :], re.M)
    end = match.end() + following.start() if following else len(text)
    return text[match.start() : end]


def _validate_spec_package(
    package: Mapping[str, tuple[Path, str]],
    master_frontmatter: Mapping[str, str],
    errors: list[dict[str, Any]],
) -> None:
    module_id = str(master_frontmatter.get("module_id") or "").upper()
    fingerprint = str(master_frontmatter.get("evidence_fingerprint") or "").lower()
    run_id = str(master_frontmatter.get("extraction_run_id") or "")
    expected = {
        "operation_details": "operation_details",
        "decoded_source": "decoded_source",
        "database_reference": "database_reference",
    }
    for key, reference_kind in expected.items():
        child = package.get(key)
        if not child:
            continue
        child_path, child_text = child
        metadata = _parse_frontmatter(child_text)
        mismatches: list[str] = []
        if metadata.get("artifact_kind") != "legacy_evidence_reference":
            mismatches.append("artifact_kind")
        if metadata.get("reference_kind") != reference_kind:
            mismatches.append("reference_kind")
        if str(metadata.get("module_id") or "").upper() != module_id:
            mismatches.append("module_id")
        if str(metadata.get("evidence_fingerprint") or "").lower() != fingerprint:
            mismatches.append("evidence_fingerprint")
        if str(metadata.get("extraction_run_id") or "") != run_id:
            mismatches.append("extraction_run_id")
        if mismatches:
            errors.append(
                _issue(
                    "SPEC_PACKAGE_CHILD_METADATA_MISMATCH",
                    f"Child {child_path.name} does not match master package metadata: {', '.join(mismatches)}.",
                )
            )

    master_text = package.get("master", (Path(), ""))[1]
    section_six = _numbered_section_text(master_text, "6")
    business_rule_match = re.search(
        r'<!--\s*oracle-evidence:start\s+key="business-rules"\s*-->(.*?)'
        r'<!--\s*oracle-evidence:end\s+key="business-rules"\s*-->',
        master_text,
        re.S,
    )
    section_twelve = business_rule_match.group(1) if business_rule_match else _numbered_section_text(master_text, "12")
    oversized_six = [len(line) for line in section_six.splitlines() if line.startswith("|") and len(line) > 4000]
    if oversized_six:
        errors.append(
            _issue(
                "SPEC_OPERATION_LEDGER_UNREADABLE",
                "Section 6 contains an oversized table row; move exhaustive path evidence to the operation-detail child.",
            )
        )
    oversized_rules = [len(line) for line in section_twelve.splitlines() if line.startswith("|") and len(line) > 5000]
    if oversized_rules:
        errors.append(
            _issue(
                "SPEC_BUSINESS_RULE_ROW_UNREADABLE",
                "Section 12 contains an oversized rule row, usually caused by repeating a path-wide message set.",
            )
        )
    repeated_rule_payloads: dict[str, int] = defaultdict(int)
    for line in section_twelve.splitlines():
        if not line.startswith("|"):
            continue
        cells = [cell.strip() for cell in re.split(r"(?<!\\)\|", line.strip("|"))]
        if len(cells) < 5:
            continue
        message_or_outcome = cells[3]
        if len(message_or_outcome) > 500:
            repeated_rule_payloads[message_or_outcome] += 1
    if any(count > 1 for count in repeated_rule_payloads.values()):
        errors.append(
            _issue(
                "SPEC_PATH_MESSAGE_SET_REPEATED",
                "Section 12 repeats a long path-wide message/outcome payload across multiple rule rows.",
            )
        )
    if section_twelve and "Message code" not in section_twelve:
        errors.append(
            _issue(
                "SPEC_BUSINESS_RULE_MESSAGE_ASSOCIATION_MISSING",
                "Section 12 must expose business condition, message code/text, effect, and association basis.",
            )
        )


def _validate_complete_specification_contract(
    bundle: EvidenceBundle,
    spec_text: str,
    frontmatter: Mapping[str, str],
    errors: list[dict[str, Any]],
    package_text: str | None = None,
) -> None:
    """Enforce the evidence-only output contract for specifications generated by this skill.

    Historical and hand-authored specifications without ``artifact_kind`` keep
    the guard's backward-compatible checks.  New generated specifications opt
    into the strict contract and cannot pass with a field-complete but
    behavior-incomplete document.
    """
    if str(frontmatter.get("artifact_kind") or "").lower() != "legacy_evidence_specification":
        return

    sections = _spec_sections(spec_text)
    missing_sections = sorted(set(CANONICAL_SECTIONS) - sections, key=_section_sort_key)
    if missing_sections:
        errors.append(
            _issue(
                "SPECIFICATION_CONTRACT_INCOMPLETE",
                "Generated extraction specification is missing canonical sections: "
                + ", ".join(missing_sections),
                sections=missing_sections,
            )
        )

    required_markers = set().union(*SECTION_MARKERS.values())
    starts = MARKER_START_RE.findall(spec_text)
    ends = MARKER_END_RE.findall(spec_text)
    missing_markers = sorted(
        key for key in required_markers if starts.count(key) != 1 or ends.count(key) != 1
    )
    if missing_markers:
        errors.append(
            _issue(
                "SPECIFICATION_MARKER_CONTRACT_INCOMPLETE",
                "Generated extraction specification lacks exactly one balanced region for: "
                + ", ".join(missing_markers),
            )
        )
    package_text = package_text or spec_text
    if "[truncated]" in package_text.casefold():
        errors.append(
            _issue(
                "SPECIFICATION_EVIDENCE_TRUNCATED",
                "Generated specification contains a truncation marker; the durable evidence surface must be lossless.",
            )
        )
    if re.search(r"\+\d+\s+more\b", package_text, re.I):
        errors.append(
            _issue(
                "SPECIFICATION_EVIDENCE_SUMMARIZED_LOSSILY",
                "Generated specification contains a lossy '+N more' placeholder.",
            )
        )

    missing_decoded_sources: list[str] = []
    modules = bundle.aggregate.get("modules", [])
    selected_units = [
        unit
        for module in modules
        if isinstance(module, Mapping)
        for collection in ("triggers", "program_units")
        for unit in module.get(collection, [])
        if isinstance(unit, Mapping)
    ]
    rendered_source_blocks = {
        match.group("hash").lower(): match.group("code").replace("\r\n", "\n").replace("\r", "\n")
        for match in re.finditer(
            r"Decoded source SHA-256:\s*`(?P<hash>[0-9a-fA-F]{64})`"
            r".*?\n(?P<fence>~{3,})sql\r?\n(?P<code>.*?)\r?\n(?P=fence)",
            package_text,
            re.S,
        )
    }
    for unit in selected_units:
        decoded_source = (
            str(unit.get("code") or "")
            .replace("\r\n", "\n")
            .replace("\r", "\n")
            .strip()
        )
        digest = str(unit.get("code_sha256") or "").lower()
        rendered_code = rendered_source_blocks.get(digest) if digest else None
        if decoded_source and rendered_code != decoded_source:
            missing_decoded_sources.append(
                str(unit.get("id") or unit.get("name") or unit.get("locator") or "decoded source")
            )
    if missing_decoded_sources:
        errors.append(
            _issue(
                "SPECIFICATION_DECODED_SOURCE_MISSING",
                "Generated specification omits exact decoded trigger or program-unit source.",
                record_ids=sorted(missing_decoded_sources),
            )
        )

    missing_records: list[str] = []
    for record in bundle.records("source_inventory"):
        locator = str(record.get("relative_path") or record.get("path") or "")
        if locator and locator not in package_text:
            missing_records.append(locator)
    for record in bundle.records("behavior_ledger"):
        entry = record.get("entry_point") or {}
        entry_symbol = (
            str(entry.get("symbol") or entry.get("scope") or "")
            if isinstance(entry, Mapping)
            else str(entry)
        )
        operation = str(record.get("operation") or "")
        if entry_symbol and (entry_symbol not in package_text or operation not in package_text):
            missing_records.append(f"{operation}/{entry_symbol}")
    for record in bundle.records("gaps"):
        subject = str(record.get("subject") or record.get("expected_artifact_or_behavior") or "")
        if subject and subject not in package_text:
            missing_records.append(subject)
    if missing_records:
        errors.append(
            _issue(
                "SPECIFICATION_MATERIAL_RECORDS_MISSING",
                "Generated specification omits source paths, behavior entries, or gap subjects.",
                record_ids=sorted(missing_records),
            )
        )
    module_id = next(iter(_module_ids(bundle)), str(frontmatter.get("module_id") or "UNKNOWN").upper())
    if str(frontmatter.get("module_evidence_id") or "") != f"MOD-{module_id}":
        errors.append(
            _issue(
                "SPECIFICATION_MODULE_ID_INVALID",
                f"Generated specification must use the single governed package ID MOD-{module_id}.",
            )
        )


def _stable_id(record: Mapping[str, Any], *keys: str) -> str:
    for key in keys:
        value = record.get(key)
        if isinstance(value, str) and value:
            return value
    return ""


def _gap_precise(gap: Mapping[str, Any]) -> tuple[bool, list[str]]:
    missing: list[str] = []
    gap_id = _stable_id(gap, "gap_id", "id")
    if not GAP_ID_RE.fullmatch(gap_id):
        missing.append("stable gap_id")
    for key in ("gap_kind", "status", "classification"):
        if not gap.get(key):
            missing.append(key)
    if not (gap.get("subject") or gap.get("expected_artifact_or_behavior")):
        missing.append("subject/expected_artifact_or_behavior")
    if not (
        gap.get("affected_operations") or gap.get("affected_fact_ids") or gap.get("affected_path_ids")
        or gap.get("affected_behavior")
    ):
        missing.append("affected behavior references")
    return not missing, missing


def _validate_gaps(
    current: EvidenceBundle,
    previous: EvidenceBundle | None,
    appendix_i: str,
    errors: list[dict[str, Any]],
    warnings: list[dict[str, Any]],
) -> tuple[dict[str, dict[str, Any]], set[str]]:
    current_gaps = {
        _stable_id(row, "gap_id", "id"): row
        for row in current.records("gaps")
        if _stable_id(row, "gap_id", "id")
    }
    precise_ids: set[str] = set()
    for gap_id, gap in current_gaps.items():
        precise, missing = _gap_precise(gap)
        if not precise:
            errors.append(
                _issue(
                    "GAP_IMPRECISE",
                    f"{gap_id} is not precise; missing {', '.join(missing)}.",
                    record_ids=[gap_id],
                    sections=["Appendix I"],
                )
            )
        else:
            precise_ids.add(gap_id)
        gap_subject = str(gap.get("subject") or gap.get("expected_artifact_or_behavior") or "")
        if gap_id not in appendix_i and (not gap_subject or gap_subject not in appendix_i):
            errors.append(
                _issue(
                    "GAP_NOT_VISIBLE",
                    f"{gap_id} and its human-readable subject are absent from Appendix I.",
                    record_ids=[gap_id],
                    sections=["Appendix I"],
                )
            )
        status = str(gap.get("status") or "").lower()
        resolution = gap.get("resolution_evidence")
        resolution_rows = resolution if isinstance(resolution, list) else [resolution] if resolution else []
        has_resolution_locator = bool(gap.get("assumption_or_decision_id")) or any(
            isinstance(row, Mapping)
            and any(row.get(key) for key in ("source_id", "locator", "decision_id", "runtime_observation_id"))
            for row in resolution_rows
        )
        if status == "resolved" and not has_resolution_locator:
            errors.append(
                _issue(
                    "GAP_RESOLUTION_UNSUPPORTED",
                    f"{gap_id} is resolved without source/locator/runtime/decision resolution evidence.",
                    record_ids=[gap_id],
                    sections=["Appendix I", "Appendix J"],
                )
            )
        elif status in {"open", "narrowed", "reopened"} and precise:
            warnings.append(
                _issue(
                    "OPEN_REGISTERED_GAP",
                    f"{gap_id} remains {status}; this is conditional readiness, not an extraction validation failure.",
                    record_ids=[gap_id],
                    sections=["Appendix I"],
                )
            )

    if previous is not None:
        previous_gaps = {
            _stable_id(row, "gap_id", "id"): row
            for row in previous.records("gaps")
            if _stable_id(row, "gap_id", "id")
        }
        for gap_id, old in previous_gaps.items():
            if gap_id not in current_gaps:
                errors.append(
                    _issue(
                        "GAP_HISTORY_LOST",
                        f"Previously registered {gap_id} is missing from current evidence; resolved gaps must be retained.",
                        record_ids=[gap_id],
                        sections=["Appendix I", "Appendix J"],
                    )
                )
                continue
            new = current_gaps[gap_id]
            for key in ("gap_kind", "subject"):
                if old.get(key) and new.get(key) and old.get(key) != new.get(key):
                    errors.append(
                        _issue(
                            "GAP_IDENTITY_CHANGED",
                            f"{gap_id} changed semantic identity field {key}.",
                            record_ids=[gap_id],
                        )
                    )
            if str(old.get("status")).lower() == "resolved" and str(new.get("status")).lower() == "open":
                errors.append(
                    _issue(
                        "GAP_REOPEN_STATUS_INVALID",
                        f"{gap_id} must use status reopened when prior resolution is invalidated.",
                        record_ids=[gap_id],
                    )
                )
    return current_gaps, precise_ids


def _int_value(value: Any, default: int = 0) -> int:
    if isinstance(value, bool):
        return int(value)
    if isinstance(value, int):
        return value
    if isinstance(value, float) and value.is_integer():
        return int(value)
    if isinstance(value, list):
        return len(value)
    try:
        return int(str(value))
    except (TypeError, ValueError):
        return default


def _coverage_records(component: Any) -> list[dict[str, Any]]:
    records = _records(component)
    if records:
        return records
    found: list[dict[str, Any]] = []

    def walk(value: Any, path: list[str]) -> None:
        if not isinstance(value, Mapping):
            return
        if any(key in value for key in ("denominator", "total")) and any(
            key in value for key in ("numerator", "classified", "resolved", "covered")
        ):
            row = dict(value)
            row.setdefault("dimension", ".".join(path))
            found.append(row)
            return
        for key, child in value.items():
            if key not in {"metadata", "run", "extractor"}:
                walk(child, path + [str(key)])

    walk(component, [])
    return found


def _normalize_metric(row: Mapping[str, Any]) -> dict[str, Any]:
    dimension = str(row.get("dimension") or row.get("metric_id") or row.get("name") or "unknown").lower()
    dimension = re.sub(r"[^a-z0-9]+", "_", dimension).strip("_")
    denominator = _int_value(row.get("denominator", row.get("total", 0)))
    numerator = _int_value(
        row.get("numerator", row.get("classified", row.get("resolved", row.get("covered", 0))))
    )
    exclusions = _int_value(row.get("exclusions", row.get("excluded_count", 0)))
    unresolved = _int_value(row.get("unresolved_count", row.get("open_unknown", row.get("unresolved", 0))))
    record_ids = [str(value) for value in row.get("record_ids", []) if value]
    unresolved_ids = [str(value) for value in row.get("unresolved_record_ids", []) if value]
    if not unresolved_ids and unresolved:
        unresolved_ids = [value for value in record_ids if value.startswith("GAP-")]
    return {
        "metric_id": str(row.get("metric_id") or dimension).upper(),
        "dimension": dimension,
        "denominator": denominator,
        "numerator": numerator,
        "exclusions": exclusions,
        "unresolved_count": unresolved,
        "record_ids": record_ids,
        "unresolved_record_ids": unresolved_ids,
        "declared_status": str(row.get("status") or "").lower(),
    }


def _dimension_matches(dimension: str, aliases: Sequence[str]) -> bool:
    normalized = dimension.lower()
    return any(alias == normalized or alias in normalized for alias in aliases)


def _validate_coverage(
    bundle: EvidenceBundle,
    gaps: Mapping[str, Mapping[str, Any]],
    precise_gap_ids: set[str],
    errors: list[dict[str, Any]],
    warnings: list[dict[str, Any]],
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    metrics = [_normalize_metric(row) for row in _coverage_records(bundle.component("coverage"))]
    if bundle.audit:
        audit_status = str(bundle.audit.get("status") or "pending").lower()
        audit_passed = audit_status in {"pass", "passed", "pass_with_registered_gaps", "pass_with_assumptions"}
        metrics.append(
            _normalize_metric(
                {
                    "metric_id": "COV-INDEPENDENT-AUDIT",
                    "dimension": "independent_audit",
                    "denominator": 1,
                    "numerator": 1 if audit_passed else 0,
                    "unresolved_count": 0 if audit_passed else 1,
                    "record_ids": [str(bundle.audit.get("run_id") or "audit-status")],
                    "unresolved_record_ids": [
                        str(finding.get("finding_id") or finding.get("id"))
                        for finding in bundle.audit.get("findings", [])
                        if str(finding.get("severity") or "").lower() in {"blocking", "error", "high"}
                    ] or (["AUDIT-PENDING"] if not audit_passed else []),
                    "status": "measured" if audit_passed else "fail",
                }
            )
        )

    def associated_gap_ids(metric: Mapping[str, Any]) -> set[str]:
        unresolved_records = {str(value).upper() for value in metric["unresolved_record_ids"]}
        result = {gap_id for gap_id in unresolved_records if gap_id in precise_gap_ids}
        for gap_id, gap in gaps.items():
            if gap_id not in precise_gap_ids:
                continue
            affected = {
                str(value).upper()
                for key in ("affected_fact_ids", "affected_path_ids")
                for value in (gap.get(key) or [])
            }
            subject = str(gap.get("subject") or "").upper()
            if unresolved_records & affected or (subject and subject in unresolved_records):
                result.add(gap_id)
        return result

    for metric in metrics:
        reason_ids = metric["record_ids"]
        if min(
            metric["denominator"],
            metric["numerator"],
            metric["exclusions"],
            metric["unresolved_count"],
        ) < 0:
            errors.append(_issue("COVERAGE_COUNT_INVALID", f"{metric['metric_id']} has a negative count.", record_ids=reason_ids))
            continue
        if metric["numerator"] > metric["denominator"] or metric["unresolved_count"] > metric["denominator"]:
            errors.append(_issue("COVERAGE_COUNT_INVALID", f"{metric['metric_id']} counts exceed its denominator.", record_ids=reason_ids))
        accounted_without_exclusions = metric["numerator"] + metric["unresolved_count"]
        accounted_with_exclusions = accounted_without_exclusions + metric["exclusions"]
        if metric["denominator"] and metric["denominator"] not in {
            accounted_without_exclusions,
            accounted_with_exclusions,
        }:
            errors.append(
                _issue(
                    "COVERAGE_RECONCILIATION_FAILED",
                    f"{metric['metric_id']} does not reconcile: denominator={metric['denominator']}, "
                    f"numerator={metric['numerator']}, exclusions={metric['exclusions']}, "
                    f"unresolved={metric['unresolved_count']}.",
                    record_ids=reason_ids,
                )
            )
        if metric["unresolved_count"]:
            resolved_by_gaps = associated_gap_ids(metric)
            if len(resolved_by_gaps) < metric["unresolved_count"]:
                errors.append(
                    _issue(
                        "COVERAGE_UNACCOUNTED",
                        f"{metric['metric_id']} has unresolved records without precise gap IDs.",
                        record_ids=metric["unresolved_record_ids"],
                    )
                )
        if metric["declared_status"] in {"not_evaluated", "not_evaluated_pre_specification"}:
            errors.append(
                _issue(
                    "SPECIFICATION_COVERAGE_NOT_EVALUATED",
                    f"{metric['metric_id']} is still marked {metric['declared_status']}.",
                    record_ids=metric["record_ids"],
                    sections=["20", "Appendix I"],
                )
            )

    gates: list[dict[str, Any]] = []
    for gate_id, aliases in GATE_DIMENSION_ALIASES.items():
        matched = [metric for metric in metrics if _dimension_matches(metric["dimension"], aliases)]
        if not matched:
            gates.append(
                {
                    "gate_id": gate_id,
                    "level": "evidence",
                    "status": "not_applicable",
                    "numerator": 0,
                    "denominator": 0,
                    "record_ids": [],
                    "blocking_gap_ids": [],
                    "reason": "No corresponding coverage metric was emitted.",
                }
            )
            continue
        denominator = sum(metric["denominator"] for metric in matched)
        numerator = sum(metric["numerator"] for metric in matched)
        unresolved = sum(metric["unresolved_count"] for metric in matched)
        record_ids = sorted({item for metric in matched for item in metric["record_ids"]})
        unresolved_ids = sorted({item for metric in matched for item in metric["unresolved_record_ids"]})
        accounted_gap_ids = sorted({gap_id for metric in matched for gap_id in associated_gap_ids(metric)})
        unaccounted = unresolved and len(accounted_gap_ids) < unresolved
        declared_fail = any(
            metric["declared_status"] in {"fail", "not_evaluated", "not_evaluated_pre_specification"}
            for metric in matched
        )
        reconciled = all(
            metric["denominator"]
            in {
                metric["numerator"] + metric["unresolved_count"],
                metric["numerator"] + metric["exclusions"] + metric["unresolved_count"],
            }
            for metric in matched
            if metric["denominator"]
        )
        if denominator == 0:
            status = "not_applicable"
            reason = "The emitted metric has a zero denominator."
        elif declared_fail or unaccounted or not reconciled:
            status = "fail"
            reason = "Coverage is failed, unaccounted, or arithmetically inconsistent."
        elif unresolved:
            status = "pass_with_registered_gaps"
            reason = "All unresolved records are accounted for by precise evidence gaps."
        else:
            status = "pass"
            reason = "All records are dispositioned."
        gates.append(
            {
                "gate_id": gate_id,
                "level": "evidence",
                "status": status,
                "numerator": numerator,
                "denominator": denominator,
                "record_ids": record_ids,
                "blocking_gap_ids": unresolved_ids if status == "fail" else [],
                "reason": reason,
            }
        )

    _detect_zero_count_regressions(bundle, metrics, errors, warnings)
    return metrics, gates


def _detect_zero_count_regressions(
    bundle: EvidenceBundle,
    metrics: Sequence[Mapping[str, Any]],
    errors: list[dict[str, Any]],
    warnings: list[dict[str, Any]],
) -> None:
    sources = bundle.records("source_inventory")
    facts = bundle.records("normalized_evidence")
    paths = bundle.records("behavior_ledger")
    fact_kinds = [str(row.get("fact_kind") or row.get("kind") or "").lower() for row in facts]

    readable_forms = any(
        str(row.get("source_role") or "").lower() in {"forms_xml", "fmt"}
        and str(row.get("availability") or "").lower() in {"readable", "partial"}
        and str(row.get("parse_status") or "").lower() in {"parsed", "partially_parsed"}
        for row in sources
    )
    readable_ddl = any(
        str(row.get("source_role") or "").lower() == "ddl"
        and str(row.get("availability") or "").lower() == "readable"
        and str(row.get("parse_status") or "").lower() == "parsed"
        for row in sources
    )
    block_facts = sum(kind in {"block", "block_crud", "forms_block"} for kind in fact_kinds)
    item_facts = sum(kind in {"item", "item_crud", "forms_item"} for kind in fact_kinds)
    ddl_facts = sum(kind in {"database_object", "ddl_object", "table", "view", "sequence"} for kind in fact_kinds)

    def max_denominator(aliases: Sequence[str]) -> int | None:
        matches = [metric["denominator"] for metric in metrics if _dimension_matches(str(metric["dimension"]), aliases)]
        return max(matches) if matches else None

    required_metrics = {
        "source": (bool(sources), ("source_classification", "source_files")),
        "block": (bool(block_facts), ("block", "forms_structure")),
        "item": (bool(item_facts), ("item", "effective_crud")),
        "behavior": (bool(paths), ("behavior", "operation_paths", "entry_point")),
        "DDL": (bool(ddl_facts or readable_ddl), ("ddl", "database_reference", "database_object")),
    }
    for label, (applicable, aliases) in required_metrics.items():
        if applicable and not any(_dimension_matches(str(metric["dimension"]), aliases) for metric in metrics):
            errors.append(
                _issue(
                    "COVERAGE_METRIC_MISSING",
                    f"{label} evidence exists but no corresponding coverage metric was emitted.",
                )
            )

    if readable_forms and block_facts == 0:
        errors.append(
            _issue(
                "ZERO_COUNT_FORMS_BLOCKS",
                "Readable parsed Forms XML/FMT produced zero block facts; possible namespace/parser regression.",
            )
        )
    if readable_forms and item_facts == 0:
        errors.append(
            _issue(
                "ZERO_COUNT_FORMS_ITEMS",
                "Readable parsed Forms XML/FMT produced zero item facts; possible namespace/parser regression.",
            )
        )
    if readable_forms and max_denominator(("blocks", "block_crud", "forms_structure")) == 0:
        errors.append(_issue("ZERO_COUNT_BLOCK_COVERAGE", "Forms block coverage denominator is zero despite readable Forms source."))
    if readable_forms and max_denominator(("items", "item_crud", "forms_structure")) == 0:
        errors.append(_issue("ZERO_COUNT_ITEM_COVERAGE", "Forms item coverage denominator is zero despite readable Forms source."))
    if readable_ddl and ddl_facts == 0:
        errors.append(
            _issue(
                "ZERO_COUNT_DDL_OBJECTS",
                "Readable parsed DDL produced zero database-object facts; possible DDL parser regression.",
            )
        )
    if any(kind in {"trigger", "program_unit", "call_edge"} for kind in fact_kinds) and not paths:
        warnings.append(
            _issue(
                "ZERO_COUNT_BEHAVIOR_PATHS",
                "Executable Forms facts exist but the behavior ledger contains zero paths; verify reachability disposition.",
            )
        )


def _metric_category(metric: Mapping[str, Any]) -> set[str]:
    dimension = str(metric.get("dimension") or "")
    result: set[str] = set()
    aliases = {
        "sources": ("source",),
        "blocks": ("block", "forms_structure"),
        "items": ("item", "physical_mapping", "effective_crud"),
        "behavior": ("behavior", "operation", "entry_point", "call", "transaction", "message", "validation"),
        "ddl": ("ddl", "database_reference", "database_object"),
        "audit": ("audit",),
    }
    for category, names in aliases.items():
        if _dimension_matches(dimension, names):
            result.add(category)
    return result


def _coverage_supports_claim(
    required: Sequence[str], metrics: Sequence[Mapping[str, Any]], gaps: Mapping[str, Mapping[str, Any]]
) -> tuple[bool, list[str]]:
    missing: list[str] = []
    for category in required:
        matched = [metric for metric in metrics if category in _metric_category(metric)]
        if not matched or any(metric["denominator"] == 0 or metric["unresolved_count"] for metric in matched):
            missing.append(category)
    open_gap_ids = [
        gap_id
        for gap_id, gap in gaps.items()
        if str(gap.get("status") or "").lower() in {"open", "narrowed", "reopened"}
        and str(gap.get("classification") or "").lower()
        in {"source_gap", "extraction_gap", "evidence_conflict", "runtime_validation_gap"}
    ]
    if open_gap_ids and any(category in required for category in ("sources", "behavior", "ddl")):
        missing.append("open behavior-affecting gaps")
    return not missing, sorted(set(missing))


def _validate_spec_claims(
    spec_text: str,
    metrics: Sequence[Mapping[str, Any]],
    gaps: Mapping[str, Mapping[str, Any]],
    errors: list[dict[str, Any]],
) -> None:
    full_inventory = re.compile(r"\bfull\b.{0,40}\binventor(?:y|ies)\b|\binventor(?:y|ies)\b.{0,40}\bfull\b", re.I)
    appendix_b = _section_text(spec_text, "B")
    item_denominators = [
        metric["denominator"]
        for metric in metrics
        if "item_crud" in str(metric.get("dimension") or "")
        or str(metric.get("dimension") or "") in {"items", "item_inventory"}
    ]

    def inventory_row_count() -> int:
        count = 0
        for row in appendix_b.splitlines():
            if not row.strip().startswith("|"):
                continue
            cells = [cell.strip() for cell in row.strip().strip("|").split("|")]
            if not cells or all(re.fullmatch(r":?-{3,}:?", cell or "---") for cell in cells):
                continue
            first = cells[0].lower()
            if first in {"evidence id", "field id", "block.item", "legacy item/column"}:
                continue
            count += 1
        return count

    in_code_fence = False
    fence_character = ""
    fence_length = 0
    for line_number, line in enumerate(spec_text.splitlines(), 1):
        stripped = line.strip()
        fence_match = re.match(r"^(`{3,}|~{3,})", stripped)
        if fence_match:
            marker = fence_match.group(1)
            if not in_code_fence:
                in_code_fence = True
                fence_character = marker[0]
                fence_length = len(marker)
            elif marker[0] == fence_character and len(marker) >= fence_length:
                in_code_fence = False
                fence_character = ""
                fence_length = 0
            continue
        if in_code_fence or not stripped:
            continue
        if full_inventory.search(stripped):
            supported, missing = _coverage_supports_claim(("items",), metrics, gaps)
            if not supported:
                errors.append(
                    _issue(
                        "MISLEADING_FULL_INVENTORY",
                        f"Line {line_number} claims a full inventory without reconciled item coverage ({', '.join(missing)}).",
                        sections=["Appendix B"],
                        line=line_number,
                    )
                )
            elif item_denominators and appendix_b:
                expected = max(item_denominators)
                actual = inventory_row_count()
                if actual != expected:
                    errors.append(
                        _issue(
                            "FULL_INVENTORY_ROW_MISMATCH",
                            f"Appendix B claims a full inventory but has {actual} data rows; evidence item denominator is {expected}.",
                            sections=["Appendix B"],
                            line=line_number,
                        )
                    )
        for claim_code, pattern, required in LIMITING_CLAIMS:
            if not pattern.search(stripped):
                continue
            if claim_code == "ABSOLUTE_DELETE" and re.search(
                r"\bON\s+DELETE\s+(?:CASCADE|SET\s+NULL)\b",
                stripped,
                re.I,
            ):
                # An exact FK clause is positive DDL evidence about that
                # constraint, not a module-wide claim that every delete always
                # cascades or is blocked.
                continue
            bounded = bool(
                re.search(r"\b(?:design-time|supplied\s+readable|within\s+the\s+supplied|no\s+additional\s+.+\s+was\s+found)\b", stripped, re.I)
                and re.search(r"GAP-[A-Z0-9_-]+", stripped)
            )
            supported, missing = _coverage_supports_claim(required, metrics, gaps)
            if not supported and not bounded:
                errors.append(
                    _issue(
                        "UNSUPPORTED_LIMITING_CLAIM",
                        f"Line {line_number} contains unsupported {claim_code.lower().replace('_', ' ')} certainty; "
                        f"coverage is missing or unresolved for {', '.join(missing)}.",
                        line=line_number,
                    )
                )


def _validate_visual_region_specification(
    spec_text: str, facts: Sequence[Mapping[str, Any]], errors: list[dict[str, Any]]
) -> None:
    """Require rendered field/grid tables to reconcile to extracted visible regions.

    Older evidence models do not contain item_visual_placement facts, so this
    rule activates only for models produced by the region-aware compiler.
    """
    placements = [
        fact for fact in facts
        if str(fact.get("fact_kind") or "") == "item_visual_placement"
        and isinstance(fact.get("value"), Mapping)
        and fact["value"].get("rendered", fact["value"].get("visible") is not False)
    ]
    if not placements:
        return

    tab_labels = {
        str(fact.get("value", {}).get("id") or fact.get("subject", {}).get("key") or ""): str(fact.get("value", {}).get("label") or "")
        for fact in facts if str(fact.get("fact_kind") or "") == "tab"
    }
    section_match = re.search(r"^##\s+8(?:\.|\s).*$", spec_text, re.I | re.M)
    if not section_match:
        errors.append(_issue("FIELD_SPECIFICATION_SECTION_MISSING", "Specification lacks Section 8 for visible-region field coverage.", sections=["8"]))
        return
    next_section = re.search(r"^##\s+9(?:\.|\s).*$", spec_text[section_match.end():], re.I | re.M)
    section = spec_text[section_match.end(): section_match.end() + next_section.start()] if next_section else spec_text[section_match.end():]

    def heading_body(heading: str) -> str | None:
        match = re.search(rf"^###\s+{re.escape(heading)}\s*$", section, re.I | re.M)
        if not match:
            return None
        following = re.search(r"^#{2,3}\s+", section[match.end():], re.M)
        return section[match.end(): match.end() + following.start()] if following else section[match.end():]

    groups: dict[tuple[str, str], list[Mapping[str, Any]]] = {}
    for fact in placements:
        value = fact["value"]
        if value.get("presentation_shape") == "grid_column":
            key = ("Grid", str(value.get("block") or "UNKNOWN"))
        elif value.get("region_kind") == "tab_page":
            label = str(value.get("tab_label") or tab_labels.get(str(value.get("tab_page") or "")) or value.get("tab_page") or "UNKNOWN")
            key = ("Tab", label)
        else:
            key = ("Section", str(value.get("region_id") or "UNASSIGNED"))
        groups.setdefault(key, []).append(fact)

    for (kind, name), group in sorted(groups.items()):
        heading = f"{kind}: {name}"
        body = heading_body(heading)
        ids = [fact["fact_id"] for fact in group]
        if body is None:
            errors.append(_issue("VISUAL_REGION_SECTION_MISSING", f"Section 8 is missing the required heading '{heading}'.", record_ids=ids, sections=["8"]))
            continue
        if not re.search(r"^\s*\|.+\|\s*$", body, re.M):
            errors.append(_issue("VISUAL_REGION_TABLE_MISSING", f"'{heading}' has no Markdown field/column table.", record_ids=ids, sections=["8"]))
            continue
        missing = [
            f"{fact['value'].get('block')}.{fact['value'].get('item')}"
            for fact in group
            if f"{fact['value'].get('block')}.{fact['value'].get('item')}" not in body
        ]
        if missing:
            errors.append(_issue("VISUAL_REGION_ITEM_UNACCOUNTED", f"'{heading}' does not account for: {', '.join(missing)}.", record_ids=ids, sections=["8"]))


def _validate_poc_assumption_trace(
    spec_text: str,
    frontmatter: Mapping[str, str],
    gaps: Mapping[str, Mapping[str, Any]],
    errors: list[dict[str, Any]],
) -> None:
    readiness = str(frontmatter.get("poc_forward_engineering_readiness") or "").lower()
    if "ready" not in readiness:
        return
    for gap_id, gap in gaps.items():
        if str(gap.get("status") or "").lower() not in {"open", "narrowed", "reopened"}:
            continue
        assumption = gap.get("poc_assumption")
        missing: list[str] = []
        if not isinstance(assumption, Mapping):
            missing.append("poc_assumption")
            assumption = {}
        status = str(assumption.get("status") or "")
        if not status.startswith("accepted_for_current_poc"):
            missing.append("POC acceptance status")
        for key in (
            "assumption_id", "bounded_scope", "assumed_target_behavior", "acceptance_decision_id",
            "business_validation_state", "validation_owner", "acceptance_test_ids",
            "destructive_containment", "production_use",
        ):
            if not assumption.get(key):
                missing.append(key)
        if missing:
            errors.append(
                _issue(
                    "POC_ASSUMPTION_UNGOVERNED",
                    f"{gap_id} cannot support POC readiness; missing {', '.join(missing)}.",
                    record_ids=[gap_id, str(assumption.get("assumption_id") or "")],
                    sections=["19", "21", "Appendix I"],
                )
            )
            continue
        decision_id = str(assumption["acceptance_decision_id"])
        test_ids = [str(value) for value in assumption.get("acceptance_test_ids", []) if value]
        missing_spec_ids = [identifier for identifier in [decision_id, *test_ids] if identifier not in spec_text]
        if missing_spec_ids:
            errors.append(
                _issue(
                    "POC_ASSUMPTION_TRACE_MISSING",
                    f"{gap_id} references POC decision/tests absent from the specification: {', '.join(missing_spec_ids)}.",
                    record_ids=[gap_id, *missing_spec_ids],
                    sections=["19", "21", "Appendix I"],
                )
            )


def _normalize_section(value: Any) -> str | None:
    if isinstance(value, Mapping):
        value = value.get("section_key") or value.get("section")
    if value is None:
        return None
    text = str(value).strip()
    number = re.match(r"^(?:Section\s+)?(\d+)\b", text, re.I)
    if number:
        return number.group(1)
    appendix = re.match(r"^(?:###\s+)?Appendix\s+([A-J])\b", text, re.I)
    if appendix:
        return f"Appendix {appendix.group(1).upper()}"
    return text if text in CANONICAL_SECTIONS else None


def _classify_reason(value: str) -> str:
    upper = value.upper()
    if upper.startswith("PATH-") or any(word in upper for word in ("TRIGGER", "ROUTINE", "CALL", "TRANSACTION", "OPERATION")):
        return "behavior"
    if upper.startswith("GAP-"):
        return "gap"
    if "ITEM_CRUD" in upper or "/ITEM" in upper:
        return "item"
    if "BLOCK_CRUD" in upper or "/BLOCK" in upper:
        return "block"
    if any(word in upper for word in ("DDL", "DATABASE_OBJECT", "TABLE", "VIEW", "SEQUENCE", "CONSTRAINT")):
        return "ddl"
    if any(word in upper for word in ("MAPPING", "COLUMN")):
        return "mapping"
    if "MESSAGE" in upper or "VALIDATION" in upper:
        return "message"
    if upper.startswith("SRC-") or "SOURCE" in upper:
        return "source"
    if "AUDIT" in upper:
        return "audit"
    return "source"


def _delta_component(bundle: EvidenceBundle, explicit_path: str | Path | None) -> Any:
    if explicit_path:
        errors: list[dict[str, Any]] = []
        data = _read_json(Path(explicit_path).resolve(), errors)
        if errors:
            return {"_load_errors": errors, "records": []}
        return data
    return bundle.component("source_delta")


def _extract_human_conflicts(bundle: EvidenceBundle, delta: Any) -> list[dict[str, Any]]:
    values: list[Any] = []
    for holder in (bundle.aggregate, delta):
        if isinstance(holder, Mapping):
            direct = holder.get("human_decision_conflicts")
            if isinstance(direct, list):
                values.extend(direct)
            conflicts = holder.get("conflicts")
            if isinstance(conflicts, list):
                values.extend(
                    item
                    for item in conflicts
                    if isinstance(item, Mapping)
                    and str(item.get("classification") or item.get("conflict_kind") or "").lower()
                    in {"target_decision", "human_decision", "human_decision_conflict"}
                )
    result: list[dict[str, Any]] = []
    for index, value in enumerate(values):
        if isinstance(value, str):
            result.append({"conflict_id": f"HUMAN-CONFLICT-{index + 1:03d}", "description": value, "status": "open"})
        elif isinstance(value, Mapping):
            result.append(dict(value))
    return result


def _section_impact(
    current: EvidenceBundle,
    previous: EvidenceBundle | None,
    delta: Any,
    stale: bool,
    spec_sections: set[str],
    errors: list[dict[str, Any]],
) -> tuple[list[dict[str, Any]], list[str], list[dict[str, Any]]]:
    human_conflicts = _extract_human_conflicts(current, delta)
    for conflict in human_conflicts:
        status = str(conflict.get("status") or "open").lower()
        if status not in {"resolved", "accepted"}:
            record_id = _stable_id(conflict, "conflict_id", "id")
            errors.append(
                _issue(
                    "HUMAN_DECISION_CONFLICT",
                    "Incremental evidence conflicts with a preserved human decision.",
                    record_ids=[record_id],
                )
            )

    if not stale:
        return [], sorted(spec_sections or set(CANONICAL_SECTIONS), key=_section_sort_key), human_conflicts

    impact: dict[str, set[str]] = {}
    delta_records = _records(delta)
    if isinstance(delta, Mapping):
        for raw in delta.get("affected_spec_sections", []):
            section = _normalize_section(raw)
            if section:
                reason_ids: list[str] = []
                if isinstance(raw, Mapping):
                    reason_ids = [str(item) for item in raw.get("reason_record_ids", [])]
                impact.setdefault(section, set()).update(reason_ids or ["SOURCE-DELTA"])

    incomplete_records: list[str] = []
    for index, record in enumerate(delta_records):
        change = str(record.get("change") or "changed").lower()
        if change == "unchanged":
            continue
        record_id = _stable_id(record, "delta_id", "source_id", "source_key") or f"DELTA-{index + 1}"
        explicit_sections = record.get("affected_spec_sections") or record.get("affected_section_keys") or []
        reason_ids = [
            str(item)
            for key in ("affected_fact_ids", "affected_path_ids", "affected_gap_ids", "affected_semantic_keys")
            for item in (record.get(key) or [])
        ]
        if explicit_sections:
            for raw in explicit_sections:
                section = _normalize_section(raw)
                if section:
                    impact.setdefault(section, set()).update(reason_ids or [record_id])
        elif reason_ids:
            for reason in reason_ids:
                category = _classify_reason(reason)
                for section in SECTION_IMPACT[category]:
                    impact.setdefault(section, set()).add(reason)
            for section in SECTION_IMPACT["source"]:
                impact.setdefault(section, set()).add(record_id)
        else:
            incomplete_records.append(record_id)

    if not delta_records and previous is not None:
        old_records: dict[str, tuple[str, Mapping[str, Any]]] = {}
        new_records: dict[str, tuple[str, Mapping[str, Any]]] = {}
        for component, id_keys in (
            ("normalized_evidence", ("fact_id", "id")),
            ("behavior_ledger", ("path_id", "id")),
            ("gaps", ("gap_id", "id")),
        ):
            for row in previous.records(component):
                record_id = _stable_id(row, *id_keys)
                if record_id:
                    old_records[record_id] = (component, row)
            for row in current.records(component):
                record_id = _stable_id(row, *id_keys)
                if record_id:
                    new_records[record_id] = (component, row)
        for record_id in sorted(set(old_records) | set(new_records)):
            if old_records.get(record_id) != new_records.get(record_id):
                category = _classify_reason(record_id)
                for section in SECTION_IMPACT[category]:
                    impact.setdefault(section, set()).add(record_id)

    if incomplete_records or not impact:
        errors.append(
            _issue(
                "SECTION_IMPACT_INCOMPLETE",
                "The evidence fingerprint changed but the affected specification section boundary is incomplete.",
                record_ids=incomplete_records,
            )
        )
        if not impact:
            for section in CANONICAL_SECTIONS:
                impact.setdefault(section, set()).add("UNBOUNDED-SOURCE-DELTA")

    stale_sections = [
        {
            "section_key": section,
            "reason_record_ids": sorted(reason_ids),
            "patch_required": True,
        }
        for section, reason_ids in sorted(impact.items(), key=lambda pair: _section_sort_key(pair[0]))
    ]
    known_sections = spec_sections or set(CANONICAL_SECTIONS)
    preserved = sorted(known_sections - set(impact), key=_section_sort_key)
    return stale_sections, preserved, human_conflicts


def _validate_appendices(spec_text: str, errors: list[dict[str, Any]]) -> tuple[str, str]:
    appendix_i = _section_text(spec_text, "I")
    appendix_j = _section_text(spec_text, "J")
    if not appendix_i:
        errors.append(_issue("APPENDIX_I_MISSING", "Mandatory Appendix I is missing.", sections=["Appendix I"]))
    else:
        if not re.search(r"Coverage\s+Summary", appendix_i, re.I):
            errors.append(_issue("APPENDIX_I_COVERAGE_MISSING", "Appendix I lacks its Coverage Summary.", sections=["Appendix I"]))
        if not re.search(r"(?:Missing\s+Source|Evidence\s+Gaps?)", appendix_i, re.I):
            errors.append(_issue("APPENDIX_I_GAPS_MISSING", "Appendix I lacks the missing-source/evidence-gap register.", sections=["Appendix I"]))
    if not appendix_j:
        errors.append(_issue("APPENDIX_J_MISSING", "Mandatory Appendix J is missing.", sections=["Appendix J"]))
    else:
        if not re.search(r"Run\s+ID", appendix_j, re.I) or not re.search(r"Evidence\s+fingerprint", appendix_j, re.I):
            errors.append(_issue("APPENDIX_J_HISTORY_INCOMPLETE", "Appendix J lacks run ID or evidence fingerprint history columns.", sections=["Appendix J"]))
    return appendix_i, appendix_j


def _validate_markers(
    spec_text: str,
    frontmatter: Mapping[str, str],
    stale_sections: Sequence[Mapping[str, Any]],
    errors: list[dict[str, Any]],
) -> None:
    starts = MARKER_START_RE.findall(spec_text)
    ends = MARKER_END_RE.findall(spec_text)
    start_set, end_set = set(starts), set(ends)
    for key in sorted(start_set | end_set):
        start_count, end_count = starts.count(key), ends.count(key)
        if start_count != 1 or end_count != 1:
            errors.append(
                _issue(
                    "EVIDENCE_MARKER_UNBALANCED",
                    f"Evidence marker {key!r} has {start_count} starts and {end_count} ends; exactly one pair is required.",
                )
            )
            continue
        start_match = re.search(
            rf'^<!--\s*oracle-evidence:start\s+key="{re.escape(key)}"\s*-->\s*$', spec_text, re.M
        )
        end_match = re.search(
            rf'^<!--\s*oracle-evidence:end\s+key="{re.escape(key)}"\s*-->\s*$', spec_text, re.M
        )
        if start_match and end_match and end_match.start() < start_match.end():
            errors.append(_issue("EVIDENCE_MARKER_ORDER", f"Evidence marker {key!r} ends before it starts."))

    # Markers are optional in the lightweight one-time migration workflow.
    # Enforce impacted-region boundaries only when a specification opted in by
    # including at least one marker pair.
    if not start_set:
        return

    for stale in stale_sections:
        section = str(stale.get("section_key") or "")
        expected = SECTION_MARKERS.get(section, set())
        if expected and not expected.issubset(start_set):
            missing = sorted(expected - start_set)
            errors.append(
                _issue(
                    "IMPACTED_MARKER_MISSING",
                    f"Affected {section} cannot be safely patched because marker(s) {', '.join(missing)} are absent.",
                    record_ids=stale.get("reason_record_ids", []),
                    sections=[section],
                )
            )


def _validate_local_links(
    spec_path: Path,
    spec_text: str,
    frontmatter: Mapping[str, str],
    errors: list[dict[str, Any]],
) -> None:
    candidates: list[tuple[str, str]] = []
    for match in re.finditer(r"!?\[[^\]]*\]\((?:<([^>]+)>|([^\s\)]+))(?:\s+['\"][^\)]*['\"])?\)", spec_text):
        candidates.append((match.group(1) or match.group(2), "Markdown link"))
    for match in re.finditer(r"<img\b[^>]*\bsrc=[\"']([^\"']+)[\"']", spec_text, re.I):
        candidates.append((match.group(1), "image"))
    for key in ("legacy_ui_screenshot", "source_manifest", "evidence_model"):
        value = frontmatter.get(key)
        if value:
            candidates.append((value, key))

    seen: set[str] = set()
    for raw, kind in candidates:
        target = raw.strip()
        if target in seen:
            continue
        seen.add(target)
        if not target or any(char in target for char in "<>"):
            continue
        if re.match(r"^(?:https?|mailto|data):", target, re.I) or target.startswith("#"):
            continue
        target = unquote(target.split("#", 1)[0])
        target = re.sub(r":\d+$", "", target)
        candidate = Path(target)
        resolved = candidate if candidate.is_absolute() else (spec_path.parent / candidate).resolve()
        if not resolved.exists():
            errors.append(
                _issue(
                    "SPEC_LOCAL_LINK_BROKEN",
                    f"{kind} target does not exist: {raw}",
                )
            )


def _canonical_sha(value: Any) -> str:
    return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")).hexdigest()


def run_guard(
    evidence: str | Path,
    spec: str | Path,
    *,
    previous_evidence: str | Path | None = None,
    source_delta: str | Path | None = None,
) -> dict[str, Any]:
    errors: list[dict[str, Any]] = []
    warnings: list[dict[str, Any]] = []
    current = load_evidence(evidence)
    previous = load_evidence(previous_evidence) if previous_evidence else None
    _validate_metadata(current, errors, warnings)
    if previous is not None:
        _validate_metadata(previous, errors, warnings)

    spec_path = Path(spec).resolve()
    try:
        spec_text = spec_path.read_text(encoding="utf-8-sig")
    except (OSError, UnicodeError) as exc:
        errors.append(_issue("SPEC_READ_FAILED", f"Cannot read {spec_path}: {exc}"))
        spec_text = ""
    frontmatter = _parse_frontmatter(spec_text)
    spec_package = _load_spec_package(spec_path, spec_text, errors)
    package_text = "\n".join(text for _, text in spec_package.values())
    _validate_complete_specification_contract(current, spec_text, frontmatter, errors, package_text)
    _validate_spec_package(spec_package, frontmatter, errors)
    appendix_i, _ = _validate_appendices(spec_text, errors)
    current_modules = _module_ids(current)
    module_id = next(iter(current_modules), str(frontmatter.get("module_id") or "UNKNOWN").upper())
    spec_module = str(frontmatter.get("module_id") or "").upper()
    if not spec_module:
        errors.append(_issue("SPEC_MODULE_MISSING", "Specification front matter is missing module_id."))
    elif current_modules and spec_module not in current_modules:
        errors.append(
            _issue(
                "SPEC_MODULE_MISMATCH",
                f"Specification module {spec_module} does not match evidence module {module_id}.",
            )
        )

    current_evidence_fps, current_manifest_fps = _fingerprints(current)
    current_fingerprints = current_evidence_fps | current_manifest_fps
    spec_fingerprint = str(frontmatter.get("evidence_fingerprint") or "").lower()
    if not SHA256_RE.fullmatch(spec_fingerprint):
        errors.append(_issue("SPEC_FINGERPRINT_INVALID", "Specification front matter lacks a valid evidence_fingerprint."))
    stale = bool(current_fingerprints and spec_fingerprint not in current_fingerprints)
    if stale:
        errors.append(
            _issue(
                "SPEC_STALE_FINGERPRINT",
                "Specification evidence_fingerprint does not match the current evidence run.",
                sections=["1", "Appendix J"],
            )
        )

    previous_fingerprints: set[str] = set()
    if previous is not None:
        previous_fingerprints = set().union(*_fingerprints(previous))
    delta = _delta_component(current, source_delta)
    if isinstance(delta, Mapping) and delta.get("_load_errors"):
        errors.extend(delta["_load_errors"])
    if previous is not None and current_fingerprints != previous_fingerprints and not _records(delta) and not (
        isinstance(delta, Mapping) and delta.get("affected_spec_sections")
    ):
        warnings.append(
            _issue(
                "SOURCE_DELTA_NOT_SUPPLIED",
                "Evidence fingerprint changed without source-delta records; record comparison will be used where possible.",
            )
        )

    current_gaps, precise_gap_ids = _validate_gaps(current, previous, appendix_i, errors, warnings)
    metrics, gates = _validate_coverage(current, current_gaps, precise_gap_ids, errors, warnings)
    _validate_spec_claims(spec_text, metrics, current_gaps, errors)
    _validate_visual_region_specification(spec_text, current.records("normalized_evidence"), errors)
    _validate_poc_assumption_trace(spec_text, frontmatter, current_gaps, errors)

    spec_sections = _spec_sections(spec_text)
    stale_sections, preserved_sections, human_conflicts = _section_impact(
        current, previous, delta, stale, spec_sections, errors
    )
    _validate_markers(spec_text, frontmatter, stale_sections, errors)
    for _, (document_path, document_text) in spec_package.items():
        _validate_local_links(document_path, document_text, _parse_frontmatter(document_text), errors)

    failing_gate_ids = [gate["gate_id"] for gate in gates if gate["status"] == "fail"]
    for gate_id in failing_gate_ids:
        if not any(issue["code"] == "READINESS_GATE_FAILED" and gate_id in issue["record_ids"] for issue in errors):
            errors.append(
                _issue(
                    "READINESS_GATE_FAILED",
                    f"{gate_id} failed.",
                    record_ids=[gate_id],
                )
            )

    errors = _deduplicate_issues(errors)
    warnings = _deduplicate_issues(warnings)
    reason_record_ids = sorted(
        {
            record_id
            for issue in errors + warnings
            for record_id in issue.get("record_ids", [])
        }
        | {
            record_id
            for section in stale_sections
            for record_id in section.get("reason_record_ids", [])
        }
    )
    result = {
        "schema_version": "1.0",
        "guard_version": TOOL_VERSION,
        "module_id": module_id,
        "evidence_path": str(Path(evidence).resolve()),
        "spec_path": str(spec_path),
        "evidence_fingerprint": (
            sorted(current_evidence_fps)[0]
            if current_evidence_fps
            else sorted(current_manifest_fps)[0]
            if current_manifest_fps
            else None
        ),
        "spec_evidence_fingerprint": spec_fingerprint or None,
        "validation_status": "fail" if errors else "pass_with_registered_gaps" if any(
            str(gap.get("status") or "").lower() in {"open", "narrowed", "reopened"}
            for gap in current_gaps.values()
        ) else "pass",
        "errors": errors,
        "warnings": warnings,
        "readiness_gate_results": gates,
        "stale_sections": stale_sections,
        "preserved_sections": preserved_sections,
        "reason_record_ids": reason_record_ids,
        "human_decision_conflicts": human_conflicts,
        "coverage_summary": metrics,
        "gap_summary": {
            "total": len(current_gaps),
            "precisely_registered": len(precise_gap_ids),
            "open": sum(
                str(gap.get("status") or "").lower() in {"open", "narrowed", "reopened"}
                for gap in current_gaps.values()
            ),
            "resolved": sum(str(gap.get("status") or "").lower() == "resolved" for gap in current_gaps.values()),
        },
    }
    result["result_sha256"] = _canonical_sha({key: value for key, value in result.items() if key != "result_sha256"})
    return result


def _deduplicate_issues(issues: Sequence[dict[str, Any]]) -> list[dict[str, Any]]:
    result: list[dict[str, Any]] = []
    seen: set[str] = set()
    for issue in issues:
        key = json.dumps(issue, sort_keys=True, ensure_ascii=False)
        if key not in seen:
            seen.add(key)
            result.append(issue)
    return result


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Validate normalized Oracle Forms evidence and its Markdown specification."
    )
    parser.add_argument(
        "--evidence",
        required=True,
        help="Current evidence-model.json or module/run evidence directory.",
    )
    parser.add_argument("--spec", required=True, help="Markdown specification to validate.")
    parser.add_argument(
        "--previous-evidence",
        help="Optional prior evidence-model.json or module/run evidence directory.",
    )
    parser.add_argument(
        "--source-delta",
        help="Optional source-delta.json. Defaults to the current evidence artifact when present.",
    )
    parser.add_argument("--output", help="Optional JSON output path; JSON is always written to stdout.")
    return parser


def main(argv: Sequence[str] | None = None) -> int:
    args = build_parser().parse_args(argv)
    result = run_guard(
        args.evidence,
        args.spec,
        previous_evidence=args.previous_evidence,
        source_delta=args.source_delta,
    )
    serialized = json.dumps(result, indent=2, ensure_ascii=False)
    if args.output:
        output = Path(args.output).resolve()
        output.parent.mkdir(parents=True, exist_ok=True)
        output.write_text(serialized + "\n", encoding="utf-8")
    print(serialized)
    return 1 if result["errors"] else 0


if __name__ == "__main__":
    sys.exit(main())

SKILL.md

tile.json