4-Python: The C1 Validator

4-Python: The C1 Validator

The character of a question is the intensity of the not-knowing within it.

Context

The validator is the surface with the highest leverage. It does not require the user to adopt the type contract (S3), the graph executor (S5), or any other surface. It runs against any artifact that claims to be a 5QLN cycle — a Cycle Pydantic object, a dict loaded from JSON, a record produced by some other system entirely. It returns a structured report keyed to C1 §3.5: every violation tagged with the check that caught it, the corruption code (when one applies), the spec reference, and a severity level.

This article ports C1 §3.5's three-part validation protocol into Python. Syntax check, semantic check, drift check — each item in §3.5 becomes an executable function. The five corruption codes (L1, L2, L3, L4, V∅) become detectors that fire on specific patterns. The whole protocol is composable: a user can run all checks, a single category, or one specific check against an artifact in isolation.

The article is also where the asymmetry from S1 stops being a philosophical claim and becomes operational. Half the failure modes named by D1 are phenomenological — did X arrive from ∞0 or was it generated from K?did ⋂ genuinely land?was φ direct perception or theory-laden? The validator has three severity levels for exactly this reason. Definiteviolations are caught structurally. Heuristic violations are pattern-matched with explicit best-effort caveats. AttestationRequired flags are the validator naming the slot it cannot fill — and refusing to certify a cycle silently when the human side of the Membrane was empty.


The architecture

fivqln/validator/
├── __init__.py
├── violation.py              # Violation, Severity, ValidationReport types
├── checks/
│   ├── syntax.py             # §3.5 syntax checks
│   ├── semantic.py           # §3.5 semantic checks
│   ├── drift.py              # §3.5 drift checks
│   └── corruption.py         # L1, L2, L3, L4, V∅ detectors
├── adapters/
│   ├── from_cycle.py         # validate a fivqln.Cycle (S3 type)
│   ├── from_dict.py          # validate raw JSON/dict
│   └── from_artifact.py      # validate text claiming to be a cycle (best-effort)
├── validate.py               # main entry point
└── cli.py                    # `fivqln-validate path/to/artifact`

Each check is a pure function. It takes whatever it needs (a Cycle, a FormationTrail, a dict) and returns a list of Violations. The composition is mechanical — validate() runs every check and concatenates the results into a ValidationReport. This shape means a user can call any single check directly, in their own pipeline, without invoking the rest of the validator. The grammar surfaces; the wizard does not.


The Violation type

# fivqln/validator/violation.py
"""
The structured output of every validator check.

A Violation is keyed to one §3.5 check and one §2.8 corruption code where
applicable. Severity is the validator's honesty about how confidently the
check can be made — not all C1 violations are mechanically detectable.
"""

from enum import Enum
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field

from fivqln._canonical import CorruptionCode


class CheckCategory(str, Enum):
    """The three §3.5 categories. Plus 'corruption' for L-code detectors."""
    SYNTAX = "syntax"
    SEMANTIC = "semantic"
    DRIFT = "drift"
    CORRUPTION = "corruption"


class Severity(str, Enum):
    """How confidently the validator made this finding.

    DEFINITE — structurally caught. The artifact has the violation
        regardless of intent. Examples: ∞0' missing a question, an
        unknown lens code, the Constitutional Block paraphrased.

    HEURISTIC — pattern-matched with explicit best-effort caveats.
        The validator may flag a real violation, may flag a false positive.
        Examples: 'placeholder' detected in an output (L4 candidate),
        a held_by value matching a known automation marker (L2 candidate).

    ATTESTATION_REQUIRED — the slot the validator cannot fill. The check
        names what would need to be true for the cycle to be valid; only
        the human in the cycle can attest. Examples: did X arrive from ∞0
        rather than K?, did ⋂ genuinely land?, is φ direct perception?
    """
    DEFINITE = "definite"
    HEURISTIC = "heuristic"
    ATTESTATION_REQUIRED = "attestation_required"


class Violation(BaseModel):
    """One finding from one check."""
    model_config = ConfigDict(frozen=True)

    category: CheckCategory
    severity: Severity
    code: Optional[CorruptionCode] = Field(
        default=None,
        description="L1, L2, L3, L4, or V∅ — when the violation maps to one of the five",
    )
    spec_ref: str = Field(
        description="The §-reference in the Codex this violation is keyed to",
    )
    message: str
    location: Optional[str] = Field(
        default=None,
        description="JSON-path into the artifact where the violation lives",
    )


