Reference validator for the OpenAKB descriptor format (spec major v1).
Project description
openakb-validate
openakb-validate is the pure Python reference validator library for the OpenAKB
descriptor format, spec major v1. It is a library only: no CLI, no network access,
no background services, and no storage, search, freshness, authentication, or
transport mechanism.
The package validates caller-provided descriptor data and reports stable findings. It does not fetch referenced resources unless the caller supplies an explicit resolver. Source, issues, and the normative specification live in the openakb/spec repository.
Install
Install from the package index into Python 3.12 or newer:
python -m pip install openakb-validate
For repository development:
cd packages/python
uv sync
Validate a descriptor
Use validate for descriptor-only validation. The input is already-parsed JSON
data; callers own file reading and output formatting.
import json
from pathlib import Path
from openakb_validate import ValidationResult, validate
descriptor = json.loads(Path("openakb.json").read_text(encoding="utf-8"))
result: ValidationResult = validate(descriptor)
if not result.ok:
for finding in result.findings:
print(f"{finding.code}: {finding.message}")
Each Finding has a stable code, RFC 6901 JSON Pointer path, and message.
Its .name property derives the public finding name from the code catalog and,
for a code outside the catalog, echoes the code back rather than raising. Advisory
diagnostics use Advisory and appear in warnings; they never affect the
validation verdict. The re-exported json_pointer helper builds the same canonical
pointers the validator emits, for callers that construct or match against
finding.path.
The stable validation codes for spec major v1 are AKB001 through AKB012, defined
in spec §7, Validation and error codes. Those code meanings are part
of the public contract for v1 and only change through the spec process.
Extract citations
Use extract_citations to read normative [cite:] markers from Markdown prose.
The parser follows CommonMark structure, so markers in code and HTML constructs
remain literal text. Each returned Citation contains source ids in written order,
including duplicates.
from openakb_validate import Citation, extract_citations
citations: list[Citation] = extract_citations("See [cite: source_a, source_b].")
ids = [citation.ids for citation in citations]
Strict mode
Strict mode adds AKB006 unknown-core-property findings for unknown core
members outside extension payloads:
import json
from pathlib import Path
from openakb_validate import validate
descriptor = json.loads(Path("openakb.json").read_text(encoding="utf-8"))
result = validate(descriptor, strict=True)
unknown_core_findings = [
finding for finding in result.findings if finding.code == "AKB006"
]
Strict mode remains local and deterministic. It does not make network requests or
freshness decisions. Advisory warnings describe MAY-warn surfaces and never
affect result.ok.
Check content
Content checks are opt-in: check_content only fetches a descriptor's referenced
resources through a resolver you supply, then verifies hashes, provenance sidecars,
citation markers, and claim quotes. LocalFileResolver(base_dir=...) reads files
under a caller-controlled directory.
import json
from pathlib import Path
from openakb_validate import ContentReport, LocalFileResolver, check_content
descriptor = json.loads(Path("openakb.json").read_text(encoding="utf-8"))
resolver = LocalFileResolver(base_dir="example-akb")
report: ContentReport = check_content(descriptor, resolver=resolver)
if report.failed:
for check in report.failed:
print(f"FAILED {check.kind} {check.path}: {check.detail}")
elif not report.verified:
print("nothing verified: no content was fetched or there was nothing to check")
else:
print(f"verified {len(report.verified)} content reference(s)")
for finding in report.findings:
print(f"{finding.code}: {finding.message}")
Three outcomes, and why ok can be silent
Every content check reports one of three outcomes, exposed as the module constants
VERIFIED, FAILED, and UNVERIFIABLE:
- verified — the resource was fetched and matched: a hash matched, a quote was found in its capture, or citation markers resolved.
- failed — the resource was fetched and did not match: a hash mismatch, an absent quote, or malformed fetched content. Tampering surfaces here.
- unverifiable — the resource could not be fetched, or there was nothing to check it against. Nothing was proven either way; this is never an error.
report.ok is True when no check failed and no structural finding was raised. It
is deliberately not "everything verified": a report in which every check is
unverifiable — because nothing was fetched — is still ok. To tell "all good"
from "nothing checked", read the outcome accessors, which mirror report.failed:
report.verified,report.failed,report.unverifiablecollect the checks with each outcome.report.findingscarries only the structural findings raised inside content (for example, a citation marker that points at a missing source). A hash or quote mismatch is not a structural finding — it appears inreport.failed. Consuming code that watches onlyreport.findingswill therefore miss a corrupted capture; key onreport.failed(andreport.ok) to catch tampering.
Filter checks by their kind using the exported constants rather than hard-coding
strings: KIND_GUIDE_HASH, KIND_CONTENT_HASH, KIND_CITATIONS, KIND_SIDECAR,
KIND_CAPTURE, and KIND_QUOTE.
import json
from pathlib import Path
from openakb_validate import KIND_CAPTURE, LocalFileResolver, check_content
descriptor = json.loads(Path("openakb.json").read_text(encoding="utf-8"))
report = check_content(descriptor, resolver=LocalFileResolver(base_dir="example-akb"))
capture_checks = [check for check in report.checks if check.kind == KIND_CAPTURE]
Resolvers
LocalFileResolver accepts a str or os.PathLike[str] base_dir and confines
reads to files beneath it, rejecting absolute paths, .. traversal, queries, and
path parameters before any read. That path policing is specific to
LocalFileResolver.
Implement the Resolver protocol when content lives somewhere other than a local
directory:
from openakb_validate import Resolver, Unfetchable
class MappingResolver(Resolver):
"""Resolve references from an in-memory mapping."""
def __init__(self, files: dict[str, bytes]) -> None:
self._files = files
def fetch(self, uri: str) -> bytes:
try:
return self._files[uri]
except KeyError as error:
raise Unfetchable(uri) from error
check_content calls fetch(uri) with the effective reference: the descriptor
reference (a content_uri, capture_uri, guide_uri, or provenance_uri) already
joined onto the descriptor's base_uri when one is present, with any #fragment
stripped. A resolver returns the resource bytes or raises Unfetchable; anything
it cannot fetch becomes an unverifiable outcome, never an error. A custom resolver
receives none of LocalFileResolver's path guards and owns its own safety.
Validate everything in one call
Use validate_with_content when a caller wants descriptor validation, strict-mode
advisories, provenance checks, and content checks through one API.
import json
from pathlib import Path
from openakb_validate import FullReport, LocalFileResolver, validate_with_content
descriptor = json.loads(Path("openakb.json").read_text(encoding="utf-8"))
report: FullReport = validate_with_content(
descriptor,
resolver=LocalFileResolver(base_dir="example-akb"),
strict=True,
)
if not report.ok:
for finding in report.validation.findings:
print(f"{finding.code}: {finding.message}")
for check in report.content.failed:
print(f"FAILED {check.kind} {check.path}: {check.detail}")
report.ok combines structural validity and content checks
(validation.ok and content.ok), so it carries the same caveat: it does not require
that anything was verified. This is still a pure library call — callers choose where
data comes from, how findings are displayed, and whether any finding blocks their
workflow.
Conformance
The package participates in the shared
OpenAKB conformance suite
for spec major v1. Conformance fixtures are the cross-language contract for
validation behavior, including the stable AKB001 through AKB012 code mapping.
Package development emits and checks a conformance report with:
cd packages/python
uv run python tests/conformance_report.py > conformance-report.json
node ../../scripts/ci/check-conformance-report.mjs conformance-report.json
These commands are the package conformance/report gate used by CI and release checks.
Development and contributing
Use uv for package work:
cd packages/python
uv sync
uv run pytest
uv run ruff format --check .
uv run ruff check .
uv run mypy
The package vendors byte-identical copies of the published schemas from
schema/v1/ so they are available through importlib.resources. Check drift from
the repository root:
bash scripts/ci/check-schema-sync.sh
Follow the package conventions in AGENTS.md and the repository conventions in the
root AGENTS.md, and see
CONTRIBUTING.md for the
contribution process. Public artifacts must remain vendor-neutral.
License
Apache-2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openakb_validate-0.1.0.tar.gz.
File metadata
- Download URL: openakb_validate-0.1.0.tar.gz
- Upload date:
- Size: 46.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9530e10fb1c5058517b683f6e11f2b6889b930706a2d7c7a1bbc760a277bb1f7
|
|
| MD5 |
2607674e4ac7e69efff52bc34a036ccc
|
|
| BLAKE2b-256 |
bd1c1b8c00adfe12b7c325b945ee45bcf32ed06ffec25f446296c8792428e372
|
Provenance
The following attestation bundles were made for openakb_validate-0.1.0.tar.gz:
Publisher:
publish-python.yml on openakb/spec
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openakb_validate-0.1.0.tar.gz -
Subject digest:
9530e10fb1c5058517b683f6e11f2b6889b930706a2d7c7a1bbc760a277bb1f7 - Sigstore transparency entry: 2076418516
- Sigstore integration time:
-
Permalink:
openakb/spec@41605efce87facccd96791f82947ff2075f70c07 -
Branch / Tag:
refs/tags/py-v0.1.0 - Owner: https://github.com/openakb
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@41605efce87facccd96791f82947ff2075f70c07 -
Trigger Event:
push
-
Statement type:
File details
Details for the file openakb_validate-0.1.0-py3-none-any.whl.
File metadata
- Download URL: openakb_validate-0.1.0-py3-none-any.whl
- Upload date:
- Size: 33.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83202714404300f8243663ad9fa1a28d9e891de52a771771a223402923053775
|
|
| MD5 |
6141ffc784a7380c56a67af09848dd34
|
|
| BLAKE2b-256 |
123643d26025bb2b85758710725c866523d156ba05089e94a2bfc06144acfed7
|
Provenance
The following attestation bundles were made for openakb_validate-0.1.0-py3-none-any.whl:
Publisher:
publish-python.yml on openakb/spec
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openakb_validate-0.1.0-py3-none-any.whl -
Subject digest:
83202714404300f8243663ad9fa1a28d9e891de52a771771a223402923053775 - Sigstore transparency entry: 2076418755
- Sigstore integration time:
-
Permalink:
openakb/spec@41605efce87facccd96791f82947ff2075f70c07 -
Branch / Tag:
refs/tags/py-v0.1.0 - Owner: https://github.com/openakb
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@41605efce87facccd96791f82947ff2075f70c07 -
Trigger Event:
push
-
Statement type: