Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ in the vendored catalogue, never from a hardcoded column in the check itself. Th
one code base validate against several specification versions at once: relaxing a `MUST` to a
`SHOULD` upstream turns a failure into a warning here with no code change.

One severity does not come from the catalogue, because it is not about the document at all. A
check that raises something it does not expect reports `fault`: a defect in this package, not a
finding. It fails the run like `fail` does, but says the check produced no verdict, so the
document is neither condemned nor cleared. A fault keeps the id of the check that raised it
rather than reporting under one of its own, so "which part of the validator broke" is answered by
the same identifier that names what it was trying to establish, and the run still reports every
other target - one broken check costs one check.

A rule absent from the selected version's catalogue, or marked deprecated there, is skipped with
a message saying so, rather than checked anyway - older meta-schema versions ship no catalogue at
all and skip the whole `rule.*` family. A false positive costs far more than a missed finding,
Expand Down
12 changes: 9 additions & 3 deletions src/oold/validation/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from .meta_store import MetaSchemaError, describe_store, fetch_remote, load_index, resolve_selection
from .meta_vendor import vendor_version
from .pipeline import Options, run_compliance, validate_directory, validate_instance, validate_schema
from .report import FAIL, OK, SKIP, WARN, Report
from .report import FAIL, FAULT, OK, SKIP, WARN, Report

EXIT_OK = 0
EXIT_FAILED = 1
Expand Down Expand Up @@ -86,6 +86,9 @@ def _classify(target: Path) -> str:
FAIL: {"fg": "red", "bold": True},
WARN: {"fg": "yellow"},
SKIP: {"fg": "cyan"},
# Brighter than FAIL on purpose: a fault is our bug, and the reader should not spend time
# looking for it in their own document.
FAULT: {"fg": "magenta", "bold": True},
}

_meta_option = click.option(
Expand Down Expand Up @@ -136,17 +139,20 @@ def _print_human(report: Report, verbose: bool) -> None:
versions = ", ".join(report.meta_versions) or "none"
click.echo(f"{status} {report.source}")
click.echo(f" meta-schema: {versions}")
# Faults appear only when there are some. A permanent "0 fault(s)" would train the reader to
# skip the field, which is the opposite of what it is for.
faults = f", {counts[FAULT]} VALIDATOR FAULT(S)" if counts[FAULT] else ""
click.echo(
f" {counts[OK]} ok, {counts[FAIL]} failed, {counts[WARN]} warning(s), "
f"{counts[SKIP]} skipped, across {len(report.targets())} target(s)"
f"{counts[SKIP]} skipped{faults}, across {len(report.targets())} target(s)"
)

shown = report.checks if verbose else [c for c in report.checks if c.status != OK]
if shown:
click.echo()
for check in shown:
style = _STATUS_STYLE.get(check.status, {})
label = click.style(check.status.upper().ljust(4), **style)
label = click.style(check.status.upper().ljust(5), **style)
rule = click.style(f" {check.rule}", fg="blue") if check.rule else ""
version = f" [{check.meta_version}]" if check.meta_version else ""
message = f": {check.message}" if check.message else ""
Expand Down
6 changes: 5 additions & 1 deletion src/oold/validation/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ class CheckResult(BaseModel):

id: str = Field(description="The check id, e.g. lint.container or rule.id-fragment.")
target: str = Field(description="What was checked: a schema file, an instance, or a directory entry.")
status: str = Field(description="ok, fail, warn, or skip.")
status: str = Field(
description="ok, fail, warn, skip, or fault. A fault is a defect in the validator: the "
"check raised something it does not expect, so it produced no verdict and the document "
"is neither condemned nor cleared. Like fail it makes the run not pass."
)
message: str = Field(default="", description="Why the check produced this status, when it is not ok.")
detail: dict[str, Any] | None = Field(
default=None, description="Extra structured detail behind the message. Only present with verbosity='full'."
Expand Down
Loading
Loading