class ValidationReport(BaseModel):
    """The structured output of validate()."""
    model_config = ConfigDict(frozen=True)

    violations: tuple[Violation, ...] = ()

    @property
    def is_clean(self) -> bool:
        """True iff no DEFINITE violations were found.

        Note: HEURISTIC and ATTESTATION_REQUIRED findings do not block
        is_clean. The validator distinguishes 'caught a structural failure'
        from 'noted something that needs human attestation.' A clean report
        is not the same as a certified cycle — see the docstring on
        is_certified."""
        return not any(v.severity == Severity.DEFINITE for v in self.violations)

    @property
    def is_certified(self) -> bool:
        """True iff is_clean AND every ATTESTATION_REQUIRED has been answered.

        This will be False on first validation. The user resolves attestations
        by addressing the slots the validator named — typically through the
        human-in-the-loop interrupts that S5 (LangGraph) and S6 (Agent SDK)
        will provide. is_certified is what a guardrail downstream of those
        surfaces actually checks."""
        return self.is_clean and not any(
            v.severity == Severity.ATTESTATION_REQUIRED for v in self.violations
        )

    def by_category(self, category: CheckCategory) -> tuple[Violation, ...]:
        return tuple(v for v in self.violations if v.category == category)

    def by_code(self, code: CorruptionCode) -> tuple[Violation, ...]:
        return tuple(v for v in self.violations if v.code == code)

The two-property design — is_clean and is_certified — is the asymmetry made operational. A cycle can pass every structural check the validator knows how to run and still not be certified, because the human attestations have not been answered. This is the validator refusing to certify silently. L3 — claiming to decode ∞0 directly — is what would happen if the validator pretended is_clean were enough.


The main entry point

# fivqln/validator/validate.py
"""
The main entry point. Accepts a Cycle, a dict, or anything Pydantic can
parse into a Cycle. Runs every check. Returns a ValidationReport.
"""

from typing import Union

from fivqln.types import Cycle
from fivqln.validator.violation import ValidationReport, Violation
from fivqln.validator.checks import syntax, semantic, drift, corruption


Validatable = Union[Cycle, dict, str]
"""Things the validator accepts. Strings are parsed as JSON."""


def validate(artifact: Validatable) -> ValidationReport:
    """Run every check against the artifact. Return a ValidationReport.

    Acceptance shape:
      - Cycle (S3 Pydantic type) — runs natively
      - dict — parsed into a Cycle via Pydantic; parse failures become
        DEFINITE syntax violations
      - str — parsed as JSON, then as above

    The function is total: it always returns a ValidationReport, never
    raises on bad input. Bad input is itself a violation."""
    cycle, parse_violations = _coerce(artifact)
    if cycle is None:
        # Could not parse at all — return only the parse violations.
        return ValidationReport(violations=tuple(parse_violations))

    violations: list[Violation] = list(parse_violations)
    violations.extend(syntax.run_all(cycle))
    violations.extend(semantic.run_all(cycle))
    violations.extend(drift.run_all(cycle))
    violations.extend(corruption.run_all(cycle))

    return ValidationReport(violations=tuple(violations))


def _coerce(artifact: Validatable) -> tuple[Cycle | None, list[Violation]]:
    """Convert an input into a Cycle or accumulate parse violations."""
    import json

    if isinstance(artifact, Cycle):
        return artifact, []

    if isinstance(artifact, str):
        try:
            artifact = json.loads(artifact)
        except json.JSONDecodeError as e:
            from fivqln.validator.violation import (
                Violation, CheckCategory, Severity,
            )
            return None, [Violation(
                category=CheckCategory.SYNTAX,
                severity=Severity.DEFINITE,
                spec_ref="§3.5 syntax",
                message=f"Artifact is not valid JSON: {e}",
            )]

    if isinstance(artifact, dict):
        try:
            return Cycle.model_validate(artifact), []
        except Exception as e:
            from fivqln.validator.violation import (
                Violation, CheckCategory, Severity,
            )
            return None, [Violation(
                category=CheckCategory.SYNTAX,
                severity=Severity.DEFINITE,
                spec_ref="§3.5 syntax",
                message=f"Artifact does not parse as a Cycle: {e}",
            )]

    return None, [Violation(
        category=CheckCategory.SYNTAX,
        severity=Severity.DEFINITE,
        spec_ref="§3.5 syntax",
        message=f"Unsupported artifact type: {type(artifact).__name__}",
    )]

