Validation

What the validators check, and what a passing run does and does not establish. Read the second half before quoting the first.

Rendered from intransic-skills/skills/intransic/validation/README.mdat v0.2.2 · 50e2c2f38f69

Intransic Validation

Mechanical checks that the Foundation, evidence ledger and Trust Wall remain consistent with each other and with the repository.


What these checks can and cannot establish

They CAN establish:

They CANNOT establish:

A fully passing run means "nothing is claimed that the evidence does not support" — nothing more. Do not treat a green run as an assurance result.


Setup

Python 3.9+ with two dev-only packages. These are not application dependencies and must never be added to the application's own manifest.

python3 -m venv .venv
. .venv/bin/activate
pip install -r intransic/validation/requirements.txt

Running

python3 intransic/validation/validate_evidence.py     # ledger consistency (E-01..E-12)
python3 intransic/validation/validate_foundation.py   # foundation integrity (F-01..F-11)
python3 intransic/validation/validate_trust_wall.py   # publication safety + conformance (T-*, R-*)

Run all three:

for v in evidence foundation trust_wall; do
  python3 intransic/validation/validate_$v.py || echo "FAILED: $v"
done

Exit codes: 0 no errors · 1 errors found · 2 missing dependency or required file.

Run order matters when diagnosing: the ledger is the source of truth, so fix validate_evidence findings first — Foundation and Trust Wall errors are often downstream of a ledger problem.

The validators locate the repository root by walking up from their own location to find intransic/validation/validation-rules.yaml. Set INTRANSIC_ROOT to override, which is occasionally needed in CI images that stage the tree elsewhere.


Severities

Severity Meaning
error Blocks publication or merge. Fix before proceeding.
warning Requires human judgement. Staleness and violated invariants surface here.
info Advisory (e.g. a line-number citation without a symbol).

The rules

Defined in validation-rules.yaml, grouped as:

F, E and T are stack-independent and should be carried across repositories unchanged.


Why T-04 exists

Public wording lives in the ledger beside its evidence, and trust/claims.json is generated from it. T-04 fails if published text no longer matches its approved wording — which is what happens when someone edits the Trust Wall by hand. Regenerate rather than edit.

Evidence strength

E-03 asks whether a verified claim carries evidence at all. That was never enough on its own: it counts items without asking what any of them establishes, so a single citation of a file that merely exists satisfied it.

The ladder, weakest first, with the default applied when an item does not declare strength::

Strength Establishes Default for
existence a file, symbol or interface is present documentation, git, schema
implementation an active code path, or committed configuration implementation, configuration, infrastructure
behavioural an executed test asserts the behaviour test
operational the running system was directly observed observation
deployed production configuration or telemetry —
independent external audit or certification —

E-11 — verified requires behavioural or better. Below that the supportable status is implemented_unverified, which the vocabulary already provides for exactly this case.

E-12 — no publishable status (verified, implemented_unverified, partial) may rest entirely on documentation or git evidence. Documentation is a claim to be verified; a commit shows a change was made, not that it survived.

Both also appear as conditional subschemas in evidence.schema.json, so a ledger fails E-01 on the same grounds even where these checks are ported to another language.

Declaring a weaker strength

The default maps from the evidence type, and a type cannot distinguish a live code path from a dormant one. Where the citation is weaker than its type implies, say so:

  - type: implementation
    path: src/Ports/IEvidenceStore.cs
    symbol: IEvidenceStore
    strength: existence      # interface only; nothing binds an implementation
    explanation: …

There is no mechanism preventing an assessor declaring a strength the citation does not deserve — the ledger is written by hand, and this check reads the ledger. What it removes is the silent case: verified can no longer be reached by accident, only by an assertion someone made on the record.

Why the R-* rules exist at all

The first repository this ran against had a verified authorisation control silently reverted by a merge. No test asserted the property, so nothing failed, and the control was gone for a day before anyone noticed. An R-* rule was written to detect that exact shape.

It is a backstop, not a substitute: the durable fix is a regression test asserting the behaviour. A static check can spot a known-bad shape; only a test can prove a behaviour. If you find yourself writing an R-* rule where a test would do, write the test.


Field-level PII and the processor register

Two ways an evidence-backed Foundation can still drift specifically around personal data, and the two checks that close them:

A classification section that lists categories but not fields. "Personal identifiers: name, email" describes a shape, not the actual columns. A new field can land — a government health identifier, a date of birth — and nothing fails, because nothing was checking. data.classifications[].fields (see foundation.schema.json) traces each class to concrete path + symbol evidence, the same way every other Foundation claim is traced. F-10 checks those paths resolve. The pii_coverage conformance kind goes the other direction: it scans your source for field names that look like PII and warns when one isn't named anywhere in the data section — a check the Foundation cannot run on itself, since it only knows what it was told.

Both directions are needed. F-10 catches a stale reference in the Foundation; pii_coverage catches a Foundation that was never updated for a change to the code. Neither proves correctness — pii_coverage is a name match, and a badly named field defeats it as easily as a well-named one satisfies it. Treat a hit as "go classify or exclude this", not as a verdict.

An outbound data flow to a third party with no record of it. privacy.processors is a register: name, category, what data leaves, why the sharing is appropriate (basis), and evidence. F-11 requires every entry to be complete and its evidence to resolve — the same discipline applied to every other claim. This is Foundation-level bookkeeping, not an audit: it does not by itself demonstrate a lawful basis exists, only that one has been stated and can be checked against its evidence. Cloud hosting, payment processing, an LLM provider and an identity provider are all processors; list them even though only some read as "integrations" in the architectural sense.


Infrastructure the Foundation never mentions

Every other rule here works by falsification: a claim is made, and evidence either carries it or contradicts it. That leaves one thing structurally invisible. A Foundation cannot contradict a claim nobody made, so a network posture that was never written down passes every check by never being asserted. Silence is not a status in the vocabulary, and it is the state most security findings actually live in.

infra_posture inverts the direction for that case. It reads a captured infrastructure snapshot — the committed output of az, gcloud, kubectl, terraform show -json or equivalent — and for each posture found in it, asks whether the Foundation's boundaries or security sections mention it at all. An undeclared posture becomes a finding. A declared one does not, whatever the Foundation says about it: "we accept this until Phase 2" is an account, and accounting for a posture is all this rule asks for.

It reads a snapshot rather than calling a cloud CLI on purpose. A validator that shells out needs credentials, a network and a particular cloud, and stops being reproducible — the same run against the same commit could disagree with itself. The snapshot is evidence: captured once, committed, reviewable in a diff, and subject to the same staleness questions as any other evidence.

What a finding establishes is narrow. The snapshot contains something; the Foundation does not mention it. Not that the posture is wrong — a database open to a cloud's whole service range can be a considered decision — and not that a mentioned posture is adequately controlled. It says nobody wrote it down.

Refresh the snapshot on the same cadence as the Foundation. A stale one reports the posture of a system that no longer exists, which is worse than no rule at all, because it reads as a pass.

A rule whose target is missing is not a pass

If a rule's files: globs match nothing, the validator emits a warning saying the check was skipped, not silence. A renamed entry point should not quietly turn a security check into a no-op.

Adding a rule

  1. Add it to validation-rules.yaml with an ID, severity, and an explicit establishes: statement saying what it does not prove.
  2. Pick a kind: — text, config_secrets, mock_binding, pii_coverage or infra_posture. Most rules are text.
  3. Prefer warnings for anything needing human judgement — a noisy error trains people to ignore the tool.

You should not need to edit Python to add a rule. If you do, the kind you need is probably worth adding to the skill rather than to one repository.