Syntax checks

# fivqln/validator/checks/syntax.py
"""
§3.5 syntax checks. These verify the structural integrity of the artifact:
symbols resolve, equations match, the five phases are accounted for.
"""

from fivqln.types import Cycle, Phase
from fivqln._canonical import LENSES
from fivqln.constitutional_block import CONSTITUTIONAL_BLOCK
from fivqln.validator.violation import (
    Violation, CheckCategory, Severity,
)


def run_all(cycle: Cycle) -> list[Violation]:
    return [
        v for check in (
            check_constitutional_block_present,
            check_lenses_in_trail_are_known,
            check_phase_outputs_are_correct_types,
        )
        for v in check(cycle)
    ]


def check_constitutional_block_present(cycle: Cycle) -> list[Violation]:
    """C1 §3.6: every emitted surface carries the Constitutional Block.

    For a Pydantic Cycle this is trivially true (the import-time check
    in fivqln.constitutional_block guarantees it). For artifacts loaded
    from raw dicts, we cannot inspect the surface — only the cycle data.
    This check is therefore a no-op on Cycle objects but kept here for
    parity with future from_artifact adapter."""
    # Surface-level check; not applicable at the Cycle level.
    return []


def check_lenses_in_trail_are_known(cycle: Cycle) -> list[Violation]:
    """C1 §3.5 syntax: 'all 25 sub-phases available.'

    Every lens code that appears in the formation trail must be one of
    the canonical 25. Unknown lens codes fail."""
    out = []
    for i, entry in enumerate(cycle.trail.entries):
        if entry.lens is not None and entry.lens not in LENSES:
            out.append(Violation(
                category=CheckCategory.SYNTAX,
                severity=Severity.DEFINITE,
                spec_ref="§3.5 syntax / §1.5",
                message=(
                    f"Unknown lens code '{entry.lens}' in formation trail. "
                    f"Must be one of the 25 canonical lenses (SS through VV)."
                ),
                location=f"$.trail.entries[{i}].lens",
            ))
    return out


def check_phase_outputs_are_correct_types(cycle: Cycle) -> list[Violation]:
    """C1 §3.5 syntax: 'every phase carries its exact equation.'

    For Pydantic Cycles this is enforced at construction. For dict-loaded
    cycles, Pydantic.model_validate has already raised — so reaching this
    check means the types are correct. Kept as a no-op for documentation
    of where this check would live if a non-Pydantic adapter is added."""
    return []

The interesting feature of the syntax module is what is not there. Most syntax checks are enforced at construction by Pydantic in S3 — by the time the validator runs, type-level violations have already been caught. The validator runs the checks that survive the type system, and explicitly notes (via no-op functions with docstrings) where future adapters would need to reintroduce checks. This is the type contract carrying its weight.


Semantic checks

# fivqln/validator/checks/semantic.py
"""
§3.5 semantic checks. These verify that the cycle's outputs cohere with
each other — the adaptive context chain is unbroken, B/B''/∞0' are
distinct, ∞0' carries a question.
"""

from fivqln.types import Cycle
from fivqln.validator.violation import (
    Violation, CheckCategory, Severity,
)


def run_all(cycle: Cycle) -> list[Violation]:
    return [
        v for check in (
            check_adaptive_context_chain_unbroken,
            check_alpha_carried_into_seed,
            check_three_v_outputs_are_distinct,
            check_lens_serves_does_not_replace,
        )
        for v in check(cycle)
    ]


def check_adaptive_context_chain_unbroken(cycle: Cycle) -> list[Violation]:
    """§2.6, §3.3: each phase's prior outputs must be present.

    G requires X. Q requires X+α+Y. P requires X+α+Y+Z. V requires the
    full trace. If a downstream output is present but an upstream one is
    missing, the chain is broken."""
    out = []
    chain = [
        ("pattern", ["spark"], "G requires X"),
        ("resonance", ["spark", "pattern"], "Q requires X + α + Y"),
        ("flow", ["spark", "pattern", "resonance"], "P requires X + α + Y + Z"),
        ("benefit", ["spark", "pattern", "resonance", "flow"], "V requires full trace"),
        ("seed", ["spark", "pattern", "resonance", "flow"], "B'' requires full trace"),
        ("enriched_return", ["spark", "pattern", "resonance", "flow"], "∞0' requires full trace"),
    ]
    for downstream, required, message in chain:
        if getattr(cycle, downstream) is not None:
            for prior in required:
                if getattr(cycle, prior) is None:
                    out.append(Violation(
                        category=CheckCategory.SEMANTIC,
                        severity=Severity.DEFINITE,
                        spec_ref="§2.6, §3.3",
                        message=(
                            f"Adaptive context chain broken: {downstream} present "
                            f"but {prior} is missing. {message}."
                        ),
                        location=f"$.{downstream}",
                    ))
    return out


def check_alpha_carried_into_seed(cycle: Cycle) -> list[Violation]:
    """§2.5, R7: B'' must carry α faithfully.

    The α in the FractalSeed must be the same α discovered in G's pattern.
    If they differ, B'' has not carried α — composition reconstructed
    something else."""
    if cycle.seed is None or cycle.pattern is None:
        return []
    if cycle.seed.alpha != cycle.pattern.alpha:
        return [Violation(
            category=CheckCategory.SEMANTIC,
            severity=Severity.DEFINITE,
            spec_ref="§2.5, R7",
            message=(
                "B'' does not carry α faithfully. The CoreEssence in the "
                "FractalSeed differs from the CoreEssence validated in G. "
                "R7: 'composition produces the artifact from the analysis' — "
                "α must be preserved across the two passes."
            ),
            location="$.seed.alpha",
        )]
    return []


def check_three_v_outputs_are_distinct(cycle: Cycle) -> list[Violation]:
    """§3.5 semantic: 'B, B'', ∞0' are three distinct things.'

    They must be three different objects with three different decoding
    steps. Conflating any two is a semantic failure."""
    if not (cycle.benefit and cycle.seed and cycle.enriched_return):
        return []
    out = []
    if cycle.benefit.fulfillment in cycle.seed.artifact:
        # Heuristic, not definitive — fulfillment text appearing inside the
        # artifact is suspicious but not necessarily a conflation.
        out.append(Violation(
            category=CheckCategory.SEMANTIC,
            severity=Severity.HEURISTIC,
            spec_ref="§3.5 semantic",
            message=(
                "B's fulfillment text appears verbatim inside B''. This "
                "suggests B may have been read off B'' rather than decoded "
                "as a distinct output. Verify the two were composed separately."
            ),
            location="$.benefit / $.seed",
        ))
    return out


def check_lens_serves_does_not_replace(cycle: Cycle) -> list[Violation]:
    """D1 Rule 3: sub-phases articulate, never replace.

    For any phase with a validated output, at least one non-lens trail
    entry must exist. A phase whose trail contains only lens-tagged
    entries means the output came purely from lens exploration, which
    violates Rule 3 Case 1 ('the input IS the output emerging' must
    occur outside a lens).
    """
    from fivqln.types import Phase
    phase_to_output = {
        Phase.S: cycle.spark,
        Phase.G: cycle.pattern,
        Phase.Q: cycle.resonance,
        Phase.P: cycle.flow,
        Phase.V: cycle.seed,
    }
    out = []
    for phase, output in phase_to_output.items():
        if output is None:
            continue
        phase_entries = [e for e in cycle.trail.entries if e.phase == phase]
        if not phase_entries:
            continue
        if not any(e.lens is None for e in phase_entries):
            out.append(Violation(
                category=CheckCategory.SEMANTIC,
                severity=Severity.DEFINITE,
                spec_ref="D1 Rule 3",
                message=(
                    f"D1 Rule 3 violation at phase {phase.value}: output "
                    f"is present but every trail entry for this phase is "
                    f"lens-tagged. Lens contributions serve formation; "
                    f"they do not replace the output."
                ),
                location=f"$.trail.entries (phase={phase.value})",
            ))
    return out

Drift checks

# fivqln/validator/checks/drift.py
"""
§3.5 drift checks. These verify that the artifact has not silently
diverged from the canonical spec — no renamed symbols, no extra
corruption codes, no paraphrased equations.
"""

from fivqln.types import Cycle
from fivqln._canonical import CorruptionCode
from fivqln.validator.violation import (
    Violation, CheckCategory, Severity,
)


def run_all(cycle: Cycle) -> list[Violation]:
    # Most drift is caught at import time by fivqln._canonical and
    # fivqln.constitutional_block raising ImportError. What remains is
    # drift in the artifact itself — content claiming to use codes or
    # symbols outside the canonical set.
    return list(check_no_unknown_corruption_in_trail(cycle))


def check_no_unknown_corruption_in_trail(cycle: Cycle) -> list[Violation]:
    """§3.5 drift: 'No corruption code added beyond five.'

    If the trail or any phase output references corruption codes by name,
    they must be one of L1, L2, L3, L4, V∅."""
    known = {c.value for c in CorruptionCode}
    out = []
    for i, entry in enumerate(cycle.trail.entries):
        for token in entry.operation.split() + entry.output_excerpt.split():
            # Heuristic token scan — anything matching the L-code shape
            # but not in the canonical five is flagged.
            if token.startswith("L") and len(token) <= 3 and token not in known:
                if token[1:].isdigit() or token == "L0":
                    out.append(Violation(
                        category=CheckCategory.DRIFT,
                        severity=Severity.DEFINITE,
                        spec_ref="§3.5 drift / §2.8",
                        message=(
                            f"Trail entry references corruption code '{token}' "
                            f"which is not in the canonical five "
                            f"({sorted(known)})."
                        ),
                        location=f"$.trail.entries[{i}]",
                    ))
    return out

Corruption code detection

# fivqln/validator/checks/corruption.py
"""
Detectors for the five corruption codes (§2.8). Each detector returns
violations keyed to its specific code, with the appropriate severity —
some L-codes are definitively detectable from artifact structure;
others are inherently phenomenological and can only be flagged for
human attestation.
"""

from fivqln.types import Cycle
from fivqln._canonical import CorruptionCode
from fivqln.validator.violation import (
    Violation, CheckCategory, Severity,
)


# Sentinel strings that suggest unfilled templates (L4 candidate)
PLACEHOLDER_MARKERS = (
    "TODO", "TBD", "PLACEHOLDER", "FIXME", "...", "[insert", "[your",
)

# Tokens in `held_by` that suggest an automation may have filled a
# slot the human was supposed to hold (L2 candidate)
AUTOMATION_MARKERS = (
    "agent", "bot", "gpt", "claude", "llm", "auto", "system",
)


def run_all(cycle: Cycle) -> list[Violation]:
    return [
        v for detector in (
            detect_v_empty,
            detect_l2_generating_heuristic,
            detect_l4_performing_heuristic,
            require_l2_attestation_at_spark,
            require_l3_attestation_at_quality,
        )
        for v in detector(cycle)
    ]


def detect_v_empty(cycle: Cycle) -> list[Violation]:
    """V∅: B'' present but ∞0' absent, or ∞0' present without a question.

    The first half is enforced by the Cycle model validator and will not
    reach this detector. The second half is enforced by EnrichedReturn's
    own model validator. This detector exists for dict-loaded cycles where
    the Pydantic validators may have been bypassed."""
    out = []
    if cycle.seed is not None and cycle.enriched_return is None:
        out.append(Violation(
            category=CheckCategory.CORRUPTION,
            severity=Severity.DEFINITE,
            code=CorruptionCode.V_EMPTY,
            spec_ref="D1 Rule 8, Rule 9 (V∅)",
            message=(
                "V∅ — Incomplete: no return; the cycle has no continuity. "
                "B'' is present but ∞0' is absent. D1 Rule 8: 'No V without ∞0'.'"
            ),
            location="$.enriched_return",
        ))
    if cycle.enriched_return is not None and "?" not in cycle.enriched_return.question:
        out.append(Violation(
            category=CheckCategory.CORRUPTION,
            severity=Severity.DEFINITE,
            code=CorruptionCode.V_EMPTY,
            spec_ref="D1 Rule 8, Rule 9 (V∅)",
            message=(
                "V∅ — Incomplete: ∞0' is present but carries no question. "
                "D1 Rule 8: 'No question = not ∞0'.' The cycle has no continuity."
            ),
            location="$.enriched_return.question",
        ))
    return out


def detect_l4_performing_heuristic(cycle: Cycle) -> list[Violation]:
    """L4: form without substance.

    Heuristic — looks for placeholder markers in any phase output. A real
    L4 violation is phenomenological (the operation was empty even if the
    symbols were correctly used) and cannot be machine-verified. Placeholder
    text is a strong but not definitive signal."""
    out = []
    fields_to_scan = (
        ("spark.question", cycle.spark.question if cycle.spark else None),
        ("pattern.pattern_description", cycle.pattern.pattern_description if cycle.pattern else None),
        ("resonance.key_description", cycle.resonance.key_description if cycle.resonance else None),
        ("flow.flow_description", cycle.flow.flow_description if cycle.flow else None),
        ("seed.artifact", cycle.seed.artifact if cycle.seed else None),
    )
    for path, value in fields_to_scan:
        if value is None:
            continue
        for marker in PLACEHOLDER_MARKERS:
            if marker.lower() in value.lower():
                out.append(Violation(
                    category=CheckCategory.CORRUPTION,
                    severity=Severity.HEURISTIC,
                    code=CorruptionCode.L4,
                    spec_ref="D1 Rule 9 (L4 Performing)",
                    message=(
                        f"L4 — Performing: the appearance of depth may be filling "
                        f"the center. Placeholder marker '{marker}' detected in "
                        f"{path}. Verify the slot was filled by genuine decoding, "
                        f"not by a template."
                    ),
                    location=f"$.{path}",
                ))
                break
    return out


def detect_l2_generating_heuristic(cycle: Cycle) -> list[Violation]:
    """L2: X manufactured from K instead of received from ∞0.

    Heuristic — looks for automation markers in held_by. A held_by value
    like 'gpt-4' or 'claude' suggests the spark was generated by a system
    rather than received by a human. Definitive L2 detection is
    impossible from artifact structure alone."""
    if cycle.spark is None:
        return []
    held_by_lower = cycle.spark.held_by.lower()
    for marker in AUTOMATION_MARKERS:
        if marker in held_by_lower:
            return [Violation(
                category=CheckCategory.CORRUPTION,
                severity=Severity.HEURISTIC,
                code=CorruptionCode.L2,
                spec_ref="D1 Rule 9 (L2 Generating), Rule 10 (asymmetry)",
                message=(
                    f"L2 — Generating: a produced spark may be filling the "
                    f"center instead of a received one. spark.held_by "
                    f"('{cycle.spark.held_by}') contains automation marker "
                    f"'{marker}'. D1 Rule 10 places ∞0 on the human side of "
                    f"the Membrane; sparks must be received, not produced."
                ),
                location="$.spark.held_by",
            )]
    return []


def require_l2_attestation_at_spark(cycle: Cycle) -> list[Violation]:
    """L2 attestation: every spark requires human attestation that X
    arrived from ∞0 rather than was assembled from K.

    The validator cannot machine-verify this. The check exists to surface
    the requirement explicitly so downstream consumers cannot pretend it
    was checked when it was not."""
    if cycle.spark is None:
        return []
    return [Violation(
        category=CheckCategory.CORRUPTION,
        severity=Severity.ATTESTATION_REQUIRED,
        code=CorruptionCode.L2,
        spec_ref="D1 Rule 9 (L2 Generating)",
        message=(
            "L2 attestation required: was X received from ∞0, or produced "
            "from K? Only the human inquirer can attest. The center must "
            "not be filled with a produced spark. This cannot be machine-verified."
        ),
        location="$.spark",
    )]


def require_l3_attestation_at_quality(cycle: Cycle) -> list[Violation]:
    """L3 attestation: did ⋂ genuinely land, or was Z argued into place?

    D1 §2.3: '⋂ cannot be manufactured. It arrives.' The validator can
    confirm that φ and Ω were named and that something the inquirer
    called the landing followed; it cannot confirm the landing was real."""
    if cycle.resonance is None:
        return []
    return [Violation(
        category=CheckCategory.CORRUPTION,
        severity=Severity.ATTESTATION_REQUIRED,
        code=CorruptionCode.L3,
        spec_ref="D1 Rule 9 (L3 Claiming)",
        message=(
            "L3 attestation required: did ⋂ genuinely land, or was the "
            "center filled with false access to ∞0? Only the human can "
            "attest the landing was real. This cannot be machine-verified."
        ),
        location="$.resonance.intersection",
    )]

The two require_*_attestation_at_* functions are doing the load-bearing work of this surface. They emit findings that the validator cannot resolve. A consumer downstream — a CI pipeline, a graph executor, an MCP client — must either (a) collect the human's attestation through whatever interface it provides, or (b) accept that is_certified will return False. The validator refuses to silently certify a cycle whose human side was empty. This is the asymmetry made executable.


A real run

from datetime import datetime, timezone
from fivqln.symbols import CoreEssence
from fivqln.types import (
    Cycle, ValidatedSpark, ValidatedPattern, FormationEntry, Phase,
)
from fivqln.validator import validate, Severity


cycle = Cycle(
    spark=ValidatedSpark(
        question="What grammar does the substrate need to carry the spec faithfully?",
        received_at=datetime.now(timezone.utc),
        held_by="amihai",
    ),
    pattern=ValidatedPattern(
        alpha=CoreEssence(
            description="Substrate-independence of the grammar",
            expressions=("reST carries it", "Pydantic carries it"),
        ),
        pattern_description="The grammar holds across substrates that have their own formal structure.",
    ),
)
cycle.trail.append(FormationEntry(
    timestamp=datetime.now(timezone.utc),
    phase=Phase.G,
    lens="GQ",
    operation="testing α against expressions for resonance",
    output_excerpt="reST carries it / Pydantic carries it",
))

report = validate(cycle)

# is_clean: True — no DEFINITE violations
# is_certified: False — L2 attestation at spark is unanswered
print(f"clean: {report.is_clean}, certified: {report.is_certified}")

for v in report.violations:
    if v.severity == Severity.ATTESTATION_REQUIRED:
        print(f"[{v.code}] {v.message}")

The cycle passes every structural check. The validator still refuses to certify it because the L2 attestation at S is open. The user has a choice: attest to it (through whatever mechanism the consuming surface provides) or accept that this cycle is structurally clean but not certified. The honesty is the product.


CLI

# fivqln/validator/cli.py
"""
Command-line entry point. Drop-in for CI pipelines.

  $ fivqln-validate cycle.json
  $ fivqln-validate --strict cycle.json    # exit 1 if not is_certified
"""

import argparse
import json
import sys
from pathlib import Path

from fivqln.validator import validate


def main() -> int:
    parser = argparse.ArgumentParser(description="Validate a 5QLN artifact.")
    parser.add_argument("path", type=Path)
    parser.add_argument(
        "--strict",
        action="store_true",
        help="Exit non-zero if the artifact is not certified (i.e., has "
             "outstanding attestations). Default exits non-zero only on "
             "DEFINITE violations.",
    )
    args = parser.parse_args()

    artifact = json.loads(args.path.read_text())
    report = validate(artifact)

    for v in report.violations:
        print(f"[{v.severity}] [{v.category}] {v.spec_ref}: {v.message}")

    if not report.is_clean:
        return 1
    if args.strict and not report.is_certified:
        return 2
    return 0


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

A CI pipeline that wants the guardrail without committing to the rest of the framework runs fivqln-validate --strict path/to/cycle.json and checks the exit code. The validator's docstrings tell the developer exactly which §-references each violation maps to, so the path from "build failed" to "fix the spec drift" is short.


What this surface enables

The validator is the smallest viable adoption point for 5QLN. A team that wants to enforce structural discipline on artifacts produced by their existing systems can install fivqln, point the CLI at any JSON file, and get a structured report keyed to the spec — without adopting the type contract, the graph executor, the agent SDK, or any other surface.

For the rest of the series, the validator is the runtime invariant. S5's LangGraph nodes will call validate() after each phase node completes. S6's Anthropic Agent SDK tool implementations will call validate() after every tool result. S7's MCP server will expose validate() as a callable tool. Every surface that follows treats the validator as the trusted ground — the place where C1 §3.5 lives in code.

The two-property design (is_clean vs is_certified) is what carries the asymmetry forward into every consuming surface. A surface that ships is_clean to a downstream consumer when it should have shipped is_certified has silently committed L3 — claiming to decode ∞0 directly — by treating the validator's structural pass as a phenomenological pass. The naming of the two properties makes the discipline visible at every API boundary.


Closing

The validator is small. Its judgments are explicit about their own confidence. The slots it cannot fill are named, not hidden. A clean run is not a certified run, and a certified run requires the human to have answered.

Ahead: S5 — Python: The Cycle as a LangGraph. The cycle as a typed state graph. Adaptive context chain as explicit state passing between nodes. Human-in-the-loop interrupts where the validator's ATTESTATION_REQUIRED flags become real questions to the inquirer. The first surface in which the cycle runs.


5QLN © 2026 Amihai Loven. Open under the 5QLN Open Source License.

Amihai Loven

Amihai Loven

Jeonju. South Korea